mirror of
https://github.com/xpltdco/media-rip.git
synced 2026-06-02 09:44:30 -06:00
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
140 lines
4.1 KiB
TypeScript
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')
|
|
})
|
|
})
|