Add a date-independence test (freezes Date at two different days via a preload). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
79 lines
4.3 KiB
TypeScript
79 lines
4.3 KiB
TypeScript
// @vitest-environment node
|
|
/**
|
|
* Drift guard for the Sitesmith catalog. The panel ships a generated file
|
|
* (whp: web-files/libs/sitesmith-catalog.generated.json) built from THIS
|
|
* editor's real component / section / template definitions. This test
|
|
* regenerates the catalog in a child process and compares it byte-for-byte to
|
|
* the committed file, so adding or changing a component, prop, enum, section
|
|
* family or template without regenerating FAILS here.
|
|
*
|
|
* Where the committed file lives:
|
|
* $SITESMITH_CATALOG_FILE (absolute path), else
|
|
* ../../whp/web-files/libs/sitesmith-catalog.generated.json relative to craft/
|
|
*
|
|
* Regenerate after an editor change:
|
|
* node scripts/gen-sitesmith-catalog.mjs --out <path-to-whp>/web-files/libs/sitesmith-catalog.generated.json
|
|
* then commit the whp file alongside the site-builder change.
|
|
*/
|
|
import { describe, test, expect } from 'vitest';
|
|
// No @types/node in this package (same trick as ColumnLayout.stack-parity.test.tsx).
|
|
const proc = (globalThis as any).process;
|
|
const fs = proc.getBuiltinModule('node:fs');
|
|
const path = proc.getBuiltinModule('node:path');
|
|
const { execFileSync } = proc.getBuiltinModule('node:child_process');
|
|
|
|
const craftRoot: string = path.resolve(decodeURIComponent(new URL('.', import.meta.url).pathname), '../..');
|
|
const committedPath: string = proc.env.SITESMITH_CATALOG_FILE
|
|
?? path.resolve(craftRoot, '../../whp/web-files/libs/sitesmith-catalog.generated.json');
|
|
|
|
function regenerate(fakeNow?: string): string {
|
|
const args = ['scripts/gen-sitesmith-catalog.mjs', '--stdout'];
|
|
if (fakeNow) args.unshift('-r', path.join(craftRoot, 'scripts/fake-date-preload.cjs'));
|
|
return execFileSync(proc.execPath, args, {
|
|
env: fakeNow ? { ...proc.env, FAKE_NOW: fakeNow } : proc.env,
|
|
cwd: craftRoot, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, timeout: 120_000,
|
|
});
|
|
}
|
|
|
|
describe('sitesmith catalog', () => {
|
|
test('committed generated catalog matches the editor definitions (no drift)', () => {
|
|
if (!fs.existsSync(committedPath)) {
|
|
throw new Error(`Committed catalog not found at ${committedPath}. Set SITESMITH_CATALOG_FILE to the whp worktree path, or generate it with: node scripts/gen-sitesmith-catalog.mjs --out ${committedPath}`);
|
|
}
|
|
const fresh = regenerate();
|
|
const committed = fs.readFileSync(committedPath, 'utf8');
|
|
if (fresh !== committed) {
|
|
const a = JSON.parse(fresh), b = JSON.parse(committed);
|
|
const diff = Object.keys(a.components).filter((k) => JSON.stringify(a.components[k]) !== JSON.stringify(b.components?.[k]));
|
|
throw new Error(`Sitesmith catalog drift. Regenerate with: node scripts/gen-sitesmith-catalog.mjs --out ${committedPath}\nChanged/new components: ${diff.join(', ') || '(none; sections/templates/tokens/palettes differ)'}`);
|
|
}
|
|
expect(fresh).toBe(committed);
|
|
}, 130_000);
|
|
|
|
test('catalog is date-independent (Countdown default date is volatile and must not leak in)', () => {
|
|
const a = regenerate('2026-12-01T12:00:00Z');
|
|
const b = regenerate('2027-03-09T12:00:00Z');
|
|
expect(a).toBe(b);
|
|
expect(JSON.parse(a).components.Countdown.props.targetDate.default).toBeUndefined();
|
|
expect(a).not.toMatch(/"\d{4}-\d{2}-\d{2}"/);
|
|
}, 130_000);
|
|
|
|
test('catalog covers every resolver component, with derived facts', () => {
|
|
const cat = JSON.parse(regenerate());
|
|
const resolver = fs.readFileSync(path.join(craftRoot, 'src/components/resolver.ts'), 'utf8');
|
|
const names = [...resolver.matchAll(/^ ([A-Z]\w+),$/gm)].map((m: RegExpMatchArray) => m[1]);
|
|
expect(Object.keys(cat.components).sort()).toEqual(names.sort());
|
|
expect(cat.components.Icon.icon).toBe('fontawesome');
|
|
expect(cat.components.FeaturesGrid.icon).toBe('text'); // plain-text icon, not Font Awesome
|
|
expect(cat.components.ColumnLayout.children).toBe('columns');
|
|
expect(cat.components.ColumnLayout.props.split.enum).toContain('50-50');
|
|
expect(cat.components.HeroSimple.animation).toBeUndefined();
|
|
expect(cat.components.Heading.animation).toBe(true);
|
|
expect(cat.components.HeroSimple.props.bgGradientFrom).toMatchObject({ color: true, tokenDefault: true });
|
|
expect(cat.common.props.animation).toContain('fade-in');
|
|
expect(cat.tokens.colorTokens).toContain('var(--wsb-primary)');
|
|
expect(cat.sections.length).toBeGreaterThan(40);
|
|
expect(cat.templates.length).toBe(16);
|
|
}, 130_000);
|
|
});
|