52 lines
2.6 KiB
TypeScript
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};`));
|
|
}
|
|
});
|