import { beforeEach, describe, expect, it, vi } from 'vitest' import { nextTick } from 'vue' import { createPinia, setActivePinia } from 'pinia' import { useTheme } from '../../composables/useTheme' import { useSettingsStore } from '../../stores/settings' function mockMatchMedia(initialMatches = false) { let matches = initialMatches let changeListener: ((event: MediaQueryListEvent) => void) | undefined const mediaQuery = { get matches() { return matches }, media: '(prefers-color-scheme: dark)', onchange: null, addEventListener: vi.fn((_event: 'change', listener: (event: MediaQueryListEvent) => void) => { changeListener = listener }), removeEventListener: vi.fn(), addListener: vi.fn(), removeListener: vi.fn(), dispatchEvent: vi.fn(), } as unknown as MediaQueryList Object.defineProperty(window, 'matchMedia', { configurable: true, writable: true, value: vi.fn(() => mediaQuery), }) return { mediaQuery, setMatches(value: boolean) { matches = value changeListener?.({ matches: value } as MediaQueryListEvent) }, } } describe('useTheme', () => { beforeEach(() => { setActivePinia(createPinia()) vi.restoreAllMocks() const storage: Record = {} vi.stubGlobal('localStorage', { getItem: vi.fn((key: string) => storage[key] ?? null), setItem: vi.fn((key: string, value: string) => { storage[key] = value }), removeItem: vi.fn((key: string) => { delete storage[key] }), clear: vi.fn(() => { for (const key of Object.keys(storage)) delete storage[key] }), }) document.documentElement.classList.remove('dark') }) it('reads system color scheme from the settings store reactively', async () => { const system = mockMatchMedia(true) const settings = useSettingsStore() const { isDark } = useTheme() expect(settings.theme).toBe('system') expect(settings.systemPrefersDark).toBe(true) expect(isDark.value).toBe(true) expect(document.documentElement.classList.contains('dark')).toBe(true) system.setMatches(false) await nextTick() expect(settings.systemPrefersDark).toBe(false) expect(isDark.value).toBe(false) expect(document.documentElement.classList.contains('dark')).toBe(false) }) it('keeps explicit light and dark choices independent from system changes', async () => { const system = mockMatchMedia(true) const { isDark, setTheme } = useTheme() setTheme('light') await nextTick() expect(isDark.value).toBe(false) system.setMatches(false) await nextTick() expect(isDark.value).toBe(false) setTheme('dark') await nextTick() expect(isDark.value).toBe(true) }) })