@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
|
@@ -12,7 +12,9 @@ const obsoleteSourceReferences = [
|
|
|
12
12
|
'site/config.md',
|
|
13
13
|
'site/theme.md',
|
|
14
14
|
'site/sitewide-content.md',
|
|
15
|
+
'site/routes/',
|
|
15
16
|
'route-content.md',
|
|
17
|
+
'docs/routes.md',
|
|
16
18
|
'docs/site-structure.md',
|
|
17
19
|
];
|
|
18
20
|
const obsoleteSiteFilenames = new Set([
|
|
@@ -129,7 +131,10 @@ const checkObsoleteSiteFiles = async () => {
|
|
|
129
131
|
];
|
|
130
132
|
const files = (await Promise.all(siteRoots.map(collectFiles))).flat();
|
|
131
133
|
const obsolete = files
|
|
132
|
-
.filter((filePath) =>
|
|
134
|
+
.filter((filePath) => (
|
|
135
|
+
obsoleteSiteFilenames.has(path.basename(filePath))
|
|
136
|
+
|| filePath.split(path.sep).includes('routes')
|
|
137
|
+
))
|
|
133
138
|
.map((filePath) => path.relative(repoRoot, filePath));
|
|
134
139
|
|
|
135
140
|
assert.deepEqual(obsolete, [], `Obsolete Norna site files:\n${obsolete.join('\n')}`);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import projectContext from '../editors/vscode/norna-project.cjs';
|
|
6
|
+
import yamlSchemaCompletions from '../editors/vscode/yaml-schema-completions.cjs';
|
|
5
7
|
import { documentationRef } from './lib/documentation-links.mjs';
|
|
6
8
|
import {
|
|
7
9
|
findNornaSiteRoot,
|
|
@@ -17,13 +19,27 @@ import {
|
|
|
17
19
|
const root = await mkdtemp(path.join(os.tmpdir(), 'norna-editor-language-'));
|
|
18
20
|
const siteRoot = path.join(root, 'site');
|
|
19
21
|
const homeContentPath = path.join(siteRoot, 'content.md');
|
|
20
|
-
const
|
|
22
|
+
const pageContentPath = path.join(siteRoot, 'pages', '010-about', 'content.md');
|
|
23
|
+
const pageThemePath = path.join(siteRoot, 'pages', '010-about', 'theme.yaml');
|
|
24
|
+
const nestedPageContentPath = path.join(siteRoot, 'pages', '010-about', 'pages', '020-team', 'content.md');
|
|
25
|
+
const nestedPageThemePath = path.join(siteRoot, 'pages', '010-about', 'pages', '020-team', 'theme.yaml');
|
|
26
|
+
const installedNornaRoot = path.join(root, 'node_modules', '@janga', 'norna');
|
|
27
|
+
const packageManifestPath = path.join(installedNornaRoot, 'schemas', 'manifest.json');
|
|
28
|
+
const {
|
|
29
|
+
getNornaDocumentContext,
|
|
30
|
+
getNornaProjectContext,
|
|
31
|
+
supportedEditorApiVersion,
|
|
32
|
+
supportedSchemaVersion,
|
|
33
|
+
} = projectContext;
|
|
34
|
+
const { getYamlSchemaSnippetCompletions } = yamlSchemaCompletions;
|
|
21
35
|
|
|
22
36
|
const homeSource = `---
|
|
23
|
-
|
|
24
|
-
description: Exercises project-local image discovery.
|
|
37
|
+
page:
|
|
38
|
+
description: Exercises project-local image discovery.
|
|
25
39
|
---
|
|
26
40
|
|
|
41
|
+
# Editor fixture
|
|
42
|
+
|
|
27
43
|
## Intro {#intro}
|
|
28
44
|
|
|
29
45
|
Text with a missing note {note-ref}.
|
|
@@ -33,28 +49,43 @@ Text with a missing note {note-ref}.
|
|
|
33
49
|
- image: portrait.jpg
|
|
34
50
|
\`\`\`
|
|
35
51
|
`;
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
description: About this fixture.
|
|
52
|
+
const pageSource = `---
|
|
53
|
+
page:
|
|
54
|
+
description: About this fixture.
|
|
39
55
|
---
|
|
40
56
|
|
|
57
|
+
# About
|
|
58
|
+
|
|
41
59
|
## Team {#team}
|
|
42
60
|
|
|
43
|
-
|
|
61
|
+
Page content.
|
|
44
62
|
`;
|
|
45
63
|
|
|
46
64
|
try {
|
|
47
|
-
await mkdir(path.join(
|
|
48
|
-
await mkdir(path.join(siteRoot, '
|
|
65
|
+
await mkdir(path.join(installedNornaRoot, 'schemas'), { recursive: true });
|
|
66
|
+
await mkdir(path.join(siteRoot, 'images'), { recursive: true });
|
|
67
|
+
await mkdir(path.join(siteRoot, 'pages', '010-about', 'images'), { recursive: true });
|
|
68
|
+
await mkdir(path.dirname(nestedPageContentPath), { recursive: true });
|
|
49
69
|
await mkdir(path.join(siteRoot, 'public'), { recursive: true });
|
|
70
|
+
await writeFile(path.join(root, 'package.json'), JSON.stringify({ name: 'editor-fixture', version: '1.0.0' }));
|
|
71
|
+
await writeFile(path.join(installedNornaRoot, 'package.json'), JSON.stringify({ name: '@janga/norna', version: '9.8.7' }));
|
|
72
|
+
await writeFile(packageManifestPath, JSON.stringify({
|
|
73
|
+
editorApiVersion: supportedEditorApiVersion,
|
|
74
|
+
files: {
|
|
75
|
+
config: 'config.schema.json',
|
|
76
|
+
contentFrontmatter: 'content-frontmatter.schema.json',
|
|
77
|
+
sitewideContent: 'sitewide-content.schema.json',
|
|
78
|
+
theme: 'theme.schema.json',
|
|
79
|
+
},
|
|
80
|
+
schemaVersion: supportedSchemaVersion,
|
|
81
|
+
}));
|
|
50
82
|
await writeFile(path.join(siteRoot, 'config.yaml'), 'url: https://example.com/\n');
|
|
51
|
-
await writeFile(path.join(siteRoot, '
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
height: 2rem
|
|
83
|
+
await writeFile(path.join(siteRoot, 'theme.yaml'), 'preset: project\n');
|
|
84
|
+
await writeFile(path.join(siteRoot, 'sitewide-content.yaml'), `logo:
|
|
85
|
+
height: 2rem
|
|
55
86
|
`);
|
|
56
|
-
await writeFile(path.join(siteRoot, 'images', '
|
|
57
|
-
await writeFile(path.join(siteRoot, '
|
|
87
|
+
await writeFile(path.join(siteRoot, 'images', 'local.jpg'), 'local');
|
|
88
|
+
await writeFile(path.join(siteRoot, 'pages', '010-about', 'images', 'portrait.jpg'), 'portrait');
|
|
58
89
|
await writeFile(path.join(siteRoot, 'public', 'logo.svg'), '<svg/>');
|
|
59
90
|
await writeFile(path.join(siteRoot, 'public', 'logo.png'), 'logo');
|
|
60
91
|
await writeFile(path.join(siteRoot, 'public', 'favicon.svg'), '<svg/>');
|
|
@@ -62,9 +93,104 @@ try {
|
|
|
62
93
|
await writeFile(path.join(siteRoot, 'public', 'favion.ico'), 'typo');
|
|
63
94
|
await writeFile(path.join(siteRoot, 'public', 'favicon-32x32.png'), 'unrecognized');
|
|
64
95
|
await writeFile(homeContentPath, homeSource);
|
|
65
|
-
await writeFile(
|
|
96
|
+
await writeFile(pageContentPath, pageSource);
|
|
97
|
+
await writeFile(pageThemePath, 'preset: portfolio\n');
|
|
98
|
+
await writeFile(nestedPageContentPath, pageSource.replace('# About', '# Team'));
|
|
99
|
+
await writeFile(nestedPageThemePath, 'preset: documentation\n');
|
|
66
100
|
|
|
67
101
|
assert.equal(await findNornaSiteRoot(homeContentPath), siteRoot);
|
|
102
|
+
assert.equal(getNornaDocumentContext(homeContentPath).schemaKind, 'contentFrontmatter');
|
|
103
|
+
assert.equal(getNornaDocumentContext(homeContentPath).nornaPackage.root, installedNornaRoot);
|
|
104
|
+
assert.equal(getNornaDocumentContext(path.join(siteRoot, 'config.yaml')).schemaKind, 'config');
|
|
105
|
+
assert.equal(getNornaDocumentContext(path.join(siteRoot, 'theme.yaml')).schemaKind, 'theme');
|
|
106
|
+
assert.equal(getNornaDocumentContext(path.join(siteRoot, 'sitewide-content.yaml')).schemaKind, 'sitewideContent');
|
|
107
|
+
assert.equal(getNornaDocumentContext(pageContentPath).pageDirectory, '010-about');
|
|
108
|
+
assert.equal(getNornaDocumentContext(pageThemePath).schemaKind, 'theme');
|
|
109
|
+
assert.equal(getNornaDocumentContext(nestedPageContentPath).pageDirectory, '010-about/pages/020-team');
|
|
110
|
+
assert.equal(getNornaDocumentContext(nestedPageContentPath).schemaKind, 'contentFrontmatter');
|
|
111
|
+
assert.equal(getNornaDocumentContext(nestedPageThemePath).schemaKind, 'theme');
|
|
112
|
+
assert.equal(getNornaProjectContext(path.join(siteRoot, 'public', 'logo.svg')).siteRoot, siteRoot);
|
|
113
|
+
assert.equal(getNornaDocumentContext(path.join(siteRoot, 'public', 'logo.svg')), null);
|
|
114
|
+
assert.equal(getNornaDocumentContext(path.join(root, 'README.md')), null);
|
|
115
|
+
assert.equal(getNornaDocumentContext(path.join(root, 'docs', 'content.md')), null);
|
|
116
|
+
await mkdir(path.join(siteRoot, 'pages', 'about'), { recursive: true });
|
|
117
|
+
await writeFile(path.join(siteRoot, 'pages', 'about', 'content.md'), pageSource);
|
|
118
|
+
assert.equal(getNornaDocumentContext(path.join(siteRoot, 'pages', 'about', 'content.md')), null);
|
|
119
|
+
await mkdir(path.join(siteRoot, 'pages', '010-about', 'nested'), { recursive: true });
|
|
120
|
+
await writeFile(path.join(siteRoot, 'pages', '010-about', 'nested', 'content.md'), pageSource);
|
|
121
|
+
assert.equal(getNornaDocumentContext(path.join(siteRoot, 'pages', '010-about', 'nested', 'content.md')), null);
|
|
122
|
+
|
|
123
|
+
const compatibleManifest = await readFile(packageManifestPath, 'utf8');
|
|
124
|
+
await writeFile(packageManifestPath, JSON.stringify({
|
|
125
|
+
...JSON.parse(compatibleManifest),
|
|
126
|
+
editorApiVersion: supportedEditorApiVersion + 1,
|
|
127
|
+
}));
|
|
128
|
+
assert.equal(getNornaDocumentContext(homeContentPath).schemaCompatible, true);
|
|
129
|
+
assert.equal(getNornaDocumentContext(homeContentPath).editorCompatible, false);
|
|
130
|
+
await writeFile(packageManifestPath, JSON.stringify({
|
|
131
|
+
...JSON.parse(compatibleManifest),
|
|
132
|
+
schemaVersion: supportedSchemaVersion + 1,
|
|
133
|
+
}));
|
|
134
|
+
assert.equal(getNornaDocumentContext(homeContentPath).schemaCompatible, false);
|
|
135
|
+
await writeFile(packageManifestPath, compatibleManifest);
|
|
136
|
+
|
|
137
|
+
const sitewideSchema = JSON.parse(await readFile(path.join(process.cwd(), 'schemas', 'sitewide-content.schema.json'), 'utf8'));
|
|
138
|
+
const bannerSource = 'banners:\n - ';
|
|
139
|
+
const bannerSnippets = getYamlSchemaSnippetCompletions({
|
|
140
|
+
lineText: ' - ',
|
|
141
|
+
offset: bannerSource.length,
|
|
142
|
+
schema: sitewideSchema,
|
|
143
|
+
source: bannerSource,
|
|
144
|
+
});
|
|
145
|
+
assert.equal(bannerSnippets.length, 1);
|
|
146
|
+
assert.equal(bannerSnippets[0].label, 'Norna: Warning banner');
|
|
147
|
+
assert.equal(bannerSnippets[0].text, ` - id: \${1:project-status}
|
|
148
|
+
tone: warning
|
|
149
|
+
title: \${2:Important notice}
|
|
150
|
+
text: \${3:Brief explanation.}`);
|
|
151
|
+
assert.deepEqual(getYamlSchemaSnippetCompletions({
|
|
152
|
+
lineText: ' - id: existing',
|
|
153
|
+
offset: bannerSource.length,
|
|
154
|
+
schema: sitewideSchema,
|
|
155
|
+
source: bannerSource,
|
|
156
|
+
}), []);
|
|
157
|
+
const unrelatedSequence = 'other:\n - ';
|
|
158
|
+
assert.deepEqual(getYamlSchemaSnippetCompletions({
|
|
159
|
+
lineText: ' - ',
|
|
160
|
+
offset: unrelatedSequence.length,
|
|
161
|
+
schema: sitewideSchema,
|
|
162
|
+
source: unrelatedSequence,
|
|
163
|
+
}), []);
|
|
164
|
+
const themeSchema = JSON.parse(await readFile(path.join(process.cwd(), 'schemas', 'theme.schema.json'), 'utf8'));
|
|
165
|
+
const gutterSource = 'layout:\n gutter: ';
|
|
166
|
+
const gutterSnippets = getYamlSchemaSnippetCompletions({
|
|
167
|
+
lineText: ' gutter: ',
|
|
168
|
+
offset: gutterSource.length,
|
|
169
|
+
schema: themeSchema,
|
|
170
|
+
source: gutterSource,
|
|
171
|
+
});
|
|
172
|
+
assert.equal(gutterSnippets[0].label, 'Norna: Responsive page gutter');
|
|
173
|
+
assert.equal(gutterSnippets[0].text, ` gutter:
|
|
174
|
+
desktop: \${1:clamp(1.25rem, 4vw, 3rem)}
|
|
175
|
+
mobile: \${2:1rem}`);
|
|
176
|
+
const typographySource = 'typography:\n overrides: ';
|
|
177
|
+
const typographySnippets = getYamlSchemaSnippetCompletions({
|
|
178
|
+
lineText: ' overrides: ',
|
|
179
|
+
offset: typographySource.length,
|
|
180
|
+
schema: themeSchema,
|
|
181
|
+
source: typographySource,
|
|
182
|
+
});
|
|
183
|
+
assert.equal(typographySnippets[0].label, 'Norna: Typography overrides');
|
|
184
|
+
assert.match(typographySnippets[0].text, /headings:\n h2:/);
|
|
185
|
+
const buildInfoSource = 'footer:\n buildInfo: ';
|
|
186
|
+
const buildInfoSnippets = getYamlSchemaSnippetCompletions({
|
|
187
|
+
lineText: ' buildInfo: ',
|
|
188
|
+
offset: buildInfoSource.length,
|
|
189
|
+
schema: sitewideSchema,
|
|
190
|
+
source: buildInfoSource,
|
|
191
|
+
});
|
|
192
|
+
assert.deepEqual(buildInfoSnippets, []);
|
|
193
|
+
|
|
68
194
|
const publicAssetStatus = await getSitePublicAssetStatus(homeContentPath);
|
|
69
195
|
assert.deepEqual(publicAssetStatus.logos, ['logo.png', 'logo.svg']);
|
|
70
196
|
assert.deepEqual(publicAssetStatus.browserIcons, ['favicon.svg']);
|
|
@@ -75,12 +201,14 @@ try {
|
|
|
75
201
|
await rm(path.join(siteRoot, 'public', 'logo.svg'), { force: true });
|
|
76
202
|
await rm(path.join(siteRoot, 'public', 'logo.png'), { force: true });
|
|
77
203
|
const missingLogoStatus = await getSitePublicAssetStatus(homeContentPath);
|
|
78
|
-
assert.ok(missingLogoStatus.issues.some(({ code, line }) => code === 'missing-logo-file' && line ===
|
|
204
|
+
assert.ok(missingLogoStatus.issues.some(({ code, line }) => code === 'missing-logo-file' && line === 1));
|
|
79
205
|
assert.equal(nornaBlockDefinitions['norna-image-stack'].description.includes('vertical stack'), true);
|
|
80
206
|
assert.match(
|
|
81
207
|
nornaBlockDefinitions['norna-image-stack'].documentation,
|
|
82
|
-
new RegExp(`/blob/${documentationRef.replaceAll('.', '\\.')}\/docs/content\\.md#
|
|
208
|
+
new RegExp(`/blob/${documentationRef.replaceAll('.', '\\.')}\/docs/content\\.md#image-stack`),
|
|
83
209
|
);
|
|
210
|
+
assert.match(nornaBlockDefinitions['norna-image-carousel'].documentation, /docs\/content\.md#image-carousel/);
|
|
211
|
+
assert.match(nornaBlockDefinitions['norna-card-list'].documentation, /docs\/content\.md#card-list/);
|
|
84
212
|
assert.equal(nornaBlockDefinitions['norna-card-list'].options.layout.default, 'image-top');
|
|
85
213
|
|
|
86
214
|
const stackFieldSource = homeSource.replace('- image: portrait.jpg', '- image: portrait.jpg\n ');
|
|
@@ -116,7 +244,7 @@ try {
|
|
|
116
244
|
line: localDefinitionLine,
|
|
117
245
|
source: homeSource,
|
|
118
246
|
});
|
|
119
|
-
assert.deepEqual(localDefinition.files, [path.join(siteRoot, 'images', '
|
|
247
|
+
assert.deepEqual(localDefinition.files, [path.join(siteRoot, 'images', 'local.jpg')]);
|
|
120
248
|
|
|
121
249
|
const diagnostics = await getMarkdownDiagnostics({ documentPath: homeContentPath, source: homeSource });
|
|
122
250
|
assert.ok(diagnostics.some(({ code, message }) => code === 'image-needs-sync' && message.includes('Run "norna content:sync"')));
|
|
@@ -125,7 +253,23 @@ try {
|
|
|
125
253
|
documentPath: homeContentPath,
|
|
126
254
|
source: homeSource.replace('## Intro {#intro}', '## Intro'),
|
|
127
255
|
});
|
|
128
|
-
assert.
|
|
256
|
+
assert.equal(missingIdDiagnostics.some(({ code }) => code === 'missing-section-id'), false);
|
|
257
|
+
const duplicateIdDiagnostics = await getMarkdownDiagnostics({
|
|
258
|
+
documentPath: homeContentPath,
|
|
259
|
+
source: homeSource.replace('## Intro {#intro}', '## Intro\n\n### Intro'),
|
|
260
|
+
});
|
|
261
|
+
assert.ok(duplicateIdDiagnostics.some(({ code }) => code === 'duplicate-heading-id'));
|
|
262
|
+
const missingTitleDiagnostics = await getMarkdownDiagnostics({
|
|
263
|
+
documentPath: homeContentPath,
|
|
264
|
+
source: homeSource.replace('# Editor fixture', '## Editor fixture {#editor-fixture}'),
|
|
265
|
+
});
|
|
266
|
+
assert.ok(missingTitleDiagnostics.some(({ code }) => code === 'missing-page-title'));
|
|
267
|
+
const codeHeadingDiagnostics = await getMarkdownDiagnostics({
|
|
268
|
+
documentPath: homeContentPath,
|
|
269
|
+
source: `${homeSource}\n\`\`\`md\n# Example title\n## Example section\n\`\`\`\n`,
|
|
270
|
+
});
|
|
271
|
+
assert.equal(codeHeadingDiagnostics.some(({ code }) => code === 'duplicate-page-title'), false);
|
|
272
|
+
assert.equal(codeHeadingDiagnostics.some(({ code }) => code === 'duplicate-heading-id'), false);
|
|
129
273
|
const unclosedBlockDiagnostics = await getMarkdownDiagnostics({
|
|
130
274
|
documentPath: homeContentPath,
|
|
131
275
|
source: homeSource.replace(/\n```\n$/, '\n'),
|
|
@@ -137,14 +281,14 @@ try {
|
|
|
137
281
|
});
|
|
138
282
|
assert.ok(markdownImageDiagnostics.some(({ code }) => code === 'local-markdown-image'));
|
|
139
283
|
|
|
140
|
-
await writeFile(
|
|
284
|
+
await writeFile(pageContentPath, pageSource.replace('Page content.', `\`\`\`norna-image-stack
|
|
141
285
|
- image: portrait.jpg
|
|
142
286
|
\`\`\``));
|
|
143
287
|
const sharedDiagnostics = await getMarkdownDiagnostics({ documentPath: homeContentPath, source: homeSource });
|
|
144
288
|
assert.ok(sharedDiagnostics.some(({ message }) => message.includes('is still referenced by')));
|
|
145
289
|
|
|
146
|
-
await mkdir(path.join(siteRoot, '
|
|
147
|
-
await writeFile(path.join(siteRoot, '
|
|
290
|
+
await mkdir(path.join(siteRoot, 'pages', '020-contact', 'images'), { recursive: true });
|
|
291
|
+
await writeFile(path.join(siteRoot, 'pages', '020-contact', 'images', 'portrait.jpg'), 'duplicate');
|
|
148
292
|
const ambiguousDiagnostics = await getMarkdownDiagnostics({ documentPath: homeContentPath, source: homeSource });
|
|
149
293
|
assert.ok(ambiguousDiagnostics.some(({ message }) => message.includes('is ambiguous')));
|
|
150
294
|
|
|
@@ -41,7 +41,7 @@ try {
|
|
|
41
41
|
const directSiteRoot = path.join(tempRoot, 'current-directory-site');
|
|
42
42
|
await mkdir(directSiteRoot, { recursive: true });
|
|
43
43
|
await writeFile(path.join(directSiteRoot, 'config.yaml'), 'url: https://example.com/\n');
|
|
44
|
-
await writeFile(path.join(directSiteRoot, 'content.md'), '
|
|
44
|
+
await writeFile(path.join(directSiteRoot, 'content.md'), '# Direct Site\n\n## Intro {#intro}\n\nText.\n');
|
|
45
45
|
const directSiteProjectRoot = await realpath(tempRoot);
|
|
46
46
|
const directSiteDoctorResult = runCli(['doctor'], {
|
|
47
47
|
cwd: directSiteRoot,
|
|
@@ -185,6 +185,7 @@ try {
|
|
|
185
185
|
"['run', 'schemas:generate']",
|
|
186
186
|
"['test']",
|
|
187
187
|
"['commit', '-m', `Release ${releaseTag}`]",
|
|
188
|
+
"['tag', '-a', releaseTag, '-m', `Release ${releaseTag}`]",
|
|
188
189
|
"['run', 'release:publish']",
|
|
189
190
|
"['push', '--follow-tags']",
|
|
190
191
|
];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import {
|
|
3
|
+
formatHeadingIdentifierIssue,
|
|
4
|
+
getHeadingIdentifierIssues,
|
|
5
|
+
getMarkdownHeadings,
|
|
6
|
+
slugifyHeadingText,
|
|
7
|
+
} from './lib/heading-ids.mjs';
|
|
8
|
+
|
|
9
|
+
assert.equal(slugifyHeadingText('Förstå Norna'), 'forsta-norna');
|
|
10
|
+
assert.equal(slugifyHeadingText('Café Über Straße'), 'cafe-uber-strasse');
|
|
11
|
+
assert.equal(slugifyHeadingText('Smørrebrød & œuvre'), 'smorrebrod-oeuvre');
|
|
12
|
+
assert.equal(slugifyHeadingText("What's new?"), 'whats-new');
|
|
13
|
+
|
|
14
|
+
const { headings } = await getMarkdownHeadings(`# Page title
|
|
15
|
+
|
|
16
|
+
## A **bold** [heading](https://example.com)
|
|
17
|
+
|
|
18
|
+
### Café details
|
|
19
|
+
|
|
20
|
+
## Renamed heading {#stable-id}
|
|
21
|
+
|
|
22
|
+
\`\`\`md
|
|
23
|
+
## Not a heading
|
|
24
|
+
\`\`\`
|
|
25
|
+
`);
|
|
26
|
+
|
|
27
|
+
assert.deepEqual(
|
|
28
|
+
headings.map(({ depth, explicitId, id, title }) => ({ depth, explicitId, id, title })),
|
|
29
|
+
[
|
|
30
|
+
{ depth: 1, explicitId: null, id: null, title: 'Page title' },
|
|
31
|
+
{ depth: 2, explicitId: null, id: 'a-bold-heading', title: 'A bold heading' },
|
|
32
|
+
{ depth: 3, explicitId: null, id: 'cafe-details', title: 'Café details' },
|
|
33
|
+
{ depth: 2, explicitId: 'stable-id', id: 'stable-id', title: 'Renamed heading' },
|
|
34
|
+
],
|
|
35
|
+
);
|
|
36
|
+
assert.deepEqual(getHeadingIdentifierIssues(headings), []);
|
|
37
|
+
|
|
38
|
+
const collision = await getMarkdownHeadings(`# Page
|
|
39
|
+
|
|
40
|
+
## Förstå
|
|
41
|
+
|
|
42
|
+
### Forsta
|
|
43
|
+
`);
|
|
44
|
+
assert.equal(getHeadingIdentifierIssues(collision.headings)[0]?.code, 'duplicate-heading-id');
|
|
45
|
+
assert.equal(
|
|
46
|
+
formatHeadingIdentifierIssue(getHeadingIdentifierIssues(collision.headings)[0], 'site/content.md'),
|
|
47
|
+
[
|
|
48
|
+
'site/content.md: Two headings resolve to id "forsta".',
|
|
49
|
+
'- line 3: "Förstå"',
|
|
50
|
+
'- line 5: "Forsta"',
|
|
51
|
+
'Add a unique explicit id to at least one heading, for example {#forsta-details}.',
|
|
52
|
+
].join('\n'),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const invalid = await getMarkdownHeadings(`# Page
|
|
56
|
+
|
|
57
|
+
## Symbols 🎉
|
|
58
|
+
|
|
59
|
+
### 日本語
|
|
60
|
+
|
|
61
|
+
## Explicit {#Not Valid}
|
|
62
|
+
`);
|
|
63
|
+
assert.deepEqual(
|
|
64
|
+
getHeadingIdentifierIssues(invalid.headings).map(({ code }) => code),
|
|
65
|
+
['empty-heading-id', 'invalid-heading-id'],
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
console.log('Heading id tests passed.');
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import {
|
|
3
|
+
getAutomaticNavigationMode,
|
|
4
|
+
navigationModeNames,
|
|
5
|
+
resolveNavigationModel,
|
|
6
|
+
} from './lib/navigation-model.mjs';
|
|
7
|
+
|
|
8
|
+
const home = (headings = []) => ({
|
|
9
|
+
isHome: true,
|
|
10
|
+
listed: true,
|
|
11
|
+
depth: 1,
|
|
12
|
+
headings,
|
|
13
|
+
});
|
|
14
|
+
const page = (headings = [], overrides = {}) => ({
|
|
15
|
+
isHome: false,
|
|
16
|
+
listed: true,
|
|
17
|
+
depth: 1,
|
|
18
|
+
headings,
|
|
19
|
+
...overrides,
|
|
20
|
+
});
|
|
21
|
+
const h2 = { depth: 2 };
|
|
22
|
+
const h3 = { depth: 3 };
|
|
23
|
+
|
|
24
|
+
assert.deepEqual(navigationModeNames, ['automatic', 'sections', 'top', 'tree']);
|
|
25
|
+
assert.equal(getAutomaticNavigationMode([home([h2, h3])]), 'sections');
|
|
26
|
+
assert.equal(getAutomaticNavigationMode([home([h2]), page([h2])]), 'top');
|
|
27
|
+
assert.equal(getAutomaticNavigationMode([home([h2]), page([h2, h3])]), 'tree');
|
|
28
|
+
assert.equal(
|
|
29
|
+
getAutomaticNavigationMode([home([h2]), page([h2, h3], { listed: false })]),
|
|
30
|
+
'sections',
|
|
31
|
+
);
|
|
32
|
+
assert.equal(
|
|
33
|
+
getAutomaticNavigationMode([home([h2]), page([], { depth: 2 })]),
|
|
34
|
+
'top',
|
|
35
|
+
);
|
|
36
|
+
assert.equal(
|
|
37
|
+
getAutomaticNavigationMode([home([h2]), page([h2], { depth: 2 })]),
|
|
38
|
+
'tree',
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
for (const mode of ['sections', 'top', 'tree']) {
|
|
42
|
+
const resolved = resolveNavigationModel({
|
|
43
|
+
mode,
|
|
44
|
+
pages: [home([h2]), page([h2, h3])],
|
|
45
|
+
});
|
|
46
|
+
assert.equal(resolved.requestedMode, mode);
|
|
47
|
+
assert.equal(resolved.mode, mode);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const automatic = resolveNavigationModel({
|
|
51
|
+
pages: [home([h2]), page([h2, h3])],
|
|
52
|
+
});
|
|
53
|
+
assert.equal(automatic.requestedMode, 'automatic');
|
|
54
|
+
assert.equal(automatic.mode, 'tree');
|
|
55
|
+
assert.equal(automatic.listedPageCount, 2);
|
|
56
|
+
assert.equal(automatic.maximumDepth, 3);
|
|
57
|
+
|
|
58
|
+
assert.throws(
|
|
59
|
+
() => resolveNavigationModel({ mode: 'sidebar', pages: [home()] }),
|
|
60
|
+
/Unknown navigation mode "sidebar".*automatic, sections, top, tree/,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
console.log('Navigation model test passed.');
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { access, cp, mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
const repoRoot = path.resolve(import.meta.dirname, '..');
|
|
9
|
+
const fixtureRoot = path.join(repoRoot, 'fixtures', 'nested-pages');
|
|
10
|
+
const tempParent = path.join(repoRoot, 'node_modules', '.cache');
|
|
11
|
+
await mkdir(tempParent, { recursive: true });
|
|
12
|
+
const tempRoot = await mkdtemp(path.join(tempParent, 'norna-nested-pages-'));
|
|
13
|
+
const fixtureCopyRoot = path.join(tempRoot, 'nested-pages');
|
|
14
|
+
const nornaBin = path.join(repoRoot, 'bin', 'norna.mjs');
|
|
15
|
+
const runNorna = (siteDir, ...args) => execFileAsync(process.execPath, [
|
|
16
|
+
nornaBin,
|
|
17
|
+
'--site-dir',
|
|
18
|
+
siteDir,
|
|
19
|
+
...args,
|
|
20
|
+
], {
|
|
21
|
+
cwd: repoRoot,
|
|
22
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
23
|
+
});
|
|
24
|
+
const runGit = (args) => execFileAsync('git', args, {
|
|
25
|
+
cwd: fixtureCopyRoot,
|
|
26
|
+
env: {
|
|
27
|
+
...process.env,
|
|
28
|
+
GIT_AUTHOR_NAME: 'Norna Test',
|
|
29
|
+
GIT_AUTHOR_EMAIL: 'norna@example.test',
|
|
30
|
+
GIT_COMMITTER_NAME: 'Norna Test',
|
|
31
|
+
GIT_COMMITTER_EMAIL: 'norna@example.test',
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
const fileExists = (filePath) => access(filePath).then(() => true, () => false);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await cp(fixtureRoot, fixtureCopyRoot, { recursive: true });
|
|
38
|
+
const siteDir = path.join(fixtureCopyRoot, 'site');
|
|
39
|
+
await runNorna(siteDir, 'content:check');
|
|
40
|
+
await runNorna(siteDir, 'build');
|
|
41
|
+
|
|
42
|
+
const distDir = path.join(fixtureCopyRoot, 'dist');
|
|
43
|
+
const expectedPages = [
|
|
44
|
+
'index.html',
|
|
45
|
+
'guides/index.html',
|
|
46
|
+
'guides/installation/index.html',
|
|
47
|
+
'guides/installation/macos/index.html',
|
|
48
|
+
'guides/workflows/index.html',
|
|
49
|
+
'reference/index.html',
|
|
50
|
+
'reference/installation/index.html',
|
|
51
|
+
];
|
|
52
|
+
for (const pagePath of expectedPages) {
|
|
53
|
+
assert.match(await readFile(path.join(distDir, pagePath), 'utf8'), /<main\b[^>]*id="main-content"/);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const rootHtml = await readFile(path.join(distDir, 'index.html'), 'utf8');
|
|
57
|
+
const guidesHtml = await readFile(path.join(distDir, 'guides', 'index.html'), 'utf8');
|
|
58
|
+
const installationHtml = await readFile(path.join(distDir, 'guides', 'installation', 'index.html'), 'utf8');
|
|
59
|
+
const macosHtml = await readFile(path.join(distDir, 'guides', 'installation', 'macos', 'index.html'), 'utf8');
|
|
60
|
+
assert.match(rootHtml, /--color-page: #000000/);
|
|
61
|
+
assert.match(guidesHtml, /--color-page: #ffffff/);
|
|
62
|
+
assert.match(installationHtml, /--color-page: #ffffff/);
|
|
63
|
+
assert.match(macosHtml, /--color-page: #ffffff/);
|
|
64
|
+
assert.match(installationHtml, /Three nested page levels connected in sequence/);
|
|
65
|
+
assert.match(installationHtml, /\/original\/pages\/010-guides\/pages\/010-installation\/images\/diagram-[a-f0-9]+\.svg/);
|
|
66
|
+
assert.match(macosHtml, /class="site-nav-item site-nav-item-has-submenu site-nav-item-current-branch"/);
|
|
67
|
+
assert.match(macosHtml, /href="\/guides\/installation\/macos\/" aria-current="page"/);
|
|
68
|
+
assert.match(macosHtml, /<details class="mobile-navigation-branch" open>/);
|
|
69
|
+
assert.match(macosHtml, /<nav class="site-breadcrumbs" aria-label="Breadcrumb"><ol><li><a href="\/guides\/">Guides<\/a><\/li><li><a href="\/guides\/installation\/">Installation<\/a><\/li><li><span aria-current="page">macOS<\/span>/);
|
|
70
|
+
for (const submenuMatch of macosHtml.matchAll(/<div class="site-nav-submenu">([\s\S]*?)<\/div>/g)) {
|
|
71
|
+
assert.doesNotMatch(submenuMatch[1], /href="[^"#]*#[^"]+"/);
|
|
72
|
+
}
|
|
73
|
+
assert.match(macosHtml, /<nav class="mobile-nav-view" aria-label="On this page">/);
|
|
74
|
+
assert.ok(
|
|
75
|
+
macosHtml.indexOf('href="/guides/installation/"') < macosHtml.indexOf('href="/guides/workflows/"'),
|
|
76
|
+
'Nested sibling navigation should follow page directory order.',
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
await runGit(['init']);
|
|
80
|
+
await runGit(['add', '.']);
|
|
81
|
+
await runGit(['commit', '-m', 'Initial nested fixture']);
|
|
82
|
+
const installationContentPath = path.join(
|
|
83
|
+
siteDir,
|
|
84
|
+
'pages',
|
|
85
|
+
'010-guides',
|
|
86
|
+
'pages',
|
|
87
|
+
'010-installation',
|
|
88
|
+
'content.md',
|
|
89
|
+
);
|
|
90
|
+
const workflowsContentPath = path.join(siteDir, 'pages', '010-guides', 'pages', '020-workflows', 'content.md');
|
|
91
|
+
const imageBlockPattern = /\n```norna-image-stack\n[\s\S]*?\n```\n/;
|
|
92
|
+
const installationContent = await readFile(installationContentPath, 'utf8');
|
|
93
|
+
const imageBlock = installationContent.match(imageBlockPattern)?.[0];
|
|
94
|
+
assert.ok(imageBlock, 'The nested fixture needs a managed image block to exercise content:sync.');
|
|
95
|
+
await writeFile(installationContentPath, installationContent.replace(imageBlockPattern, '\n'));
|
|
96
|
+
await writeFile(workflowsContentPath, `${await readFile(workflowsContentPath, 'utf8')}${imageBlock}`);
|
|
97
|
+
await runGit(['add', '.']);
|
|
98
|
+
await runGit(['commit', '-m', 'Move nested image reference']);
|
|
99
|
+
await runNorna(siteDir, 'content:sync', '--yes');
|
|
100
|
+
|
|
101
|
+
const previousImagePath = path.join(siteDir, 'pages', '010-guides', 'pages', '010-installation', 'images', 'diagram.svg');
|
|
102
|
+
const nextImagePath = path.join(siteDir, 'pages', '010-guides', 'pages', '020-workflows', 'images', 'diagram.svg');
|
|
103
|
+
assert.equal(await fileExists(previousImagePath), false);
|
|
104
|
+
assert.equal(await fileExists(nextImagePath), true);
|
|
105
|
+
await runNorna(siteDir, 'content:check');
|
|
106
|
+
|
|
107
|
+
console.log('Nested pages fixture check passed.');
|
|
108
|
+
} finally {
|
|
109
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
110
|
+
}
|