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, style: {} as Record }; 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', '', {}, 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};`)); } });