@teleporthq/teleport-project-generator-next 0.43.74 → 0.43.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/__tests__/analytics-project-plugin.test.ts +35 -2
  2. package/__tests__/analytics-tracked-pages.test.ts +90 -0
  3. package/__tests__/content-category-page-end2end.test.ts +205 -0
  4. package/__tests__/content-category-pages.test.ts +366 -0
  5. package/__tests__/content-site-index.test.ts +607 -0
  6. package/__tests__/rich-text-editor-embed-format.test.ts +75 -3
  7. package/dist/cjs/analytics/project-plugin.js +3 -3
  8. package/dist/cjs/analytics/project-plugin.js.map +1 -1
  9. package/dist/cjs/analytics/tracker-component.d.ts +3 -1
  10. package/dist/cjs/analytics/tracker-component.d.ts.map +1 -1
  11. package/dist/cjs/analytics/tracker-component.js +12 -3
  12. package/dist/cjs/analytics/tracker-component.js.map +1 -1
  13. package/dist/cjs/analytics/tracker-source.d.ts +1 -1
  14. package/dist/cjs/analytics/tracker-source.d.ts.map +1 -1
  15. package/dist/cjs/analytics/tracker-source.js +1 -1
  16. package/dist/cjs/analytics/tracker-source.js.map +1 -1
  17. package/dist/cjs/blog/arranged-order-runtime.d.ts +9 -0
  18. package/dist/cjs/blog/arranged-order-runtime.d.ts.map +1 -0
  19. package/dist/cjs/blog/arranged-order-runtime.js +12 -0
  20. package/dist/cjs/blog/arranged-order-runtime.js.map +1 -0
  21. package/dist/cjs/blog/blog-context-generator.d.ts +1 -1
  22. package/dist/cjs/blog/blog-context-generator.d.ts.map +1 -1
  23. package/dist/cjs/blog/blog-context-generator.js +3 -2
  24. package/dist/cjs/blog/blog-context-generator.js.map +1 -1
  25. package/dist/cjs/blog/content-category-pages.d.ts +40 -0
  26. package/dist/cjs/blog/content-category-pages.d.ts.map +1 -0
  27. package/dist/cjs/blog/content-category-pages.js +80 -0
  28. package/dist/cjs/blog/content-category-pages.js.map +1 -0
  29. package/dist/cjs/blog/content-site-index.d.ts +49 -0
  30. package/dist/cjs/blog/content-site-index.d.ts.map +1 -0
  31. package/dist/cjs/blog/content-site-index.js +213 -0
  32. package/dist/cjs/blog/content-site-index.js.map +1 -0
  33. package/dist/cjs/blog/project-plugin.d.ts +3 -1
  34. package/dist/cjs/blog/project-plugin.d.ts.map +1 -1
  35. package/dist/cjs/blog/project-plugin.js +69 -4
  36. package/dist/cjs/blog/project-plugin.js.map +1 -1
  37. package/dist/cjs/blog/rss-feed.d.ts.map +1 -1
  38. package/dist/cjs/blog/rss-feed.js +2 -1
  39. package/dist/cjs/blog/rss-feed.js.map +1 -1
  40. package/dist/cjs/internationalization/locale-mapper-component.d.ts +1 -0
  41. package/dist/cjs/internationalization/locale-mapper-component.d.ts.map +1 -1
  42. package/dist/cjs/internationalization/locale-mapper-component.js +20 -2
  43. package/dist/cjs/internationalization/locale-mapper-component.js.map +1 -1
  44. package/dist/cjs/rich-text-editor/component-generator.d.ts.map +1 -1
  45. package/dist/cjs/rich-text-editor/component-generator.js +10 -7
  46. package/dist/cjs/rich-text-editor/component-generator.js.map +1 -1
  47. package/dist/cjs/rich-text-editor/content-runtime-module.d.ts +9 -0
  48. package/dist/cjs/rich-text-editor/content-runtime-module.d.ts.map +1 -0
  49. package/dist/cjs/rich-text-editor/content-runtime-module.js +29 -0
  50. package/dist/cjs/rich-text-editor/content-runtime-module.js.map +1 -0
  51. package/dist/cjs/rich-text-editor/content-support-source.d.ts +10 -0
  52. package/dist/cjs/rich-text-editor/content-support-source.d.ts.map +1 -0
  53. package/dist/cjs/rich-text-editor/content-support-source.js +20 -0
  54. package/dist/cjs/rich-text-editor/content-support-source.js.map +1 -0
  55. package/dist/cjs/rich-text-editor/project-plugin.d.ts.map +1 -1
  56. package/dist/cjs/rich-text-editor/project-plugin.js +4 -1
  57. package/dist/cjs/rich-text-editor/project-plugin.js.map +1 -1
  58. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  59. package/dist/esm/analytics/project-plugin.js +4 -4
  60. package/dist/esm/analytics/project-plugin.js.map +1 -1
  61. package/dist/esm/analytics/tracker-component.d.ts +3 -1
  62. package/dist/esm/analytics/tracker-component.d.ts.map +1 -1
  63. package/dist/esm/analytics/tracker-component.js +10 -2
  64. package/dist/esm/analytics/tracker-component.js.map +1 -1
  65. package/dist/esm/analytics/tracker-source.d.ts +1 -1
  66. package/dist/esm/analytics/tracker-source.d.ts.map +1 -1
  67. package/dist/esm/analytics/tracker-source.js +1 -1
  68. package/dist/esm/analytics/tracker-source.js.map +1 -1
  69. package/dist/esm/blog/arranged-order-runtime.d.ts +9 -0
  70. package/dist/esm/blog/arranged-order-runtime.d.ts.map +1 -0
  71. package/dist/esm/blog/arranged-order-runtime.js +9 -0
  72. package/dist/esm/blog/arranged-order-runtime.js.map +1 -0
  73. package/dist/esm/blog/blog-context-generator.d.ts +1 -1
  74. package/dist/esm/blog/blog-context-generator.d.ts.map +1 -1
  75. package/dist/esm/blog/blog-context-generator.js +3 -2
  76. package/dist/esm/blog/blog-context-generator.js.map +1 -1
  77. package/dist/esm/blog/content-category-pages.d.ts +40 -0
  78. package/dist/esm/blog/content-category-pages.d.ts.map +1 -0
  79. package/dist/esm/blog/content-category-pages.js +75 -0
  80. package/dist/esm/blog/content-category-pages.js.map +1 -0
  81. package/dist/esm/blog/content-site-index.d.ts +49 -0
  82. package/dist/esm/blog/content-site-index.d.ts.map +1 -0
  83. package/dist/esm/blog/content-site-index.js +205 -0
  84. package/dist/esm/blog/content-site-index.js.map +1 -0
  85. package/dist/esm/blog/project-plugin.d.ts +3 -1
  86. package/dist/esm/blog/project-plugin.d.ts.map +1 -1
  87. package/dist/esm/blog/project-plugin.js +70 -5
  88. package/dist/esm/blog/project-plugin.js.map +1 -1
  89. package/dist/esm/blog/rss-feed.d.ts.map +1 -1
  90. package/dist/esm/blog/rss-feed.js +2 -1
  91. package/dist/esm/blog/rss-feed.js.map +1 -1
  92. package/dist/esm/internationalization/locale-mapper-component.d.ts +1 -0
  93. package/dist/esm/internationalization/locale-mapper-component.d.ts.map +1 -1
  94. package/dist/esm/internationalization/locale-mapper-component.js +19 -1
  95. package/dist/esm/internationalization/locale-mapper-component.js.map +1 -1
  96. package/dist/esm/rich-text-editor/component-generator.d.ts.map +1 -1
  97. package/dist/esm/rich-text-editor/component-generator.js +10 -7
  98. package/dist/esm/rich-text-editor/component-generator.js.map +1 -1
  99. package/dist/esm/rich-text-editor/content-runtime-module.d.ts +9 -0
  100. package/dist/esm/rich-text-editor/content-runtime-module.d.ts.map +1 -0
  101. package/dist/esm/rich-text-editor/content-runtime-module.js +25 -0
  102. package/dist/esm/rich-text-editor/content-runtime-module.js.map +1 -0
  103. package/dist/esm/rich-text-editor/content-support-source.d.ts +10 -0
  104. package/dist/esm/rich-text-editor/content-support-source.d.ts.map +1 -0
  105. package/dist/esm/rich-text-editor/content-support-source.js +17 -0
  106. package/dist/esm/rich-text-editor/content-support-source.js.map +1 -0
  107. package/dist/esm/rich-text-editor/project-plugin.d.ts.map +1 -1
  108. package/dist/esm/rich-text-editor/project-plugin.js +4 -1
  109. package/dist/esm/rich-text-editor/project-plugin.js.map +1 -1
  110. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  111. package/package.json +19 -19
  112. package/src/analytics/project-plugin.ts +2 -2
  113. package/src/analytics/tracker-component.ts +12 -3
  114. package/src/analytics/tracker-source.ts +74 -3
  115. package/src/blog/arranged-order-runtime.ts +85 -0
  116. package/src/blog/blog-context-generator.ts +19 -1
  117. package/src/blog/content-category-pages.ts +212 -0
  118. package/src/blog/content-site-index.ts +945 -0
  119. package/src/blog/project-plugin.ts +81 -4
  120. package/src/blog/rss-feed.ts +2 -1
  121. package/src/internationalization/locale-mapper-component.ts +23 -0
  122. package/src/rich-text-editor/component-generator.ts +63 -17
  123. package/src/rich-text-editor/content-runtime-module.ts +26 -0
  124. package/src/rich-text-editor/content-support-source.ts +97 -0
  125. package/src/rich-text-editor/project-plugin.ts +4 -1
