66f651cf5c
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
87 lines
2.9 KiB
TypeScript
87 lines
2.9 KiB
TypeScript
import { describe, expect, test, beforeEach, vi } from 'vitest';
|
|
|
|
function setupLocalStorage(initial: Record<string, string> = {}) {
|
|
const store: Record<string, string> = { ...initial };
|
|
vi.stubGlobal('localStorage', {
|
|
getItem: (k: string) => (k in store ? store[k] : null),
|
|
setItem: (k: string, v: string) => { store[k] = v; },
|
|
removeItem: (k: string) => { delete store[k]; }
|
|
});
|
|
return store;
|
|
}
|
|
|
|
function setupMatchMedia(prefersLight: boolean) {
|
|
const listeners: ((e: { matches: boolean }) => void)[] = [];
|
|
const mql = {
|
|
matches: prefersLight,
|
|
addEventListener: (_: string, cb: (e: { matches: boolean }) => void) => listeners.push(cb),
|
|
removeEventListener: () => {}
|
|
};
|
|
vi.stubGlobal('window', {
|
|
matchMedia: () => mql
|
|
});
|
|
return {
|
|
mql,
|
|
listeners,
|
|
fire: (matches: boolean) => {
|
|
mql.matches = matches;
|
|
listeners.forEach((cb) => cb({ matches }));
|
|
}
|
|
};
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
vi.unstubAllGlobals();
|
|
if (typeof document !== 'undefined') {
|
|
document.documentElement.removeAttribute('data-theme');
|
|
}
|
|
});
|
|
|
|
describe('theme store', () => {
|
|
test('default preference is system; resolves via matchMedia', async () => {
|
|
setupLocalStorage();
|
|
setupMatchMedia(false); // prefers-dark
|
|
const mod = await import('./theme.svelte');
|
|
expect(mod.theme.value).toBe('system');
|
|
expect(mod.resolvedTheme.value).toBe('dark');
|
|
});
|
|
|
|
test('explicit preference from localStorage is honored', async () => {
|
|
setupLocalStorage({ 'minstrel:theme': 'light' });
|
|
setupMatchMedia(false);
|
|
const mod = await import('./theme.svelte');
|
|
expect(mod.theme.value).toBe('light');
|
|
expect(mod.resolvedTheme.value).toBe('light');
|
|
});
|
|
|
|
test('setTheme persists, updates resolved, sets data-theme attribute', async () => {
|
|
const store = setupLocalStorage();
|
|
setupMatchMedia(true);
|
|
const mod = await import('./theme.svelte');
|
|
mod.setTheme('dark');
|
|
expect(store['minstrel:theme']).toBe('dark');
|
|
expect(mod.theme.value).toBe('dark');
|
|
expect(mod.resolvedTheme.value).toBe('dark');
|
|
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
|
|
});
|
|
|
|
test('matchMedia change updates resolved while in system mode', async () => {
|
|
setupLocalStorage();
|
|
const mm = setupMatchMedia(false); // prefers-dark
|
|
const mod = await import('./theme.svelte');
|
|
expect(mod.resolvedTheme.value).toBe('dark');
|
|
mm.fire(true); // OS flipped to light
|
|
expect(mod.resolvedTheme.value).toBe('light');
|
|
});
|
|
|
|
test('matchMedia change does NOT update resolved when explicit preference set', async () => {
|
|
setupLocalStorage({ 'minstrel:theme': 'dark' });
|
|
const mm = setupMatchMedia(false);
|
|
const mod = await import('./theme.svelte');
|
|
expect(mod.resolvedTheme.value).toBe('dark');
|
|
mm.fire(true); // OS prefers light, but user explicitly chose dark
|
|
expect(mod.resolvedTheme.value).toBe('dark');
|
|
});
|
|
});
|