Add tests for auth and theme contexts

This commit is contained in:
Tiago Yamamoto 2026-01-01 15:39:53 -03:00
parent cb9ccc9792
commit b8599b4d08
2 changed files with 220 additions and 0 deletions

View file

@ -0,0 +1,125 @@
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { AuthProvider, useAuth } from './AuthContext'
const navigateMock = vi.fn()
const setTokenMock = vi.fn()
const logoutMock = vi.fn().mockResolvedValue(undefined)
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom')
return {
...actual,
useNavigate: () => navigateMock
}
})
vi.mock('../services/apiClient', () => ({
apiClient: {
setToken: setTokenMock
}
}))
vi.mock('../services/auth', () => ({
authService: {
logout: logoutMock
}
}))
function AuthConsumer() {
const { user, loading, login, logout, setUser } = useAuth()
return (
<div>
<div data-testid="loading">{loading ? 'loading' : 'ready'}</div>
<div data-testid="username">{user?.name ?? 'guest'}</div>
<button onClick={() => login('token-123', 'admin', 'Admin User', '1')}>login admin</button>
<button onClick={() => login('token-456', 'delivery', 'Delivery User', '2')}>login delivery</button>
<button
onClick={() =>
setUser({
token: 'token-999',
role: 'seller',
name: 'Seller User',
id: '3',
companyId: 'company-1'
})
}
>
set user
</button>
<button onClick={logout}>logout</button>
</div>
)
}
describe('AuthProvider', () => {
beforeEach(() => {
localStorage.clear()
navigateMock.mockReset()
setTokenMock.mockReset()
logoutMock.mockClear()
})
afterEach(() => {
localStorage.clear()
})
it('hydrates user from localStorage and sets token', async () => {
const persisted = { token: 'persisted-token', role: 'admin', name: 'Persisted', id: '10' }
localStorage.setItem('mp-auth-user', JSON.stringify(persisted))
render(
<AuthProvider>
<AuthConsumer />
</AuthProvider>
)
expect(screen.getByTestId('username')).toHaveTextContent('Persisted')
expect(setTokenMock).toHaveBeenCalledWith('persisted-token')
await waitFor(() => expect(screen.getByTestId('loading')).toHaveTextContent('ready'))
})
it('logs in and navigates based on role', async () => {
const user = userEvent.setup()
render(
<AuthProvider>
<AuthConsumer />
</AuthProvider>
)
await user.click(screen.getByRole('button', { name: /login admin/i }))
expect(navigateMock).toHaveBeenCalledWith('/dashboard', { replace: true })
expect(localStorage.getItem('mp-auth-user')).toContain('Admin User')
await user.click(screen.getByRole('button', { name: /login delivery/i }))
expect(navigateMock).toHaveBeenCalledWith('/entregas', { replace: true })
})
it('clears session on logout', async () => {
const user = userEvent.setup()
render(
<AuthProvider>
<AuthConsumer />
</AuthProvider>
)
await user.click(screen.getByRole('button', { name: /set user/i }))
expect(localStorage.getItem('mp-auth-user')).toContain('Seller User')
await user.click(screen.getByRole('button', { name: /logout/i }))
expect(logoutMock).toHaveBeenCalledTimes(1)
await waitFor(() => expect(localStorage.getItem('mp-auth-user'))).toBeNull()
expect(navigateMock).toHaveBeenCalledWith('/login', { replace: true })
expect(setTokenMock).toHaveBeenCalledWith(null)
})
it('throws when useAuth is used outside provider', () => {
const ConsoleError = console.error
console.error = vi.fn()
expect(() => render(<AuthConsumer />)).toThrow('useAuth must be used within AuthProvider')
console.error = ConsoleError
})
})

View file

@ -0,0 +1,95 @@
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
})
})