@@ -29,7 +29,11 @@ const GTM_ASSET = {
29
29
  options: { target: 'body' },
30
30
  }
31
31
 
32
- function makeStructure(analyticsEnabled: boolean, assets: unknown[] = []): ProjectPluginStructure {
32
+ function makeStructure(
33
+ analyticsEnabled: boolean,
34
+ assets: unknown[] = [],
35
+ disabledPaths?: string[]
36
+ ): ProjectPluginStructure {
33
37
  const files = new Map()
34
38
  files.set('_app', {
35
39
  path: ['pages'],
@@ -41,7 +45,9 @@ function makeStructure(analyticsEnabled: boolean, assets: unknown[] = []): Proje
41
45
  name: 'test-project',
42
46
  globals: { settings: { title: 'Test', language: 'en' }, assets },
43
47
  root: {} as never,
44
- ...(analyticsEnabled ? { analytics: { enabled: true } } : {}),
48
+ ...(analyticsEnabled
49
+ ? { analytics: { enabled: true, ...(disabledPaths ? { disabledPaths } : {}) } }
50
+ : {}),
45
51
  },
46
52
  files,
47
53
  dependencies: {},
@@ -186,6 +192,33 @@ describe('NextAnalyticsProjectPlugin', () => {
186
192
  ).not.toThrow()
187
193
  })
188
194
 
195
+ it('bakes the pages kept out of analytics into the tracker component', async () => {
196
+ const plugin = new NextAnalyticsProjectPlugin()
197
+ const structure = makeStructure(true, [], ['/admin-panel/*', '/admin-panel', '/secret'])
198
+
199
+ await plugin.runAfter(structure)
200
+
201
+ const tracker = structure.files.get('teleport-analytics-tracker').files[0].content
202
+ expect(tracker).toContain('const EXCLUDED_PATHS = ["/admin-panel/*","/admin-panel","/secret"]')
203
+ expect(tracker).toContain(
204
+ 'initTeleportAnalytics({ excludedPaths: EXCLUDED_PATHS, locales: router.locales || [] })'
205
+ )
206
+ expect(structure.files.get('teleport-analytics-lib').files[0].content).toContain(
207
+ 'function isPathExcluded'
208
+ )
209
+ })
210
+
211
+ it('tracks every page when the editor sent no list', async () => {
212
+ const plugin = new NextAnalyticsProjectPlugin()
213
+ const structure = makeStructure(true)
214
+
215
+ await plugin.runAfter(structure)
216
+
217
+ expect(structure.files.get('teleport-analytics-tracker').files[0].content).toContain(
218
+ 'const EXCLUDED_PATHS = []'
219
+ )
220
+ })
221
+
189
222
  it('preserves env values already set by the GUI mapper', async () => {
190
223
  const plugin = new NextAnalyticsProjectPlugin()
191
224
  const structure = makeStructure(true)
@@ -0,0 +1,90 @@
1
+ import { TRACKER_SOURCE } from '../src/analytics/tracker-source'
2
+
3
+ /**
4
+ * The emitted tracker, executed: the module syntax is stripped the way a
5
+ * bundler resolves it, and the matching functions plus the two module-level
6
+ * lists are handed back so the tests drive the real code, not a copy of it.
7
+ */
8
+ interface TrackerMatching {
9
+ setExcludedPaths: (paths: string[]) => void
10
+ setLocales: (locales: string[]) => void
11
+ isPathExcluded: (path: string) => boolean
12
+ normalizeTrackedPath: (path: string) => string
13
+ }
14
+
15
+ function loadMatching(): TrackerMatching {
16
+ const body = TRACKER_SOURCE.replace(/^export\s+/gm, '')
17
+ const factory = new Function(
18
+ `${body}
19
+ return {
20
+ setExcludedPaths: function (paths) { excludedPaths = paths },
21
+ setLocales: function (locales) { siteLocales = locales },
22
+ isPathExcluded: isPathExcluded,
23
+ normalizeTrackedPath: normalizeTrackedPath,
24
+ }`
25
+ )
26
+ return factory() as TrackerMatching
27
+ }
28
+
29
+ describe('tracked pages — the emitted tracker', () => {
30
+ it('tracks every page when no list was baked in', () => {
31
+ const tracker = loadMatching()
32
+ expect(tracker.isPathExcluded('/admin-panel/orders')).toBe(false)
33
+ expect(tracker.isPathExcluded('/')).toBe(false)
34
+ })
35
+
36
+ it('keeps a listed page out, exactly or by prefix, however the address is written', () => {
37
+ const tracker = loadMatching()
38
+ tracker.setExcludedPaths(['/admin-panel', '/admin-panel/*', '/secret'])
39
+
40
+ expect(tracker.isPathExcluded('/admin-panel')).toBe(true)
41
+ expect(tracker.isPathExcluded('/admin-panel/')).toBe(true)
42
+ expect(tracker.isPathExcluded('/admin-panel/orders/42')).toBe(true)
43
+ expect(tracker.isPathExcluded('/secret?tab=1#top')).toBe(true)
44
+
45
+ expect(tracker.isPathExcluded('/')).toBe(false)
46
+ expect(tracker.isPathExcluded('/secrets')).toBe(false)
47
+ expect(tracker.isPathExcluded('/admin-panels')).toBe(false)
48
+ })
49
+
50
+ it('matches a dynamic page by its published prefix without touching its listing page', () => {
51
+ const tracker = loadMatching()
52
+ tracker.setExcludedPaths(['/products/*'])
53
+
54
+ expect(tracker.isPathExcluded('/products/42')).toBe(true)
55
+ expect(tracker.isPathExcluded('/products')).toBe(false)
56
+ })
57
+
58
+ it('judges a locale-prefixed address against the same list', () => {
59
+ const tracker = loadMatching()
60
+ tracker.setExcludedPaths(['/admin-panel/*', '/'])
61
+ tracker.setLocales(['en', 'fr'])
62
+
63
+ expect(tracker.normalizeTrackedPath('/fr/admin-panel/orders/')).toBe('/admin-panel/orders')
64
+ expect(tracker.isPathExcluded('/fr/admin-panel/orders')).toBe(true)
65
+ expect(tracker.isPathExcluded('/fr')).toBe(true)
66
+ expect(tracker.isPathExcluded('/fr/about')).toBe(false)
67
+ })
68
+
69
+ it('silences a page kept out of analytics instead of giving it a page identity', () => {
70
+ expect(TRACKER_SOURCE).toMatch(
71
+ /if \(isPathExcluded\(newPath\)\) \{[\s\S]*?currentPath = newPath\s*pageLoadId = null\s*return/
72
+ )
73
+ // Commerce steps guard on the page identity like clicks and heartbeats do.
74
+ expect(TRACKER_SOURCE).toContain('if (!initialized || !isTrackingPossible() || !pageLoadId) {')
75
+ })
76
+ })
77
+
78
+ describe('click matching — what a funnel step can see', () => {
79
+ it('reports the clicked element’s classes, capped, so ".class" steps match', () => {
80
+ expect(TRACKER_SOURCE).toContain(
81
+ "Array.prototype.slice.call(target.classList).join(' ').slice(0, 255)"
82
+ )
83
+ })
84
+
85
+ it('counts a click on any element marked data-tp-track', () => {
86
+ expect(TRACKER_SOURCE).toContain(
87
+ `'a, button, [role="button"], input[type="submit"], [data-tp-event], [data-tp-track]'`
88
+ )
89
+ })
90
+ })
@@ -0,0 +1,205 @@
1
+ import { parse } from '@babel/parser'
2
+ import { GeneratedFolder, ProjectUIDL } from '@teleporthq/teleport-types'
3
+ import { ContentTables } from '@teleporthq/teleport-shared'
4
+ import { generateSafeFileName } from '@teleporthq/teleport-plugin-next-data-source'
5
+ import uidlSample from '../../../examples/test-samples/project-sample.json'
6
+ import { createNextProjectGenerator } from '../src'
7
+ import NextTemplate from '../src/project-template'
8
+
9
+ /**
10
+ * A content preset's CATEGORY PAGE end to end: the editor exports one dynamic
11
+ * page at `/help/category/[slug]` whose `getStaticProps` / `getStaticPaths`
12
+ * are EXTERNAL resources — two functions of the `content-category-pages`
13
+ * module the blog project plugin emits — instead of a table fetch. Generated
14
+ * code is the only place that contract proves itself: the page must import
15
+ * the functions by the names teleport-shared derives, call the resolver with
16
+ * the route's slug, expose the result as the page prop the page's bindings
17
+ * read, 404 on an empty answer, and revalidate; the module must exist and read
18
+ * the preset's posts through its generated fetcher.
19
+ */
20
+
21
+ const template = JSON.parse(JSON.stringify(NextTemplate)) as GeneratedFolder
22
+
23
+ const DATA_SOURCE_ID = 'ds-help-content'
24
+ const TABLE = ContentTables.contentTablesByKey('help').posts
25
+ const MODULE = ContentTables.CONTENT_CATEGORY_PAGES_MODULE
26
+ const RESOLVER = ContentTables.contentCategoryPageResolverName('help')
27
+ const LISTER = ContentTables.contentCategoryPathsListerName('help')
28
+ const ENTITY = 'helpCenterCategory'
29
+
30
+ const dependency = { type: 'local', path: MODULE, meta: { namedImport: true } }
31
+
32
+ const buildUidl = (): ProjectUIDL => {
33
+ const uidl = JSON.parse(JSON.stringify(uidlSample)) as ProjectUIDL
34
+
35
+ uidl.dataSources = {
36
+ [DATA_SOURCE_ID]: {
37
+ id: DATA_SOURCE_ID,
38
+ name: 'Content',
39
+ type: 'teleport',
40
+ config: {
41
+ selectedTables: {
42
+ [TABLE]: {
43
+ name: TABLE,
44
+ columns: [
45
+ { name: 'id', type: 'uuid' },
46
+ { name: 'title', type: 'varchar' },
47
+ { name: 'slug', type: 'varchar' },
48
+ { name: 'status', type: 'varchar' },
49
+ { name: 'category_filter_ids', type: 'text' },
50
+ ],
51
+ },
52
+ },
53
+ },
54
+ },
55
+ } as never
56
+
57
+ // The editor emits the block for every project with a data source; the
58
+ // category page registers no fetcher of its own in it.
59
+ uidl.resources = { items: {}, resourceMappers: {} } as never
60
+
61
+ uidl.helpCenterSettings = {
62
+ categories: [
63
+ { id: 'guides', name: 'Guides', slug: 'guides', parentId: null, order: 0, children: [] },
64
+ ],
65
+ categoryPages: {
66
+ dataSourceId: DATA_SOURCE_ID,
67
+ postPath: '/help',
68
+ postUrlField: 'slug',
69
+ categoryPath: '/help/category',
70
+ },
71
+ } as never
72
+
73
+ const route = {
74
+ value: 'help/category/Help-Category',
75
+ pageId: 'TQ_help_category_page',
76
+ seo: { title: 'Category' },
77
+ pageOptions: {
78
+ stateDefinitions: {},
79
+ propDefinitions: {},
80
+ navLink: '/help/category/[slug]',
81
+ dynamicRouteAttribute: 'slug',
82
+ initialPropsData: {
83
+ exposeAs: { name: ENTITY, valuePath: ['data'] },
84
+ resource: {
85
+ name: RESOLVER,
86
+ dependency,
87
+ params: { slug: { type: 'expr', content: "params['slug']" } },
88
+ },
89
+ cache: { revalidate: 60 },
90
+ },
91
+ initialPathsData: {
92
+ exposeAs: { name: `${ENTITY}Paths`, valuePath: ['data'], itemValuePath: ['slug'] },
93
+ resource: { name: LISTER, dependency },
94
+ },
95
+ },
96
+ }
97
+
98
+ const pageNode = {
99
+ type: 'conditional',
100
+ content: {
101
+ reference: { type: 'dynamic', content: { referenceType: 'state', id: 'route' } },
102
+ value: 'help/category/Help-Category',
103
+ node: {
104
+ type: 'element',
105
+ content: {
106
+ elementType: 'container',
107
+ children: [
108
+ {
109
+ type: 'element',
110
+ content: {
111
+ elementType: 'text',
112
+ children: [
113
+ {
114
+ type: 'dynamic',
115
+ content: {
116
+ referenceType: 'prop',
117
+ id: ENTITY,
118
+ refPath: ['name'],
119
+ fallback: '',
120
+ },
121
+ },
122
+ ],
123
+ },
124
+ },
125
+ ],
126
+ },
127
+ },
128
+ },
129
+ }
130
+
131
+ uidl.root.stateDefinitions?.route.values?.push(route as never)
132
+ uidl.root.node.content.children?.push(pageNode as never)
133
+ return uidl
134
+ }
135
+
136
+ const findFile = (folder: GeneratedFolder, path: string[]): string => {
137
+ let current: GeneratedFolder | undefined = folder
138
+ for (const segment of path.slice(0, -1)) {
139
+ current = current?.subFolders.find((sub) => sub.name === segment)
140
+ }
141
+ const file = current?.files.find((entry) => entry.name === path[path.length - 1])
142
+ expect(file).toBeDefined()
143
+ return file?.content as string
144
+ }
145
+
146
+ const expectValidJs = (source: string) => {
147
+ expect(() => parse(source, { sourceType: 'module', plugins: ['jsx'] })).not.toThrow()
148
+ }
149
+
150
+ describe('a content preset category page, generated', () => {
151
+ let page: string
152
+ let module: string
153
+ let blogContext: string
154
+
155
+ beforeAll(async () => {
156
+ const generator = createNextProjectGenerator()
157
+ const output = await generator.generateProject(buildUidl(), template)
158
+ page = findFile(output, ['pages', 'help', 'category', '[slug]'])
159
+ module = findFile(output, ['content-category-pages'])
160
+ blogContext = findFile(output, ['blog-context'])
161
+ })
162
+
163
+ it('resolves the category at the slug through the generated module, as the page prop', () => {
164
+ expectValidJs(page)
165
+ expect(page).toMatch(
166
+ new RegExp(
167
+ `import \\{[\\s\\S]*\\b${RESOLVER}\\b[\\s\\S]*\\} from '${MODULE.replace('/', '\\/')}'`
168
+ )
169
+ )
170
+ expect(page).toMatch(new RegExp(`await ${RESOLVER}\\(\\{[\\s\\S]*slug: params\\['slug'\\]`))
171
+ expect(page).toMatch(
172
+ /if \(!response\?\.data\) \{\s*return \{\s*notFound: true,\s*revalidate: 60/
173
+ )
174
+ expect(page).toContain(`${ENTITY}: response?.data`)
175
+ // The page's text reads the prop the resolver exposed, however the
176
+ // generator spells the optional chain.
177
+ expect(page).toMatch(new RegExp(`props\\.${ENTITY}\\??\\.(\\['name'\\]|name)`))
178
+ })
179
+
180
+ it('lists every category slug for getStaticPaths through the module', () => {
181
+ expect(page).toMatch(
182
+ new RegExp(
183
+ `import \\{[\\s\\S]*\\b${LISTER}\\b[\\s\\S]*\\} from '${MODULE.replace('/', '\\/')}'`
184
+ )
185
+ )
186
+ expect(page).toContain(`await ${LISTER}(`)
187
+ expect(page).toContain("fallback: 'blocking'")
188
+ })
189
+
190
+ it('emits the module over the baked taxonomy and the preset’s posts fetcher', () => {
191
+ expectValidJs(module)
192
+ expect(module).toContain(`export async function ${RESOLVER}(params)`)
193
+ expect(module).toContain(`export async function ${LISTER}()`)
194
+ expect(module).toContain(
195
+ `import helpPosts from './utils/data-sources/${generateSafeFileName(
196
+ 'teleport',
197
+ TABLE,
198
+ DATA_SOURCE_ID
199
+ )}'`
200
+ )
201
+ expect(module).toContain("from '@/blog-context'")
202
+ expect(blogContext).toContain('export const HELP_CATEGORY_TREE')
203
+ expect(blogContext).toContain('export const localizeCategoryTree')
204
+ })
205
+ })