Register
Login
Resources
Docs Blog Datasets Glossary Case Studies Tutorials & Webinars
Product
Data Engine LLMs Platform Enterprise
Pricing Explore
Connect to our Discord channel

pages.js 6.2 KB

You have to be logged in to leave a comment. Sign In
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
  1. const path = require('path')
  2. const { loadPages, loadPageMap } = require('../../lib/pages')
  3. const languageCodes = Object.keys(require('../../lib/languages'))
  4. const { liquid } = require('../../lib/render-content')
  5. const patterns = require('../../lib/patterns')
  6. const GithubSlugger = require('github-slugger')
  7. const slugger = new GithubSlugger()
  8. const Entities = require('html-entities').XmlEntities
  9. const entities = new Entities()
  10. const { chain, difference } = require('lodash')
  11. const checkIfNextVersionOnly = require('../../lib/check-if-next-version-only')
  12. describe('pages module', () => {
  13. jest.setTimeout(60 * 1000)
  14. let pages
  15. beforeAll(async (done) => {
  16. pages = await loadPages()
  17. done()
  18. })
  19. describe('loadPages', () => {
  20. test('yields a non-empty array of Page objects', async () => {
  21. expect(Array.isArray(pages)).toBe(true)
  22. expect(pages.length).toBeGreaterThan(100)
  23. })
  24. test('every page has a `languageCode`', async () => {
  25. expect(pages.every(page => languageCodes.includes(page.languageCode))).toBe(true)
  26. })
  27. test('every page has a non-empty `permalinks` array', async () => {
  28. const brokenPages = pages
  29. .filter(page => !Array.isArray(page.permalinks) || page.permalinks.length === 0)
  30. // Ignore pages that only have "next" versions specified and therefore no permalinks;
  31. // These pages are not broken, they just won't render in the currently supported versions.
  32. .filter(page => !Object.values(page.versions).every(pageVersion => checkIfNextVersionOnly(pageVersion)))
  33. const expectation = JSON.stringify(brokenPages.map(page => page.fullPath), null, 2)
  34. expect(brokenPages.length, expectation).toBe(0)
  35. })
  36. // **TODO** fix duplicate redirects after new site tree feature flag is enabled
  37. // we can't put this in tests/redirects because duplicate routes have already been
  38. // overwritten during context.pages.redirects object assignment and can't be searched for
  39. test.skip('redirect_from routes are unique across English pages', () => {
  40. const sourceRedirectFrom = chain(pages)
  41. .filter(['languageCode', 'en'])
  42. .filter('redirect_from')
  43. .map('redirect_from')
  44. .flatten()
  45. .value()
  46. const duplicates = sourceRedirectFrom.reduce((acc, el, i, arr) => {
  47. if (arr.indexOf(el) !== i && acc.indexOf(el) < 0) acc.push(el)
  48. return acc
  49. }, [])
  50. const message = `Found ${duplicates.length} duplicate redirect_from ${duplicates.length === 1 ? 'path' : 'paths'}.\n
  51. ${duplicates.join('\n')}`
  52. expect(duplicates.length, message).toBe(0)
  53. })
  54. test('every English page has a filename that matches its slugified title', async () => {
  55. const nonMatches = pages
  56. .filter(page => {
  57. slugger.reset()
  58. return page.languageCode === 'en' && // only check English
  59. !page.relativePath.includes('index.md') && // ignore TOCs
  60. !page.allowTitleToDifferFromFilename && // ignore docs with override
  61. slugger.slug(entities.decode(page.title)) !== path.basename(page.relativePath, '.md')
  62. })
  63. // make the output easier to read
  64. .map(page => {
  65. return JSON.stringify({
  66. file: path.basename(page.relativePath),
  67. title: page.title,
  68. path: page.fullPath
  69. }, null, 2)
  70. })
  71. const message = `
  72. Found ${nonMatches.length} ${nonMatches.length === 1 ? 'file' : 'files'} that do not match their slugified titles.\n
  73. ${nonMatches.join('\n')}\n
  74. To fix, run script/reconcile-filenames-with-ids.js\n\n`
  75. expect(nonMatches.length, message).toBe(0)
  76. })
  77. test('every page has valid frontmatter', async () => {
  78. const frontmatterErrors = chain(pages)
  79. // .filter(page => page.languageCode === 'en')
  80. .map(page => page.frontmatterErrors)
  81. .flatten()
  82. .value()
  83. const failureMessage = JSON.stringify(frontmatterErrors, null, 2) +
  84. '\n\n' +
  85. chain(frontmatterErrors).map('filepath').join('\n').value()
  86. expect(frontmatterErrors.length, failureMessage).toBe(0)
  87. })
  88. test('every page has valid Liquid templating', async () => {
  89. const liquidErrors = []
  90. for (const page of pages) {
  91. const markdown = page.raw
  92. if (!patterns.hasLiquid.test(markdown)) continue
  93. try {
  94. await liquid.parse(markdown)
  95. } catch (error) {
  96. liquidErrors.push({
  97. filename: page.fullPath,
  98. error: error.message
  99. })
  100. }
  101. }
  102. const failureMessage = JSON.stringify(liquidErrors, null, 2)
  103. expect(liquidErrors.length, failureMessage).toBe(0)
  104. })
  105. test('every non-English page has a matching English page', async () => {
  106. const englishPaths = chain(pages)
  107. .filter(page => page.languageCode === 'en')
  108. .map(page => page.relativePath)
  109. .value()
  110. const nonEnglishPaths = chain(pages)
  111. .filter(page => page.languageCode !== 'en')
  112. .map(page => page.relativePath)
  113. .uniq()
  114. .value()
  115. const diff = difference(nonEnglishPaths, englishPaths)
  116. const failureMessage = `Unmatched non-English pages:\n - ${diff.join('\n - ')}`
  117. expect(diff.length, failureMessage).toBe(0)
  118. })
  119. })
  120. describe('loadPageMap', () => {
  121. let pageMap
  122. beforeAll(async () => {
  123. pageMap = await loadPageMap(pages)
  124. })
  125. test('yields a non-empty object with more unique entries than pages', async () => {
  126. // Why does it contain MORE unique entries, you ask?
  127. // TL;DR: The pages array contains one item per Page + language, with a `permalinks` array
  128. // property for each product version supported (free-pro-team, enterprise-server@2.22, etc.)
  129. // The pageMap, on the other hand, is keyed by unique URLs, so it has 1-N (where N is the
  130. // number of product versions supported) keys pointing to the same Page + language object
  131. expect(Array.isArray(pageMap)).toBe(false)
  132. expect(Object.keys(pageMap).length).toBeGreaterThan(pages.length)
  133. })
  134. test('has an identical key list to the deep permalinks of the array', async () => {
  135. const allPermalinks = pages.flatMap(page => page.permalinks.map(pl => pl.href)).sort()
  136. const allPageUrls = Object.keys(pageMap).sort()
  137. expect(allPageUrls).toEqual(allPermalinks)
  138. })
  139. })
  140. })
Tip!

Press p or to see the previous file or, n or to see the next file

Comments

Loading...