35% front tests
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { AuthProvider, useAuth } from '@/auth/AuthContext'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
// Мокаем localStorage
|
||||
const localStorageMock = (() => {
|
||||
let store: Record<string, string> = {}
|
||||
return {
|
||||
getItem: vi.fn((key: string) => store[key] || null),
|
||||
setItem: vi.fn((key: string, value: string) => {
|
||||
store[key] = value
|
||||
}),
|
||||
removeItem: vi.fn((key: string) => {
|
||||
delete store[key]
|
||||
}),
|
||||
clear: vi.fn(() => {
|
||||
store = {}
|
||||
}),
|
||||
}
|
||||
})()
|
||||
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: localStorageMock,
|
||||
})
|
||||
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
)
|
||||
|
||||
describe('AuthContext', () => {
|
||||
beforeEach(() => {
|
||||
localStorageMock.clear()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('useAuth', () => {
|
||||
it('должен выбрасывать ошибку при использовании вне AuthProvider', () => {
|
||||
// Отключаем console.error для этого теста
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
|
||||
expect(() => {
|
||||
renderHook(() => useAuth())
|
||||
}).toThrow('useAuth must be used within an AuthProvider')
|
||||
|
||||
consoleSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
describe('initial state', () => {
|
||||
it('должен инициализироваться с token из localStorage', () => {
|
||||
localStorageMock.getItem.mockReturnValue('test-token-123')
|
||||
|
||||
const { result } = renderHook(() => useAuth(), { wrapper })
|
||||
|
||||
expect(result.current.token).toBe('test-token-123')
|
||||
expect(result.current.isAuthenticated).toBe(true)
|
||||
expect(localStorageMock.getItem).toHaveBeenCalledWith('token')
|
||||
})
|
||||
|
||||
it('должен инициализироваться с null если token отсутствует в localStorage', () => {
|
||||
localStorageMock.getItem.mockReturnValue(null)
|
||||
|
||||
const { result } = renderHook(() => useAuth(), { wrapper })
|
||||
|
||||
expect(result.current.token).toBe(null)
|
||||
expect(result.current.isAuthenticated).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('login', () => {
|
||||
it('должен сохранять токен в localStorage и state', () => {
|
||||
const { result } = renderHook(() => useAuth(), { wrapper })
|
||||
|
||||
act(() => {
|
||||
result.current.login('new-token-456')
|
||||
})
|
||||
|
||||
expect(result.current.token).toBe('new-token-456')
|
||||
expect(result.current.isAuthenticated).toBe(true)
|
||||
expect(localStorageMock.setItem).toHaveBeenCalledWith('token', 'new-token-456')
|
||||
})
|
||||
|
||||
it('должен обновлять isAuthenticated после login', () => {
|
||||
const { result } = renderHook(() => useAuth(), { wrapper })
|
||||
|
||||
expect(result.current.isAuthenticated).toBe(false)
|
||||
|
||||
act(() => {
|
||||
result.current.login('another-token')
|
||||
})
|
||||
|
||||
expect(result.current.isAuthenticated).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('logout', () => {
|
||||
it('должен удалять токен из localStorage и state', () => {
|
||||
localStorageMock.getItem.mockReturnValue('existing-token')
|
||||
|
||||
const { result } = renderHook(() => useAuth(), { wrapper })
|
||||
|
||||
expect(result.current.token).toBe('existing-token')
|
||||
|
||||
act(() => {
|
||||
result.current.logout()
|
||||
})
|
||||
|
||||
expect(result.current.token).toBe(null)
|
||||
expect(result.current.isAuthenticated).toBe(false)
|
||||
expect(localStorageMock.removeItem).toHaveBeenCalledWith('token')
|
||||
})
|
||||
|
||||
it('должен корректно работать logout когда token уже null', () => {
|
||||
const { result } = renderHook(() => useAuth(), { wrapper })
|
||||
|
||||
act(() => {
|
||||
result.current.logout()
|
||||
})
|
||||
|
||||
expect(result.current.token).toBe(null)
|
||||
expect(result.current.isAuthenticated).toBe(false)
|
||||
expect(localStorageMock.removeItem).toHaveBeenCalledWith('token')
|
||||
})
|
||||
})
|
||||
|
||||
describe('isAuthenticated', () => {
|
||||
it('должен возвращать true когда token существует', () => {
|
||||
localStorageMock.getItem.mockReturnValue('valid-token')
|
||||
|
||||
const { result } = renderHook(() => useAuth(), { wrapper })
|
||||
|
||||
expect(result.current.isAuthenticated).toBe(true)
|
||||
})
|
||||
|
||||
it('должен возвращать false когда token null', () => {
|
||||
localStorageMock.getItem.mockReturnValue(null)
|
||||
|
||||
const { result } = renderHook(() => useAuth(), { wrapper })
|
||||
|
||||
expect(result.current.isAuthenticated).toBe(false)
|
||||
})
|
||||
|
||||
it('должен возвращать false когда token пустая строка', () => {
|
||||
localStorageMock.getItem.mockReturnValue('')
|
||||
|
||||
const { result } = renderHook(() => useAuth(), { wrapper })
|
||||
|
||||
expect(result.current.isAuthenticated).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('context methods', () => {
|
||||
it('должен предоставлять метод login', () => {
|
||||
const { result } = renderHook(() => useAuth(), { wrapper })
|
||||
|
||||
expect(result.current.login).toBeDefined()
|
||||
expect(typeof result.current.login).toBe('function')
|
||||
})
|
||||
|
||||
it('должен предоставлять метод logout', () => {
|
||||
const { result } = renderHook(() => useAuth(), { wrapper })
|
||||
|
||||
expect(result.current.logout).toBeDefined()
|
||||
expect(typeof result.current.logout).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('multiple login/logout cycles', () => {
|
||||
it('должен корректно обрабатывать несколько циклов login/logout', () => {
|
||||
const { result } = renderHook(() => useAuth(), { wrapper })
|
||||
|
||||
// Первый цикл
|
||||
act(() => {
|
||||
result.current.login('token-1')
|
||||
})
|
||||
expect(result.current.token).toBe('token-1')
|
||||
|
||||
act(() => {
|
||||
result.current.logout()
|
||||
})
|
||||
expect(result.current.token).toBe(null)
|
||||
|
||||
// Второй цикл
|
||||
act(() => {
|
||||
result.current.login('token-2')
|
||||
})
|
||||
expect(result.current.token).toBe('token-2')
|
||||
|
||||
act(() => {
|
||||
result.current.logout()
|
||||
})
|
||||
expect(result.current.token).toBe(null)
|
||||
|
||||
// Третий цикл
|
||||
act(() => {
|
||||
result.current.login('token-3')
|
||||
})
|
||||
expect(result.current.token).toBe('token-3')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,174 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, waitFor } from '@testing-library/react'
|
||||
import { MemoryRouter } from 'react-router-dom'
|
||||
import { AxiosInterceptor } from '../../src/auth/AxiosInterceptor'
|
||||
import { AuthProvider } from '../../src/auth/AuthContext'
|
||||
import { ThemeProvider } from '../../src/ThemeContext'
|
||||
import { Logger } from '../../src/utils/logger'
|
||||
|
||||
const mockNavigate = vi.fn()
|
||||
const mockLogout = vi.fn()
|
||||
|
||||
// Мок api с интерсепторами - используем vi.hoisted для подъёма
|
||||
const mocks = vi.hoisted(() => ({
|
||||
responseUse: vi.fn(),
|
||||
responseEject: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('../../src/api', () => ({
|
||||
default: {
|
||||
interceptors: {
|
||||
request: {
|
||||
use: vi.fn(),
|
||||
eject: vi.fn(),
|
||||
},
|
||||
response: {
|
||||
use: mocks.responseUse,
|
||||
eject: mocks.responseEject,
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
// Мок react-router-dom
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const actual = await vi.importActual('react-router-dom')
|
||||
return {
|
||||
...(actual as object),
|
||||
useNavigate: () => mockNavigate,
|
||||
useLocation: () => ({ pathname: '/subscriptions' }),
|
||||
}
|
||||
})
|
||||
|
||||
// Мок AuthContext
|
||||
vi.mock('../../src/auth/AuthContext', async () => {
|
||||
const actual = await vi.importActual('../../src/auth/AuthContext')
|
||||
return {
|
||||
...(actual as object),
|
||||
useAuth: () => ({ logout: mockLogout }),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../src/utils/logger', () => ({
|
||||
Logger: {
|
||||
warn: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
describe('AxiosInterceptor', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockNavigate.mockClear()
|
||||
mockLogout.mockClear()
|
||||
vi.mocked(Logger.warn).mockClear()
|
||||
vi.mocked(Logger.debug).mockClear()
|
||||
mocks.responseUse.mockClear()
|
||||
mocks.responseEject.mockClear()
|
||||
})
|
||||
|
||||
it('должен рендериться без ошибок и возвращать null', () => {
|
||||
const { container } = render(
|
||||
<MemoryRouter>
|
||||
<AuthProvider>
|
||||
<ThemeProvider>
|
||||
<AxiosInterceptor />
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
|
||||
it('должен регистрировать interceptor при монтировании', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<AuthProvider>
|
||||
<ThemeProvider>
|
||||
<AxiosInterceptor />
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.responseUse).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// Проверяем что был зарегистрирован обработчик
|
||||
const interceptorCall = vi.mocked(mocks.responseUse).mock.calls[0]
|
||||
expect(interceptorCall).toBeDefined()
|
||||
expect(typeof interceptorCall?.[0]).toBe('function') // success handler
|
||||
expect(typeof interceptorCall?.[1]).toBe('function') // error handler
|
||||
})
|
||||
|
||||
it('должен пропускать успешные ответы', () => {
|
||||
const successHandler = (response: unknown) => response
|
||||
|
||||
const response = { data: { test: 'value' }, status: 200 }
|
||||
const result = successHandler(response)
|
||||
|
||||
expect(result).toEqual(response)
|
||||
})
|
||||
|
||||
it('должен отклонять ошибки не 401', async () => {
|
||||
const errorHandler = (error: unknown) => Promise.reject(error)
|
||||
|
||||
const errorResponse = { response: { status: 500 } }
|
||||
|
||||
await expect(errorHandler(errorResponse)).rejects.toEqual(errorResponse)
|
||||
})
|
||||
|
||||
it('должен удалять interceptor при размонтировании', async () => {
|
||||
const { unmount } = render(
|
||||
<MemoryRouter>
|
||||
<AuthProvider>
|
||||
<ThemeProvider>
|
||||
<AxiosInterceptor />
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.responseUse).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
unmount()
|
||||
|
||||
expect(mocks.responseEject).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('должен обрабатывать 401 ошибки', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<AuthProvider>
|
||||
<ThemeProvider>
|
||||
<AxiosInterceptor />
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.responseUse).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
const interceptorCall = vi.mocked(mocks.responseUse).mock.calls[0]
|
||||
const errorHandler = interceptorCall?.[1]
|
||||
|
||||
if (errorHandler) {
|
||||
try {
|
||||
await errorHandler({ response: { status: 401 } })
|
||||
} catch {
|
||||
// Ожидаем что ошибка будет проброшена дальше
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем что logout и navigate были вызваны
|
||||
expect(mockLogout).toHaveBeenCalled()
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/login')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,135 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { MemoryRouter, Routes, Route } from 'react-router-dom'
|
||||
import PublicRoute from '@/auth/PublicRoute'
|
||||
import { AuthProvider } from '@/auth/AuthContext'
|
||||
|
||||
// Мокаем localStorage
|
||||
const localStorageMock = (() => {
|
||||
let store: Record<string, string> = {}
|
||||
return {
|
||||
getItem: vi.fn((key: string) => store[key] || null),
|
||||
setItem: vi.fn((key: string, value: string) => {
|
||||
store[key] = value
|
||||
}),
|
||||
removeItem: vi.fn((key: string) => {
|
||||
delete store[key]
|
||||
}),
|
||||
clear: vi.fn(() => {
|
||||
store = {}
|
||||
}),
|
||||
}
|
||||
})()
|
||||
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: localStorageMock,
|
||||
})
|
||||
|
||||
describe('PublicRoute', () => {
|
||||
beforeEach(() => {
|
||||
localStorageMock.clear()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('должен рендерить Outlet когда пользователь не авторизован', () => {
|
||||
localStorageMock.getItem.mockReturnValue(null)
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/login']}>
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
<Route element={<PublicRoute />}>
|
||||
<Route path="/login" element={<div data-testid="login-page">Login Page</div>} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('login-page')).toBeInTheDocument()
|
||||
expect(screen.getByText('Login Page')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('должен перенаправлять на / когда пользователь авторизован', () => {
|
||||
localStorageMock.getItem.mockReturnValue('valid-token')
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/login']}>
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
<Route element={<PublicRoute />}>
|
||||
<Route path="/login" element={<div data-testid="login-page">Login Page</div>} />
|
||||
</Route>
|
||||
<Route path="/" element={<div data-testid="home-page">Home Page</div>} />
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
// Должен показать главную страницу вместо login
|
||||
expect(screen.getByTestId('home-page')).toBeInTheDocument()
|
||||
expect(screen.getByText('Home Page')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('должен работать с несколькими публичными роутами', () => {
|
||||
localStorageMock.getItem.mockReturnValue(null)
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/register']}>
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
<Route element={<PublicRoute />}>
|
||||
<Route path="/login" element={<div data-testid="login-page">Login Page</div>} />
|
||||
<Route path="/register" element={<div data-testid="register-page">Register Page</div>} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('register-page')).toBeInTheDocument()
|
||||
expect(screen.getByText('Register Page')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('должен перенаправлять авторизованного пользователя с любого публичного роута', () => {
|
||||
localStorageMock.getItem.mockReturnValue('valid-token')
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/register']}>
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
<Route element={<PublicRoute />}>
|
||||
<Route path="/login" element={<div data-testid="login-page">Login Page</div>} />
|
||||
<Route path="/register" element={<div data-testid="register-page">Register Page</div>} />
|
||||
</Route>
|
||||
<Route path="/" element={<div data-testid="home-page">Home Page</div>} />
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
// Должен показать главную страницу
|
||||
expect(screen.getByTestId('home-page')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('register-page')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('должен использовать replace при перенаправлении', () => {
|
||||
localStorageMock.getItem.mockReturnValue('valid-token')
|
||||
|
||||
const { container } = render(
|
||||
<MemoryRouter initialEntries={['/login']}>
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
<Route element={<PublicRoute />}>
|
||||
<Route path="/login" element={<div>Login</div>} />
|
||||
</Route>
|
||||
<Route path="/" element={<div>Home</div>} />
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
// Проверяем что рендерится Home
|
||||
expect(container.textContent).toContain('Home')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,186 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { MemoryRouter, Routes, Route } from 'react-router-dom'
|
||||
import RequireAuth from '@/auth/RequireAuth'
|
||||
import { AuthProvider } from '@/auth/AuthContext'
|
||||
|
||||
// Мокаем localStorage
|
||||
const localStorageMock = (() => {
|
||||
let store: Record<string, string> = {}
|
||||
return {
|
||||
getItem: vi.fn((key: string) => store[key] || null),
|
||||
setItem: vi.fn((key: string, value: string) => {
|
||||
store[key] = value
|
||||
}),
|
||||
removeItem: vi.fn((key: string) => {
|
||||
delete store[key]
|
||||
}),
|
||||
clear: vi.fn(() => {
|
||||
store = {}
|
||||
}),
|
||||
}
|
||||
})()
|
||||
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: localStorageMock,
|
||||
})
|
||||
|
||||
describe('RequireAuth', () => {
|
||||
beforeEach(() => {
|
||||
localStorageMock.clear()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('должен рендерить children когда пользователь авторизован', () => {
|
||||
localStorageMock.getItem.mockReturnValue('valid-token')
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/protected']}>
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/protected"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<div data-testid="protected-content">Protected Content</div>
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('protected-content')).toBeInTheDocument()
|
||||
expect(screen.getByText('Protected Content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('должен перенаправлять на /login когда пользователь не авторизован', () => {
|
||||
localStorageMock.getItem.mockReturnValue(null)
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/protected']}>
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/protected"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<div data-testid="protected-content">Protected Content</div>
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route path="/login" element={<div data-testid="login-page">Login Page</div>} />
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('login-page')).toBeInTheDocument()
|
||||
expect(screen.getByText('Login Page')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('должен сохранять state.from с текущим location при перенаправлении на login', () => {
|
||||
localStorageMock.getItem.mockReturnValue(null)
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/settings']}>
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<div data-testid="settings-content">Settings</div>
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route path="/login" element={<div data-testid="login-page">Login Page</div>} />
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
// Должен показать login страницу
|
||||
expect(screen.getByTestId('login-page')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('должен использовать replace: true при перенаправлении', () => {
|
||||
// Этот тест проверяет поведение Navigate компонента
|
||||
// В реальном сценарии replace предотвращает добавление записи в историю
|
||||
localStorageMock.getItem.mockReturnValue(null)
|
||||
|
||||
const { container } = render(
|
||||
<MemoryRouter initialEntries={['/protected']}>
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/protected"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<div>Protected</div>
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route path="/login" element={<div>Login</div>} />
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
// Проверяем что рендерится login
|
||||
expect(container.textContent).toContain('Login')
|
||||
})
|
||||
|
||||
it('должен работать с вложенными роутами', () => {
|
||||
localStorageMock.getItem.mockReturnValue('valid-token')
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/dashboard/profile']}>
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/dashboard/*"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<Routes>
|
||||
<Route path="profile" element={<div data-testid="profile">Profile</div>} />
|
||||
</Routes>
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('profile')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('должен блокировать доступ к защищённому маршруту без авторизации', () => {
|
||||
localStorageMock.getItem.mockReturnValue(null)
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/admin']}>
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/admin"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<div data-testid="admin-content">Admin Panel</div>
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route path="/login" element={<div data-testid="login-page">Login Page</div>} />
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
// Контент админки не должен быть доступен
|
||||
expect(screen.queryByTestId('admin-content')).not.toBeInTheDocument()
|
||||
// Должна показываться страница логина
|
||||
expect(screen.getByTestId('login-page')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user