-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest-structures.ts
More file actions
84 lines (71 loc) · 3.54 KB
/
test-structures.ts
File metadata and controls
84 lines (71 loc) · 3.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// T2a runtime smoke test: registry behavior + structure invariants.
// Run: npx tsx test-structures.ts
import { listStructures, getStructure, registerStructure } from './src/structures.js';
import type { SceneNode, Structure } from './src/types.js';
let failures = 0;
const check = (cond: boolean, msg: string) => {
if (cond) { console.log(` ✓ ${msg}`); } else { console.error(` ✗ ${msg}`); failures++; };
};
const AXES = {
heroTreatment: ['none', 'marquee', 'split', 'stat-led', 'editorial'],
density: ['airy', 'balanced', 'dense'],
rhythm: ['uniform', 'alternating', 'asymmetric'],
alignment: ['centered', 'left', 'split'],
} as const;
// Only these fields may carry a `$token` ref; everything else must be literal
// (the A-P4 theming split — geometry stays crash-safe on unthemed canvases).
const COLOR_FIELDS = new Set(['fill', 'color', 'stroke', 'iconColor']);
function walk(node: SceneNode, visit: (n: SceneNode) => void): void {
visit(node);
node.children?.forEach((c) => walk(c, visit));
}
const NAMES = ['marquee-hero', 'bento-grid', 'stat-led', 'editorial-longform', 'split-workbench', 'catalogue'];
console.log('listStructures()');
const list = listStructures();
check(list.length === NAMES.length, `returns ${NAMES.length} structures (got ${list.length})`);
check(list.every((s) => !!s.name && !!s.description && !!s.axes), 'each entry has name + description + axes');
console.log('getStructure()');
for (const name of NAMES) check(!!getStructure(name), `resolves '${name}'`);
check(getStructure('does-not-exist') === undefined, 'unknown name → undefined');
console.log('taxonomy — every structure tagged on all 4 axes with valid values');
for (const name of NAMES) {
const s = getStructure(name)!;
for (const axis of Object.keys(AXES) as (keyof typeof AXES)[]) {
const v = s.axes[axis];
check((AXES[axis] as readonly string[]).includes(v), `${name}.${axis} = '${v}' is valid`);
}
}
console.log('structure has placeholder nodes');
for (const name of NAMES) {
const s = getStructure(name)!;
check(Array.isArray(s.nodes) && s.nodes.length > 0, `${name} has nodes`);
}
console.log('theming split — only color fields hold $tokens; geometry is literal (A-P4)');
for (const name of NAMES) {
const s = getStructure(name)!;
let violations: string[] = [];
s.nodes.forEach((root) => walk(root, (n) => {
for (const [k, val] of Object.entries(n)) {
if (typeof val === 'string' && val.startsWith('$') && !COLOR_FIELDS.has(k)) {
violations.push(`${n.id}.${k}=${val}`);
}
}
}));
check(violations.length === 0, `${name} keeps $tokens in color fields only${violations.length ? ` (offenders: ${violations.join(', ')})` : ''}`);
}
console.log('all node ids are unique within a structure');
for (const name of NAMES) {
const s = getStructure(name)!;
const ids: string[] = [];
s.nodes.forEach((root) => walk(root, (n) => ids.push(n.id)));
check(new Set(ids).size === ids.length, `${name} has ${ids.length} unique ids`);
}
console.log('registerStructure() adds a new entry');
const stub: Structure = {
name: 'test-stub', description: 'temp', axes: { heroTreatment: 'none', density: 'balanced', rhythm: 'uniform', alignment: 'left' }, nodes: [{ id: 't', type: 'frame' }],
};
registerStructure(stub);
check(getStructure('test-stub')?.description === 'temp', 'registered stub is retrievable');
check(listStructures().length === NAMES.length + 1, `list now reports ${NAMES.length + 1}`);
console.log(failures === 0 ? '\nT2a SMOKE TEST PASSED ✅' : `\nT2a SMOKE TEST FAILED ✗ (${failures})`);
process.exit(failures === 0 ? 0 : 1);