@janga/norna 0.7.16 → 0.7.17
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/README.md +6 -6
- package/astro.config.mjs +35 -3
- package/docs/README.md +10 -10
- package/docs/commands.md +2 -2
- package/docs/content.md +82 -102
- package/docs/design/navigation-and-theme-plan.md +115 -0
- package/docs/design/norna-diagram-design.md +9 -9
- package/docs/design/site-examples-structure.md +9 -9
- package/docs/engine-development.md +9 -9
- package/docs/getting-started.md +20 -15
- package/docs/images-and-metadata.md +10 -9
- package/docs/pages.md +134 -0
- package/docs/public-files.md +49 -36
- package/docs/publishing.md +19 -3
- package/docs/site-files.md +29 -28
- package/docs/sitewide-content.md +20 -36
- package/docs/theme.md +20 -20
- package/docs/typography.md +10 -10
- package/examples/README.md +2 -2
- package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/site.schema.json +17 -41
- package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/sitewide.schema.json +26 -67
- package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/theme.schema.json +185 -88
- package/examples/complete-sites/dog-shelter-multi-page/.astro/data-store.json +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/.astro/dev-local.json +4 -4
- package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.json +5 -5
- package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.log +33 -23
- package/examples/complete-sites/dog-shelter-multi-page/README.md +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/_metadata.json +8 -8
- package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +20 -20
- package/examples/complete-sites/dog-shelter-multi-page/site/content.md +3 -4
- package/examples/complete-sites/dog-shelter-multi-page/site/{routes → pages}/010-dogs/content.md +3 -4
- package/examples/complete-sites/dog-shelter-multi-page/site/{routes → pages}/020-adopt/content.md +3 -4
- package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.yaml +2 -4
- package/examples/complete-sites/dog-shelter-single-page/.astro/collections/site.schema.json +17 -41
- package/examples/complete-sites/dog-shelter-single-page/.astro/collections/sitewide.schema.json +26 -67
- package/examples/complete-sites/dog-shelter-single-page/.astro/collections/theme.schema.json +185 -88
- package/examples/complete-sites/dog-shelter-single-page/.astro/data-store.json +1 -1
- package/examples/complete-sites/dog-shelter-single-page/.astro/dev-local.json +8 -0
- package/examples/complete-sites/dog-shelter-single-page/.astro/dev.json +5 -5
- package/examples/complete-sites/dog-shelter-single-page/.astro/dev.log +16 -12
- package/examples/complete-sites/dog-shelter-single-page/README.md +1 -1
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/_metadata.json +8 -8
- package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +12 -12
- package/examples/complete-sites/dog-shelter-single-page/site/content.md +5 -2
- package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.yaml +2 -4
- package/examples/feature-demos/media-and-surfaces/.astro/collections/site.schema.json +17 -41
- package/examples/feature-demos/media-and-surfaces/.astro/collections/sitewide.schema.json +26 -67
- package/examples/feature-demos/media-and-surfaces/.astro/collections/theme.schema.json +185 -88
- package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +1 -1
- package/examples/feature-demos/media-and-surfaces/.astro/dev.log +4 -14
- package/examples/feature-demos/media-and-surfaces/README.md +2 -2
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/_metadata.json +9 -9
- package/examples/feature-demos/media-and-surfaces/site/.norna/generated-images.json +16 -16
- package/examples/feature-demos/media-and-surfaces/site/content.md +4 -7
- package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/010-media/content.md +6 -5
- package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/020-surfaces/content.md +7 -6
- package/examples/feature-demos/media-and-surfaces/site/sitewide-content.yaml +0 -2
- package/examples/feature-demos/sitewide-content/README.md +3 -3
- package/examples/feature-demos/sitewide-content/site/content.md +8 -7
- package/examples/feature-demos/sitewide-content/site/pages/010-logo-and-navigation/content.md +17 -0
- package/examples/feature-demos/sitewide-content/site/{routes → pages}/020-notices/content.md +7 -6
- package/examples/feature-demos/sitewide-content/site/{routes → pages}/030-footer/content.md +6 -5
- package/examples/feature-demos/sitewide-content/site/sitewide-content.yaml +4 -13
- package/examples/feature-demos/theme-presets/README.md +1 -1
- package/examples/feature-demos/theme-presets/site/.norna/generated-images.json +60 -60
- package/examples/feature-demos/theme-presets/site/content.md +4 -7
- package/examples/feature-demos/theme-presets/site/{routes → pages}/010-portfolio/content.md +4 -7
- package/examples/feature-demos/theme-presets/site/{routes → pages}/020-documentation/content.md +4 -7
- package/examples/feature-demos/theme-presets/site/{routes → pages}/030-project/content.md +4 -7
- package/examples/feature-demos/theme-presets/site/{routes → pages}/040-statement/content.md +4 -7
- package/examples/feature-demos/theme-presets/site/sitewide-content.yaml +1 -10
- package/fixtures/basic/site/content.md +5 -6
- package/fixtures/basic/site/sitewide-content.yaml +1 -8
- package/fixtures/content-model-v2/site/content.md +11 -13
- package/fixtures/content-model-v2/site/pages/010-guide/content.md +18 -0
- package/fixtures/nested-pages/site/.norna/generated-images.json +10 -0
- package/fixtures/nested-pages/site/config.yaml +1 -0
- package/fixtures/nested-pages/site/content.md +15 -0
- package/fixtures/nested-pages/site/pages/010-guides/content.md +15 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/content.md +21 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/images/diagram.svg +21 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/pages/010-macos/content.md +14 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/020-workflows/content.md +11 -0
- package/fixtures/nested-pages/site/pages/010-guides/theme.yaml +3 -0
- package/fixtures/nested-pages/site/pages/020-reference/content.md +10 -0
- package/fixtures/nested-pages/site/pages/020-reference/pages/010-installation/content.md +11 -0
- package/fixtures/nested-pages/site/theme.yaml +3 -0
- package/package.json +7 -4
- package/schemas/config.schema.json +5 -5
- package/schemas/content-frontmatter.schema.json +28 -63
- package/schemas/manifest.json +1 -0
- package/schemas/sitewide-content.schema.json +58 -132
- package/schemas/theme.schema.json +396 -86
- package/scripts/check-config.mjs +6 -5
- package/scripts/deploy-site.mjs +6 -8
- package/scripts/doctor.mjs +2 -2
- package/scripts/generate-images.mjs +11 -18
- package/scripts/generate-schemas.mjs +3 -217
- package/scripts/lib/base-path-redirect.mjs +9 -0
- package/scripts/lib/editor-language-service.mjs +126 -66
- package/scripts/lib/heading-ids.mjs +161 -0
- package/scripts/lib/navigation-model.mjs +51 -0
- package/scripts/lib/norna-markdown-blocks.mjs +3 -4
- package/scripts/lib/page-model.mjs +83 -0
- package/scripts/lib/project-config.mjs +28 -0
- package/scripts/lib/schema-definitions.mjs +41 -46
- package/scripts/lib/schema-editor-metadata.mjs +475 -0
- package/scripts/lib/schema-value-definitions.mjs +6 -6
- package/scripts/lib/site-content.mjs +124 -87
- package/scripts/lib/site-paths.mjs +2 -2
- package/scripts/lib/theme-config.mjs +9 -16
- package/scripts/lib/theme-presets.mjs +9 -1
- package/scripts/release.mjs +1 -1
- package/scripts/show-typography.mjs +26 -20
- package/scripts/sync-content-sections.mjs +106 -65
- package/scripts/{test-temporary-visibility.mjs → test-banner-visibility.mjs} +7 -24
- package/scripts/test-client-javascript.mjs +39 -42
- package/scripts/test-content-check.mjs +138 -54
- package/scripts/test-content-model-v2.mjs +445 -228
- package/scripts/test-documentation.mjs +6 -1
- package/scripts/test-editor-language-service.mjs +168 -24
- package/scripts/test-engine-commands.mjs +2 -1
- package/scripts/test-heading-ids.mjs +68 -0
- package/scripts/test-navigation-model.mjs +63 -0
- package/scripts/test-nested-pages.mjs +110 -0
- package/scripts/test-package-check.mjs +31 -40
- package/scripts/test-page-model.mjs +80 -0
- package/scripts/test-project-config.mjs +25 -1
- package/scripts/test-schemas.mjs +66 -14
- package/scripts/test-theme-presets.mjs +33 -24
- package/src/components/NavigationPageTree.astro +68 -0
- package/src/components/SectionNavigationScript.astro +110 -5
- package/src/components/SiteBreadcrumbs.astro +29 -0
- package/src/components/SiteNavigation.astro +65 -69
- package/src/components/SitePage.astro +70 -46
- package/src/components/SiteSection.astro +27 -20
- package/src/content.config.ts +9 -5
- package/src/layouts/BaseLayout.astro +11 -20
- package/src/lib/pageThemes.ts +42 -0
- package/src/lib/sectionContent.ts +109 -106
- package/src/lib/siteNavigation.ts +48 -0
- package/src/lib/sitePages.ts +85 -38
- package/src/pages/{[slug].astro → [...slug].astro} +2 -2
- package/src/styles/global.css +373 -55
- package/starters/basic/README.md +5 -7
- package/starters/basic/site/content.md +7 -4
- package/starters/basic/site/sitewide-content.yaml +1 -10
- package/starters/project/README.md +8 -8
- package/starters/project/site/content.md +5 -4
- package/starters/project/site/{routes → pages}/010-guide/content.md +5 -4
- package/starters/project/site/sitewide-content.yaml +1 -10
- package/docs/routes.md +0 -121
- package/examples/feature-demos/sitewide-content/site/routes/010-identity/content.md +0 -20
- package/fixtures/content-model-v2/site/routes/010-guide/content.md +0 -17
- package/scripts/lib/route-model.mjs +0 -17
- package/scripts/test-route-model.mjs +0 -32
- package/src/lib/routeThemes.ts +0 -41
- /package/examples/complete-sites/dog-shelter-multi-page/site/images/{intro/shelter-dog.jpg → shelter-dog.jpg} +0 -0
- /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/010-dogs/images/dogs → pages/010-dogs/images}/rex.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/010-dogs/images/dogs → pages/010-dogs/images}/rover.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/020-adopt/images/adopt → pages/020-adopt/images}/adoption-dog.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-single-page/site/images/{you-can-help/foster-dog.jpg → foster-dog.jpg} +0 -0
- /package/examples/complete-sites/dog-shelter-single-page/site/images/{what-we-do/rescue-dog.jpg → rescue-dog.jpg} +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-carousel.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-stack.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-surfaces.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-one.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-three.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-two.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/stack → pages/010-media/images}/stack-one.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/stack → pages/010-media/images}/stack-two.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/020-surfaces/theme.yaml +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/010-portfolio/images/portfolio → pages/010-portfolio/images}/puppy-portrait.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/010-portfolio/images/portfolio → pages/010-portfolio/images}/shepherd-portrait.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/010-portfolio/images/portfolio → pages/010-portfolio/images}/white-puppy.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes → pages}/010-portfolio/theme.yaml +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/020-documentation/images/documentation → pages/020-documentation/images}/black-schnauzer.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/020-documentation/images/documentation → pages/020-documentation/images}/retriever-portrait.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/020-documentation/images/documentation → pages/020-documentation/images}/schnauzer-portrait.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes → pages}/020-documentation/theme.yaml +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/030-project/images/project → pages/030-project/images}/boxer-portrait.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/030-project/images/project → pages/030-project/images}/grass-puppy.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/030-project/images/project → pages/030-project/images}/grey-street-dog.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes → pages}/030-project/theme.yaml +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/040-statement/images/statement → pages/040-statement/images}/black-pedigree.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/040-statement/images/statement → pages/040-statement/images}/sheepdog-grass.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/040-statement/images/statement → pages/040-statement/images}/sheepdog-wide.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes → pages}/040-statement/theme.yaml +0 -0
- /package/fixtures/basic/site/images/{work/.gitkeep → .gitkeep} +0 -0
- /package/fixtures/content-model-v2/site/images/{card-section/adopt.svg → adopt.svg} +0 -0
- /package/fixtures/content-model-v2/site/images/{intro/detail.jpg → detail.jpg} +0 -0
- /package/fixtures/content-model-v2/site/images/{intro/duplicate.jpg → duplicate.jpg} +0 -0
- /package/fixtures/content-model-v2/site/images/{card-section/foster.svg → foster.svg} +0 -0
- /package/fixtures/content-model-v2/site/images/{intro/hero.jpg → hero.jpg} +0 -0
- /package/fixtures/content-model-v2/site/images/{plain/image.jpg → image.jpg} +0 -0
- /package/fixtures/content-model-v2/site/images/{timed/slide-one.jpg → slide-one.jpg} +0 -0
- /package/fixtures/content-model-v2/site/images/{timed/slide-three.jpg → slide-three.jpg} +0 -0
- /package/fixtures/content-model-v2/site/images/{timed/slide-two.jpg → slide-two.jpg} +0 -0
- /package/fixtures/content-model-v2/site/{routes/010-guide/images/intro → pages/010-guide/images}/duplicate.jpg +0 -0
- /package/starters/basic/site/images/{work/.gitkeep → .gitkeep} +0 -0
|
@@ -109,89 +109,191 @@ test('content model v2 fixture checks and builds', async () => {
|
|
|
109
109
|
await runNorna(['--site-dir', siteDir, 'build']);
|
|
110
110
|
|
|
111
111
|
const manifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
|
|
112
|
-
assert.ok(manifest['images/
|
|
113
|
-
assert.ok(manifest['
|
|
112
|
+
assert.ok(manifest['images/duplicate.jpg']);
|
|
113
|
+
assert.ok(manifest['pages/010-guide/images/duplicate.jpg']);
|
|
114
|
+
|
|
115
|
+
const homepageHtml = await readFile(path.join(path.dirname(siteDir), 'dist', 'index.html'), 'utf8');
|
|
116
|
+
assert.match(homepageHtml, /site-section-page-title/);
|
|
117
|
+
assert.match(homepageHtml, /site-section-page-title-empty/);
|
|
118
|
+
assert.match(homepageHtml, /fetchpriority="high"/);
|
|
119
|
+
} finally {
|
|
120
|
+
await rm(root, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('the removed routes directory is rejected with a page migration hint', async () => {
|
|
125
|
+
const { root, siteDir } = await createTempSite();
|
|
126
|
+
try {
|
|
127
|
+
await writeFile(path.join(siteDir, 'content.md'), `# Home
|
|
128
|
+
Home content.
|
|
129
|
+
`);
|
|
130
|
+
await mkdir(path.join(siteDir, 'routes', '010-guide'), { recursive: true });
|
|
131
|
+
await writeFile(path.join(siteDir, 'routes', '010-guide', 'content.md'), 'Old page content.\n');
|
|
132
|
+
|
|
133
|
+
await assert.rejects(
|
|
134
|
+
runNorna(['--site-dir', siteDir, 'content:check']),
|
|
135
|
+
(error) => {
|
|
136
|
+
assert.match(error.output, /site\/routes is no longer supported\. Rename it to site\/pages and use NNN-page-id directory names\./);
|
|
137
|
+
return true;
|
|
138
|
+
},
|
|
139
|
+
);
|
|
114
140
|
} finally {
|
|
115
141
|
await rm(root, { recursive: true, force: true });
|
|
116
142
|
}
|
|
117
143
|
});
|
|
118
144
|
|
|
119
|
-
test('
|
|
145
|
+
test('page theme replaces visual theme without changing site navigation', async () => {
|
|
120
146
|
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
121
147
|
try {
|
|
122
148
|
await writeFile(path.join(siteDir, 'theme.yaml'), `typography:
|
|
123
149
|
profile: reading
|
|
124
150
|
`);
|
|
125
|
-
await
|
|
126
|
-
label: Root Brand
|
|
127
|
-
`);
|
|
128
|
-
await mkdir(path.join(siteDir, 'routes', '010-guide'), { recursive: true });
|
|
151
|
+
await mkdir(path.join(siteDir, 'pages', '010-guide'), { recursive: true });
|
|
129
152
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
130
|
-
|
|
131
|
-
description: Root page
|
|
153
|
+
page:
|
|
154
|
+
description: Root page
|
|
132
155
|
---
|
|
133
156
|
|
|
157
|
+
# Root
|
|
158
|
+
|
|
134
159
|
## Home {#home}
|
|
135
160
|
|
|
136
161
|
Root content.
|
|
137
162
|
`);
|
|
138
|
-
await writeFile(path.join(siteDir, '
|
|
139
|
-
|
|
140
|
-
description: Guide page
|
|
141
|
-
navigation:
|
|
142
|
-
label: Guide
|
|
163
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'content.md'), `---
|
|
164
|
+
page:
|
|
165
|
+
description: Guide page
|
|
143
166
|
---
|
|
144
167
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
Route content.
|
|
168
|
+
# Guide
|
|
169
|
+
Page content.
|
|
148
170
|
`);
|
|
149
|
-
await writeFile(path.join(siteDir, '
|
|
171
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'theme.yaml'), `typography:
|
|
150
172
|
profile: statement
|
|
151
173
|
palette: light
|
|
152
174
|
`);
|
|
153
175
|
|
|
154
176
|
await runNorna(['--site-dir', siteDir, 'build']);
|
|
155
177
|
const rootHtml = await readFile(path.join(path.dirname(siteDir), 'dist', 'index.html'), 'utf8');
|
|
156
|
-
const
|
|
178
|
+
const pageHtml = await readFile(path.join(path.dirname(siteDir), 'dist', 'guide', 'index.html'), 'utf8');
|
|
157
179
|
assert.match(rootHtml, /--color-page: #000000/);
|
|
158
|
-
assert.match(
|
|
159
|
-
assert.match(
|
|
180
|
+
assert.match(pageHtml, /--color-page: #ffffff/);
|
|
181
|
+
assert.match(pageHtml, /<a href="\/">\s*Root\s*<\/a>/);
|
|
160
182
|
} finally {
|
|
161
183
|
await rm(root, { recursive: true, force: true });
|
|
162
184
|
}
|
|
163
185
|
});
|
|
164
186
|
|
|
165
|
-
test('
|
|
187
|
+
test('page metadata, navigation logo, and page listing have separate roles', async () => {
|
|
166
188
|
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
167
189
|
try {
|
|
168
|
-
await mkdir(path.join(siteDir, '
|
|
190
|
+
await mkdir(path.join(siteDir, 'public'), { recursive: true });
|
|
191
|
+
await writeFile(path.join(siteDir, 'public', 'logo.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"/>');
|
|
192
|
+
await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `logo:
|
|
193
|
+
height: 2rem
|
|
194
|
+
`);
|
|
195
|
+
await writeFile(path.join(siteDir, 'content.md'), `# Welcome
|
|
196
|
+
|
|
197
|
+
## Intro {#intro}
|
|
198
|
+
|
|
199
|
+
Homepage content.
|
|
200
|
+
`);
|
|
201
|
+
await mkdir(path.join(siteDir, 'pages', '010-guide'), { recursive: true });
|
|
202
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'content.md'), `---
|
|
203
|
+
page:
|
|
204
|
+
description: The visible guide page.
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
# Guide
|
|
208
|
+
Guide content.
|
|
209
|
+
`);
|
|
210
|
+
await mkdir(path.join(siteDir, 'pages', '020-private'), { recursive: true });
|
|
211
|
+
await writeFile(path.join(siteDir, 'pages', '020-private', 'content.md'), `---
|
|
212
|
+
navigation:
|
|
213
|
+
listed: false
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
# Unlisted
|
|
217
|
+
Public but unlisted content.
|
|
218
|
+
`);
|
|
219
|
+
|
|
220
|
+
await runNorna(['--site-dir', siteDir, 'build']);
|
|
221
|
+
const distDir = path.join(path.dirname(siteDir), 'dist');
|
|
222
|
+
const homeHtml = await readFile(path.join(distDir, 'index.html'), 'utf8');
|
|
223
|
+
const guideHtml = await readFile(path.join(distDir, 'guide', 'index.html'), 'utf8');
|
|
224
|
+
const unlistedHtml = await readFile(path.join(distDir, 'private', 'index.html'), 'utf8');
|
|
225
|
+
|
|
226
|
+
assert.match(homeHtml, /<title>Welcome<\/title>/);
|
|
227
|
+
assert.doesNotMatch(homeHtml, /<meta name="description"/);
|
|
228
|
+
assert.match(homeHtml, /<img class="site-brand-logo"[^>]+alt="Welcome"/);
|
|
229
|
+
assert.match(homeHtml, /<a href="\/" aria-current="page">\s*Welcome\s*<\/a>/);
|
|
230
|
+
assert.match(homeHtml, /<a href="\/guide\/">\s*Guide\s*<\/a>/);
|
|
231
|
+
assert.doesNotMatch(homeHtml, />\s*Unlisted\s*<\/a>/);
|
|
232
|
+
assert.match(guideHtml, /<meta name="description" content="The visible guide page\."/);
|
|
233
|
+
assert.match(unlistedHtml, /Public but unlisted content/);
|
|
234
|
+
} finally {
|
|
235
|
+
await rm(root, { recursive: true, force: true });
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('footer build information uses the site language and fixed formatting', async () => {
|
|
240
|
+
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
241
|
+
try {
|
|
242
|
+
await writeFile(path.join(siteDir, 'config.yaml'), 'url: https://example.com/\nlanguage: sv-SE\n');
|
|
243
|
+
await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `footer:
|
|
244
|
+
buildInfo: true
|
|
245
|
+
`);
|
|
169
246
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
170
|
-
|
|
171
|
-
description:
|
|
247
|
+
page:
|
|
248
|
+
description: Footer build information.
|
|
172
249
|
---
|
|
173
250
|
|
|
251
|
+
# Footer
|
|
252
|
+
|
|
174
253
|
## Home {#home}
|
|
175
254
|
|
|
176
|
-
|
|
255
|
+
Content.
|
|
177
256
|
`);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
257
|
+
|
|
258
|
+
await runNorna(['--site-dir', siteDir, 'build']);
|
|
259
|
+
const html = await readFile(path.join(path.dirname(siteDir), 'dist', 'index.html'), 'utf8');
|
|
260
|
+
assert.match(html, /<span>Byggd [^<]+<\/span>/);
|
|
261
|
+
} finally {
|
|
262
|
+
await rm(root, { recursive: true, force: true });
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('page theme cannot define navigation logo settings', async () => {
|
|
267
|
+
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
268
|
+
try {
|
|
269
|
+
await mkdir(path.join(siteDir, 'pages', '010-guide'), { recursive: true });
|
|
270
|
+
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
271
|
+
page:
|
|
272
|
+
description: Root page
|
|
181
273
|
---
|
|
182
274
|
|
|
183
|
-
|
|
275
|
+
# Root
|
|
276
|
+
|
|
277
|
+
## Home {#home}
|
|
278
|
+
|
|
279
|
+
Root content.
|
|
280
|
+
`);
|
|
281
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'content.md'), `---
|
|
282
|
+
page:
|
|
283
|
+
description: Guide page
|
|
284
|
+
---
|
|
184
285
|
|
|
185
|
-
|
|
286
|
+
# Guide
|
|
287
|
+
Page content.
|
|
186
288
|
`);
|
|
187
|
-
await writeFile(path.join(siteDir, '
|
|
188
|
-
|
|
289
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'theme.yaml'), `logo:
|
|
290
|
+
height: 3rem
|
|
189
291
|
`);
|
|
190
292
|
|
|
191
293
|
await assert.rejects(
|
|
192
294
|
runNorna(['--site-dir', siteDir, 'config:check']),
|
|
193
295
|
(error) => {
|
|
194
|
-
assert.match(error.output, /may not define navigation
|
|
296
|
+
assert.match(error.output, /may not define navigation logo settings\. Optional logo display settings belong under "logo:" in site\/sitewide-content\.yaml/);
|
|
195
297
|
return true;
|
|
196
298
|
},
|
|
197
299
|
);
|
|
@@ -203,16 +305,14 @@ Route content.
|
|
|
203
305
|
test('navigation logo settings require one conventional logo file', async () => {
|
|
204
306
|
const { root, siteDir } = await createTempSite();
|
|
205
307
|
try {
|
|
206
|
-
await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `
|
|
207
|
-
|
|
208
|
-
logo:
|
|
209
|
-
height: 2rem
|
|
308
|
+
await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `logo:
|
|
309
|
+
height: 2rem
|
|
210
310
|
`);
|
|
211
311
|
|
|
212
312
|
await assert.rejects(
|
|
213
313
|
runNorna(['--site-dir', siteDir, 'config:check']),
|
|
214
314
|
(error) => {
|
|
215
|
-
assert.match(error.output, /
|
|
315
|
+
assert.match(error.output, /Site-wide logo is configured, but no logo file was found/);
|
|
216
316
|
assert.match(error.output, /site\/public\/logo\.svg/);
|
|
217
317
|
return true;
|
|
218
318
|
},
|
|
@@ -225,9 +325,6 @@ test('navigation logo settings require one conventional logo file', async () =>
|
|
|
225
325
|
test('multiple conventional navigation logo files stop config validation', async () => {
|
|
226
326
|
const { root, siteDir } = await createTempSite();
|
|
227
327
|
try {
|
|
228
|
-
await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `navigation:
|
|
229
|
-
label: Conflicting logos
|
|
230
|
-
`);
|
|
231
328
|
await mkdir(path.join(siteDir, 'public'), { recursive: true });
|
|
232
329
|
await writeFile(path.join(siteDir, 'public', 'logo.svg'), '<svg/>');
|
|
233
330
|
await writeFile(path.join(siteDir, 'public', 'logo.png'), 'png');
|
|
@@ -250,10 +347,12 @@ test('content:check warns for local Markdown images but not external Markdown im
|
|
|
250
347
|
const { root, siteDir } = await createTempSite();
|
|
251
348
|
try {
|
|
252
349
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
253
|
-
|
|
254
|
-
description: Fixture
|
|
350
|
+
page:
|
|
351
|
+
description: Fixture
|
|
255
352
|
---
|
|
256
353
|
|
|
354
|
+
# Markdown Image Warning
|
|
355
|
+
|
|
257
356
|
## Intro {#intro}
|
|
258
357
|
|
|
259
358
|

|
|
@@ -276,10 +375,12 @@ test('managed SVG images are copied as static image output', async () => {
|
|
|
276
375
|
const { root, siteDir } = await createTempSite();
|
|
277
376
|
try {
|
|
278
377
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
279
|
-
|
|
280
|
-
description: Fixture
|
|
378
|
+
page:
|
|
379
|
+
description: Fixture
|
|
281
380
|
---
|
|
282
381
|
|
|
382
|
+
# SVG Fixture
|
|
383
|
+
|
|
283
384
|
## Intro {#intro}
|
|
284
385
|
|
|
285
386
|
\`\`\`norna-image-stack
|
|
@@ -287,18 +388,18 @@ description: Fixture
|
|
|
287
388
|
alt: Diagram
|
|
288
389
|
\`\`\`
|
|
289
390
|
`);
|
|
290
|
-
await mkdir(path.join(siteDir, 'images'
|
|
291
|
-
await writeFile(path.join(siteDir, 'images', '
|
|
391
|
+
await mkdir(path.join(siteDir, 'images'), { recursive: true });
|
|
392
|
+
await writeFile(path.join(siteDir, 'images', 'diagram.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 620"><rect width="900" height="620"/></svg>\n');
|
|
292
393
|
|
|
293
394
|
await runContentScript(siteDir, ['--check']);
|
|
294
395
|
await runNorna(['--site-dir', siteDir, 'images']);
|
|
295
396
|
|
|
296
397
|
const manifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
|
|
297
|
-
const entry = manifest['images/
|
|
398
|
+
const entry = manifest['images/diagram.svg'];
|
|
298
399
|
assert.equal(entry.kind, 'static');
|
|
299
400
|
assert.equal(entry.width, 900);
|
|
300
401
|
assert.equal(entry.height, 620);
|
|
301
|
-
assert.match(entry.src, /^\/images\/original\/images\/
|
|
402
|
+
assert.match(entry.src, /^\/images\/original\/images\/diagram-[a-f0-9]{8}\.svg$/);
|
|
302
403
|
assert.equal(entry.variants, undefined);
|
|
303
404
|
assert.equal(await fileExists(path.join(siteDir, '.norna', 'public', entry.src.replace(/^\//, ''))), true);
|
|
304
405
|
} finally {
|
|
@@ -310,10 +411,12 @@ test('inline notes render as linked numbered margin notes', async () => {
|
|
|
310
411
|
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
311
412
|
try {
|
|
312
413
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
313
|
-
|
|
314
|
-
description: Fixture
|
|
414
|
+
page:
|
|
415
|
+
description: Fixture
|
|
315
416
|
---
|
|
316
417
|
|
|
418
|
+
# Inline notes
|
|
419
|
+
|
|
317
420
|
## Intro {#intro}
|
|
318
421
|
|
|
319
422
|
The first paragraph points to an explanation.{note-ref}
|
|
@@ -348,10 +451,12 @@ test('updated managed SVG images get updated static output', async () => {
|
|
|
348
451
|
const { root, siteDir } = await createTempSite();
|
|
349
452
|
try {
|
|
350
453
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
351
|
-
|
|
352
|
-
description: Fixture
|
|
454
|
+
page:
|
|
455
|
+
description: Fixture
|
|
353
456
|
---
|
|
354
457
|
|
|
458
|
+
# SVG Update Fixture
|
|
459
|
+
|
|
355
460
|
## Intro {#intro}
|
|
356
461
|
|
|
357
462
|
\`\`\`norna-image-stack
|
|
@@ -359,22 +464,22 @@ description: Fixture
|
|
|
359
464
|
alt: Diagram
|
|
360
465
|
\`\`\`
|
|
361
466
|
`);
|
|
362
|
-
await mkdir(path.join(siteDir, 'images'
|
|
363
|
-
const sourcePath = path.join(siteDir, 'images', '
|
|
467
|
+
await mkdir(path.join(siteDir, 'images'), { recursive: true });
|
|
468
|
+
const sourcePath = path.join(siteDir, 'images', 'diagram.svg');
|
|
364
469
|
await writeFile(sourcePath, '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 620"><rect width="900" height="620" fill="red"/></svg>\n');
|
|
365
470
|
|
|
366
471
|
await runContentScript(siteDir, ['--check']);
|
|
367
472
|
await runNorna(['--site-dir', siteDir, 'images']);
|
|
368
473
|
|
|
369
474
|
const firstManifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
|
|
370
|
-
const firstEntry = firstManifest['images/
|
|
475
|
+
const firstEntry = firstManifest['images/diagram.svg'];
|
|
371
476
|
const firstOutputPath = path.join(siteDir, '.norna', 'public', firstEntry.src.replace(/^\//, ''));
|
|
372
477
|
|
|
373
478
|
await writeFile(sourcePath, '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 620"><rect width="900" height="620" fill="blue"/></svg>\n');
|
|
374
479
|
await runNorna(['--site-dir', siteDir, 'images']);
|
|
375
480
|
|
|
376
481
|
const secondManifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
|
|
377
|
-
const secondEntry = secondManifest['images/
|
|
482
|
+
const secondEntry = secondManifest['images/diagram.svg'];
|
|
378
483
|
const secondOutputPath = path.join(siteDir, '.norna', 'public', secondEntry.src.replace(/^\//, ''));
|
|
379
484
|
|
|
380
485
|
assert.notEqual(secondEntry.sourceHash, firstEntry.sourceHash);
|
|
@@ -390,10 +495,12 @@ test('content:check warns when carousel SVG images have no intrinsic aspect rati
|
|
|
390
495
|
const { root, siteDir } = await createTempSite();
|
|
391
496
|
try {
|
|
392
497
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
393
|
-
|
|
394
|
-
description: Fixture
|
|
498
|
+
page:
|
|
499
|
+
description: Fixture
|
|
395
500
|
---
|
|
396
501
|
|
|
502
|
+
# SVG Carousel Fixture
|
|
503
|
+
|
|
397
504
|
## Intro {#intro}
|
|
398
505
|
|
|
399
506
|
\`\`\`norna-image-carousel
|
|
@@ -403,9 +510,9 @@ description: Fixture
|
|
|
403
510
|
alt: Second
|
|
404
511
|
\`\`\`
|
|
405
512
|
`);
|
|
406
|
-
await mkdir(path.join(siteDir, 'images'
|
|
407
|
-
await writeFile(path.join(siteDir, 'images', '
|
|
408
|
-
await writeFile(path.join(siteDir, 'images', '
|
|
513
|
+
await mkdir(path.join(siteDir, 'images'), { recursive: true });
|
|
514
|
+
await writeFile(path.join(siteDir, 'images', 'first.svg'), '<svg xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100"/></svg>\n');
|
|
515
|
+
await writeFile(path.join(siteDir, 'images', 'second.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect width="100" height="100"/></svg>\n');
|
|
409
516
|
|
|
410
517
|
const { stdout } = await runContentScript(siteDir, ['--check']);
|
|
411
518
|
assert.match(stdout, /Content check completed with warnings\./);
|
|
@@ -419,10 +526,12 @@ test('norna-card-list images are managed image references', async () => {
|
|
|
419
526
|
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
420
527
|
try {
|
|
421
528
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
422
|
-
|
|
423
|
-
description: Fixture
|
|
529
|
+
page:
|
|
530
|
+
description: Fixture
|
|
424
531
|
---
|
|
425
532
|
|
|
533
|
+
# Card List Fixture
|
|
534
|
+
|
|
426
535
|
## Help {#help}
|
|
427
536
|
|
|
428
537
|
\`\`\`norna-card-list
|
|
@@ -440,15 +549,15 @@ width: narrow
|
|
|
440
549
|
text: Support the shelter.
|
|
441
550
|
\`\`\`
|
|
442
551
|
`);
|
|
443
|
-
await mkdir(path.join(siteDir, 'images'
|
|
444
|
-
await writeFile(path.join(siteDir, 'images', '
|
|
552
|
+
await mkdir(path.join(siteDir, 'images'), { recursive: true });
|
|
553
|
+
await writeFile(path.join(siteDir, 'images', 'adopt.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 100"><rect width="160" height="100"/></svg>\n');
|
|
445
554
|
|
|
446
555
|
const { stdout } = await runContentScript(siteDir, ['--check']);
|
|
447
556
|
assert.match(stdout, /Content check passed\./);
|
|
448
557
|
|
|
449
558
|
await runNorna(['--site-dir', siteDir, 'images']);
|
|
450
559
|
const manifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
|
|
451
|
-
assert.equal(manifest['images/
|
|
560
|
+
assert.equal(manifest['images/adopt.svg'].kind, 'static');
|
|
452
561
|
|
|
453
562
|
await runNorna(['--site-dir', siteDir, 'build']);
|
|
454
563
|
const html = await readFile(path.join(root, 'dist', 'index.html'), 'utf8');
|
|
@@ -460,7 +569,7 @@ width: narrow
|
|
|
460
569
|
assert.match(html, /class="card-list-badge">Recommended<\/p>/);
|
|
461
570
|
assert.match(html, /Give a dog a new home\./);
|
|
462
571
|
assert.match(html, /href="\/adopt\/"/);
|
|
463
|
-
assert.match(html, /src="\/images\/original\/images\/
|
|
572
|
+
assert.match(html, /src="\/images\/original\/images\/adopt-[a-f0-9]{8}\.svg"/);
|
|
464
573
|
} finally {
|
|
465
574
|
await rm(root, { recursive: true, force: true });
|
|
466
575
|
}
|
|
@@ -470,10 +579,12 @@ test('content:check fails when a norna-card-list image file is missing', async (
|
|
|
470
579
|
const { root, siteDir } = await createTempSite();
|
|
471
580
|
try {
|
|
472
581
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
473
|
-
|
|
474
|
-
description: Fixture
|
|
582
|
+
page:
|
|
583
|
+
description: Fixture
|
|
475
584
|
---
|
|
476
585
|
|
|
586
|
+
# Missing Card Image
|
|
587
|
+
|
|
477
588
|
## Help {#help}
|
|
478
589
|
|
|
479
590
|
\`\`\`norna-card-list
|
|
@@ -485,7 +596,7 @@ description: Fixture
|
|
|
485
596
|
await assert.rejects(
|
|
486
597
|
() => runContentScript(siteDir, ['--check']),
|
|
487
598
|
(error) => {
|
|
488
|
-
assert.match(error.output, /Image "missing\.svg" does not exist at .*site\/images\/
|
|
599
|
+
assert.match(error.output, /Image "missing\.svg" does not exist at .*site\/images\/missing\.svg or anywhere under any page image root\./);
|
|
489
600
|
return true;
|
|
490
601
|
},
|
|
491
602
|
);
|
|
@@ -498,10 +609,12 @@ test('content:check fails for malformed norna-card-list blocks', async () => {
|
|
|
498
609
|
const { root, siteDir } = await createTempSite();
|
|
499
610
|
try {
|
|
500
611
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
501
|
-
|
|
502
|
-
description: Fixture
|
|
612
|
+
page:
|
|
613
|
+
description: Fixture
|
|
503
614
|
---
|
|
504
615
|
|
|
616
|
+
# Malformed Card List
|
|
617
|
+
|
|
505
618
|
## Help {#help}
|
|
506
619
|
|
|
507
620
|
\`\`\`norna-card-list
|
|
@@ -525,10 +638,12 @@ test('content:check fails for invalid norna-card-list options', async () => {
|
|
|
525
638
|
const { root, siteDir } = await createTempSite();
|
|
526
639
|
try {
|
|
527
640
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
528
|
-
|
|
529
|
-
description: Fixture
|
|
641
|
+
page:
|
|
642
|
+
description: Fixture
|
|
530
643
|
---
|
|
531
644
|
|
|
645
|
+
# Invalid Card List Options
|
|
646
|
+
|
|
532
647
|
## Help {#help}
|
|
533
648
|
|
|
534
649
|
\`\`\`norna-card-list
|
|
@@ -558,10 +673,12 @@ test('content:check fails for invalid norna-card-list width', async () => {
|
|
|
558
673
|
const { root, siteDir } = await createTempSite();
|
|
559
674
|
try {
|
|
560
675
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
561
|
-
|
|
562
|
-
description: Fixture
|
|
676
|
+
page:
|
|
677
|
+
description: Fixture
|
|
563
678
|
---
|
|
564
679
|
|
|
680
|
+
# Invalid Card List Width
|
|
681
|
+
|
|
565
682
|
## Help {#help}
|
|
566
683
|
|
|
567
684
|
\`\`\`norna-card-list
|
|
@@ -588,10 +705,12 @@ test('content:check fails for invalid norna-card-list size', async () => {
|
|
|
588
705
|
const { root, siteDir } = await createTempSite();
|
|
589
706
|
try {
|
|
590
707
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
591
|
-
|
|
592
|
-
description: Fixture
|
|
708
|
+
page:
|
|
709
|
+
description: Fixture
|
|
593
710
|
---
|
|
594
711
|
|
|
712
|
+
# Invalid Card List Size
|
|
713
|
+
|
|
595
714
|
## Help {#help}
|
|
596
715
|
|
|
597
716
|
\`\`\`norna-card-list
|
|
@@ -618,10 +737,12 @@ test('content:sync moves Norna-managed images inside the same page image root',
|
|
|
618
737
|
const { root, siteDir } = await createTempSite();
|
|
619
738
|
try {
|
|
620
739
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
621
|
-
|
|
622
|
-
description: Fixture
|
|
740
|
+
page:
|
|
741
|
+
description: Fixture
|
|
623
742
|
---
|
|
624
743
|
|
|
744
|
+
# Sync Fixture
|
|
745
|
+
|
|
625
746
|
## Work {#work}
|
|
626
747
|
|
|
627
748
|
\`\`\`norna-image-stack
|
|
@@ -634,7 +755,7 @@ description: Fixture
|
|
|
634
755
|
|
|
635
756
|
await runContentScript(siteDir, ['--write', '--yes']);
|
|
636
757
|
|
|
637
|
-
const moved = await readFile(path.join(siteDir, 'images', '
|
|
758
|
+
const moved = await readFile(path.join(siteDir, 'images', 'moved.jpg'), 'utf8');
|
|
638
759
|
assert.equal(moved, 'fixture image');
|
|
639
760
|
} finally {
|
|
640
761
|
await rm(root, { recursive: true, force: true });
|
|
@@ -645,10 +766,12 @@ test('content:sync moves managed SVG images inside the same page image root', as
|
|
|
645
766
|
const { root, siteDir } = await createTempSite();
|
|
646
767
|
try {
|
|
647
768
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
648
|
-
|
|
649
|
-
description: Fixture
|
|
769
|
+
page:
|
|
770
|
+
description: Fixture
|
|
650
771
|
---
|
|
651
772
|
|
|
773
|
+
# SVG Sync Fixture
|
|
774
|
+
|
|
652
775
|
## Work {#work}
|
|
653
776
|
|
|
654
777
|
\`\`\`norna-image-stack
|
|
@@ -661,161 +784,179 @@ description: Fixture
|
|
|
661
784
|
|
|
662
785
|
await runContentScript(siteDir, ['--write', '--yes']);
|
|
663
786
|
|
|
664
|
-
const moved = await readFile(path.join(siteDir, 'images', '
|
|
787
|
+
const moved = await readFile(path.join(siteDir, 'images', 'moved.svg'), 'utf8');
|
|
665
788
|
assert.match(moved, /viewBox="0 0 10 10"/);
|
|
666
789
|
} finally {
|
|
667
790
|
await rm(root, { recursive: true, force: true });
|
|
668
791
|
}
|
|
669
792
|
});
|
|
670
793
|
|
|
671
|
-
test('content:sync moves Norna-managed images across
|
|
794
|
+
test('content:sync moves Norna-managed images across page image roots when git is clean', async () => {
|
|
672
795
|
const { root, siteDir } = await createTempSite();
|
|
673
796
|
try {
|
|
674
|
-
const
|
|
675
|
-
const
|
|
676
|
-
await mkdir(path.join(
|
|
677
|
-
await mkdir(
|
|
797
|
+
const sourcePageDir = path.join(siteDir, 'pages', '010-source');
|
|
798
|
+
const targetPageDir = path.join(siteDir, 'pages', '020-target');
|
|
799
|
+
await mkdir(path.join(sourcePageDir, 'images'), { recursive: true });
|
|
800
|
+
await mkdir(targetPageDir, { recursive: true });
|
|
678
801
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
679
|
-
|
|
680
|
-
description: Fixture
|
|
802
|
+
page:
|
|
803
|
+
description: Fixture
|
|
681
804
|
---
|
|
682
805
|
|
|
806
|
+
# Cross Page Sync
|
|
807
|
+
|
|
683
808
|
## Home {#home}
|
|
684
809
|
|
|
685
810
|
Text.
|
|
686
811
|
`);
|
|
687
|
-
await writeFile(path.join(
|
|
688
|
-
|
|
689
|
-
description: Fixture
|
|
812
|
+
await writeFile(path.join(sourcePageDir, 'content.md'), `---
|
|
813
|
+
page:
|
|
814
|
+
description: Fixture
|
|
690
815
|
---
|
|
691
816
|
|
|
817
|
+
# Source
|
|
818
|
+
|
|
692
819
|
## Old {#old}
|
|
693
820
|
|
|
694
821
|
Text.
|
|
695
822
|
`);
|
|
696
|
-
await writeFile(path.join(
|
|
697
|
-
|
|
698
|
-
description: Fixture
|
|
823
|
+
await writeFile(path.join(targetPageDir, 'content.md'), `---
|
|
824
|
+
page:
|
|
825
|
+
description: Fixture
|
|
699
826
|
---
|
|
700
827
|
|
|
828
|
+
# Target
|
|
829
|
+
|
|
701
830
|
## Work {#work}
|
|
702
831
|
|
|
703
832
|
\`\`\`norna-image-stack
|
|
704
833
|
- image: moved.jpg
|
|
705
834
|
\`\`\`
|
|
706
835
|
`);
|
|
707
|
-
await writeFile(path.join(
|
|
836
|
+
await writeFile(path.join(sourcePageDir, 'images', 'moved.jpg'), 'page image');
|
|
708
837
|
await initCleanGitWorktree(root);
|
|
709
838
|
|
|
710
839
|
await runContentScript(siteDir, ['--write', '--yes']);
|
|
711
840
|
|
|
712
|
-
assert.equal(await fileExists(path.join(
|
|
713
|
-
assert.equal(await fileExists(path.join(
|
|
841
|
+
assert.equal(await fileExists(path.join(targetPageDir, 'images', 'moved.jpg')), true);
|
|
842
|
+
assert.equal(await fileExists(path.join(sourcePageDir, 'images', 'moved.jpg')), false);
|
|
714
843
|
} finally {
|
|
715
844
|
await rm(root, { recursive: true, force: true });
|
|
716
845
|
}
|
|
717
846
|
});
|
|
718
847
|
|
|
719
|
-
test('content:sync refuses cross-
|
|
848
|
+
test('content:sync refuses cross-page image moves when git status is dirty', async () => {
|
|
720
849
|
const { root, siteDir } = await createTempSite();
|
|
721
850
|
try {
|
|
722
|
-
const
|
|
723
|
-
const
|
|
724
|
-
await mkdir(path.join(
|
|
725
|
-
await mkdir(
|
|
851
|
+
const sourcePageDir = path.join(siteDir, 'pages', '010-source');
|
|
852
|
+
const targetPageDir = path.join(siteDir, 'pages', '020-target');
|
|
853
|
+
await mkdir(path.join(sourcePageDir, 'images'), { recursive: true });
|
|
854
|
+
await mkdir(targetPageDir, { recursive: true });
|
|
726
855
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
727
|
-
|
|
728
|
-
description: Fixture
|
|
856
|
+
page:
|
|
857
|
+
description: Fixture
|
|
729
858
|
---
|
|
730
859
|
|
|
860
|
+
# Dirty Cross Page Sync
|
|
861
|
+
|
|
731
862
|
## Home {#home}
|
|
732
863
|
|
|
733
864
|
Text.
|
|
734
865
|
`);
|
|
735
|
-
await writeFile(path.join(
|
|
736
|
-
|
|
737
|
-
description: Fixture
|
|
866
|
+
await writeFile(path.join(sourcePageDir, 'content.md'), `---
|
|
867
|
+
page:
|
|
868
|
+
description: Fixture
|
|
738
869
|
---
|
|
739
870
|
|
|
871
|
+
# Source
|
|
872
|
+
|
|
740
873
|
## Old {#old}
|
|
741
874
|
|
|
742
875
|
Text.
|
|
743
876
|
`);
|
|
744
|
-
await writeFile(path.join(
|
|
745
|
-
|
|
746
|
-
description: Fixture
|
|
877
|
+
await writeFile(path.join(targetPageDir, 'content.md'), `---
|
|
878
|
+
page:
|
|
879
|
+
description: Fixture
|
|
747
880
|
---
|
|
748
881
|
|
|
882
|
+
# Target
|
|
883
|
+
|
|
749
884
|
## Work {#work}
|
|
750
885
|
|
|
751
886
|
\`\`\`norna-image-stack
|
|
752
887
|
- image: moved.jpg
|
|
753
888
|
\`\`\`
|
|
754
889
|
`);
|
|
755
|
-
await writeFile(path.join(
|
|
890
|
+
await writeFile(path.join(sourcePageDir, 'images', 'moved.jpg'), 'page image');
|
|
756
891
|
await initCleanGitWorktree(root);
|
|
757
892
|
await writeFile(path.join(root, 'dirty.txt'), 'dirty');
|
|
758
893
|
|
|
759
894
|
await assert.rejects(
|
|
760
895
|
() => runContentScript(siteDir, ['--write', '--yes']),
|
|
761
896
|
(error) => {
|
|
762
|
-
assert.match(error.output, /Cross-
|
|
897
|
+
assert.match(error.output, /Cross-page content sync requires a clean git working tree before moving files between page image roots\./);
|
|
763
898
|
assert.match(error.output, /dirty\.txt/);
|
|
764
899
|
return true;
|
|
765
900
|
},
|
|
766
901
|
);
|
|
767
902
|
|
|
768
|
-
assert.equal(await fileExists(path.join(
|
|
769
|
-
assert.equal(await fileExists(path.join(
|
|
903
|
+
assert.equal(await fileExists(path.join(targetPageDir, 'images', 'moved.jpg')), false);
|
|
904
|
+
assert.equal(await fileExists(path.join(sourcePageDir, 'images', 'moved.jpg')), true);
|
|
770
905
|
} finally {
|
|
771
906
|
await rm(root, { recursive: true, force: true });
|
|
772
907
|
}
|
|
773
908
|
});
|
|
774
909
|
|
|
775
|
-
test('content:check reports cross-
|
|
910
|
+
test('content:check reports cross-page image moves without requiring clean git status', async () => {
|
|
776
911
|
const { root, siteDir } = await createTempSite();
|
|
777
912
|
try {
|
|
778
|
-
const
|
|
779
|
-
const
|
|
780
|
-
await mkdir(path.join(
|
|
781
|
-
await mkdir(
|
|
913
|
+
const sourcePageDir = path.join(siteDir, 'pages', '010-source');
|
|
914
|
+
const targetPageDir = path.join(siteDir, 'pages', '020-target');
|
|
915
|
+
await mkdir(path.join(sourcePageDir, 'images'), { recursive: true });
|
|
916
|
+
await mkdir(targetPageDir, { recursive: true });
|
|
782
917
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
783
|
-
|
|
784
|
-
description: Fixture
|
|
918
|
+
page:
|
|
919
|
+
description: Fixture
|
|
785
920
|
---
|
|
786
921
|
|
|
922
|
+
# Cross Page Check
|
|
923
|
+
|
|
787
924
|
## Home {#home}
|
|
788
925
|
|
|
789
926
|
Text.
|
|
790
927
|
`);
|
|
791
|
-
await writeFile(path.join(
|
|
792
|
-
|
|
793
|
-
description: Fixture
|
|
928
|
+
await writeFile(path.join(sourcePageDir, 'content.md'), `---
|
|
929
|
+
page:
|
|
930
|
+
description: Fixture
|
|
794
931
|
---
|
|
795
932
|
|
|
933
|
+
# Source
|
|
934
|
+
|
|
796
935
|
## Old {#old}
|
|
797
936
|
|
|
798
937
|
Text.
|
|
799
938
|
`);
|
|
800
|
-
await writeFile(path.join(
|
|
801
|
-
|
|
802
|
-
description: Fixture
|
|
939
|
+
await writeFile(path.join(targetPageDir, 'content.md'), `---
|
|
940
|
+
page:
|
|
941
|
+
description: Fixture
|
|
803
942
|
---
|
|
804
943
|
|
|
944
|
+
# Target
|
|
945
|
+
|
|
805
946
|
## Work {#work}
|
|
806
947
|
|
|
807
948
|
\`\`\`norna-image-stack
|
|
808
949
|
- image: moved.jpg
|
|
809
950
|
\`\`\`
|
|
810
951
|
`);
|
|
811
|
-
await writeFile(path.join(
|
|
952
|
+
await writeFile(path.join(sourcePageDir, 'images', 'moved.jpg'), 'page image');
|
|
812
953
|
await writeFile(path.join(root, 'dirty.txt'), 'dirty');
|
|
813
954
|
|
|
814
955
|
await assert.rejects(
|
|
815
956
|
() => runContentScript(siteDir, ['--check']),
|
|
816
957
|
(error) => {
|
|
817
|
-
assert.match(error.output, /Image "moved\.jpg" is used here but is located in .*site\/
|
|
818
|
-
assert.match(error.output, /Run norna content:sync to move it from .*site\/
|
|
958
|
+
assert.match(error.output, /Image "moved\.jpg" is used here but is located in .*site\/pages\/010-source\/images\/moved\.jpg\./);
|
|
959
|
+
assert.match(error.output, /Run norna content:sync to move it from .*site\/pages\/010-source to .*site\/pages\/020-target\./);
|
|
819
960
|
assert.doesNotMatch(error.output, /requires a clean git working tree before moving files/);
|
|
820
961
|
return true;
|
|
821
962
|
},
|
|
@@ -825,56 +966,63 @@ description: Fixture
|
|
|
825
966
|
}
|
|
826
967
|
});
|
|
827
968
|
|
|
828
|
-
test('content:sync refuses to move a cross-
|
|
969
|
+
test('content:sync refuses to move a cross-page image still referenced by its current section', async () => {
|
|
829
970
|
const { root, siteDir } = await createTempSite();
|
|
830
971
|
try {
|
|
831
|
-
const
|
|
832
|
-
const
|
|
833
|
-
await mkdir(path.join(
|
|
834
|
-
await mkdir(
|
|
972
|
+
const sourcePageDir = path.join(siteDir, 'pages', '010-source');
|
|
973
|
+
const targetPageDir = path.join(siteDir, 'pages', '020-target');
|
|
974
|
+
await mkdir(path.join(sourcePageDir, 'images'), { recursive: true });
|
|
975
|
+
await mkdir(targetPageDir, { recursive: true });
|
|
835
976
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
836
|
-
|
|
837
|
-
description: Fixture
|
|
977
|
+
page:
|
|
978
|
+
description: Fixture
|
|
838
979
|
---
|
|
839
980
|
|
|
981
|
+
# Cross Page Copy
|
|
982
|
+
|
|
840
983
|
## Home {#home}
|
|
841
984
|
|
|
842
985
|
Text.
|
|
843
986
|
`);
|
|
844
|
-
await writeFile(path.join(
|
|
845
|
-
|
|
846
|
-
description: Fixture
|
|
987
|
+
await writeFile(path.join(sourcePageDir, 'content.md'), `---
|
|
988
|
+
page:
|
|
989
|
+
description: Fixture
|
|
847
990
|
---
|
|
848
991
|
|
|
992
|
+
# Source
|
|
993
|
+
|
|
849
994
|
## Old {#old}
|
|
850
995
|
|
|
851
996
|
\`\`\`norna-image-stack
|
|
852
997
|
- image: shared.jpg
|
|
853
998
|
\`\`\`
|
|
854
999
|
`);
|
|
855
|
-
await writeFile(path.join(
|
|
856
|
-
|
|
857
|
-
description: Fixture
|
|
1000
|
+
await writeFile(path.join(targetPageDir, 'content.md'), `---
|
|
1001
|
+
page:
|
|
1002
|
+
description: Fixture
|
|
858
1003
|
---
|
|
859
1004
|
|
|
1005
|
+
# Target
|
|
1006
|
+
|
|
860
1007
|
## Work {#work}
|
|
861
1008
|
|
|
862
1009
|
\`\`\`norna-image-stack
|
|
863
1010
|
- image: shared.jpg
|
|
864
1011
|
\`\`\`
|
|
865
1012
|
`);
|
|
866
|
-
await writeFile(path.join(
|
|
1013
|
+
await writeFile(path.join(sourcePageDir, 'images', 'shared.jpg'), 'page image');
|
|
1014
|
+
await initCleanGitWorktree(root);
|
|
867
1015
|
|
|
868
1016
|
await assert.rejects(
|
|
869
1017
|
() => runContentScript(siteDir, ['--write', '--yes']),
|
|
870
1018
|
(error) => {
|
|
871
|
-
assert.match(error.output, /Cannot relocate "shared\.jpg" from .*site\/
|
|
1019
|
+
assert.match(error.output, /Cannot relocate "shared\.jpg" from .*site\/pages\/010-source\/images\/shared\.jpg because it is still referenced from .*site\/pages\/010-source\/content\.md \[old\]\./);
|
|
872
1020
|
return true;
|
|
873
1021
|
},
|
|
874
1022
|
);
|
|
875
1023
|
|
|
876
|
-
assert.equal(await fileExists(path.join(
|
|
877
|
-
assert.equal(await fileExists(path.join(
|
|
1024
|
+
assert.equal(await fileExists(path.join(targetPageDir, 'images', 'shared.jpg')), false);
|
|
1025
|
+
assert.equal(await fileExists(path.join(sourcePageDir, 'images', 'shared.jpg')), true);
|
|
878
1026
|
} finally {
|
|
879
1027
|
await rm(root, { recursive: true, force: true });
|
|
880
1028
|
}
|
|
@@ -884,10 +1032,12 @@ test('CLI content:sync refreshes generated images after moving a carousel image'
|
|
|
884
1032
|
const { root, siteDir } = await createTempSite();
|
|
885
1033
|
try {
|
|
886
1034
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
887
|
-
|
|
888
|
-
description: Fixture
|
|
1035
|
+
page:
|
|
1036
|
+
description: Fixture
|
|
889
1037
|
---
|
|
890
1038
|
|
|
1039
|
+
# Sync Fixture
|
|
1040
|
+
|
|
891
1041
|
## Work {#work}
|
|
892
1042
|
|
|
893
1043
|
\`\`\`norna-image-carousel
|
|
@@ -898,41 +1048,48 @@ description: Fixture
|
|
|
898
1048
|
\`\`\`
|
|
899
1049
|
`);
|
|
900
1050
|
await mkdir(path.join(siteDir, 'images', 'old'), { recursive: true });
|
|
901
|
-
await
|
|
902
|
-
await cp(path.join(fixtureSiteDir, 'images', '
|
|
903
|
-
await cp(path.join(fixtureSiteDir, 'images', 'intro', 'detail.jpg'), path.join(siteDir, 'images', 'work', 'slide-two.jpg'), { force: true });
|
|
1051
|
+
await cp(path.join(fixtureSiteDir, 'images', 'hero.jpg'), path.join(siteDir, 'images', 'old', 'slide-one.jpg'));
|
|
1052
|
+
await cp(path.join(fixtureSiteDir, 'images', 'detail.jpg'), path.join(siteDir, 'images', 'slide-two.jpg'), { force: true });
|
|
904
1053
|
|
|
905
1054
|
await runNorna(['--site-dir', siteDir, 'content:sync', '--yes']);
|
|
906
1055
|
|
|
907
1056
|
const manifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
|
|
908
|
-
assert.equal(await fileExists(path.join(siteDir, 'images', '
|
|
909
|
-
assert.ok(manifest['images/
|
|
910
|
-
assert.ok(manifest['images/
|
|
1057
|
+
assert.equal(await fileExists(path.join(siteDir, 'images', 'slide-one.jpg')), true);
|
|
1058
|
+
assert.ok(manifest['images/slide-one.jpg']);
|
|
1059
|
+
assert.ok(manifest['images/slide-two.jpg']);
|
|
911
1060
|
} finally {
|
|
912
1061
|
await rm(root, { recursive: true, force: true });
|
|
913
1062
|
}
|
|
914
1063
|
});
|
|
915
1064
|
|
|
916
|
-
test('
|
|
917
|
-
const { root, siteDir } = await createTempSite();
|
|
1065
|
+
test('automatic H2 and H3 ids render while explicit ids remain stable', async () => {
|
|
1066
|
+
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
918
1067
|
try {
|
|
919
1068
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
920
|
-
|
|
921
|
-
description: Fixture
|
|
1069
|
+
page:
|
|
1070
|
+
description: Fixture
|
|
922
1071
|
---
|
|
923
1072
|
|
|
924
|
-
|
|
1073
|
+
# Heading IDs
|
|
1074
|
+
|
|
1075
|
+
## Café and **code**
|
|
1076
|
+
|
|
1077
|
+
### More details
|
|
1078
|
+
|
|
1079
|
+
Automatic identifiers.
|
|
925
1080
|
|
|
926
|
-
|
|
1081
|
+
## Renamed heading {#stable-id}
|
|
1082
|
+
|
|
1083
|
+
Explicit identifier.
|
|
927
1084
|
`);
|
|
928
1085
|
|
|
929
|
-
await
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
);
|
|
1086
|
+
await runContentScript(siteDir, ['--check']);
|
|
1087
|
+
await runNorna(['--site-dir', siteDir, 'build']);
|
|
1088
|
+
|
|
1089
|
+
const html = await readFile(path.join(root, 'dist', 'index.html'), 'utf8');
|
|
1090
|
+
assert.match(html, /<h2 id="cafe-and-code">Café and <strong>code<\/strong><\/h2>/);
|
|
1091
|
+
assert.match(html, /<h3 id="more-details">More details<\/h3>/);
|
|
1092
|
+
assert.match(html, /<h2 id="stable-id">Renamed heading<\/h2>/);
|
|
936
1093
|
} finally {
|
|
937
1094
|
await rm(root, { recursive: true, force: true });
|
|
938
1095
|
}
|
|
@@ -942,10 +1099,12 @@ test('content:check fails for malformed Norna image blocks', async () => {
|
|
|
942
1099
|
const { root, siteDir } = await createTempSite();
|
|
943
1100
|
try {
|
|
944
1101
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
945
|
-
|
|
946
|
-
description: Fixture
|
|
1102
|
+
page:
|
|
1103
|
+
description: Fixture
|
|
947
1104
|
---
|
|
948
1105
|
|
|
1106
|
+
# Malformed Block
|
|
1107
|
+
|
|
949
1108
|
## Intro {#intro}
|
|
950
1109
|
|
|
951
1110
|
\`\`\`norna-image-stack
|
|
@@ -972,10 +1131,12 @@ test('content:check fails for unknown Norna block names', async () => {
|
|
|
972
1131
|
await mkdir(path.join(siteDir, 'images', 'plain'), { recursive: true });
|
|
973
1132
|
await writeFile(path.join(siteDir, 'images', 'plain', 'image.jpg'), 'fixture image');
|
|
974
1133
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
975
|
-
|
|
976
|
-
description: Fixture
|
|
1134
|
+
page:
|
|
1135
|
+
description: Fixture
|
|
977
1136
|
---
|
|
978
1137
|
|
|
1138
|
+
# Unknown Block
|
|
1139
|
+
|
|
979
1140
|
## Plain {#plain}
|
|
980
1141
|
|
|
981
1142
|
\`\`\`norna-gallery-stack
|
|
@@ -1001,10 +1162,12 @@ test('content:check rejects the removed norna-note block', async () => {
|
|
|
1001
1162
|
const { root, siteDir } = await createTempSite();
|
|
1002
1163
|
try {
|
|
1003
1164
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1004
|
-
|
|
1005
|
-
description: Fixture
|
|
1165
|
+
page:
|
|
1166
|
+
description: Fixture
|
|
1006
1167
|
---
|
|
1007
1168
|
|
|
1169
|
+
# Removed note block
|
|
1170
|
+
|
|
1008
1171
|
## Intro {#intro}
|
|
1009
1172
|
|
|
1010
1173
|
\`\`\`norna-note
|
|
@@ -1027,13 +1190,15 @@ This block is no longer supported.
|
|
|
1027
1190
|
test('content:check supports tilde fenced Norna blocks', async () => {
|
|
1028
1191
|
const { root, siteDir } = await createTempSite();
|
|
1029
1192
|
try {
|
|
1030
|
-
await mkdir(path.join(siteDir, 'images'
|
|
1031
|
-
await writeFile(path.join(siteDir, 'images', '
|
|
1193
|
+
await mkdir(path.join(siteDir, 'images'), { recursive: true });
|
|
1194
|
+
await writeFile(path.join(siteDir, 'images', 'hero.jpg'), 'fixture image');
|
|
1032
1195
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1033
|
-
|
|
1034
|
-
description: Fixture
|
|
1196
|
+
page:
|
|
1197
|
+
description: Fixture
|
|
1035
1198
|
---
|
|
1036
1199
|
|
|
1200
|
+
# Tilde Fence
|
|
1201
|
+
|
|
1037
1202
|
## Intro {#intro}
|
|
1038
1203
|
|
|
1039
1204
|
~~~norna-image-stack
|
|
@@ -1052,10 +1217,12 @@ test('content:check ignores Norna blocks shown inside longer code fences', async
|
|
|
1052
1217
|
const { root, siteDir } = await createTempSite();
|
|
1053
1218
|
try {
|
|
1054
1219
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1055
|
-
|
|
1056
|
-
description: Fixture
|
|
1220
|
+
page:
|
|
1221
|
+
description: Fixture
|
|
1057
1222
|
---
|
|
1058
1223
|
|
|
1224
|
+
# Code Example
|
|
1225
|
+
|
|
1059
1226
|
## Intro {#intro}
|
|
1060
1227
|
|
|
1061
1228
|
\`\`\`\`md
|
|
@@ -1076,10 +1243,12 @@ test('content:check fails for likely Norna block names outside fences', async ()
|
|
|
1076
1243
|
const { root, siteDir } = await createTempSite();
|
|
1077
1244
|
try {
|
|
1078
1245
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1079
|
-
|
|
1080
|
-
description: Fixture
|
|
1246
|
+
page:
|
|
1247
|
+
description: Fixture
|
|
1081
1248
|
---
|
|
1082
1249
|
|
|
1250
|
+
# Missing Fence
|
|
1251
|
+
|
|
1083
1252
|
## Intro {#intro}
|
|
1084
1253
|
|
|
1085
1254
|
norna-image-stack
|
|
@@ -1103,10 +1272,12 @@ test('content:check fails for short Norna block fences', async () => {
|
|
|
1103
1272
|
const { root, siteDir } = await createTempSite();
|
|
1104
1273
|
try {
|
|
1105
1274
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1106
|
-
|
|
1107
|
-
description: Fixture
|
|
1275
|
+
page:
|
|
1276
|
+
description: Fixture
|
|
1108
1277
|
---
|
|
1109
1278
|
|
|
1279
|
+
# Short Fence
|
|
1280
|
+
|
|
1110
1281
|
## Intro {#intro}
|
|
1111
1282
|
|
|
1112
1283
|
\`\`norna-image-stack
|
|
@@ -1131,10 +1302,12 @@ test('content:check fails for unclosed Norna blocks', async () => {
|
|
|
1131
1302
|
const { root, siteDir } = await createTempSite();
|
|
1132
1303
|
try {
|
|
1133
1304
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1134
|
-
|
|
1135
|
-
description: Fixture
|
|
1305
|
+
page:
|
|
1306
|
+
description: Fixture
|
|
1136
1307
|
---
|
|
1137
1308
|
|
|
1309
|
+
# Unclosed Fence
|
|
1310
|
+
|
|
1138
1311
|
## Intro {#intro}
|
|
1139
1312
|
|
|
1140
1313
|
\`\`\`norna-image-stack
|
|
@@ -1159,10 +1332,12 @@ test('content:check continues after malformed Norna blocks', async () => {
|
|
|
1159
1332
|
const { root, siteDir } = await createTempSite();
|
|
1160
1333
|
try {
|
|
1161
1334
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1162
|
-
|
|
1163
|
-
description: Fixture
|
|
1335
|
+
page:
|
|
1336
|
+
description: Fixture
|
|
1164
1337
|
---
|
|
1165
1338
|
|
|
1339
|
+
# Multiple Issues
|
|
1340
|
+
|
|
1166
1341
|
## Intro {#intro}
|
|
1167
1342
|
|
|
1168
1343
|
\`\`\`norna-image-stack
|
|
@@ -1187,8 +1362,8 @@ image broken.jpg
|
|
|
1187
1362
|
() => runContentScript(siteDir, ['--check']),
|
|
1188
1363
|
(error) => {
|
|
1189
1364
|
assert.match(error.output, /Invalid norna-image-stack entry "image broken\.jpg"\. Start each image with "- image: filename\.jpg"\./);
|
|
1190
|
-
assert.match(error.output, /Image "missing-intro\.jpg" does not exist at .*site\/images\/
|
|
1191
|
-
assert.match(error.output, /Image "missing-more\.jpg" does not exist at .*site\/images\/
|
|
1365
|
+
assert.match(error.output, /Image "missing-intro\.jpg" does not exist at .*site\/images\/missing-intro\.jpg or anywhere under any page image root\./);
|
|
1366
|
+
assert.match(error.output, /Image "missing-more\.jpg" does not exist at .*site\/images\/missing-more\.jpg or anywhere under any page image root\./);
|
|
1192
1367
|
return true;
|
|
1193
1368
|
},
|
|
1194
1369
|
);
|
|
@@ -1200,13 +1375,15 @@ image broken.jpg
|
|
|
1200
1375
|
test('content:check allows Norna images without alt text', async () => {
|
|
1201
1376
|
const { root, siteDir } = await createTempSite();
|
|
1202
1377
|
try {
|
|
1203
|
-
await mkdir(path.join(siteDir, 'images'
|
|
1204
|
-
await writeFile(path.join(siteDir, 'images', '
|
|
1378
|
+
await mkdir(path.join(siteDir, 'images'), { recursive: true });
|
|
1379
|
+
await writeFile(path.join(siteDir, 'images', 'hero.jpg'), 'fixture image');
|
|
1205
1380
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1206
|
-
|
|
1207
|
-
description: Fixture
|
|
1381
|
+
page:
|
|
1382
|
+
description: Fixture
|
|
1208
1383
|
---
|
|
1209
1384
|
|
|
1385
|
+
# Missing Alt
|
|
1386
|
+
|
|
1210
1387
|
## Intro {#intro}
|
|
1211
1388
|
|
|
1212
1389
|
\`\`\`norna-image-stack
|
|
@@ -1225,10 +1402,12 @@ test('content:check reports single-image carousel and missing file together', as
|
|
|
1225
1402
|
const { root, siteDir } = await createTempSite();
|
|
1226
1403
|
try {
|
|
1227
1404
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1228
|
-
|
|
1229
|
-
description: Fixture
|
|
1405
|
+
page:
|
|
1406
|
+
description: Fixture
|
|
1230
1407
|
---
|
|
1231
1408
|
|
|
1409
|
+
# Beginner Errors
|
|
1410
|
+
|
|
1232
1411
|
## Plain {#plain}
|
|
1233
1412
|
|
|
1234
1413
|
\`\`\`norna-image-carousel
|
|
@@ -1240,7 +1419,7 @@ description: Fixture
|
|
|
1240
1419
|
() => runContentScript(siteDir, ['--check']),
|
|
1241
1420
|
(error) => {
|
|
1242
1421
|
assert.match(error.output, /norna-image-carousel on line \d+ contains 1 image\. A carousel needs at least two images\./);
|
|
1243
|
-
assert.match(error.output, /Image "foo\.jpg" does not exist at .*site\/images\/
|
|
1422
|
+
assert.match(error.output, /Image "foo\.jpg" does not exist at .*site\/images\/foo\.jpg or anywhere under any page image root\./);
|
|
1244
1423
|
return true;
|
|
1245
1424
|
},
|
|
1246
1425
|
);
|
|
@@ -1253,10 +1432,12 @@ test('content:check fails when a Norna-managed image file is missing', async ()
|
|
|
1253
1432
|
const { root, siteDir } = await createTempSite();
|
|
1254
1433
|
try {
|
|
1255
1434
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1256
|
-
|
|
1257
|
-
description: Fixture
|
|
1435
|
+
page:
|
|
1436
|
+
description: Fixture
|
|
1258
1437
|
---
|
|
1259
1438
|
|
|
1439
|
+
# Missing Image
|
|
1440
|
+
|
|
1260
1441
|
## Intro {#intro}
|
|
1261
1442
|
|
|
1262
1443
|
\`\`\`norna-image-stack
|
|
@@ -1268,7 +1449,7 @@ description: Fixture
|
|
|
1268
1449
|
await assert.rejects(
|
|
1269
1450
|
() => runContentScript(siteDir, ['--check']),
|
|
1270
1451
|
(error) => {
|
|
1271
|
-
assert.match(error.output, /Image "missing\.jpg" does not exist at .*site\/images\/
|
|
1452
|
+
assert.match(error.output, /Image "missing\.jpg" does not exist at .*site\/images\/missing\.jpg or anywhere under any page image root\./);
|
|
1272
1453
|
return true;
|
|
1273
1454
|
},
|
|
1274
1455
|
);
|
|
@@ -1281,10 +1462,12 @@ test('content:check fails when a page repeats a section id', async () => {
|
|
|
1281
1462
|
const { root, siteDir } = await createTempSite();
|
|
1282
1463
|
try {
|
|
1283
1464
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1284
|
-
|
|
1285
|
-
description: Fixture
|
|
1465
|
+
page:
|
|
1466
|
+
description: Fixture
|
|
1286
1467
|
---
|
|
1287
1468
|
|
|
1469
|
+
# Duplicate Section
|
|
1470
|
+
|
|
1288
1471
|
## Intro {#intro}
|
|
1289
1472
|
|
|
1290
1473
|
First.
|
|
@@ -1297,7 +1480,7 @@ Second.
|
|
|
1297
1480
|
await assert.rejects(
|
|
1298
1481
|
() => runContentScript(siteDir, ['--check']),
|
|
1299
1482
|
(error) => {
|
|
1300
|
-
assert.match(error.output, /
|
|
1483
|
+
assert.match(error.output, /Two headings resolve to id "intro"\./);
|
|
1301
1484
|
return true;
|
|
1302
1485
|
},
|
|
1303
1486
|
);
|
|
@@ -1306,16 +1489,51 @@ Second.
|
|
|
1306
1489
|
}
|
|
1307
1490
|
});
|
|
1308
1491
|
|
|
1309
|
-
test('content:check
|
|
1492
|
+
test('content:check reports collisions between automatic H2 and H3 ids', async () => {
|
|
1310
1493
|
const { root, siteDir } = await createTempSite();
|
|
1311
1494
|
try {
|
|
1312
1495
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1313
|
-
|
|
1314
|
-
description: Fixture
|
|
1496
|
+
page:
|
|
1497
|
+
description: Fixture
|
|
1498
|
+
---
|
|
1499
|
+
|
|
1500
|
+
# Automatic collision
|
|
1501
|
+
|
|
1502
|
+
## Förstå
|
|
1503
|
+
|
|
1504
|
+
Section text.
|
|
1505
|
+
|
|
1506
|
+
### Forsta
|
|
1507
|
+
|
|
1508
|
+
Topic text.
|
|
1509
|
+
`);
|
|
1510
|
+
|
|
1511
|
+
await assert.rejects(
|
|
1512
|
+
() => runContentScript(siteDir, ['--check']),
|
|
1513
|
+
(error) => {
|
|
1514
|
+
assert.match(error.output, /Two headings resolve to id "forsta"\./);
|
|
1515
|
+
assert.match(error.output, /The other heading is on line \d+\./);
|
|
1516
|
+
assert.match(error.output, /Add a unique explicit id to at least one heading/);
|
|
1517
|
+
return true;
|
|
1518
|
+
},
|
|
1519
|
+
);
|
|
1520
|
+
} finally {
|
|
1521
|
+
await rm(root, { recursive: true, force: true });
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
test('content:check rejects removed sections frontmatter', async () => {
|
|
1526
|
+
const { root, siteDir } = await createTempSite();
|
|
1527
|
+
try {
|
|
1528
|
+
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1529
|
+
page:
|
|
1530
|
+
description: Fixture
|
|
1315
1531
|
sections:
|
|
1316
|
-
|
|
1532
|
+
intro: {}
|
|
1317
1533
|
---
|
|
1318
1534
|
|
|
1535
|
+
# Removed Section Metadata
|
|
1536
|
+
|
|
1319
1537
|
## Intro {#intro}
|
|
1320
1538
|
|
|
1321
1539
|
Text.
|
|
@@ -1324,7 +1542,7 @@ Text.
|
|
|
1324
1542
|
await assert.rejects(
|
|
1325
1543
|
() => runContentScript(siteDir, ['--check']),
|
|
1326
1544
|
(error) => {
|
|
1327
|
-
assert.match(error.output, /
|
|
1545
|
+
assert.match(error.output, /defines "sections" at the top level, but it is not a valid top-level content field\./);
|
|
1328
1546
|
return true;
|
|
1329
1547
|
},
|
|
1330
1548
|
);
|
|
@@ -1337,10 +1555,12 @@ test('content:sync refuses ambiguous image relocation', async () => {
|
|
|
1337
1555
|
const { root, siteDir } = await createTempSite();
|
|
1338
1556
|
try {
|
|
1339
1557
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1340
|
-
|
|
1341
|
-
description: Fixture
|
|
1558
|
+
page:
|
|
1559
|
+
description: Fixture
|
|
1342
1560
|
---
|
|
1343
1561
|
|
|
1562
|
+
# Ambiguous Move
|
|
1563
|
+
|
|
1344
1564
|
## Work {#work}
|
|
1345
1565
|
|
|
1346
1566
|
\`\`\`norna-image-stack
|
|
@@ -1363,7 +1583,7 @@ description: Fixture
|
|
|
1363
1583
|
},
|
|
1364
1584
|
);
|
|
1365
1585
|
|
|
1366
|
-
assert.equal(await fileExists(path.join(siteDir, 'images', '
|
|
1586
|
+
assert.equal(await fileExists(path.join(siteDir, 'images', 'shared.jpg')), false);
|
|
1367
1587
|
assert.equal(await fileExists(path.join(siteDir, 'images', 'old-a', 'shared.jpg')), true);
|
|
1368
1588
|
assert.equal(await fileExists(path.join(siteDir, 'images', 'old-b', 'shared.jpg')), true);
|
|
1369
1589
|
} finally {
|
|
@@ -1371,14 +1591,16 @@ description: Fixture
|
|
|
1371
1591
|
}
|
|
1372
1592
|
});
|
|
1373
1593
|
|
|
1374
|
-
test('content:sync
|
|
1594
|
+
test('content:sync moves one page-local image once when multiple sections reference it', async () => {
|
|
1375
1595
|
const { root, siteDir } = await createTempSite();
|
|
1376
1596
|
try {
|
|
1377
1597
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1378
|
-
|
|
1379
|
-
description: Fixture
|
|
1598
|
+
page:
|
|
1599
|
+
description: Fixture
|
|
1380
1600
|
---
|
|
1381
1601
|
|
|
1602
|
+
# Copied Image Reference
|
|
1603
|
+
|
|
1382
1604
|
## Intro {#intro}
|
|
1383
1605
|
|
|
1384
1606
|
\`\`\`norna-image-stack
|
|
@@ -1396,16 +1618,11 @@ description: Fixture
|
|
|
1396
1618
|
await mkdir(path.join(siteDir, 'images', 'intro'), { recursive: true });
|
|
1397
1619
|
await writeFile(path.join(siteDir, 'images', 'intro', 'shared.jpg'), 'shared image');
|
|
1398
1620
|
|
|
1399
|
-
await
|
|
1400
|
-
() => runContentScript(siteDir, ['--write', '--yes']),
|
|
1401
|
-
(error) => {
|
|
1402
|
-
assert.match(error.output, /Cannot relocate "shared\.jpg" from .*site\/images\/intro\/shared\.jpg because it is still referenced from .*site\/content\.md \[intro\]\./);
|
|
1403
|
-
return true;
|
|
1404
|
-
},
|
|
1405
|
-
);
|
|
1621
|
+
await runContentScript(siteDir, ['--write', '--yes']);
|
|
1406
1622
|
|
|
1407
|
-
assert.equal(await fileExists(path.join(siteDir, 'images', 'intro', 'shared.jpg')),
|
|
1408
|
-
assert.equal(await fileExists(path.join(siteDir, 'images', '
|
|
1623
|
+
assert.equal(await fileExists(path.join(siteDir, 'images', 'intro', 'shared.jpg')), false);
|
|
1624
|
+
assert.equal(await fileExists(path.join(siteDir, 'images', 'shared.jpg')), true);
|
|
1625
|
+
await runContentScript(siteDir, ['--check']);
|
|
1409
1626
|
} finally {
|
|
1410
1627
|
await rm(root, { recursive: true, force: true });
|
|
1411
1628
|
}
|