saveinmed/frontend/src/context/ThemeContext.test.tsx

95 lines
3 KiB
TypeScript

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 (
<div>
<div data-testid="theme">{theme}</div>
<div data-testid="resolved">{resolvedTheme}</div>
<button onClick={() => setTheme('dark')}>set dark</button>
<button onClick={toggleTheme}>toggle</button>
</div>
)
}
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(
<ThemeProvider>
<ThemeConsumer />
</ThemeProvider>
)
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(
<ThemeProvider>
<ThemeConsumer />
</ThemeProvider>
)
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(
<ThemeProvider>
<ThemeConsumer />
</ThemeProvider>
)
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(<ThemeConsumer />)).toThrow('useTheme must be used within ThemeProvider')
console.error = ConsoleError
})
})