-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbadges.test.mjs
More file actions
109 lines (92 loc) · 5.69 KB
/
Copy pathbadges.test.mjs
File metadata and controls
109 lines (92 loc) · 5.69 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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { readFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { loadPacks } from '../../../engine/pack_loader/pack-registry.mjs';
import { parseBadge, renderBadge, shade, restyleAll, badgeFiles, idForBadge } from '../badges/render.mjs';
// The badge files are the artwork's source of truth — a manifest names only the
// path — so there is nothing to regenerate them FROM, and the drift these tests
// guard is a different one: the shared template. `restyle` re-emits every badge
// around its own parsed colour and glyph, so a badge left behind by a template
// change (or hand-edited into a shape the tool can no longer read) fails here
// rather than sitting in the set looking subtly wrong.
//
// The row a README shows is the wiring converge's, not this tool's — adoption
// writes it and the nightly keeps it true (engine/scheduler/converge-wiring.mjs).
// What is checked here is that this repo, the one member no nightly maintains,
// still shows what that converge would write.
const REPO = fileURLToPath(new URL('../../..', import.meta.url));
const tracked = () => new Set(execFileSync('git', ['ls-files'], { cwd: REPO, encoding: 'utf8' }).split('\n').filter(Boolean));
const packBadgePath = (pack) => `${pack.local ? '.claudinite/local/packs' : 'packs'}/${pack.id}/${pack.badge}`;
test('every pack declares a badge path, and the file it names is there and tracked', async () => {
const known = tracked();
const bad = [];
for (const pack of await loadPacks({ localRoot: REPO })) {
if (typeof pack.badge !== 'string' || !pack.badge.trim() || pack.badge.startsWith('/') || pack.badge.includes('..')) {
bad.push(`${pack.id}: badge must be a path relative to the pack directory, e.g. badge: 'badge.svg'`);
continue;
}
const rel = packBadgePath(pack);
if (!existsSync(join(REPO, rel))) bad.push(`${pack.id}: declares ${pack.badge}, which does not exist`);
else if (!known.has(rel)) bad.push(`${rel} is untracked — a badge git does not track ships to no consumer`);
}
assert.deepEqual(bad, []);
});
test('every badge is current with the template', () => {
const { files, unreadable } = restyleAll(REPO);
assert.deepEqual(unreadable, [], 'these files are not badges this tool can read — fix or delete them');
const stale = [...files].filter(([rel, content]) => readFileSync(join(REPO, rel), 'utf8') !== content).map(([rel]) => rel);
assert.deepEqual(stale, [], 'these badges were left behind by a template change — re-run `node dev/tools/badges/render.mjs restyle`');
});
test('every badge is titled with the pack whose directory holds it', () => {
const wrong = badgeFiles(REPO)
.map((rel) => ({ rel, spec: parseBadge(readFileSync(join(REPO, rel), 'utf8')) }))
.filter(({ rel, spec }) => spec.id !== idForBadge(rel))
.map(({ rel, spec }) => `${rel} is titled "${spec.id}"`);
assert.deepEqual(wrong, [], 'a badge titled with another pack shows the wrong name on hover and in a screen reader');
});
test("this repo's README row is what the wiring converge would write", async () => {
// The canon home never baselines (no vendored mount, so the task self-skips),
// so its row is the one in the fleet that no nightly maintains. Asserting it
// against the converger rather than a regex of its own keeps the format in ONE
// place and proves the row a consumer gets is the row this repo shows.
const { badgeRowEntries, renderBadgeRow } = await import('../../../engine/scheduler/converge-wiring.mjs');
const { loadConfig } = await import('../../../engine/checks/helpers/repo-context.mjs');
const row = renderBadgeRow(await badgeRowEntries(REPO, loadConfig(REPO)));
assert.ok(readFileSync(join(REPO, 'README.md'), 'utf8').includes(row),
'the README row and the pack declaration have drifted — run the wiring converge, or update the row to match');
});
// --- the renderer itself ----------------------------------------------------
const SPEC = { id: 'demo-pack', color: '#102030', glyph: 'M8 8h16' };
test('a badge is a self-contained 32px tile carrying its own colour and glyph', () => {
const svg = renderBadge(SPEC);
assert.match(svg, /viewBox="0 0 32 32" width="32" height="32"/);
assert.match(svg, /<title>demo-pack<\/title>/);
assert.match(svg, /stop-color="#102030"/);
assert.match(svg, /d="M8 8h16"/);
// The darkened stop is derived from the declared colour, never stored beside it.
assert.match(svg, /stop-color="#0c1925"/);
// Namespaced, or two badges in one document would paint with one fill.
assert.match(svg, /id="b-demo-pack"/);
});
test('a rendered badge parses back into the spec that produced it', () => {
assert.deepEqual(parseBadge(renderBadge(SPEC)), SPEC);
});
test('a file that is not a readable badge parses to null rather than a default', () => {
assert.equal(parseBadge('<svg></svg>'), null);
assert.equal(parseBadge(renderBadge(SPEC).replace(/<title>[^<]+<\/title>/, '')), null);
assert.equal(parseBadge(renderBadge(SPEC).replace(/<path d="[^"]+"/, '<path')), null);
});
test('a colour that is not a #rrggbb hex is refused, not rendered', () => {
assert.throws(() => renderBadge({ ...SPEC, color: 'blue' }), /#rrggbb/);
});
test('shading is clamped, so a near-black tile still renders a valid colour', () => {
assert.equal(shade('#000000', -0.22), '#000000');
assert.equal(shade('#ffffff', -0.22), '#c7c7c7');
});
test('a badge takes its id from the directory it sits in', () => {
assert.equal(idForBadge('packs/tidy-repo/badge.svg'), 'tidy-repo');
assert.equal(idForBadge('.claudinite/local/packs/canon-curation/badge.svg'), 'canon-curation');
});