- Overhaul content detection: expand all query/response classifiers with broader AI response patterns (news, music, books, TV, places, websites) - Add Nostr detection (isNostrQuery, isNostrLikeResponse) and App detection (isAppQuery, isAppLikeResponse) classifiers - Add bare domain extraction from AI text (e.g. "check out damus.io") - Add Apps tab with curated database of ~30 Nostr + Bitcoin ecosystem apps (clients, wallets, privacy tools, node software, dev tools) - Create AppsGrid + AppDetail components with search, filtering, how-to - Wire app extraction and Nostr detection into useContentPanel - Add PromptIndex badges for Apps and Nostr tabs - Chat UX: dedicated history button, settings modal (memory + advanced), fix collapsed chat v-if/v-else chain bug - Web search: add Brave Search API as primary backend, expand SearXNG pool - iOS HIG: comprehensive mobile UX rules in cursor rules + CLAUDE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
105 lines
3.1 KiB
TypeScript
105 lines
3.1 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
import { useTheme } from './useTheme'
|
|
|
|
describe('useTheme', () => {
|
|
beforeEach(() => {
|
|
localStorage.clear()
|
|
document.documentElement.classList.remove('dark', 'light')
|
|
// Reset module-level state by setting to default
|
|
const { setTheme } = useTheme()
|
|
setTheme('dark')
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('returns expected API', () => {
|
|
const theme = useTheme()
|
|
expect(theme.currentTheme).toBeDefined()
|
|
expect(theme.isDark).toBeDefined()
|
|
expect(theme.setTheme).toBeTypeOf('function')
|
|
expect(theme.toggleTheme).toBeTypeOf('function')
|
|
expect(theme.initTheme).toBeTypeOf('function')
|
|
})
|
|
|
|
it('defaults to dark theme', () => {
|
|
const { isDark, currentTheme } = useTheme()
|
|
expect(currentTheme.value).toBe('dark')
|
|
expect(isDark.value).toBe(true)
|
|
})
|
|
|
|
it('setTheme switches to light', () => {
|
|
const { setTheme, isDark, currentTheme } = useTheme()
|
|
setTheme('light')
|
|
|
|
expect(currentTheme.value).toBe('light')
|
|
expect(isDark.value).toBe(false)
|
|
expect(localStorage.getItem('aiui-theme')).toBe('light')
|
|
expect(document.documentElement.classList.contains('light')).toBe(true)
|
|
expect(document.documentElement.classList.contains('dark')).toBe(false)
|
|
})
|
|
|
|
it('setTheme switches to dark', () => {
|
|
const { setTheme } = useTheme()
|
|
setTheme('light')
|
|
setTheme('dark')
|
|
|
|
expect(document.documentElement.classList.contains('dark')).toBe(true)
|
|
expect(document.documentElement.classList.contains('light')).toBe(false)
|
|
expect(localStorage.getItem('aiui-theme')).toBe('dark')
|
|
})
|
|
|
|
it('toggleTheme flips between dark and light', () => {
|
|
const { toggleTheme, currentTheme } = useTheme()
|
|
|
|
expect(currentTheme.value).toBe('dark')
|
|
toggleTheme()
|
|
expect(currentTheme.value).toBe('light')
|
|
toggleTheme()
|
|
expect(currentTheme.value).toBe('dark')
|
|
})
|
|
|
|
it('initTheme restores saved preference from localStorage', () => {
|
|
localStorage.setItem('aiui-theme', 'light')
|
|
|
|
const { initTheme, currentTheme } = useTheme()
|
|
initTheme()
|
|
|
|
expect(currentTheme.value).toBe('light')
|
|
})
|
|
|
|
it('initTheme falls back to prefers-color-scheme when no saved preference', () => {
|
|
localStorage.clear()
|
|
|
|
const matchMediaSpy = vi.spyOn(window, 'matchMedia').mockReturnValue({
|
|
matches: false,
|
|
} as MediaQueryList)
|
|
|
|
const { initTheme, currentTheme } = useTheme()
|
|
initTheme()
|
|
|
|
expect(matchMediaSpy).toHaveBeenCalledWith('(prefers-color-scheme: dark)')
|
|
expect(currentTheme.value).toBe('light')
|
|
})
|
|
|
|
it('initTheme uses dark when system prefers dark', () => {
|
|
localStorage.clear()
|
|
|
|
vi.spyOn(window, 'matchMedia').mockReturnValue({
|
|
matches: true,
|
|
} as MediaQueryList)
|
|
|
|
const { initTheme, currentTheme } = useTheme()
|
|
initTheme()
|
|
|
|
expect(currentTheme.value).toBe('dark')
|
|
})
|
|
|
|
it('shares state across multiple useTheme calls', () => {
|
|
const theme1 = useTheme()
|
|
const theme2 = useTheme()
|
|
|
|
theme1.setTheme('light')
|
|
expect(theme2.currentTheme.value).toBe('light')
|
|
expect(theme2.isDark.value).toBe(false)
|
|
})
|
|
})
|