| | import path from 'path' |
| |
|
| | import { beforeAll, describe, expect, test, vi } from 'vitest' |
| | import GithubSlugger from 'github-slugger' |
| | import { decode } from 'html-entities' |
| | import { chain, pick } from 'lodash-es' |
| |
|
| | import { loadPages } from '@/frame/lib/page-data' |
| | import libLanguages from '@/languages/lib/languages-server' |
| | import { liquid } from '@/content-render/index' |
| | import patterns from '@/frame/lib/patterns' |
| | import removeFPTFromPath from '@/versions/lib/remove-fpt-from-path' |
| | import type { Page } from '@/types' |
| |
|
| | const languageCodes = Object.keys(libLanguages) |
| | const slugger = new GithubSlugger() |
| |
|
| | describe('pages module', () => { |
| | vi.setConfig({ testTimeout: 60 * 1000 }) |
| |
|
| | let pages: Page[] |
| |
|
| | beforeAll(async () => { |
| | pages = await loadPages() |
| | }) |
| |
|
| | describe('loadPages', () => { |
| | test('yields a non-empty array of Page objects', async () => { |
| | expect(Array.isArray(pages)).toBe(true) |
| | expect(pages.length).toBeGreaterThan(100) |
| | }) |
| |
|
| | test('every page has a `languageCode`', async () => { |
| | expect(pages.every((page) => languageCodes.includes(page.languageCode))).toBe(true) |
| | }) |
| |
|
| | test('every page has a non-empty `permalinks` array', async () => { |
| | const brokenPages = pages.filter( |
| | (page) => !Array.isArray(page.permalinks) || page.permalinks.length === 0, |
| | ) |
| |
|
| | const expectation = JSON.stringify( |
| | brokenPages.map((page) => page.fullPath), |
| | null, |
| | 2, |
| | ) |
| | expect(brokenPages.length, expectation).toBe(0) |
| | }) |
| |
|
| | test('redirect_from routes are unique across English pages', () => { |
| | const englishPages = chain(pages) |
| | .filter(['languageCode', 'en']) |
| | .filter('redirect_from') |
| | .map((page) => pick(page, ['redirect_from', 'applicableVersions', 'fullPath'])) |
| | .value() |
| |
|
| | |
| | const redirectToFiles = new Map<string, Set<string>>() |
| | const versionedRedirects: Array<{ path: string; file: string }> = [] |
| |
|
| | |
| | for (const page of englishPages) { |
| | const pageObj = page as Record<string, unknown> |
| | for (const redirect of pageObj.redirect_from as string[]) { |
| | for (const version of pageObj.applicableVersions as string[]) { |
| | const versioned = removeFPTFromPath(path.posix.join('/', version, redirect)) |
| | versionedRedirects.push({ path: versioned, file: pageObj.fullPath as string }) |
| | if (!redirectToFiles.has(versioned)) { |
| | redirectToFiles.set(versioned, new Set<string>()) |
| | } |
| | redirectToFiles.get(versioned)!.add(pageObj.fullPath as string) |
| | } |
| | } |
| | } |
| |
|
| | |
| | const duplicates = Array.from(redirectToFiles.entries()) |
| | .filter(([, files]) => files.size > 1) |
| | .map(([redirectPath]) => redirectPath) |
| |
|
| | |
| | const message = `Found ${duplicates.length} duplicate redirect_from path${duplicates.length === 1 ? '' : 's'}. |
| | Ensure that you don't define the same path more than once in the redirect_from property in a single file and across all English files. |
| | You may also receive this error if you have defined the same children property more than once.\n${duplicates |
| | .map((dup) => { |
| | const files = Array.from(redirectToFiles.get(dup) || []) |
| | return `${dup}\n Defined in:\n ${files.join('\n ')}` |
| | }) |
| | .join('\n\n')}` |
| | expect(duplicates.length, message).toBe(0) |
| | }) |
| |
|
| | test('every English page has a filename that matches its slugified title or shortTitle', async () => { |
| | const nonMatches = pages |
| | .filter((page) => { |
| | slugger.reset() |
| | return ( |
| | page.languageCode === 'en' && |
| | !page.relativePath.includes('index.md') && |
| | |
| | !(page as Record<string, unknown>).allowTitleToDifferFromFilename && |
| | slugger.slug(decode(page.title)) !== path.basename(page.relativePath, '.md') && |
| | slugger.slug(decode(page.shortTitle || '')) !== path.basename(page.relativePath, '.md') |
| | ) |
| | }) |
| | |
| | .map((page) => { |
| | return JSON.stringify( |
| | { |
| | file: path.basename(page.relativePath), |
| | title: page.title, |
| | path: page.fullPath, |
| | }, |
| | null, |
| | 2, |
| | ) |
| | }) |
| |
|
| | const message = ` |
| | Found ${nonMatches.length} ${ |
| | nonMatches.length === 1 ? 'file' : 'files' |
| | } that do not match their slugified titles.\n |
| | ${nonMatches.join('\n')}\n |
| | To fix, run: npm run-script update-filepaths --paths [FILEPATHS]\n\n` |
| |
|
| | expect(nonMatches.length, message).toBe(0) |
| | }) |
| |
|
| | test('every page has valid frontmatter', async () => { |
| | const frontmatterErrors = chain(pages) |
| | |
| | |
| | .map((page) => (page as Record<string, unknown>).frontmatterErrors) |
| | .filter(Boolean) |
| | .flatten() |
| | .value() |
| |
|
| | const failureMessage = `${JSON.stringify(frontmatterErrors, null, 2)}\n\n${chain( |
| | frontmatterErrors, |
| | ) |
| | .map('filepath') |
| | .join('\n') |
| | .value()}` |
| |
|
| | expect(frontmatterErrors.length, failureMessage).toBe(0) |
| | }) |
| |
|
| | test('every page has valid Liquid templating', async () => { |
| | const liquidErrors: Array<{ filename: string; error: string }> = [] |
| |
|
| | for (const page of pages) { |
| | |
| | const markdown = (page as Record<string, unknown>).raw as string |
| | if (!patterns.hasLiquid.test(markdown)) continue |
| | try { |
| | await liquid.parse(markdown) |
| | } catch (error) { |
| | liquidErrors.push({ |
| | filename: page.fullPath, |
| | error: (error as Error).message, |
| | }) |
| | } |
| | } |
| |
|
| | const failureMessage = JSON.stringify(liquidErrors, null, 2) |
| | expect(liquidErrors.length, failureMessage).toBe(0) |
| | }) |
| | }) |
| | }) |
| |
|