import { render, renderHook, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { type ReactNode } from 'react'
import { afterEach, describe, expect, it } from 'vitest'
import { ThemeProvider } from './theme-provider'
import { getThemeScript } from './theme-script'
import { useTheme } from '../../hooks/use-theme'
afterEach(() => {
document.documentElement.className = ''
document.documentElement.style.colorScheme = ''
localStorage.clear()
})
describe('getThemeScript', () => {
it('embeds the storage key and reads localStorage + system preference', () => {
const s = getThemeScript({ storageKey: 'appica-theme' })
expect(s).toContain('appica-theme')
expect(s).toContain('localStorage')
expect(s).toContain('prefers-color-scheme')
expect(s.startsWith('(')).toBe(true)
})
it('inlines a forced theme', () => {
expect(getThemeScript({ forcedTheme: 'dark' })).toContain('"dark"')
})
})
describe('ThemeProvider / useTheme', () => {
it('returns inert values without a provider', () => {
const { result } = renderHook(() => useTheme())
expect(result.current.theme).toBeUndefined()
expect(result.current.mounted).toBe(false)
})
it('exposes the default theme and reports mounted after hydration', () => {
const wrapper = ({ children }: { children: ReactNode }) => (
{children}
)
const { result } = renderHook(() => useTheme(), { wrapper })
expect(result.current.theme).toBe('light')
expect(result.current.mounted).toBe(true)
})
it('resolves "system" to light/dark via the media query', () => {
// The test stub reports `prefers-color-scheme: dark` -> false, i.e. light.
const wrapper = ({ children }: { children: ReactNode }) => (
{children}
)
const { result } = renderHook(() => useTheme(), { wrapper })
expect(result.current.theme).toBe('system')
expect(result.current.resolvedTheme).toBe('light')
expect(result.current.systemTheme).toBe('light')
})
it('applies the theme class to and persists as a raw string on change', async () => {
function Probe() {
const { theme, setTheme } = useTheme()
return
}
const user = userEvent.setup()
render(
,
)
expect(document.documentElement.classList.contains('dark')).toBe(false)
await user.click(screen.getByRole('button'))
expect(document.documentElement.classList.contains('dark')).toBe(true)
expect(document.documentElement.classList.contains('light')).toBe(false)
expect(localStorage.getItem('theme')).toBe('dark')
})
it('forcedTheme overrides storage and system', () => {
localStorage.setItem('theme', 'light')
const wrapper = ({ children }: { children: ReactNode }) => (
{children}
)
const { result } = renderHook(() => useTheme(), { wrapper })
expect(result.current.resolvedTheme).toBe('dark')
expect(result.current.forcedTheme).toBe('dark')
})
it('nested ThemeProvider is a passthrough (outer wins)', () => {
const wrapper = ({ children }: { children: ReactNode }) => (
{children}
)
const { result } = renderHook(() => useTheme(), { wrapper })
expect(result.current.resolvedTheme).toBe('dark')
})
})