-
Notifications
You must be signed in to change notification settings - Fork 68.3k
Expand file tree
/
Copy pathpages.ts
More file actions
169 lines (145 loc) · 6.36 KB
/
Copy pathpages.ts
File metadata and controls
169 lines (145 loc) · 6.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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()
// Map from redirect path to Set of file paths
const redirectToFiles = new Map<string, Set<string>>()
const versionedRedirects: Array<{ path: string; file: string }> = []
// Page objects have dynamic properties from chain/lodash that aren't fully typed
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)
}
}
}
// Only consider as duplicate if more than one unique file defines the same redirect
const duplicates = Array.from(redirectToFiles.entries())
.filter(([, files]) => files.size > 1)
.map(([redirectPath]) => redirectPath)
// Build a detailed message with sources for each duplicate
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' && // only check English
!page.relativePath.includes('index.md') && // ignore TOCs
// Page class has dynamic frontmatter properties like 'allowTitleToDifferFromFilename' not in type definition
!(page as Record<string, unknown>).allowTitleToDifferFromFilename && // ignore docs with override
slugger.slug(decode(page.title)) !== path.basename(page.relativePath, '.md') &&
slugger.slug(decode(page.shortTitle || '')) !== path.basename(page.relativePath, '.md')
)
})
// make the output easier to read
.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)
// .filter(page => page.languageCode === 'en')
// Page class has dynamic error properties like 'frontmatterErrors' not in type definition
.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) {
// Page class has dynamic properties like 'raw' markdown not in type definition
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)
})
})
})