aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-studio/test-app-util.mjs
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-09 07:45:18 -0500
committerCraig Jennings <c@cjennings.net>2026-06-09 07:45:18 -0500
commit092a2312a1d2fa1364b1d5cb9c2d71a8aefaeb8e (patch)
treee2d1f2a58e4b5af1cfe2c6fec8379b61ac80bb6a /scripts/theme-studio/test-app-util.mjs
parentec3d767435390cebedee8e3ca504d4b20d52f735 (diff)
downloaddotemacs-092a2312a1d2fa1364b1d5cb9c2d71a8aefaeb8e.tar.gz
dotemacs-092a2312a1d2fa1364b1d5cb9c2d71a8aefaeb8e.zip
test(theme-studio): extract color/slug helpers to importable modules and cover them
The pure helpers that were still stranded in app.js — normHex, ratingColor, textOn, and the filename-slug logic — had no unit tests because app.js can't be imported (it runs its bootstrap and references the data placeholders at load). Moved them into importable modules so they can be tested directly: a new app-util.js holds the color/UI-boundary trio, and slugify joins app-core.js. app.js keeps thin wrappers, so no call site changed and the built DOM is byte-identical. textOn needs rl from colormath, so generate.py's inline strip now drops import lines as well as export lines — app-util.js imports rl for its tests, and the import is stripped on inline where rl is already in the page. _faces in generate.py also gets direct tests for its prefix-strip and label derivation. New: 12 node tests (normHex, ratingColor, textOn, slugify) and 7 python tests (_faces, app-util integrity, the import strip). Coverage: app-util.js 100/100/100, app-core.js 100/94.9/100, colormath.js 100/96/100 (line/branch/func); generate.py 89% lines (the rest is the __main__ writer and the optional seed-env branch). No bugs surfaced — the logic was correct, just untested.
Diffstat (limited to 'scripts/theme-studio/test-app-util.mjs')
-rw-r--r--scripts/theme-studio/test-app-util.mjs70
1 files changed, 70 insertions, 0 deletions
diff --git a/scripts/theme-studio/test-app-util.mjs b/scripts/theme-studio/test-app-util.mjs
new file mode 100644
index 00000000..2cb08e0e
--- /dev/null
+++ b/scripts/theme-studio/test-app-util.mjs
@@ -0,0 +1,70 @@
+// Unit tests for the pure color/UI-boundary helpers (app-util.js).
+// Run: node --test scripts/theme-studio/
+
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { normHex, ratingColor, textOn } from './app-util.js';
+
+const here = fileURLToPath(new URL('.', import.meta.url));
+
+test('normHex: Normal — adds the #, lowercases, accepts an existing #', () => {
+ assert.equal(normHex('67809C'), '#67809c');
+ assert.equal(normHex('#E8BD30'), '#e8bd30');
+ assert.equal(normHex('#67809c'), '#67809c');
+});
+
+test('normHex: Boundary — trims surrounding whitespace; empty is null', () => {
+ assert.equal(normHex(' 67809c '), '#67809c');
+ assert.equal(normHex(''), null);
+ assert.equal(normHex(' '), null);
+ assert.equal(normHex('abc'), null); // 3-digit shorthand is not accepted
+});
+
+test('normHex: Error — bad characters and wrong length give null', () => {
+ assert.equal(normHex('#gggggg'), null);
+ assert.equal(normHex('#12345'), null); // 5 digits
+ assert.equal(normHex('#1234567'), null); // 7 digits
+ assert.equal(normHex('red'), null);
+});
+
+test('ratingColor: Normal — AAA green, AA grey, fail red', () => {
+ assert.equal(ratingColor(10), '#5d9b86');
+ assert.equal(ratingColor(5), '#a9b2bb');
+ assert.equal(ratingColor(2), '#cb6b4d');
+});
+
+test('ratingColor: Boundary — the AAA (7) and AA (4.5) thresholds are inclusive', () => {
+ assert.equal(ratingColor(7), '#5d9b86');
+ assert.equal(ratingColor(6.99), '#a9b2bb');
+ assert.equal(ratingColor(4.5), '#a9b2bb');
+ assert.equal(ratingColor(4.49), '#cb6b4d');
+});
+
+test('ratingColor: Error — zero and negative ratios are the fail color', () => {
+ assert.equal(ratingColor(0), '#cb6b4d');
+ assert.equal(ratingColor(-1), '#cb6b4d');
+});
+
+test('textOn: Normal — white text on black, black text on white', () => {
+ assert.equal(textOn('#000000'), '#fff');
+ assert.equal(textOn('#ffffff'), '#000');
+});
+
+test('textOn: Boundary — straddles the ~0.179 luminance crossover', () => {
+ assert.equal(textOn('#707070'), '#fff'); // just below the crossover
+ assert.equal(textOn('#777777'), '#000'); // just above the crossover
+});
+
+// Inline-integrity: the page must carry app-util.js's body (sans import/export)
+// verbatim — the same strip generate.py applies. Requires `python3 generate.py`.
+const stripModule = (s) =>
+ s.split('\n').filter((l) => !(l.startsWith('export') || l.startsWith('import')))
+ .join('\n').replace(/\s+$/, '');
+
+test('inline-integrity: theme-studio.html contains the app-util.js body verbatim', () => {
+ const body = stripModule(readFileSync(here + 'app-util.js', 'utf8'));
+ const html = readFileSync(here + 'theme-studio.html', 'utf8');
+ assert.ok(html.includes(body), 'generated page is missing the app-util.js body verbatim');
+});