Files
wikideck/tests/theme.test.ts
T
2026-10-19 12:16:49 -04:00

52 lines
2.6 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { runInNewContext } from 'node:vm';
import { DEFAULT_THEME, isTheme, resolveTheme, THEMES, THEME_STORAGE_KEY } from '../src/theme.ts';
const bootstrap = readFileSync(new URL('../public/theme-init.js', import.meta.url), 'utf8');
function boot(saved: unknown, blocked = false) {
const root = { dataset: {} as Record<string, string>, style: {} as Record<string, string> };
let meta = '';
runInNewContext(bootstrap, {
localStorage: { getItem(key: string) {
assert.equal(key, THEME_STORAGE_KEY);
if (blocked) throw new Error('Storage access denied');
return saved;
} },
document: { documentElement: root, querySelector: () => ({ setAttribute: (_key: string, value: string) => { meta = value; } }) },
});
return { theme: root.dataset.theme, scheme: root.style.colorScheme, meta };
}
test('Mocha is the default, including missing, malicious, or inaccessible preferences', () => {
assert.equal(DEFAULT_THEME, 'mocha');
for (const value of [null, undefined, '', 'unknown', 'MOCHA', '__proto__', 'constructor', '<script>alert(1)</script>', {}, 1]) {
assert.equal(isTheme(value), false);
assert.equal(resolveTheme(value), 'mocha');
assert.deepEqual(boot(value), { theme: 'mocha', scheme: 'dark', meta: '#1e1e2e' });
}
assert.deepEqual(boot('latte', true), { theme: 'mocha', scheme: 'dark', meta: '#1e1e2e' });
});
test('every saved Catppuccin flavor boots with matching browser chrome and color scheme', () => {
for (const theme of THEMES) {
assert.equal(isTheme(theme.id), true);
assert.equal(resolveTheme(theme.id), theme.id);
assert.deepEqual(boot(theme.id), { theme: theme.id, scheme: theme.id === 'latte' ? 'light' : 'dark', meta: theme.base });
}
});
test('every flavor defines all 26 official palette roles with the correct base color', () => {
const css = readFileSync(new URL('../src/themes.css', import.meta.url), 'utf8');
const roles = ['rosewater', 'flamingo', 'pink', 'mauve', 'red', 'maroon', 'peach', 'yellow', 'green', 'teal',
'sky', 'sapphire', 'blue', 'lavender', 'text', 'subtext1', 'subtext0', 'overlay2', 'overlay1', 'overlay0',
'surface2', 'surface1', 'surface0', 'base', 'mantle', 'crust'];
for (const theme of THEMES) {
const block = css.match(new RegExp(`:root\\[data-theme='${theme.id}'\\]\\s*\\{([^}]+)\\}`))?.[1];
assert.ok(block);
for (const role of roles) assert.match(block, new RegExp(`--ctp-${role}: #[a-f0-9]{6};`));
assert.ok(block.includes(`--ctp-base: ${theme.base};`));
}
});