import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { ThemeProvider, useTheme } from './ThemeContext'
function ThemeConsumer() {
const { theme, resolvedTheme, setTheme, toggleTheme } = useTheme()
return (
{theme}
{resolvedTheme}
)
}
describe('ThemeProvider', () => {
beforeEach(() => {
localStorage.clear()
})
it('loads theme from localStorage and applies resolved theme', () => {
localStorage.setItem('mp-theme', 'dark')
const matchMediaMock = vi.fn().mockReturnValue({
matches: false,
addEventListener: vi.fn(),
removeEventListener: vi.fn()
})
vi.stubGlobal('matchMedia', matchMediaMock)
render(
)
expect(screen.getByTestId('theme')).toHaveTextContent('dark')
expect(screen.getByTestId('resolved')).toHaveTextContent('dark')
expect(document.documentElement.classList.contains('dark')).toBe(true)
expect(document.documentElement.getAttribute('data-theme')).toBe('dark')
})
it('resolves system theme when set to system', async () => {
const matchMediaMock = vi.fn().mockReturnValue({
matches: true,
addEventListener: vi.fn(),
removeEventListener: vi.fn()
})
vi.stubGlobal('matchMedia', matchMediaMock)
render(
)
expect(screen.getByTestId('theme')).toHaveTextContent('system')
expect(screen.getByTestId('resolved')).toHaveTextContent('dark')
expect(document.documentElement.classList.contains('dark')).toBe(true)
const user = userEvent.setup()
await user.click(screen.getByRole('button', { name: /set dark/i }))
expect(localStorage.getItem('mp-theme')).toBe('dark')
})
it('toggles between light and dark', async () => {
const matchMediaMock = vi.fn().mockReturnValue({
matches: false,
addEventListener: vi.fn(),
removeEventListener: vi.fn()
})
vi.stubGlobal('matchMedia', matchMediaMock)
render(
)
const user = userEvent.setup()
expect(screen.getByTestId('resolved')).toHaveTextContent('light')
await user.click(screen.getByRole('button', { name: /toggle/i }))
await waitFor(() => expect(screen.getByTestId('resolved')).toHaveTextContent('dark'))
expect(document.documentElement.classList.contains('dark')).toBe(true)
})
it('throws when useTheme is used outside provider', () => {
const ConsoleError = console.error
console.error = vi.fn()
expect(() => render()).toThrow('useTheme must be used within ThemeProvider')
console.error = ConsoleError
})
})