mirror of
https://github.com/Manavarya09/design-extract.git
synced 2026-09-19 02:41:14 +08:00
49fcb190c1
process.exit() straight after process.stdout.write() drops whatever the pipe hasn't flushed. `designlang https://gov.uk --json | jq` received exactly 65536 bytes of a 70583-byte document, so any site with a large design broke JSON consumers in CI. Found by the benchmark runner, which reads through a pipe. - writeThenExit(): exit once the write is handed to the OS; used by extraction --json and drift --json (which exits non-zero on drift right after writing) - the export command's error path uses the new exit codes - bench: a family designlang marks usage "all" counts as body text, and both readers take the most used body family
118 lines
4.9 KiB
JavaScript
118 lines
4.9 KiB
JavaScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import { toHex, deltaE, colorHit, fontHit, normalizeFont, scoreSite, summarize, readDesignlang, readDembrandt } from '../bench/score.js';
|
|
|
|
describe('bench score: reading each tool', () => {
|
|
it('reads designlang primary and body family', () => {
|
|
const design = JSON.parse(readFileSync(new URL('./fixtures/example-design.json', import.meta.url), 'utf-8'));
|
|
assert.deepEqual(readDesignlang(design), { primary: '#334488', font: 'Times' });
|
|
});
|
|
|
|
it('prefers the body family over a more frequent heading family', () => {
|
|
const design = { colors: {}, typography: { families: [{ name: 'Display', count: 40, usage: 'heading' }, { name: 'Inter', count: 12, usage: 'body' }] } };
|
|
assert.equal(readDesignlang(design).font, 'Inter');
|
|
});
|
|
|
|
it('counts a family used everywhere as body, and picks the most used (gov.uk)', () => {
|
|
const design = { colors: {}, typography: { families: [
|
|
{ name: 'GDS Transport', count: 580, usage: 'all' },
|
|
{ name: 'Times', count: 6, usage: 'body' },
|
|
{ name: 'Arial', count: 3, usage: 'body' },
|
|
] } };
|
|
assert.equal(readDesignlang(design).font, 'GDS Transport');
|
|
});
|
|
|
|
it('reads dembrandt primary (rgb) and body context', () => {
|
|
const out = {
|
|
colors: { semantic: { primary: 'rgb(83, 58, 253)' } },
|
|
typography: { styles: [
|
|
{ context: 'heading-1', family: 'Display', count: 30 },
|
|
{ context: 'body', family: 'sohne-var', count: 5 },
|
|
] },
|
|
};
|
|
assert.deepEqual(readDembrandt(out), { primary: '#533afd', font: 'sohne-var' });
|
|
});
|
|
|
|
it('falls back to the most used family when nothing is labelled body', () => {
|
|
const out = { colors: { semantic: {} }, typography: { styles: [
|
|
{ context: 'text', family: 'A', count: 2 },
|
|
{ context: 'link', family: 'B', count: 3 },
|
|
{ context: 'ui', family: 'A', count: 2 },
|
|
] } };
|
|
assert.deepEqual(readDembrandt(out), { primary: null, font: 'A' });
|
|
});
|
|
});
|
|
|
|
describe('bench score: colours', () => {
|
|
it('reads hex, short hex, 8-digit hex, rgb() and colour objects', () => {
|
|
assert.equal(toHex('#533AFD'), '#533afd');
|
|
assert.equal(toHex('#fff'), '#ffffff');
|
|
assert.equal(toHex('#533afdcc'), '#533afd');
|
|
assert.equal(toHex('rgb(83, 58, 253)'), '#533afd');
|
|
assert.equal(toHex('rgba(0 112 243 / 0.5)'), '#0070f3');
|
|
assert.equal(toHex({ hex: '#0075de' }), '#0075de');
|
|
assert.equal(toHex('oklch(0.6 0.2 260)'), null);
|
|
assert.equal(toHex(undefined), null);
|
|
});
|
|
|
|
it('treats near-identical brand blues as the same colour', () => {
|
|
assert.ok(deltaE('#0070f3', '#0072f5') < 1);
|
|
assert.ok(colorHit('#0072f5', ['#0070f3']));
|
|
});
|
|
|
|
it('rejects a different hue on the same palette', () => {
|
|
// Linear: lime accent vs indigo brand.
|
|
assert.equal(colorHit('#e4f222', ['#5e6ad2']), false);
|
|
// gov.uk: green vs GOV.UK blue.
|
|
assert.equal(colorHit('#0f7a52', ['#1d70b8']), false);
|
|
});
|
|
|
|
it('accepts any listed alternate and fails on missing predictions', () => {
|
|
assert.ok(colorHit('#ffdb00', ['#0058a3', '#ffdb00']));
|
|
assert.equal(colorHit(null, ['#000000']), false);
|
|
});
|
|
});
|
|
|
|
describe('bench score: fonts', () => {
|
|
it('normalizes variable-font suffixes, quotes and accents', () => {
|
|
assert.equal(normalizeFont('"Inter Variable"'), 'inter');
|
|
assert.equal(normalizeFont('sohne-var'), 'sohne');
|
|
assert.equal(normalizeFont('Söhne'), 'sohne');
|
|
assert.equal(normalizeFont('Mona Sans VF'), 'monasans');
|
|
});
|
|
|
|
it('matches the same family under different names', () => {
|
|
assert.ok(fontHit('sohne-var', ['Söhne']));
|
|
assert.ok(fontHit('GeistSans', ['Geist']));
|
|
assert.ok(fontHit('NotionInter', ['Inter']));
|
|
});
|
|
|
|
it('does not credit a generic fallback', () => {
|
|
assert.equal(fontHit('Times', ['GDS Transport']), false);
|
|
assert.equal(fontHit('', ['Inter']), false);
|
|
});
|
|
});
|
|
|
|
describe('bench score: summary', () => {
|
|
it('scores a site and summarizes a tool across sites', () => {
|
|
const truth = { primary: ['#533afd'], font: ['Söhne'] };
|
|
assert.deepEqual(scoreSite(truth, { primary: 'rgb(83, 58, 253)', font: 'sohne-var' }), { color: true, font: true });
|
|
|
|
const rows = [
|
|
{ site: 'a', ok: true, seconds: 10, score: { color: true, font: true } },
|
|
{ site: 'b', ok: true, seconds: 20, score: { color: null, font: true } },
|
|
{ site: 'c', ok: false, seconds: 180, score: { color: false, font: false } },
|
|
];
|
|
assert.deepEqual(summarize(rows), {
|
|
sites: 3, failures: 1, colorSites: 2, colorHits: 1, fontSites: 3, fontHits: 2, medianSeconds: 15,
|
|
});
|
|
});
|
|
|
|
it('does not score a dimension without ground truth, and a failed run misses the rest', () => {
|
|
const monochrome = { primary: [], font: ['Inter'] };
|
|
assert.deepEqual(scoreSite(monochrome, { primary: '#000000', font: 'Inter Variable' }), { color: null, font: true });
|
|
assert.deepEqual(scoreSite(monochrome, null), { color: null, font: false });
|
|
});
|
|
});
|