media-rip/frontend/src/tests/stores/theme.test.ts
xpltd 40f4b56f31 Admin-controlled themes with visitor dark/light toggle
Admin Settings:
- Theme section: pick Dark Theme, Light Theme, and Default Mode
- 5 dark options (Cyberpunk/Dark/Midnight/Hacker/Neon)
- 4 light options (Light/Paper/Arctic/Solarized)
- Persisted in SQLite — survives container rebuilds
- Served via /api/config/public so frontend loads admin defaults

Visitor behavior:
- Page loads with admin's chosen default (dark or light theme)
- Sun/moon icon toggles between admin's dark and light pair
- Preference stored in cookie — persists within browser session
- No theme dropdown for visitors — admin controls the pair

Header icon simplified back to clean dark/light toggle
2026-03-22 15:58:49 -05:00

140 lines
4.1 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useThemeStore } from '@/stores/theme'
import { useConfigStore } from '@/stores/config'
// Mock document
const setAttributeMock = vi.fn()
Object.defineProperty(globalThis, 'document', {
value: {
documentElement: {
setAttribute: setAttributeMock,
},
getElementById: vi.fn(() => null),
createElement: vi.fn(() => ({ id: '', textContent: '' })),
head: { appendChild: vi.fn() },
cookie: '',
},
})
describe('theme store', () => {
beforeEach(() => {
setActivePinia(createPinia())
setAttributeMock.mockClear()
document.cookie = ''
})
it('initializes with cyberpunk as default (dark mode)', () => {
const store = useThemeStore()
store.init()
expect(store.currentTheme).toBe('cyberpunk')
expect(store.currentMode).toBe('dark')
expect(store.isDark).toBe(true)
expect(setAttributeMock).toHaveBeenCalledWith('data-theme', 'cyberpunk')
})
it('uses admin config for defaults when available', () => {
const configStore = useConfigStore()
configStore.config = {
theme_dark: 'neon',
theme_light: 'paper',
theme_default_mode: 'light',
} as any
const store = useThemeStore()
store.init()
expect(store.currentTheme).toBe('paper')
expect(store.currentMode).toBe('light')
expect(store.isDark).toBe(false)
})
it('setTheme updates state and applies to DOM', () => {
const store = useThemeStore()
store.init()
store.setTheme('light')
expect(store.currentTheme).toBe('light')
expect(store.currentMode).toBe('light')
expect(setAttributeMock).toHaveBeenCalledWith('data-theme', 'light')
})
it('setTheme ignores unknown theme IDs', () => {
const store = useThemeStore()
store.init()
store.setTheme('doesnotexist')
expect(store.currentTheme).toBe('cyberpunk')
})
it('lists 9 built-in themes', () => {
const store = useThemeStore()
expect(store.allThemes).toHaveLength(9)
expect(store.allThemes.map(t => t.id)).toEqual([
'cyberpunk', 'dark', 'midnight', 'hacker', 'neon',
'light', 'paper', 'arctic', 'solarized',
])
})
it('all built-in themes are marked builtin: true', () => {
const store = useThemeStore()
expect(store.allThemes.every(t => t.builtin)).toBe(true)
})
it('darkThemes has 5, lightThemes has 4', () => {
const store = useThemeStore()
expect(store.darkThemes).toHaveLength(5)
expect(store.lightThemes).toHaveLength(4)
})
it('currentMeta returns metadata for active theme', () => {
const store = useThemeStore()
store.init()
expect(store.currentMeta?.id).toBe('cyberpunk')
expect(store.currentMeta?.name).toBe('Cyberpunk')
})
it('isDark reflects current mode', () => {
const store = useThemeStore()
store.init()
expect(store.isDark).toBe(true)
store.setTheme('light')
expect(store.isDark).toBe(false)
store.setTheme('hacker')
expect(store.isDark).toBe(true)
})
it('toggleDarkMode switches between admin dark and light themes', () => {
const store = useThemeStore()
store.init() // cyberpunk (dark)
store.toggleDarkMode()
expect(store.currentTheme).toBe('light')
expect(store.isDark).toBe(false)
store.toggleDarkMode()
expect(store.currentTheme).toBe('cyberpunk')
expect(store.isDark).toBe(true)
})
it('toggleDarkMode uses admin-configured themes', () => {
const configStore = useConfigStore()
configStore.config = {
theme_dark: 'neon',
theme_light: 'arctic',
theme_default_mode: 'dark',
} as any
const store = useThemeStore()
store.init()
expect(store.currentTheme).toBe('neon')
store.toggleDarkMode()
expect(store.currentTheme).toBe('arctic')
store.toggleDarkMode()
expect(store.currentTheme).toBe('neon')
})
it('updateAdminConfig changes the theme pair', () => {
const store = useThemeStore()
store.init()
store.updateAdminConfig('midnight', 'solarized', 'light')
expect(store.adminDarkTheme).toBe('midnight')
expect(store.adminLightTheme).toBe('solarized')
expect(store.adminDefaultMode).toBe('light')
})
})