@setzkasten-cms/astro-admin 1.4.2 → 1.5.0
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.
- package/dist/api-routes/_auth-guard.d.ts +27 -3
- package/dist/api-routes/_auth-guard.js +5 -2
- package/dist/api-routes/_dev-session-secret.d.ts +8 -0
- package/dist/api-routes/_dev-session-secret.js +8 -0
- package/dist/api-routes/_github-token.js +1 -1
- package/dist/api-routes/_role-resolver.js +6 -3
- package/dist/api-routes/_session-secret.d.ts +19 -0
- package/dist/api-routes/_session-secret.js +7 -0
- package/dist/api-routes/_session-signing.d.ts +45 -0
- package/dist/api-routes/_session-signing.js +8 -0
- package/dist/api-routes/_webhook-dispatcher.js +4 -4
- package/dist/api-routes/asset-proxy.js +1 -1
- package/dist/api-routes/auth-callback.js +12 -5
- package/dist/api-routes/auth-logout.d.ts +4 -4
- package/dist/api-routes/auth-logout.js +8 -2
- package/dist/api-routes/auth-session.d.ts +6 -0
- package/dist/api-routes/auth-session.js +19 -19
- package/dist/api-routes/auth-setzkasten-login.js +14 -7
- package/dist/api-routes/catalog-add.js +59 -17
- package/dist/api-routes/catalog-export.js +14 -4
- package/dist/api-routes/config.d.ts +10 -3
- package/dist/api-routes/config.js +26 -4
- package/dist/api-routes/deploy-hook.js +8 -8
- package/dist/api-routes/editors.d.ts +1 -1
- package/dist/api-routes/editors.js +5 -2
- package/dist/api-routes/github-proxy.js +30 -8
- package/dist/api-routes/global-config.js +6 -3
- package/dist/api-routes/history-rollback.js +31 -14
- package/dist/api-routes/history-version.js +8 -6
- package/dist/api-routes/history.js +5 -2
- package/dist/api-routes/icons-local.js +1 -1
- package/dist/api-routes/init-add-section.js +150 -48
- package/dist/api-routes/init-apply.js +56 -42
- package/dist/api-routes/init-migrate.js +43 -36
- package/dist/api-routes/init-scan-page.d.ts +1 -1
- package/dist/api-routes/init-scan-page.js +59 -13
- package/dist/api-routes/init-scan.js +22 -7
- package/dist/api-routes/migrate-to-multi.js +5 -2
- package/dist/api-routes/pages.js +15 -4
- package/dist/api-routes/section-add.js +68 -16
- package/dist/api-routes/section-commit-pending.js +70 -22
- package/dist/api-routes/section-delete.js +49 -14
- package/dist/api-routes/section-duplicate.js +65 -16
- package/dist/api-routes/section-prepare-copy.js +15 -2
- package/dist/api-routes/section-prepare.js +25 -4
- package/dist/api-routes/setup-github-app-bounce.js +15 -1
- package/dist/api-routes/setup-github-app-branches.js +9 -6
- package/dist/api-routes/setup-github-app-callback.js +24 -1
- package/dist/api-routes/setup-github-app-credentials.d.ts +27 -0
- package/dist/api-routes/setup-github-app-credentials.js +43 -0
- package/dist/api-routes/setup-github-app-installed.js +22 -1
- package/dist/api-routes/setup-github-app-repos.js +5 -2
- package/dist/api-routes/setup-github-app.d.ts +4 -0
- package/dist/api-routes/setup-github-app.js +19 -2
- package/dist/api-routes/updater-register.js +7 -1
- package/dist/api-routes/webhooks-status.js +5 -2
- package/dist/api-routes/webhooks-test.js +9 -8
- package/dist/api-routes/webhooks.js +12 -14
- package/dist/api-routes/websites-add.js +5 -2
- package/dist/api-routes/websites-remove.js +5 -2
- package/dist/{chunk-ZQDGGWJP.js → chunk-5KMGSFCZ.js} +2 -2
- package/dist/{chunk-RHJONMLK.js → chunk-CDXCYYQR.js} +222 -5
- package/dist/{chunk-NKDATSPA.js → chunk-DP6RTINQ.js} +1 -1
- package/dist/chunk-KENFINT4.js +76 -0
- package/dist/chunk-ONP6BRZO.js +47 -0
- package/dist/{chunk-INIWFKQ3.js → chunk-Q5HV47DW.js} +33 -19
- package/dist/chunk-QVCW6EF3.js +26 -0
- package/dist/{chunk-K22A4ZBS.js → chunk-UHI6323G.js} +293 -174
- package/dist/{chunk-AM4DZXXM.js → chunk-UJAFZEX2.js} +76 -9
- package/package.json +12 -6
- package/src/api-routes/__tests__/_session-signing.test.ts +114 -0
- package/src/api-routes/__tests__/_session-test-helper.ts +27 -0
- package/src/api-routes/__tests__/add-section-helpers.test.ts +91 -97
- package/src/api-routes/__tests__/auth-guard.test.ts +46 -7
- package/src/api-routes/__tests__/catalog-api.test.ts +14 -6
- package/src/api-routes/__tests__/commit-trailers.test.ts +5 -5
- package/src/api-routes/__tests__/deferred-operations.test.ts +9 -12
- package/src/api-routes/__tests__/deploy-hook.test.ts +3 -8
- package/src/api-routes/__tests__/feature-gate.test.ts +1 -1
- package/src/api-routes/__tests__/github-cache.test.ts +1 -1
- package/src/api-routes/__tests__/github-token.test.ts +1 -1
- package/src/api-routes/__tests__/global-config-theme.test.ts +4 -4
- package/src/api-routes/__tests__/history-rollback.test.ts +6 -3
- package/src/api-routes/__tests__/history.test.ts +9 -6
- package/src/api-routes/__tests__/init-scan-page-resolve-config.test.ts +11 -3
- package/src/api-routes/__tests__/migrate-to-multi.test.ts +5 -1
- package/src/api-routes/__tests__/pages-meta-store.test.ts +10 -5
- package/src/api-routes/__tests__/pages.test.ts +7 -2
- package/src/api-routes/__tests__/patch-page-file.test.ts +71 -19
- package/src/api-routes/__tests__/route-registry.test.ts +11 -18
- package/src/api-routes/__tests__/scan-page-helpers.test.ts +13 -10
- package/src/api-routes/__tests__/section-management.test.ts +28 -28
- package/src/api-routes/__tests__/setup-github-app-callback.test.ts +58 -16
- package/src/api-routes/__tests__/setup-github-app-repos.test.ts +4 -5
- package/src/api-routes/__tests__/setup-github-app.test.ts +27 -7
- package/src/api-routes/__tests__/storage-config-for-request.test.ts +83 -0
- package/src/api-routes/__tests__/updater-register.test.ts +230 -0
- package/src/api-routes/__tests__/webhook-signing.test.ts +1 -1
- package/src/api-routes/__tests__/webhooks.test.ts +19 -7
- package/src/api-routes/__tests__/websites-add.test.ts +2 -1
- package/src/api-routes/__tests__/websites-remove.test.ts +2 -1
- package/src/api-routes/_auth-guard.ts +47 -15
- package/src/api-routes/_commit-trailers.ts +3 -2
- package/src/api-routes/_dev-session-secret.ts +79 -0
- package/src/api-routes/_github-token.ts +1 -1
- package/src/api-routes/_pages-meta-store.ts +2 -2
- package/src/api-routes/_role-resolver.ts +7 -5
- package/src/api-routes/_session-secret.ts +46 -0
- package/src/api-routes/_session-signing.ts +135 -0
- package/src/api-routes/_vercel-origin.ts +2 -6
- package/src/api-routes/_webhook-dispatcher.ts +12 -16
- package/src/api-routes/_website-resolver.ts +3 -10
- package/src/api-routes/auth-callback.ts +9 -5
- package/src/api-routes/auth-login.ts +5 -3
- package/src/api-routes/auth-logout.ts +18 -1
- package/src/api-routes/auth-session.ts +13 -21
- package/src/api-routes/auth-setzkasten-login.ts +12 -9
- package/src/api-routes/catalog-add.ts +89 -31
- package/src/api-routes/catalog-export.ts +30 -10
- package/src/api-routes/config.ts +39 -6
- package/src/api-routes/deploy-hook.ts +13 -11
- package/src/api-routes/editors.ts +33 -22
- package/src/api-routes/github-proxy.ts +25 -11
- package/src/api-routes/global-config.ts +103 -18
- package/src/api-routes/history-rollback.ts +41 -14
- package/src/api-routes/history-version.ts +5 -6
- package/src/api-routes/history.ts +3 -3
- package/src/api-routes/icons-local.ts +2 -2
- package/src/api-routes/init-add-section.ts +218 -88
- package/src/api-routes/init-apply.ts +71 -56
- package/src/api-routes/init-migrate.ts +54 -48
- package/src/api-routes/init-scan-page.ts +77 -30
- package/src/api-routes/init-scan.ts +19 -11
- package/src/api-routes/pages.ts +16 -11
- package/src/api-routes/section-add.ts +98 -27
- package/src/api-routes/section-commit-pending.ts +87 -34
- package/src/api-routes/section-delete.ts +76 -27
- package/src/api-routes/section-duplicate.ts +95 -28
- package/src/api-routes/section-management.ts +3 -7
- package/src/api-routes/section-prepare-copy.ts +29 -8
- package/src/api-routes/section-prepare.ts +38 -10
- package/src/api-routes/setup-github-app-bounce.ts +7 -1
- package/src/api-routes/setup-github-app-branches.ts +6 -7
- package/src/api-routes/setup-github-app-callback.ts +18 -1
- package/src/api-routes/setup-github-app-credentials.ts +55 -0
- package/src/api-routes/setup-github-app-installed.ts +12 -1
- package/src/api-routes/setup-github-app-repos.ts +2 -3
- package/src/api-routes/setup-github-app.ts +14 -5
- package/src/api-routes/updater-check.ts +6 -4
- package/src/api-routes/updater-register.ts +34 -20
- package/src/api-routes/updater-transfer.ts +8 -6
- package/src/api-routes/updater-unbind.ts +14 -10
- package/src/api-routes/webhooks-test.ts +9 -11
- package/src/api-routes/webhooks.ts +15 -19
- package/src/init/__tests__/page-level.test.ts +279 -105
- package/src/init/__tests__/page-list-coverage.test.ts +70 -70
- package/src/init/__tests__/patcher-child-component.test.ts +126 -0
- package/src/init/__tests__/patcher-edge-cases.test.ts +47 -23
- package/src/init/__tests__/patcher-mixed-content-wrapper.test.ts +16 -6
- package/src/init/__tests__/patcher-page-mode.test.ts +30 -20
- package/src/init/__tests__/section-pipeline.test.ts +102 -16
- package/src/init/astro-config-patcher.ts +4 -18
- package/src/init/astro-detector.ts +2 -7
- package/src/init/astro-section-analyzer-v2.ts +475 -193
- package/src/init/field-label-enricher.ts +6 -6
- package/src/init/template-patcher-v2.ts +490 -56
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* 3. Patcher mode:'page' (injects getSection instead of Astro.props)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { describe, it, expect, beforeAll } from 'vitest'
|
|
14
13
|
import { readFileSync, readdirSync } from 'fs'
|
|
15
|
-
import {
|
|
14
|
+
import { basename, join } from 'path'
|
|
15
|
+
import type { RepoFile } from '@setzkasten-cms/core/init'
|
|
16
|
+
import { beforeAll, describe, expect, it } from 'vitest'
|
|
17
|
+
import { extractLayoutImport, extractSectionImports } from '../../init/astro-detector'
|
|
16
18
|
import { analyzeAstroSection } from '../../init/astro-section-analyzer-v2'
|
|
17
19
|
import { patchTemplateForFields } from '../../init/template-patcher-v2'
|
|
18
|
-
import { extractSectionImports, extractLayoutImport } from '../../init/astro-detector'
|
|
19
|
-
import type { RepoFile } from '@setzkasten-cms/core/init'
|
|
20
20
|
|
|
21
21
|
const SECTIONS_DIR = join(import.meta.dirname!, '..', '..', '..', '..', '..', 'test-sections')
|
|
22
22
|
const PAGES_DIR = join(SECTIONS_DIR, 'pages')
|
|
@@ -34,9 +34,17 @@ describe('Page-level: pure inline content', () => {
|
|
|
34
34
|
|
|
35
35
|
beforeAll(async () => {
|
|
36
36
|
source = readFileSync(join(SECTIONS_DIR, 'PageInlineContent.astro'), 'utf-8')
|
|
37
|
-
section = await analyzeAstroSection(
|
|
37
|
+
section = await analyzeAstroSection(
|
|
38
|
+
source,
|
|
39
|
+
'_page_docs',
|
|
40
|
+
'docsPage',
|
|
41
|
+
'src/pages/docs/index.astro',
|
|
42
|
+
{ mode: 'page' },
|
|
43
|
+
)
|
|
38
44
|
groups = (section as any)._analyzerResult?.repeatedGroups ?? []
|
|
39
|
-
patched = await patchTemplateForFields(source, '_page_docs', section.fields, groups, {
|
|
45
|
+
patched = await patchTemplateForFields(source, '_page_docs', section.fields, groups, {
|
|
46
|
+
mode: 'page',
|
|
47
|
+
})
|
|
40
48
|
})
|
|
41
49
|
|
|
42
50
|
it('should detect fields from inline content', () => {
|
|
@@ -44,10 +52,10 @@ describe('Page-level: pure inline content', () => {
|
|
|
44
52
|
})
|
|
45
53
|
|
|
46
54
|
it('should detect text fields (headings, paragraphs, overline)', () => {
|
|
47
|
-
const textFields = section.fields.filter(f => f.type === 'text')
|
|
55
|
+
const textFields = section.fields.filter((f) => f.type === 'text')
|
|
48
56
|
expect(textFields.length).toBeGreaterThan(0)
|
|
49
57
|
// Should find the heading and description
|
|
50
|
-
const keys = textFields.map(f => f.key)
|
|
58
|
+
const keys = textFields.map((f) => f.key)
|
|
51
59
|
expect(keys).toContain('heading')
|
|
52
60
|
expect(keys).toContain('description')
|
|
53
61
|
})
|
|
@@ -95,7 +103,13 @@ describe('Page-level: mixed content (imports + inline)', () => {
|
|
|
95
103
|
beforeAll(async () => {
|
|
96
104
|
source = readFileSync(join(SECTIONS_DIR, 'PageMixedContent.astro'), 'utf-8')
|
|
97
105
|
// Analyze the page as a page-level section (same as init-scan-page.ts would)
|
|
98
|
-
section = await analyzeAstroSection(
|
|
106
|
+
section = await analyzeAstroSection(
|
|
107
|
+
source,
|
|
108
|
+
'_page_index',
|
|
109
|
+
'indexPage',
|
|
110
|
+
'src/pages/index.astro',
|
|
111
|
+
{ mode: 'page' },
|
|
112
|
+
)
|
|
99
113
|
groups = (section as any)._analyzerResult?.repeatedGroups ?? []
|
|
100
114
|
})
|
|
101
115
|
|
|
@@ -104,16 +118,17 @@ describe('Page-level: mixed content (imports + inline)', () => {
|
|
|
104
118
|
})
|
|
105
119
|
|
|
106
120
|
it('should detect inline text fields', () => {
|
|
107
|
-
const textFields = section.fields.filter(f => f.type === 'text')
|
|
121
|
+
const textFields = section.fields.filter((f) => f.type === 'text')
|
|
108
122
|
expect(textFields.length).toBeGreaterThan(0)
|
|
109
123
|
})
|
|
110
124
|
|
|
111
125
|
it('should detect inline text fields (heading, paragraph)', () => {
|
|
112
|
-
const textFields = section.fields.filter(f => f.type === 'text')
|
|
126
|
+
const textFields = section.fields.filter((f) => f.type === 'text')
|
|
113
127
|
expect(textFields.length).toBeGreaterThan(0)
|
|
114
128
|
// Should find "Was unsere Kunden sagen" or "Feedback von echten Nutzern."
|
|
115
129
|
const hasExpectedText = textFields.some(
|
|
116
|
-
f =>
|
|
130
|
+
(f) =>
|
|
131
|
+
typeof f.defaultValue === 'string' &&
|
|
117
132
|
(f.defaultValue.includes('Kunden') || f.defaultValue.includes('Feedback')),
|
|
118
133
|
)
|
|
119
134
|
expect(hasExpectedText, 'Should detect inline heading or paragraph text').toBe(true)
|
|
@@ -143,7 +158,13 @@ describe('Patcher mode: component vs page', () => {
|
|
|
143
158
|
|
|
144
159
|
beforeAll(async () => {
|
|
145
160
|
source = readFileSync(join(SECTIONS_DIR, 'PageInlineContent.astro'), 'utf-8')
|
|
146
|
-
section = await analyzeAstroSection(
|
|
161
|
+
section = await analyzeAstroSection(
|
|
162
|
+
source,
|
|
163
|
+
'_page_docs',
|
|
164
|
+
'docsPage',
|
|
165
|
+
'src/pages/docs/index.astro',
|
|
166
|
+
{ mode: 'page' },
|
|
167
|
+
)
|
|
147
168
|
groups = (section as any)._analyzerResult?.repeatedGroups ?? []
|
|
148
169
|
})
|
|
149
170
|
|
|
@@ -154,7 +175,9 @@ describe('Patcher mode: component vs page', () => {
|
|
|
154
175
|
})
|
|
155
176
|
|
|
156
177
|
it('page mode should inject getSection', async () => {
|
|
157
|
-
const patched = await patchTemplateForFields(source, '_page_docs', section.fields, groups, {
|
|
178
|
+
const patched = await patchTemplateForFields(source, '_page_docs', section.fields, groups, {
|
|
179
|
+
mode: 'page',
|
|
180
|
+
})
|
|
158
181
|
expect(patched).toContain("import { getSection } from 'setzkasten:content'")
|
|
159
182
|
expect(patched).toContain("getSection('_page_docs')")
|
|
160
183
|
expect(patched).not.toContain('Astro.props')
|
|
@@ -174,7 +197,13 @@ describe('Page-level: minimal page (no content)', () => {
|
|
|
174
197
|
`
|
|
175
198
|
|
|
176
199
|
it('should detect zero fields for a truly empty page', async () => {
|
|
177
|
-
const section = await analyzeAstroSection(
|
|
200
|
+
const section = await analyzeAstroSection(
|
|
201
|
+
minimalPage,
|
|
202
|
+
'_page_empty',
|
|
203
|
+
'emptyPage',
|
|
204
|
+
'src/pages/empty.astro',
|
|
205
|
+
{ mode: 'page' },
|
|
206
|
+
)
|
|
178
207
|
expect(section.fields).toHaveLength(0)
|
|
179
208
|
})
|
|
180
209
|
})
|
|
@@ -196,7 +225,13 @@ const skData = getSection('_page_about');
|
|
|
196
225
|
`
|
|
197
226
|
|
|
198
227
|
it('should detect alreadyIntegrated flag', async () => {
|
|
199
|
-
const section = await analyzeAstroSection(
|
|
228
|
+
const section = await analyzeAstroSection(
|
|
229
|
+
integratedPage,
|
|
230
|
+
'_page_about',
|
|
231
|
+
'aboutPage',
|
|
232
|
+
'src/pages/about.astro',
|
|
233
|
+
{ mode: 'page' },
|
|
234
|
+
)
|
|
200
235
|
expect(section.alreadyIntegrated).toBe(true)
|
|
201
236
|
})
|
|
202
237
|
})
|
|
@@ -229,18 +264,24 @@ describe('Full scan flow: mixed page (imports + inline)', () => {
|
|
|
229
264
|
imports = extractSectionImports(source, 'src/pages/index.astro', repoFiles, '')
|
|
230
265
|
|
|
231
266
|
// Step 2: page-level analysis (same as init-scan-page.ts after the imports loop)
|
|
232
|
-
pageSection = await analyzeAstroSection(
|
|
267
|
+
pageSection = await analyzeAstroSection(
|
|
268
|
+
source,
|
|
269
|
+
'_page_index',
|
|
270
|
+
'indexPage',
|
|
271
|
+
'src/pages/index.astro',
|
|
272
|
+
{ mode: 'page' },
|
|
273
|
+
)
|
|
233
274
|
})
|
|
234
275
|
|
|
235
276
|
it('should find imported section components via extractSectionImports', () => {
|
|
236
277
|
expect(imports.length).toBeGreaterThan(0)
|
|
237
|
-
const componentNames = imports.map(i => i.componentName)
|
|
278
|
+
const componentNames = imports.map((i) => i.componentName)
|
|
238
279
|
expect(componentNames).toContain('HeroSection')
|
|
239
280
|
expect(componentNames).toContain('FeaturesSection')
|
|
240
281
|
})
|
|
241
282
|
|
|
242
283
|
it('should resolve section keys from imports', () => {
|
|
243
|
-
const keys = imports.map(i => i.sectionKey)
|
|
284
|
+
const keys = imports.map((i) => i.sectionKey)
|
|
244
285
|
expect(keys).toContain('hero')
|
|
245
286
|
expect(keys).toContain('features')
|
|
246
287
|
})
|
|
@@ -255,7 +296,8 @@ describe('Full scan flow: mixed page (imports + inline)', () => {
|
|
|
255
296
|
expect(pageSection.fields.length).toBeGreaterThan(0)
|
|
256
297
|
// Page-level should find the inline "Was unsere Kunden sagen" heading
|
|
257
298
|
const hasInlineContent = pageSection.fields.some(
|
|
258
|
-
f =>
|
|
299
|
+
(f) =>
|
|
300
|
+
typeof f.defaultValue === 'string' &&
|
|
259
301
|
(f.defaultValue.includes('Kunden') || f.defaultValue.includes('Feedback')),
|
|
260
302
|
)
|
|
261
303
|
expect(hasInlineContent, 'Page-level should find inline text content').toBe(true)
|
|
@@ -264,7 +306,7 @@ describe('Full scan flow: mixed page (imports + inline)', () => {
|
|
|
264
306
|
it('should produce non-overlapping results (imports vs inline)', () => {
|
|
265
307
|
// Import keys are "hero", "features" — page-level key is "_page_index"
|
|
266
308
|
// They should never collide
|
|
267
|
-
const importKeys = new Set(imports.map(i => i.sectionKey))
|
|
309
|
+
const importKeys = new Set(imports.map((i) => i.sectionKey))
|
|
268
310
|
expect(importKeys.has(pageSection.key)).toBe(false)
|
|
269
311
|
})
|
|
270
312
|
|
|
@@ -296,7 +338,13 @@ describe('Full scan flow: pure inline page (no imports)', () => {
|
|
|
296
338
|
]
|
|
297
339
|
|
|
298
340
|
imports = extractSectionImports(source, 'src/pages/docs/index.astro', repoFiles, '')
|
|
299
|
-
pageSection = await analyzeAstroSection(
|
|
341
|
+
pageSection = await analyzeAstroSection(
|
|
342
|
+
source,
|
|
343
|
+
'_page_docs',
|
|
344
|
+
'docsPage',
|
|
345
|
+
'src/pages/docs/index.astro',
|
|
346
|
+
{ mode: 'page' },
|
|
347
|
+
)
|
|
300
348
|
})
|
|
301
349
|
|
|
302
350
|
it('should find zero section imports for a pure inline page', () => {
|
|
@@ -308,7 +356,7 @@ describe('Full scan flow: pure inline page (no imports)', () => {
|
|
|
308
356
|
})
|
|
309
357
|
|
|
310
358
|
it('should detect heading and description fields', () => {
|
|
311
|
-
const keys = pageSection.fields.map(f => f.key)
|
|
359
|
+
const keys = pageSection.fields.map((f) => f.key)
|
|
312
360
|
expect(keys).toContain('heading')
|
|
313
361
|
expect(keys).toContain('description')
|
|
314
362
|
})
|
|
@@ -327,23 +375,34 @@ describe('Page-level: .map()-only arrays detected', () => {
|
|
|
327
375
|
beforeAll(async () => {
|
|
328
376
|
const source = readFileSync(join(SECTIONS_DIR, 'PageInlineContent.astro'), 'utf-8')
|
|
329
377
|
// Page mode: .map()-only arrays should be kept
|
|
330
|
-
section = await analyzeAstroSection(
|
|
378
|
+
section = await analyzeAstroSection(
|
|
379
|
+
source,
|
|
380
|
+
'_page_docs',
|
|
381
|
+
'docsPage',
|
|
382
|
+
'src/pages/docs/index.astro',
|
|
383
|
+
{ mode: 'page' },
|
|
384
|
+
)
|
|
331
385
|
// Component mode (default): .map()-only arrays should be filtered
|
|
332
|
-
sectionComponent = await analyzeAstroSection(
|
|
386
|
+
sectionComponent = await analyzeAstroSection(
|
|
387
|
+
source,
|
|
388
|
+
'docs',
|
|
389
|
+
'DocsSection',
|
|
390
|
+
'src/components/docs.astro',
|
|
391
|
+
)
|
|
333
392
|
})
|
|
334
393
|
|
|
335
394
|
it('should detect the sections array on page mode', () => {
|
|
336
|
-
const arrayFields = section.fields.filter(f => f.type === 'array')
|
|
395
|
+
const arrayFields = section.fields.filter((f) => f.type === 'array')
|
|
337
396
|
expect(arrayFields.length).toBeGreaterThan(0)
|
|
338
397
|
})
|
|
339
398
|
|
|
340
399
|
it('should detect "sections" variable as a field on page mode', () => {
|
|
341
|
-
const keys = section.fields.map(f => f.key)
|
|
400
|
+
const keys = section.fields.map((f) => f.key)
|
|
342
401
|
expect(keys).toContain('sections')
|
|
343
402
|
})
|
|
344
403
|
|
|
345
404
|
it('should NOT detect "sections" variable in component mode (.map()-only filter)', () => {
|
|
346
|
-
const keys = sectionComponent.fields.map(f => f.key)
|
|
405
|
+
const keys = sectionComponent.fields.map((f) => f.key)
|
|
347
406
|
expect(keys).not.toContain('sections')
|
|
348
407
|
})
|
|
349
408
|
})
|
|
@@ -370,15 +429,33 @@ const title = 'Meine Seite';
|
|
|
370
429
|
`
|
|
371
430
|
|
|
372
431
|
it('should not remove export const prerender', async () => {
|
|
373
|
-
const section = await analyzeAstroSection(
|
|
374
|
-
|
|
432
|
+
const section = await analyzeAstroSection(
|
|
433
|
+
pageWithPrerender,
|
|
434
|
+
'_page_test',
|
|
435
|
+
'testPage',
|
|
436
|
+
'test.astro',
|
|
437
|
+
{ mode: 'page' },
|
|
438
|
+
)
|
|
439
|
+
const patched = await patchTemplateForFields(
|
|
440
|
+
pageWithPrerender,
|
|
441
|
+
'_page_test',
|
|
442
|
+
section.fields,
|
|
443
|
+
[],
|
|
444
|
+
{ mode: 'page' },
|
|
445
|
+
)
|
|
375
446
|
expect(patched).toContain('export const prerender = true')
|
|
376
447
|
expect(patched).not.toMatch(/^export\s*$/m) // no bare "export " line
|
|
377
448
|
})
|
|
378
449
|
|
|
379
450
|
it('should not detect prerender as a content field', async () => {
|
|
380
|
-
const section = await analyzeAstroSection(
|
|
381
|
-
|
|
451
|
+
const section = await analyzeAstroSection(
|
|
452
|
+
pageWithPrerender,
|
|
453
|
+
'_page_test',
|
|
454
|
+
'testPage',
|
|
455
|
+
'test.astro',
|
|
456
|
+
{ mode: 'page' },
|
|
457
|
+
)
|
|
458
|
+
const keys = section.fields.map((f) => f.key)
|
|
382
459
|
expect(keys).not.toContain('prerender')
|
|
383
460
|
})
|
|
384
461
|
})
|
|
@@ -412,7 +489,13 @@ const SECTION_COMPONENTS: Record<string, any> = {
|
|
|
412
489
|
`
|
|
413
490
|
|
|
414
491
|
it('should detect alreadyIntegrated and skip adoption', async () => {
|
|
415
|
-
const section = await analyzeAstroSection(
|
|
492
|
+
const section = await analyzeAstroSection(
|
|
493
|
+
integratedPage,
|
|
494
|
+
'_page_index',
|
|
495
|
+
'indexPage',
|
|
496
|
+
'test.astro',
|
|
497
|
+
{ mode: 'page' },
|
|
498
|
+
)
|
|
416
499
|
expect(section.alreadyIntegrated).toBe(true)
|
|
417
500
|
})
|
|
418
501
|
})
|
|
@@ -471,7 +554,13 @@ describe('Layout regions: header and footer analysis', () => {
|
|
|
471
554
|
expect(headerHtml).toBeTruthy()
|
|
472
555
|
|
|
473
556
|
const regionSource = `---\n---\n\n${headerHtml}`
|
|
474
|
-
const section = await analyzeAstroSection(
|
|
557
|
+
const section = await analyzeAstroSection(
|
|
558
|
+
regionSource,
|
|
559
|
+
'_layout_header',
|
|
560
|
+
'header',
|
|
561
|
+
'test-layout.astro',
|
|
562
|
+
{ mode: 'page' },
|
|
563
|
+
)
|
|
475
564
|
expect(section.fields.length).toBeGreaterThan(0)
|
|
476
565
|
})
|
|
477
566
|
|
|
@@ -480,21 +569,36 @@ describe('Layout regions: header and footer analysis', () => {
|
|
|
480
569
|
expect(footerHtml).toBeTruthy()
|
|
481
570
|
|
|
482
571
|
const regionSource = `---\n---\n\n${footerHtml}`
|
|
483
|
-
const section = await analyzeAstroSection(
|
|
572
|
+
const section = await analyzeAstroSection(
|
|
573
|
+
regionSource,
|
|
574
|
+
'_layout_footer',
|
|
575
|
+
'footer',
|
|
576
|
+
'test-layout.astro',
|
|
577
|
+
{ mode: 'page' },
|
|
578
|
+
)
|
|
484
579
|
expect(section.fields.length).toBeGreaterThan(0)
|
|
485
580
|
})
|
|
486
581
|
|
|
487
582
|
it('should detect text fields in header (brand name, nav links)', async () => {
|
|
488
583
|
const headerHtml = extractRegionHtml(layoutSource, 'header')!
|
|
489
584
|
const regionSource = `---\n---\n\n${headerHtml}`
|
|
490
|
-
const section = await analyzeAstroSection(
|
|
585
|
+
const section = await analyzeAstroSection(
|
|
586
|
+
regionSource,
|
|
587
|
+
'_layout_header',
|
|
588
|
+
'header',
|
|
589
|
+
'test-layout.astro',
|
|
590
|
+
{ mode: 'page' },
|
|
591
|
+
)
|
|
491
592
|
|
|
492
|
-
const textFields = section.fields.filter(f => f.type === 'text')
|
|
593
|
+
const textFields = section.fields.filter((f) => f.type === 'text')
|
|
493
594
|
expect(textFields.length).toBeGreaterThan(0)
|
|
494
595
|
// Should find brand name or nav link text
|
|
495
596
|
const hasNavContent = textFields.some(
|
|
496
|
-
f =>
|
|
497
|
-
|
|
597
|
+
(f) =>
|
|
598
|
+
typeof f.defaultValue === 'string' &&
|
|
599
|
+
(f.defaultValue.includes('MeinBrand') ||
|
|
600
|
+
f.defaultValue.includes('Features') ||
|
|
601
|
+
f.defaultValue.includes('Jetzt starten')),
|
|
498
602
|
)
|
|
499
603
|
expect(hasNavContent, 'Should detect header text content').toBe(true)
|
|
500
604
|
})
|
|
@@ -502,13 +606,22 @@ describe('Layout regions: header and footer analysis', () => {
|
|
|
502
606
|
it('should detect text fields in footer (brand, tagline, copyright)', async () => {
|
|
503
607
|
const footerHtml = extractRegionHtml(layoutSource, 'footer')!
|
|
504
608
|
const regionSource = `---\n---\n\n${footerHtml}`
|
|
505
|
-
const section = await analyzeAstroSection(
|
|
609
|
+
const section = await analyzeAstroSection(
|
|
610
|
+
regionSource,
|
|
611
|
+
'_layout_footer',
|
|
612
|
+
'footer',
|
|
613
|
+
'test-layout.astro',
|
|
614
|
+
{ mode: 'page' },
|
|
615
|
+
)
|
|
506
616
|
|
|
507
|
-
const textFields = section.fields.filter(f => f.type === 'text')
|
|
617
|
+
const textFields = section.fields.filter((f) => f.type === 'text')
|
|
508
618
|
expect(textFields.length).toBeGreaterThan(0)
|
|
509
619
|
const hasFooterContent = textFields.some(
|
|
510
|
-
f =>
|
|
511
|
-
|
|
620
|
+
(f) =>
|
|
621
|
+
typeof f.defaultValue === 'string' &&
|
|
622
|
+
(f.defaultValue.includes('MeinBrand') ||
|
|
623
|
+
f.defaultValue.includes('CMS') ||
|
|
624
|
+
f.defaultValue.includes('Rechte')),
|
|
512
625
|
)
|
|
513
626
|
expect(hasFooterContent, 'Should detect footer text content').toBe(true)
|
|
514
627
|
})
|
|
@@ -521,7 +634,9 @@ describe('Layout regions: header and footer analysis', () => {
|
|
|
521
634
|
// ---------------------------------------------------------------------------
|
|
522
635
|
|
|
523
636
|
describe('Page sweep: all pages in pages/', () => {
|
|
524
|
-
const pageFiles = readdirSync(PAGES_DIR)
|
|
637
|
+
const pageFiles = readdirSync(PAGES_DIR)
|
|
638
|
+
.filter((f) => f.endsWith('.astro'))
|
|
639
|
+
.sort()
|
|
525
640
|
|
|
526
641
|
for (const file of pageFiles) {
|
|
527
642
|
describe(file, () => {
|
|
@@ -530,8 +645,13 @@ describe('Page sweep: all pages in pages/', () => {
|
|
|
530
645
|
|
|
531
646
|
beforeAll(async () => {
|
|
532
647
|
source = readFileSync(join(PAGES_DIR, file), 'utf-8')
|
|
533
|
-
const pageKey = `_page_${basename(file, '.astro')
|
|
534
|
-
|
|
648
|
+
const pageKey = `_page_${basename(file, '.astro')
|
|
649
|
+
.replace(/([A-Z])/g, '_$1')
|
|
650
|
+
.toLowerCase()
|
|
651
|
+
.replace(/^_/, '')}`
|
|
652
|
+
section = await analyzeAstroSection(source, pageKey, 'testPage', `src/pages/${file}`, {
|
|
653
|
+
mode: 'page',
|
|
654
|
+
})
|
|
535
655
|
})
|
|
536
656
|
|
|
537
657
|
it('should not crash during analysis', () => {
|
|
@@ -567,22 +687,26 @@ describe('DocsIndexPage: nested sections[].items[] detection', () => {
|
|
|
567
687
|
beforeAll(async () => {
|
|
568
688
|
source = readFileSync(join(PAGES_DIR, 'DocsIndexPage.astro'), 'utf-8')
|
|
569
689
|
section = await analyzeAstroSection(
|
|
570
|
-
source,
|
|
690
|
+
source,
|
|
691
|
+
'_page_docs',
|
|
692
|
+
'docsPage',
|
|
693
|
+
'src/pages/docs/index.astro',
|
|
694
|
+
{ mode: 'page' },
|
|
571
695
|
)
|
|
572
696
|
})
|
|
573
697
|
|
|
574
698
|
it('should detect the outer sections array', () => {
|
|
575
|
-
const keys = section.fields.map(f => f.key)
|
|
699
|
+
const keys = section.fields.map((f) => f.key)
|
|
576
700
|
expect(keys).toContain('sections')
|
|
577
701
|
})
|
|
578
702
|
|
|
579
703
|
it('should detect sections as an array field', () => {
|
|
580
|
-
const sectionsField = section.fields.find(f => f.key === 'sections')
|
|
704
|
+
const sectionsField = section.fields.find((f) => f.key === 'sections')
|
|
581
705
|
expect(sectionsField?.type).toBe('array')
|
|
582
706
|
})
|
|
583
707
|
|
|
584
708
|
it('should detect items inside each section as an array field (not [object Object])', () => {
|
|
585
|
-
const sectionsField = section.fields.find(f => f.key === 'sections')
|
|
709
|
+
const sectionsField = section.fields.find((f) => f.key === 'sections')
|
|
586
710
|
const rows = sectionsField?.defaultValue as Array<Record<string, unknown>> | undefined
|
|
587
711
|
expect(Array.isArray(rows)).toBe(true)
|
|
588
712
|
if (rows) {
|
|
@@ -600,7 +724,7 @@ describe('DocsIndexPage: nested sections[].items[] detection', () => {
|
|
|
600
724
|
const allValues = JSON.stringify(section.fields)
|
|
601
725
|
expect(allValues).not.toContain('[object Object]')
|
|
602
726
|
// At least one item object should have a title key
|
|
603
|
-
const sectionsField = section.fields.find(f => f.key === 'sections')
|
|
727
|
+
const sectionsField = section.fields.find((f) => f.key === 'sections')
|
|
604
728
|
const rows = sectionsField?.defaultValue as Array<Record<string, unknown>> | undefined
|
|
605
729
|
if (rows?.[0]) {
|
|
606
730
|
const items = rows[0]['items'] as Array<Record<string, unknown>> | undefined
|
|
@@ -622,17 +746,21 @@ describe('EventSchedulePage: nested days[].sessions[] detection', () => {
|
|
|
622
746
|
beforeAll(async () => {
|
|
623
747
|
source = readFileSync(join(PAGES_DIR, 'EventSchedulePage.astro'), 'utf-8')
|
|
624
748
|
section = await analyzeAstroSection(
|
|
625
|
-
source,
|
|
749
|
+
source,
|
|
750
|
+
'_page_event_schedule',
|
|
751
|
+
'eventPage',
|
|
752
|
+
'src/pages/event.astro',
|
|
753
|
+
{ mode: 'page' },
|
|
626
754
|
)
|
|
627
755
|
})
|
|
628
756
|
|
|
629
757
|
it('should detect the outer days array', () => {
|
|
630
|
-
const keys = section.fields.map(f => f.key)
|
|
758
|
+
const keys = section.fields.map((f) => f.key)
|
|
631
759
|
expect(keys).toContain('days')
|
|
632
760
|
})
|
|
633
761
|
|
|
634
762
|
it('should detect sessions inside each day without [object Object]', () => {
|
|
635
|
-
const daysField = section.fields.find(f => f.key === 'days')
|
|
763
|
+
const daysField = section.fields.find((f) => f.key === 'days')
|
|
636
764
|
const rows = daysField?.defaultValue as Array<Record<string, unknown>> | undefined
|
|
637
765
|
expect(Array.isArray(rows)).toBe(true)
|
|
638
766
|
if (rows) {
|
|
@@ -644,7 +772,7 @@ describe('EventSchedulePage: nested days[].sessions[] detection', () => {
|
|
|
644
772
|
})
|
|
645
773
|
|
|
646
774
|
it('should detect session sub-fields (time, title, speaker)', () => {
|
|
647
|
-
const daysField = section.fields.find(f => f.key === 'days')
|
|
775
|
+
const daysField = section.fields.find((f) => f.key === 'days')
|
|
648
776
|
const rows = daysField?.defaultValue as Array<Record<string, unknown>> | undefined
|
|
649
777
|
const sessions = rows?.[0]?.['sessions'] as Array<Record<string, unknown>> | undefined
|
|
650
778
|
expect(sessions?.[0]).toHaveProperty('title')
|
|
@@ -652,8 +780,10 @@ describe('EventSchedulePage: nested days[].sessions[] detection', () => {
|
|
|
652
780
|
})
|
|
653
781
|
|
|
654
782
|
it('should also detect frontmatter scalar vars (eventTitle, eventLocation)', () => {
|
|
655
|
-
const keys = section.fields.map(f => f.key)
|
|
656
|
-
const hasScalars = keys.some(
|
|
783
|
+
const keys = section.fields.map((f) => f.key)
|
|
784
|
+
const hasScalars = keys.some(
|
|
785
|
+
(k) => k === 'eventTitle' || k === 'eventLocation' || k === 'eventDate',
|
|
786
|
+
)
|
|
657
787
|
expect(hasScalars).toBe(true)
|
|
658
788
|
})
|
|
659
789
|
})
|
|
@@ -669,17 +799,21 @@ describe('ChangelogPage: nested releases[].changes[] detection', () => {
|
|
|
669
799
|
beforeAll(async () => {
|
|
670
800
|
source = readFileSync(join(PAGES_DIR, 'ChangelogPage.astro'), 'utf-8')
|
|
671
801
|
section = await analyzeAstroSection(
|
|
672
|
-
source,
|
|
802
|
+
source,
|
|
803
|
+
'_page_changelog',
|
|
804
|
+
'changelogPage',
|
|
805
|
+
'src/pages/changelog.astro',
|
|
806
|
+
{ mode: 'page' },
|
|
673
807
|
)
|
|
674
808
|
})
|
|
675
809
|
|
|
676
810
|
it('should detect the releases array', () => {
|
|
677
|
-
const keys = section.fields.map(f => f.key)
|
|
811
|
+
const keys = section.fields.map((f) => f.key)
|
|
678
812
|
expect(keys).toContain('releases')
|
|
679
813
|
})
|
|
680
814
|
|
|
681
815
|
it('should detect changes inside each release without [object Object]', () => {
|
|
682
|
-
const releasesField = section.fields.find(f => f.key === 'releases')
|
|
816
|
+
const releasesField = section.fields.find((f) => f.key === 'releases')
|
|
683
817
|
const rows = releasesField?.defaultValue as Array<Record<string, unknown>> | undefined
|
|
684
818
|
expect(Array.isArray(rows)).toBe(true)
|
|
685
819
|
if (rows) {
|
|
@@ -691,7 +825,7 @@ describe('ChangelogPage: nested releases[].changes[] detection', () => {
|
|
|
691
825
|
})
|
|
692
826
|
|
|
693
827
|
it('should detect change sub-fields (type, text)', () => {
|
|
694
|
-
const releasesField = section.fields.find(f => f.key === 'releases')
|
|
828
|
+
const releasesField = section.fields.find((f) => f.key === 'releases')
|
|
695
829
|
const rows = releasesField?.defaultValue as Array<Record<string, unknown>> | undefined
|
|
696
830
|
const changes = rows?.[0]?.['changes'] as Array<Record<string, unknown>> | undefined
|
|
697
831
|
expect(changes?.[0]).toHaveProperty('text')
|
|
@@ -710,17 +844,21 @@ describe('BlogListPage: posts array and featuredPost object', () => {
|
|
|
710
844
|
beforeAll(async () => {
|
|
711
845
|
source = readFileSync(join(PAGES_DIR, 'BlogListPage.astro'), 'utf-8')
|
|
712
846
|
section = await analyzeAstroSection(
|
|
713
|
-
source,
|
|
847
|
+
source,
|
|
848
|
+
'_page_blog',
|
|
849
|
+
'blogPage',
|
|
850
|
+
'src/pages/blog/index.astro',
|
|
851
|
+
{ mode: 'page' },
|
|
714
852
|
)
|
|
715
853
|
})
|
|
716
854
|
|
|
717
855
|
it('should detect the posts array', () => {
|
|
718
|
-
const keys = section.fields.map(f => f.key)
|
|
856
|
+
const keys = section.fields.map((f) => f.key)
|
|
719
857
|
expect(keys).toContain('posts')
|
|
720
858
|
})
|
|
721
859
|
|
|
722
860
|
it('should detect posts as an array-of-objects with expected sub-fields', () => {
|
|
723
|
-
const postsField = section.fields.find(f => f.key === 'posts')
|
|
861
|
+
const postsField = section.fields.find((f) => f.key === 'posts')
|
|
724
862
|
expect(postsField?.type).toBe('array')
|
|
725
863
|
const items = postsField?.defaultValue as Array<Record<string, unknown>> | undefined
|
|
726
864
|
expect(items?.[0]).toHaveProperty('title')
|
|
@@ -732,9 +870,11 @@ describe('BlogListPage: posts array and featuredPost object', () => {
|
|
|
732
870
|
})
|
|
733
871
|
|
|
734
872
|
it('should detect featuredPost or its scalar fields', () => {
|
|
735
|
-
const keys = section.fields.map(f => f.key)
|
|
873
|
+
const keys = section.fields.map((f) => f.key)
|
|
736
874
|
// Either as a single object field or its sub-fields are detected
|
|
737
|
-
const hasFeatured = keys.some(
|
|
875
|
+
const hasFeatured = keys.some(
|
|
876
|
+
(k) => k.toLowerCase().includes('featured') || k.toLowerCase().includes('post'),
|
|
877
|
+
)
|
|
738
878
|
expect(hasFeatured).toBe(true)
|
|
739
879
|
})
|
|
740
880
|
})
|
|
@@ -750,22 +890,26 @@ describe('PricingPage: plans with nested features[] and table', () => {
|
|
|
750
890
|
beforeAll(async () => {
|
|
751
891
|
source = readFileSync(join(PAGES_DIR, 'PricingPage.astro'), 'utf-8')
|
|
752
892
|
section = await analyzeAstroSection(
|
|
753
|
-
source,
|
|
893
|
+
source,
|
|
894
|
+
'_page_pricing',
|
|
895
|
+
'pricingPage',
|
|
896
|
+
'src/pages/pricing.astro',
|
|
897
|
+
{ mode: 'page' },
|
|
754
898
|
)
|
|
755
899
|
})
|
|
756
900
|
|
|
757
901
|
it('should detect the plans array', () => {
|
|
758
|
-
const keys = section.fields.map(f => f.key)
|
|
902
|
+
const keys = section.fields.map((f) => f.key)
|
|
759
903
|
expect(keys).toContain('plans')
|
|
760
904
|
})
|
|
761
905
|
|
|
762
906
|
it('should not contain [object Object] in plans or nested features', () => {
|
|
763
|
-
const plansField = section.fields.find(f => f.key === 'plans')
|
|
907
|
+
const plansField = section.fields.find((f) => f.key === 'plans')
|
|
764
908
|
expect(JSON.stringify(plansField)).not.toContain('[object Object]')
|
|
765
909
|
})
|
|
766
910
|
|
|
767
911
|
it('should detect faqItems array', () => {
|
|
768
|
-
const keys = section.fields.map(f => f.key)
|
|
912
|
+
const keys = section.fields.map((f) => f.key)
|
|
769
913
|
expect(keys).toContain('faqItems')
|
|
770
914
|
})
|
|
771
915
|
|
|
@@ -785,18 +929,22 @@ describe('LandingIndexPage: inline maps and steps array', () => {
|
|
|
785
929
|
beforeAll(async () => {
|
|
786
930
|
source = readFileSync(join(PAGES_DIR, 'LandingIndexPage.astro'), 'utf-8')
|
|
787
931
|
section = await analyzeAstroSection(
|
|
788
|
-
source,
|
|
932
|
+
source,
|
|
933
|
+
'_page_index',
|
|
934
|
+
'indexPage',
|
|
935
|
+
'src/pages/index.astro',
|
|
936
|
+
{ mode: 'page' },
|
|
789
937
|
)
|
|
790
938
|
})
|
|
791
939
|
|
|
792
940
|
it('should detect frontmatter scalar vars (headline, ctaText)', () => {
|
|
793
|
-
const keys = section.fields.map(f => f.key)
|
|
794
|
-
const hasHero = keys.some(k => k === 'headline' || k === 'ctaText' || k === 'subheadline')
|
|
941
|
+
const keys = section.fields.map((f) => f.key)
|
|
942
|
+
const hasHero = keys.some((k) => k === 'headline' || k === 'ctaText' || k === 'subheadline')
|
|
795
943
|
expect(hasHero).toBe(true)
|
|
796
944
|
})
|
|
797
945
|
|
|
798
946
|
it('should detect the steps array', () => {
|
|
799
|
-
const keys = section.fields.map(f => f.key)
|
|
947
|
+
const keys = section.fields.map((f) => f.key)
|
|
800
948
|
expect(keys).toContain('steps')
|
|
801
949
|
})
|
|
802
950
|
|
|
@@ -816,23 +964,27 @@ describe('AboutPage: team, milestones, inline values', () => {
|
|
|
816
964
|
beforeAll(async () => {
|
|
817
965
|
source = readFileSync(join(PAGES_DIR, 'AboutPage.astro'), 'utf-8')
|
|
818
966
|
section = await analyzeAstroSection(
|
|
819
|
-
source,
|
|
967
|
+
source,
|
|
968
|
+
'_page_about',
|
|
969
|
+
'aboutPage',
|
|
970
|
+
'src/pages/about.astro',
|
|
971
|
+
{ mode: 'page' },
|
|
820
972
|
)
|
|
821
973
|
})
|
|
822
974
|
|
|
823
975
|
it('should detect the team array', () => {
|
|
824
|
-
const keys = section.fields.map(f => f.key)
|
|
976
|
+
const keys = section.fields.map((f) => f.key)
|
|
825
977
|
expect(keys).toContain('team')
|
|
826
978
|
})
|
|
827
979
|
|
|
828
980
|
it('should detect team members as objects with name/role/bio', () => {
|
|
829
|
-
const teamField = section.fields.find(f => f.key === 'team')
|
|
981
|
+
const teamField = section.fields.find((f) => f.key === 'team')
|
|
830
982
|
const items = teamField?.defaultValue as Array<Record<string, unknown>> | undefined
|
|
831
983
|
expect(items?.[0]).toHaveProperty('name')
|
|
832
984
|
})
|
|
833
985
|
|
|
834
986
|
it('should detect the milestones array', () => {
|
|
835
|
-
const keys = section.fields.map(f => f.key)
|
|
987
|
+
const keys = section.fields.map((f) => f.key)
|
|
836
988
|
expect(keys).toContain('milestones')
|
|
837
989
|
})
|
|
838
990
|
|
|
@@ -854,9 +1006,9 @@ function hasStructuralObjectString(value: unknown): boolean {
|
|
|
854
1006
|
// A string that IS exactly [object Object] or a comma-joined list of them is structural
|
|
855
1007
|
return /^\[object Object\](,\[object Object\])*$/.test(value.trim())
|
|
856
1008
|
}
|
|
857
|
-
if (Array.isArray(value)) return value.some(item => hasStructuralObjectString(item))
|
|
1009
|
+
if (Array.isArray(value)) return value.some((item) => hasStructuralObjectString(item))
|
|
858
1010
|
if (value !== null && typeof value === 'object') {
|
|
859
|
-
return Object.values(value as object).some(v => hasStructuralObjectString(v))
|
|
1011
|
+
return Object.values(value as object).some((v) => hasStructuralObjectString(v))
|
|
860
1012
|
}
|
|
861
1013
|
return false
|
|
862
1014
|
}
|
|
@@ -892,17 +1044,20 @@ describe('Docs architecture page: table row detection', () => {
|
|
|
892
1044
|
beforeAll(async () => {
|
|
893
1045
|
source = readFileSync(join(PAGES_DIR, 'DocsArchitecturePage.astro'), 'utf-8')
|
|
894
1046
|
section = await analyzeAstroSection(
|
|
895
|
-
source,
|
|
896
|
-
'
|
|
1047
|
+
source,
|
|
1048
|
+
'_page_docs_architecture',
|
|
1049
|
+
'architecturePage',
|
|
1050
|
+
'src/pages/docs/architecture.astro',
|
|
1051
|
+
{ mode: 'page' },
|
|
897
1052
|
)
|
|
898
1053
|
})
|
|
899
1054
|
|
|
900
1055
|
it('should detect the table as an array field', () => {
|
|
901
|
-
const arrayFields = section.fields.filter(f => f.type === 'array')
|
|
1056
|
+
const arrayFields = section.fields.filter((f) => f.type === 'array')
|
|
902
1057
|
expect(arrayFields.length).toBeGreaterThan(0)
|
|
903
1058
|
// Should find an array with 3 entries (the 3 tbody rows)
|
|
904
|
-
const tableField = arrayFields.find(
|
|
905
|
-
Array.isArray(f.defaultValue) && f.defaultValue.length === 3,
|
|
1059
|
+
const tableField = arrayFields.find(
|
|
1060
|
+
(f) => Array.isArray(f.defaultValue) && f.defaultValue.length === 3,
|
|
906
1061
|
)
|
|
907
1062
|
expect(tableField).toBeDefined()
|
|
908
1063
|
})
|
|
@@ -924,14 +1079,14 @@ describe('Docs architecture page: table row detection', () => {
|
|
|
924
1079
|
|
|
925
1080
|
it('should label the table field "Tabellen-Zeilen"', () => {
|
|
926
1081
|
const tableField = section.fields.find(
|
|
927
|
-
f => f.type === 'array' && Array.isArray(f.defaultValue) && f.defaultValue.length === 3,
|
|
1082
|
+
(f) => f.type === 'array' && Array.isArray(f.defaultValue) && f.defaultValue.length === 3,
|
|
928
1083
|
)
|
|
929
1084
|
expect(tableField?.label).toBe('Tabellen-Zeilen')
|
|
930
1085
|
})
|
|
931
1086
|
|
|
932
1087
|
it('should include td text in defaultValue', () => {
|
|
933
1088
|
const tableField = section.fields.find(
|
|
934
|
-
f => f.type === 'array' && Array.isArray(f.defaultValue) && f.defaultValue.length === 3,
|
|
1089
|
+
(f) => f.type === 'array' && Array.isArray(f.defaultValue) && f.defaultValue.length === 3,
|
|
935
1090
|
)
|
|
936
1091
|
const rows = tableField?.defaultValue as Array<Record<string, unknown>>
|
|
937
1092
|
// First row should contain "ContentRepository"
|
|
@@ -940,7 +1095,7 @@ describe('Docs architecture page: table row detection', () => {
|
|
|
940
1095
|
})
|
|
941
1096
|
|
|
942
1097
|
it('should also detect headings outside the table', () => {
|
|
943
|
-
const headings = section.fields.filter(f => f.key.startsWith('heading'))
|
|
1098
|
+
const headings = section.fields.filter((f) => f.key.startsWith('heading'))
|
|
944
1099
|
expect(headings.length).toBeGreaterThan(0)
|
|
945
1100
|
})
|
|
946
1101
|
})
|
|
@@ -959,30 +1114,37 @@ describe('Docs installation page: CodeBlock import handling', () => {
|
|
|
959
1114
|
})
|
|
960
1115
|
|
|
961
1116
|
it('should NOT detect CodeBlock as a section import', () => {
|
|
962
|
-
const imports = extractSectionImports(
|
|
963
|
-
|
|
964
|
-
)
|
|
965
|
-
const codeBlockImport = imports.find(i => i.componentName === 'CodeBlock')
|
|
1117
|
+
const imports = extractSectionImports(source, 'src/pages/docs/installation.astro', [], '')
|
|
1118
|
+
const codeBlockImport = imports.find((i) => i.componentName === 'CodeBlock')
|
|
966
1119
|
expect(codeBlockImport).toBeUndefined()
|
|
967
1120
|
})
|
|
968
1121
|
|
|
969
1122
|
it('should detect page-level headings on installation page', async () => {
|
|
970
1123
|
const section = await analyzeAstroSection(
|
|
971
|
-
source,
|
|
972
|
-
'
|
|
1124
|
+
source,
|
|
1125
|
+
'_page_docs_installation',
|
|
1126
|
+
'installationPage',
|
|
1127
|
+
'src/pages/docs/installation.astro',
|
|
1128
|
+
{ mode: 'page' },
|
|
973
1129
|
)
|
|
974
|
-
const headings = section.fields.filter(f => f.key.startsWith('heading'))
|
|
1130
|
+
const headings = section.fields.filter((f) => f.key.startsWith('heading'))
|
|
975
1131
|
expect(headings.length).toBeGreaterThan(0)
|
|
976
1132
|
})
|
|
977
1133
|
|
|
978
1134
|
it('should detect repeated CodeBlock components as array in page-level scan', async () => {
|
|
979
1135
|
const section = await analyzeAstroSection(
|
|
980
|
-
source,
|
|
981
|
-
'
|
|
1136
|
+
source,
|
|
1137
|
+
'_page_docs_installation',
|
|
1138
|
+
'installationPage',
|
|
1139
|
+
'src/pages/docs/installation.astro',
|
|
1140
|
+
{ mode: 'page' },
|
|
982
1141
|
)
|
|
983
1142
|
// 4 CodeBlock components → detected as a "codeBlocks" array
|
|
984
1143
|
const codeBlockField = section.fields.find(
|
|
985
|
-
f =>
|
|
1144
|
+
(f) =>
|
|
1145
|
+
f.type === 'array' &&
|
|
1146
|
+
typeof f.key === 'string' &&
|
|
1147
|
+
f.key.toLowerCase().includes('codeblock'),
|
|
986
1148
|
)
|
|
987
1149
|
expect(codeBlockField).toBeDefined()
|
|
988
1150
|
expect(Array.isArray(codeBlockField?.defaultValue)).toBe(true)
|
|
@@ -1005,10 +1167,16 @@ describe('Nested map live-edit: inner items have data-sk-field', () => {
|
|
|
1005
1167
|
beforeAll(async () => {
|
|
1006
1168
|
source = readFileSync(join(PAGES_DIR, 'DocsIndexPage.astro'), 'utf-8')
|
|
1007
1169
|
section = await analyzeAstroSection(
|
|
1008
|
-
source,
|
|
1170
|
+
source,
|
|
1171
|
+
'_page_docs',
|
|
1172
|
+
'docsPage',
|
|
1173
|
+
'src/pages/docs/index.astro',
|
|
1174
|
+
{ mode: 'page' },
|
|
1009
1175
|
)
|
|
1010
1176
|
const groups = (section as any)._analyzerResult?.repeatedGroups ?? []
|
|
1011
|
-
patched = await patchTemplateForFields(source, '_page_docs', section.fields, groups, {
|
|
1177
|
+
patched = await patchTemplateForFields(source, '_page_docs', section.fields, groups, {
|
|
1178
|
+
mode: 'page',
|
|
1179
|
+
})
|
|
1012
1180
|
})
|
|
1013
1181
|
|
|
1014
1182
|
it('should add _j index to inner .map() call', () => {
|
|
@@ -1042,17 +1210,23 @@ describe('Page-level: template literal variables not extracted as fields', () =>
|
|
|
1042
1210
|
|
|
1043
1211
|
beforeAll(async () => {
|
|
1044
1212
|
const source = readFileSync(join(SECTIONS_DIR, 'PageWithTemplateLiterals.astro'), 'utf-8')
|
|
1045
|
-
section = await analyzeAstroSection(
|
|
1213
|
+
section = await analyzeAstroSection(
|
|
1214
|
+
source,
|
|
1215
|
+
'_page_docs_example',
|
|
1216
|
+
'docsExamplePage',
|
|
1217
|
+
'src/pages/docs/example.astro',
|
|
1218
|
+
{ mode: 'page' },
|
|
1219
|
+
)
|
|
1046
1220
|
})
|
|
1047
1221
|
|
|
1048
1222
|
it('should detect the real content fields (heading, description)', () => {
|
|
1049
|
-
const keys = section.fields.map(f => f.key)
|
|
1223
|
+
const keys = section.fields.map((f) => f.key)
|
|
1050
1224
|
expect(keys).toContain('heading')
|
|
1051
1225
|
expect(keys).toContain('description')
|
|
1052
1226
|
})
|
|
1053
1227
|
|
|
1054
1228
|
it('should NOT extract variables from inside template literal strings (templates, hero, json, template)', () => {
|
|
1055
|
-
const keys = section.fields.map(f => f.key)
|
|
1229
|
+
const keys = section.fields.map((f) => f.key)
|
|
1056
1230
|
expect(keys).not.toContain('templates')
|
|
1057
1231
|
expect(keys).not.toContain('hero')
|
|
1058
1232
|
expect(keys).not.toContain('json')
|
|
@@ -1060,14 +1234,14 @@ describe('Page-level: template literal variables not extracted as fields', () =>
|
|
|
1060
1234
|
})
|
|
1061
1235
|
|
|
1062
1236
|
it('should NOT extract the code variables themselves (exampleCode, cliCode, apiCode)', () => {
|
|
1063
|
-
const keys = section.fields.map(f => f.key)
|
|
1237
|
+
const keys = section.fields.map((f) => f.key)
|
|
1064
1238
|
expect(keys).not.toContain('exampleCode')
|
|
1065
1239
|
expect(keys).not.toContain('cliCode')
|
|
1066
1240
|
expect(keys).not.toContain('apiCode')
|
|
1067
1241
|
})
|
|
1068
1242
|
|
|
1069
1243
|
it('should find only legitimate fields (no spurious template-literal variables)', () => {
|
|
1070
|
-
const keys = section.fields.map(f => f.key)
|
|
1244
|
+
const keys = section.fields.map((f) => f.key)
|
|
1071
1245
|
// Only real content fields should be present — no code variable names
|
|
1072
1246
|
const spurious = ['templates', 'hero', 'json', 'template', 'exampleCode', 'cliCode', 'apiCode']
|
|
1073
1247
|
for (const key of spurious) {
|