catalog: omit the wall-clock-derived Countdown.targetDate default so the generated catalog (and drift test) no longer flip daily
Add a date-independence test (freezes Date at two different days via a preload). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
96670b94f8
commit
01a78c9b3d
3 files changed
+31
-3
No files matched your search
@@ -0,0 +1,9 @@
|
||||
// Test helper: freeze the wall clock for a child process. FAKE_NOW=<ISO date> (node -r this).
|
||||
const RealDate = Date;
|
||||
const fixed = new RealDate(process.env.FAKE_NOW).getTime();
|
||||
if (!Number.isFinite(fixed)) throw new Error('FAKE_NOW must be a valid ISO date');
|
||||
class FakeDate extends RealDate {
|
||||
constructor(...a) { if (a.length === 0) super(fixed); else super(...a); }
|
||||
static now() { return fixed; }
|
||||
}
|
||||
globalThis.Date = FakeDate;
|
||||
@@ -106,6 +106,14 @@ function extractProps(program, file, name) {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults computed from the wall clock when the editor module loads (Countdown.targetDate
|
||||
* = today + 30 days). Emitting them would make the generated catalog (and the drift test)
|
||||
* change every day and would teach the model a stale "default" date, so they are omitted
|
||||
* from the catalog entirely.
|
||||
*/
|
||||
const VOLATILE_DEFAULTS = new Set(['Countdown.targetDate']);
|
||||
|
||||
const COLOR_RE = /^(color|bg|bgValue|gradient)$|(colou?r|bg|background|gradientfrom|gradientto)$/i;
|
||||
const isTokenRef = (v) => typeof v === 'string' && /^var\(--wsb-[a-z-]+\)$/.test(v);
|
||||
|
||||
@@ -148,7 +156,7 @@ export async function buildCatalog() {
|
||||
if (COMMON_PROPS.has(p) || p === 'children') continue;
|
||||
const e = { ...info };
|
||||
delete e.optional;
|
||||
if (p in defaults && !(typeof defaults[p] === 'string' && defaults[p].startsWith('data:'))) e.default = slimDefault(defaults[p]);
|
||||
if (p in defaults && !VOLATILE_DEFAULTS.has(`${name}.${p}`) && !(typeof defaults[p] === 'string' && defaults[p].startsWith('data:'))) e.default = slimDefault(defaults[p]);
|
||||
if (info.type !== 'boolean' && info.type !== 'number' && COLOR_RE.test(p)) {
|
||||
e.color = true;
|
||||
if (isTokenRef(defaults[p])) e.tokenDefault = true;
|
||||
|
||||
@@ -26,8 +26,11 @@ const craftRoot: string = path.resolve(decodeURIComponent(new URL('.', import.me
|
||||
const committedPath: string = proc.env.SITESMITH_CATALOG_FILE
|
||||
?? path.resolve(craftRoot, '../../whp/web-files/libs/sitesmith-catalog.generated.json');
|
||||
|
||||
function regenerate(): string {
|
||||
return execFileSync(proc.execPath, ['scripts/gen-sitesmith-catalog.mjs', '--stdout'], {
|
||||
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,
|
||||
});
|
||||
}
|
||||
@@ -47,6 +50,14 @@ describe('sitesmith catalog', () => {
|
||||
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');
|
||||
|
||||
Reference in new issue
Block a user