Files
ucvl-home-vision/test_theme.js
T

77 lines
4.2 KiB
JavaScript
Raw Normal View History

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const css = fs.readFileSync(path.join(__dirname, 'web/chinese-theme.css'), 'utf8');
const html = require('./test-support/ui-source')();
const root = css.match(/:root\s*\{([^}]+)\}/)[1];
const token = name => root.match(new RegExp('--' + name + ':\\s*(#[0-9a-f]{6})'))[1];
function luminance(hex) {
const values = hex.slice(1).match(/../g).map(v => parseInt(v, 16) / 255)
.map(v => v <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4);
return values[0] * .2126 + values[1] * .7152 + values[2] * .0722;
}
function contrast(a, b) {
const x = luminance(a), y = luminance(b);
return (Math.max(x, y) + .05) / (Math.min(x, y) + .05);
}
test('Vue theme builds locally after Bootstrap without remote fonts', () => {
const main=fs.readFileSync('frontend/src/main.js','utf8');
assert.ok(main.indexOf('bootstrap/dist/css/bootstrap.min.css')<main.indexOf('./theme.css'));
assert.doesNotMatch(fs.readFileSync('frontend/src/theme.css','utf8'), /@import|https?:|@font-face/i);
assert.match(fs.readFileSync('frontend/index.html','utf8'), /name="viewport"/);
});
test('Bootstrap polish keeps readable light and dark foregrounds and avoids remote assets', () => {
const polish=fs.readFileSync('frontend/src/bootstrap-polish.css','utf8');
const light=polish.match(/:root\s*\{([^}]+)\}/)[1];
const value=name=>light.match(new RegExp('--'+name+':\\s*(#[0-9a-f]{6})'))[1];
for(const bg of ['#ffffff','#f3f6fa','#f5f6f4']) {
assert.ok(contrast(value('ink'),bg)>=4.5);
assert.ok(contrast(value('ui-muted'),bg)>=4.5);
}
for(const bg of [value('ui-accent'),'#397359'])assert.ok(contrast('#ffffff',bg)>=4.5);
assert.ok(contrast('#a8bdc5','#1a2b35')>=4.5);
assert.doesNotMatch(polish,/@import|https?:|@font-face/);
assert.match(polish,/prefers-reduced-motion/);
assert.match(polish,/home-options-open/);
assert.match(polish,/#page-live:is\(:fullscreen,\.wall-expanded\) \.camera-meta \{[^}]+background: #172e35 !important; color: #e8f3ef !important/);
});
test('primary text, muted text and actions have at least 4.5:1 contrast', () => {
for (const [fg, bg] of [
[token('ink'), token('paper')], [token('muted'), token('paper')],
[token('muted'), token('panel')], ['#fffdf6', token('accent')],
['#c9d1be', '#213d34'], ['#244638', '#efebdc'],
['#355b3d', '#e4eddf'], ['#835428', '#f2e8d9'],
['#994437', '#fbf8ef'], ['#d9dfca', '#24372b'],
]) assert.ok(contrast(fg, bg) >= 4.5, `${fg} on ${bg}: ${contrast(fg, bg).toFixed(2)}`);
});
test('monitor fullscreen, reduced motion and mobile navigation are explicitly styled', () => {
assert.match(css, /#page-live:fullscreen\s*\{[^}]+color-scheme: dark/);
assert.match(css, /@media \(prefers-reduced-motion: reduce\)/);
assert.match(css, /overflow-x: auto/);
assert.match(css, /\.family-node\.self\s*\{[^}]+background: #f1ddb0/);
assert.match(css, /--timeline-paper:/);
});
test('recording timeline paints with theme tokens and keeps range geometry', () => {
const vm = require('node:vm');
const source = fs.readFileSync(path.join(__dirname, 'web/recording-ui.js'), 'utf8');
const draw = source.slice(source.indexOf('function drawTimeline()'), source.indexOf('function playbackParams('));
const rectangles = [];
const ctx = { scale() {}, beginPath() {}, moveTo() {}, lineTo() {}, stroke() {},
fillRect(...coordinates) { rectangles.push({ color: this.fillStyle, coordinates }); } };
const canvas = { clientWidth: 480, getContext: () => ctx };
const values = { '--timeline-paper': '#ede8dd', '--timeline-grid': '#d3ccbc', '--timeline-range': '#4c7761' };
const scope = vm.createContext({ $: () => canvas, window: { devicePixelRatio: 2 }, document: { documentElement: {} },
getComputedStyle: () => ({ getPropertyValue: name => values[name] }),
queryStart: new Date(0), queryEnd: new Date(60000), spans: [{ start: 10000, end: 20000 }] });
vm.runInContext(draw + ';drawTimeline()', scope);
assert.equal(canvas.width, 960);
assert.equal(canvas.height, 152);
assert.deepEqual(rectangles, [
{ color: '#ede8dd', coordinates: [0, 0, 480, 76] },
{ color: '#4c7761', coordinates: [80, 25, 80, 25] },
]);
assert.equal(ctx.strokeStyle, '#d3ccbc');
});