Add tests for auth and theme contexts
This commit is contained in:
parent
cb9ccc9792
commit
b8599b4d08
2 changed files with 220 additions and 0 deletions
125
marketplace/src/context/AuthContext.test.tsx
Normal file
125
marketplace/src/context/AuthContext.test.tsx
Normal 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
|
||||
})
|
||||
})
|
||||
95
marketplace/src/context/ThemeContext.test.tsx
Normal file
95
marketplace/src/context/ThemeContext.test.tsx
Normal 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
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue