61 lines
3.6 KiB
JavaScript
61 lines
3.6 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { readFile, readdir } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
|
|
const allowed = new Set(['400', '500', '700']);
|
|
async function sources(directory) {
|
|
const output = [];
|
|
for (const item of await readdir(directory, { withFileTypes: true })) {
|
|
const file = path.join(directory, item.name);
|
|
if (item.isDirectory()) output.push(...await sources(file));
|
|
else if (/\.(css|tsx?|html|svg)$/.test(item.name) && !/\.test\./.test(item.name)) output.push(file);
|
|
}
|
|
return output;
|
|
}
|
|
const files = (await Promise.all(['packages/ui/src', 'packages/org-chart/src', 'packages/org-chart-react/src', 'packages/center-graphic/src', 'src', '.storybook'].map(sources))).flat();
|
|
const contents = await Promise.all(files.map(async file => ({ file, text: await readFile(file, 'utf8') })));
|
|
|
|
test('HM source uses only Regular, Medium and Bold; font-face ranges are asset metadata', () => {
|
|
const issues = [];
|
|
for (const { file, text } of contents) {
|
|
const css = text.replace(/@font-face\s*\{[^}]*\}/g, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
for (const match of css.matchAll(/(?:font-weight\s*:\s*|fontWeight\s*:\s*)(\d+(?:\.\d+)?)/g)) {
|
|
if (!allowed.has(match[1])) issues.push(`${file}: ${match[0]}`);
|
|
}
|
|
for (const match of css.matchAll(/\bfont-(?:thin|extralight|light|semibold|extrabold|black)\b|\bfont-\[(\d+)\]/g)) {
|
|
if (!match[1] || !allowed.has(match[1])) issues.push(`${file}: ${match[0]}`);
|
|
}
|
|
}
|
|
assert.deepEqual(issues, []);
|
|
});
|
|
|
|
test('Every default HM weight token resolves to a supported value without dangling references', () => {
|
|
const tokens = new Map();
|
|
for (const { file, text } of contents.filter(item => item.file.startsWith(`packages${path.sep}ui${path.sep}src`) && item.file.endsWith('.css'))) {
|
|
for (const [, name, value] of text.matchAll(/(--hm-[\w-]+)\s*:\s*([^;{}]+);/g)) tokens.set(name, value.trim());
|
|
}
|
|
function resolve(name, chain = []) {
|
|
assert.ok(!chain.includes(name), `Circular token: ${[...chain, name].join(' -> ')}`);
|
|
assert.ok(tokens.has(name), `Missing token: ${name}`);
|
|
const value = tokens.get(name), reference = value.match(/^var\((--[\w-]+)\)$/);
|
|
return reference ? resolve(reference[1], [...chain, name]) : value;
|
|
}
|
|
for (const name of tokens.keys()) if (/weight/.test(name)) assert.ok(allowed.has(resolve(name)), `${name} = ${resolve(name)}`);
|
|
assert.equal(resolve('--hm-input-font-weight'), '400');
|
|
assert.equal(resolve('--hm-font-weight-medium'), '500');
|
|
assert.equal(resolve('--hm-font-weight-bold'), '700');
|
|
assert.equal(resolve('--hm-card-title-font-weight'), '700');
|
|
for (const { file, text } of contents) for (const [, name] of text.matchAll(/var\((--hm-(?!orgchart)[\w-]*weight[\w-]*)/g)) assert.ok(tokens.has(name), `Missing weight reference in ${file}: ${name}`);
|
|
});
|
|
|
|
test('Theme verification baseline agrees with declared default weight tokens', async () => {
|
|
const baseline = JSON.parse(await readFile('src/theme/verification-baseline.json', 'utf8'));
|
|
const declarations = new Map();
|
|
for (const { file, text } of contents.filter(item => item.file.startsWith(`packages${path.sep}ui${path.sep}src${path.sep}theme${path.sep}`) && item.file.endsWith('.css'))) {
|
|
for (const [, name, value] of text.matchAll(/(--hm-[\w-]*weight[\w-]*)\s*:\s*([^;{}]+);/g)) declarations.set(name, { file, value: value.trim() });
|
|
}
|
|
// The verification fixture captures a subset, not every later-added token.
|
|
for (const name of Object.keys(baseline).filter(name => /weight/.test(name))) assert.equal(declarations.get(name)?.value, baseline[name], name);
|
|
});
|