import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { render, screen, cleanup, act } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { ThemeProvider, useTheme } from './ThemeContext' function ThemeConsumer() { const { theme, toggle } = useTheme() return (
{theme}
) } beforeEach(() => { localStorage.clear() document.documentElement.removeAttribute('data-theme') document.documentElement.style.colorScheme = '' document.documentElement.classList.remove('dark') }) afterEach(() => { cleanup() }) describe('ThemeProvider', () => { it('defaults to "dark" when localStorage has no saved theme', () => { render() expect(screen.getByTestId('theme').textContent).toBe('dark') expect(localStorage.getItem('theme')).toBe('dark') expect(document.documentElement.getAttribute('data-theme')).toBe('dark') expect(document.documentElement.classList.contains('dark')).toBe(true) }) it('restores saved theme from localStorage', () => { localStorage.setItem('theme', 'light') render() expect(screen.getByTestId('theme').textContent).toBe('light') expect(document.documentElement.getAttribute('data-theme')).toBe('light') expect(document.documentElement.classList.contains('dark')).toBe(false) }) it('toggles from dark to light', async () => { render() expect(screen.getByTestId('theme').textContent).toBe('dark') await act(async () => { await userEvent.click(screen.getByTestId('toggle-btn')) }) expect(screen.getByTestId('theme').textContent).toBe('light') expect(localStorage.getItem('theme')).toBe('light') expect(document.documentElement.classList.contains('dark')).toBe(false) }) it('toggles from light back to dark', async () => { localStorage.setItem('theme', 'light') render() await act(async () => { await userEvent.click(screen.getByTestId('toggle-btn')) }) expect(screen.getByTestId('theme').textContent).toBe('dark') expect(localStorage.getItem('theme')).toBe('dark') expect(document.documentElement.classList.contains('dark')).toBe(true) }) it('sets colorScheme on the document element', () => { render() expect(document.documentElement.style.colorScheme).toBe('dark') }) }) describe('useTheme', () => { it('returns null when used outside ThemeProvider', () => { // useTheme uses raw useContext without a guard, so it returns null outside provider function Naked() { const ctx = useTheme() return {String(ctx)} } render() expect(screen.getByTestId('ctx').textContent).toBe('null') }) })