{section.image && (
// Plain
![]()
: the package cannot depend on a framework's image
// component, and a generated site's images are already sized by
@@ -87,7 +83,7 @@ export function ContactSection({
{section.blurb &&
{{section.blurb}}
}
- {section.locations.map(location => (
+ {section.locations.map((location) => (
0 && (
- {location.hours.map(line => (
+ {location.hours.map((line) => (
- {line}
))}
@@ -144,7 +140,7 @@ export function FaqSection({
{section.heading &&
{section.heading}}
- {section.items.map(item => (
+ {section.items.map((item) => (
{section.blurb}}
-
+
{section.columns.map((_, i) => (
@@ -84,7 +87,7 @@ export function TableSection({ section }: { section: Extract
- {section.columns.map(column => (
+ {section.columns.map((column) => (
{section.heading && {section.heading}}
- {section.stats.map(stat => (
+ {section.stats.map((stat) => (
-
{stat.label}
diff --git a/src/react/sections/ProseSections.tsx b/src/react/sections/ProseSections.tsx
index 9676052..5d2cd6a 100644
--- a/src/react/sections/ProseSections.tsx
+++ b/src/react/sections/ProseSections.tsx
@@ -50,7 +50,7 @@ export function CardsSection({
{section.blurb &&
{{section.blurb}} }
- {section.cards.map(card => (
+ {section.cards.map((card) => (
{card.icon && (
@@ -83,7 +83,7 @@ export function DefinitionsSection({
{section.blurb && {{section.blurb}} }
- {section.items.map(item => (
+ {section.items.map((item) => (
- {item.term}
-
diff --git a/src/schema.ts b/src/schema.ts
index 8088667..7434247 100644
--- a/src/schema.ts
+++ b/src/schema.ts
@@ -294,7 +294,7 @@ export const siteSpecSchema = z
// actually click — checked per page, where the fragments live. Hero
// actions get the same check when they point at a fragment.
const anchors = new Set(
- page.sections.flatMap(s => ('anchor' in s && s.anchor ? [s.anchor] : [])),
+ page.sections.flatMap((s) => ('anchor' in s && s.anchor ? [s.anchor] : [])),
);
page.sections.forEach((section, j) => {
if (section.kind !== 'hero' || !section.actions) return;
diff --git a/test/model.test.js b/test/model.test.js
index 3dc4307..e235e12 100644
--- a/test/model.test.js
+++ b/test/model.test.js
@@ -4,8 +4,18 @@ import assert from 'node:assert/strict';
import { siteNavItems, pageRendersOwnHeader, sitePageAt, href } from 'sitekit';
const PAGES = [
- { path: '', navLabel: 'Home', title: 'Home', sections: [{ kind: 'hero', statement: 'Hi', lead: [] }] },
- { path: 'menu', navLabel: 'Menu', title: 'Menu', sections: [{ kind: 'prose', paragraphs: ['x'] }] },
+ {
+ path: '',
+ navLabel: 'Home',
+ title: 'Home',
+ sections: [{ kind: 'hero', statement: 'Hi', lead: [] }],
+ },
+ {
+ path: 'menu',
+ navLabel: 'Menu',
+ title: 'Menu',
+ sections: [{ kind: 'prose', paragraphs: ['x'] }],
+ },
{ path: 'imprint', title: 'Imprint', sections: [{ kind: 'prose', paragraphs: ['x'] }] },
];
diff --git a/test/provenance.test.js b/test/provenance.test.js
index c2ec86c..78ad4ac 100644
--- a/test/provenance.test.js
+++ b/test/provenance.test.js
@@ -6,12 +6,7 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
-import {
- provenanceSchema,
- inferredPaths,
- notFoundPaths,
- assertDeliverable,
-} from 'sitekit';
+import { provenanceSchema, inferredPaths, notFoundPaths, assertDeliverable } from 'sitekit';
const PROV = {
'chrome.name': { kind: 'scraped', url: 'https://cafe-beispiel.ch/' },
diff --git a/test/react.test.js b/test/react.test.js
index fc2cd2d..1b8fb4d 100644
--- a/test/react.test.js
+++ b/test/react.test.js
@@ -13,27 +13,88 @@ import { SiteSections, SiteMasthead, SiteFooter } from 'sitekit/react';
const SECTIONS = [
{ kind: 'hero', eyebrow: 'Zürich', statement: 'Coffee, done properly.', lead: ['Since 2011.'] },
{ kind: 'prose', heading: 'About', paragraphs: ['A neighbourhood café.'] },
- { kind: 'stats', heading: 'In numbers', stats: [{ label: 'Seats', value: '24', note: 'inside' }] },
+ {
+ kind: 'stats',
+ heading: 'In numbers',
+ stats: [{ label: 'Seats', value: '24', note: 'inside' }],
+ },
{ kind: 'meter', label: 'Sourced', value: 3, of: 12, caption: 'so far' },
- { kind: 'cards', heading: 'Offers', columns: 3, cards: [{ title: 'Espresso', body: 'The house shot.', meta: 'CHF 4.50' }] },
- { kind: 'definitions', heading: 'Terms', items: [{ term: 'Cortado', detail: 'Espresso cut with milk.' }] },
- { kind: 'index', heading: 'Contents', entries: [{ label: 'Opening hours', anchor: 'hours', meta: '7d' }] },
{
- kind: 'table', anchor: 'hours', heading: 'Hours', columns: ['Day', 'Hours', 'Status'],
- rows: [['Mon–Fri', '07–18', 'done']], monoColumns: [1], statusColumn: 2, note: 'Holidays differ.',
+ kind: 'cards',
+ heading: 'Offers',
+ columns: 3,
+ cards: [{ title: 'Espresso', body: 'The house shot.', meta: 'CHF 4.50' }],
+ },
+ {
+ kind: 'definitions',
+ heading: 'Terms',
+ items: [{ term: 'Cortado', detail: 'Espresso cut with milk.' }],
+ },
+ {
+ kind: 'index',
+ heading: 'Contents',
+ entries: [{ label: 'Opening hours', anchor: 'hours', meta: '7d' }],
+ },
+ {
+ kind: 'table',
+ anchor: 'hours',
+ heading: 'Hours',
+ columns: ['Day', 'Hours', 'Status'],
+ rows: [['Mon–Fri', '07–18', 'done']],
+ monoColumns: [1],
+ statusColumn: 2,
+ note: 'Holidays differ.',
+ },
+ {
+ kind: 'feature',
+ heading: 'The baker',
+ paragraphs: ['Learned in Lyon.'],
+ quote: 'Every croissant tells.',
+ cta: { label: 'More', href: '/about' },
+ image: { src: '/founder.jpg', alt: 'The founder' },
+ },
+ {
+ kind: 'contact',
+ heading: 'Find us',
+ anchor: 'standorte',
+ locations: [
+ {
+ name: 'Café',
+ note: 'Open Sundays',
+ address: 'Limmatquai 42, 8001 Zürich',
+ phone: '+41 44 000 12 34',
+ hours: ['Mo–Fr: 06:30–18:00'],
+ },
+ ],
+ },
+ {
+ kind: 'faq',
+ heading: 'Good to know',
+ items: [{ question: 'Can I order ahead?', answer: 'Yes, until 05:00.' }],
},
- { kind: 'feature', heading: 'The baker', paragraphs: ['Learned in Lyon.'], quote: 'Every croissant tells.', cta: { label: 'More', href: '/about' }, image: { src: '/founder.jpg', alt: 'The founder' } },
- { kind: 'contact', heading: 'Find us', anchor: 'standorte', locations: [{ name: 'Café', note: 'Open Sundays', address: 'Limmatquai 42, 8001 Zürich', phone: '+41 44 000 12 34', hours: ['Mo–Fr: 06:30–18:00'] }] },
- { kind: 'faq', heading: 'Good to know', items: [{ question: 'Can I order ahead?', answer: 'Yes, until 05:00.' }] },
];
test('every section kind renders its content', () => {
const html = renderToStaticMarkup(h(SiteSections, { sections: SECTIONS }));
for (const needle of [
- 'Coffee, done properly.', 'A neighbourhood café.', 'Seats', '25%', 'Espresso',
- 'CHF 4.50', 'Cortado', '#hours', 'id="hours"', 'Mon–Fri', 'Holidays differ.',
- 'Learned in Lyon.', '«Every croissant tells.»', 'src="/founder.jpg"',
- 'id="standorte"', 'tel:+41440001234', 'Limmatquai 42', 'Can I order ahead?',
+ 'Coffee, done properly.',
+ 'A neighbourhood café.',
+ 'Seats',
+ '25%',
+ 'Espresso',
+ 'CHF 4.50',
+ 'Cortado',
+ '#hours',
+ 'id="hours"',
+ 'Mon–Fri',
+ 'Holidays differ.',
+ 'Learned in Lyon.',
+ '«Every croissant tells.»',
+ 'src="/founder.jpg"',
+ 'id="standorte"',
+ 'tel:+41440001234',
+ 'Limmatquai 42',
+ 'Can I order ahead?',
]) {
assert.ok(html.includes(needle), `expected rendered html to contain ${JSON.stringify(needle)}`);
}
@@ -48,17 +109,27 @@ test('numbering counts only headed sections, hero and table take none', () => {
assert.ok(!html.includes('>09<'), 'only headed, numberable sections take a number');
});
-const CHROME = { name: 'Café Beispiel', tagline: 'Kaffee', footerNote: 'Run by its owners.', host: 'beispiel.ch' };
-const NAV = [{ path: '', label: 'Home' }, { path: 'menu', label: 'Menu' }];
+const CHROME = {
+ name: 'Café Beispiel',
+ tagline: 'Kaffee',
+ footerNote: 'Run by its owners.',
+ host: 'beispiel.ch',
+};
+const NAV = [
+ { path: '', label: 'Home' },
+ { path: 'menu', label: 'Menu' },
+];
test('masthead defaults to plain anchors', () => {
- const html = renderToStaticMarkup(h(SiteMasthead, { chrome: CHROME, navItems: NAV, currentPath: 'menu' }));
+ const html = renderToStaticMarkup(
+ h(SiteMasthead, { chrome: CHROME, navItems: NAV, currentPath: 'menu' }),
+ );
assert.ok(html.includes(' {
- const FakeLink = props => h('a', { ...props, 'data-framework': 'yes' });
+ const FakeLink = (props) => h('a', { ...props, 'data-framework': 'yes' });
const html = renderToStaticMarkup(
h(SiteMasthead, { chrome: CHROME, navItems: NAV, currentPath: '', Link: FakeLink }),
);
@@ -75,26 +146,42 @@ test('footer shows host only when given', () => {
});
test('hero actions render: first filled, second outlined', () => {
- const html = renderToStaticMarkup(h(SiteSections, { sections: [
- { kind: 'hero', statement: 'Hi', lead: [], actions: [
- { label: 'Primary', href: '#a' }, { label: 'Secondary', href: '#b' },
- ] },
- ] }));
+ const html = renderToStaticMarkup(
+ h(SiteSections, {
+ sections: [
+ {
+ kind: 'hero',
+ statement: 'Hi',
+ lead: [],
+ actions: [
+ { label: 'Primary', href: '#a' },
+ { label: 'Secondary', href: '#b' },
+ ],
+ },
+ ],
+ }),
+ );
assert.ok(html.includes('bg-accent') && html.includes('border-2'));
assert.ok(html.includes('Primary') && html.includes('Secondary'));
});
test('a card icon renders aria-hidden', () => {
- const html = renderToStaticMarkup(h(SiteSections, { sections: [
- { kind: 'cards', cards: [{ title: 'Croissant', body: 'Butter.', icon: '\u{1F950}' }] },
- ] }));
+ const html = renderToStaticMarkup(
+ h(SiteSections, {
+ sections: [
+ { kind: 'cards', cards: [{ title: 'Croissant', body: 'Butter.', icon: '\u{1F950}' }] },
+ ],
+ }),
+ );
assert.ok(html.includes('aria-hidden'));
});
test('contact omits what the data omits — no hours, no hours list', () => {
- const html = renderToStaticMarkup(h(SiteSections, { sections: [
- { kind: 'contact', locations: [{ name: 'Atelier', address: 'Hardstrasse 15' }] },
- ] }));
+ const html = renderToStaticMarkup(
+ h(SiteSections, {
+ sections: [{ kind: 'contact', locations: [{ name: 'Atelier', address: 'Hardstrasse 15' }] }],
+ }),
+ );
assert.ok(html.includes('Hardstrasse 15'));
assert.ok(!html.includes('
{
spec.pages[0].sections.push({ kind: 'carousel', images: [] });
const result = validateSite(spec);
assert.equal(result.success, false);
- assert.ok(result.errors.some(e => e.includes('sections')));
+ assert.ok(result.errors.some((e) => e.includes('sections')));
});
test('a hero anywhere but first is rejected', () => {
@@ -54,7 +104,10 @@ test('a hero anywhere but first is rejected', () => {
spec.pages[0].sections.push({ kind: 'hero', statement: 'Second hero', lead: [] });
const result = validateSite(spec);
assert.equal(result.success, false);
- assert.ok(result.errors.some(e => e.includes('first section')), result.errors.join('\n'));
+ assert.ok(
+ result.errors.some((e) => e.includes('first section')),
+ result.errors.join('\n'),
+ );
});
test('an index entry pointing at a missing anchor is rejected', () => {
@@ -62,7 +115,10 @@ test('an index entry pointing at a missing anchor is rejected', () => {
spec.pages[0].sections[9].entries.push({ label: 'Nowhere', anchor: 'missing' });
const result = validateSite(spec);
assert.equal(result.success, false);
- assert.ok(result.errors.some(e => e.includes("'#missing'")), result.errors.join('\n'));
+ assert.ok(
+ result.errors.some((e) => e.includes("'#missing'")),
+ result.errors.join('\n'),
+ );
});
test('duplicate page paths are rejected', () => {
@@ -70,7 +126,7 @@ test('duplicate page paths are rejected', () => {
spec.pages.push({ ...fullSpec().pages[0] });
const result = validateSite(spec);
assert.equal(result.success, false);
- assert.ok(result.errors.some(e => e.includes('duplicate page path')));
+ assert.ok(result.errors.some((e) => e.includes('duplicate page path')));
});
test('a multi-segment path is rejected — pages are one segment deep', () => {
@@ -83,7 +139,10 @@ test('a multi-segment path is rejected — pages are one segment deep', () => {
test('errors carry actionable paths, not just messages', () => {
const result = validateSite({ chrome: { name: '' }, pages: [] });
assert.equal(result.success, false);
- assert.ok(result.errors.some(e => e.startsWith('chrome.')), result.errors.join('\n'));
+ assert.ok(
+ result.errors.some((e) => e.startsWith('chrome.')),
+ result.errors.join('\n'),
+ );
});
test('a bare section validates against the section schema directly', () => {
@@ -96,7 +155,10 @@ test('a hero action pointing at a missing fragment is rejected', () => {
spec.pages[0].sections[0].actions = [{ label: 'Nowhere', href: '#missing' }];
const result = validateSite(spec);
assert.equal(result.success, false);
- assert.ok(result.errors.some(e => e.includes("'#missing'")), result.errors.join('\n'));
+ assert.ok(
+ result.errors.some((e) => e.includes("'#missing'")),
+ result.errors.join('\n'),
+ );
});
test('a hero action pointing at a real anchor validates', () => {
|