M2-T07: build auth UI (login, session bootstrap, forced password change, logout)
- real Mantine login form -> POST /api/auth/login; 401 inline error; redirect when already authed - ProtectedRoute: loading state, preserves intended destination, gates force_password_change - ChangePasswordPage forced-change gate -> POST /api/auth/password - logout control in AppLayout nav -> POST /api/auth/logout - typed client only; vitest tests for the login flow
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Shared test utilities — wraps components in the providers they need.
|
||||
*
|
||||
* Usage:
|
||||
* import { renderWithProviders } from '../test-utils'
|
||||
* renderWithProviders(<LoginPage />, { initialPath: '/login' })
|
||||
*/
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { render } from '@testing-library/react'
|
||||
import { MantineProvider } from '@mantine/core'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { MemoryRouter, Routes, Route } from 'react-router-dom'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Provider wrapper
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface RenderOptions {
|
||||
/** Initial URL path (default: '/'). */
|
||||
initialPath?: string
|
||||
/**
|
||||
* Extra routes to register alongside the component under test.
|
||||
* Useful for asserting navigation (e.g. render a /home sentinel and check
|
||||
* that the component navigates there after login).
|
||||
*/
|
||||
routes?: Array<{ path: string; element: ReactNode }>
|
||||
/**
|
||||
* React-router initial entries (overrides initialPath when provided).
|
||||
* Use when you need to seed location.state (e.g. from-path for redirect-after-login).
|
||||
*/
|
||||
initialEntries?: Array<string | { pathname: string; state?: unknown }>
|
||||
}
|
||||
|
||||
/**
|
||||
* Render `ui` inside MantineProvider + a fresh QueryClientProvider + MemoryRouter.
|
||||
* SessionProvider is NOT included — tests that need session state should mock
|
||||
* `GET /api/session` via vi.fn() on the apiClient or use MSW.
|
||||
*/
|
||||
export function renderWithProviders(ui: ReactNode, options: RenderOptions = {}) {
|
||||
const { initialPath = '/', routes = [], initialEntries } = options
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
})
|
||||
|
||||
const entries = initialEntries ?? [initialPath]
|
||||
|
||||
function Wrapper() {
|
||||
return (
|
||||
<MantineProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter initialEntries={entries}>
|
||||
<Routes>
|
||||
<Route path={initialPath} element={ui} />
|
||||
{routes.map(({ path, element }) => (
|
||||
<Route key={path} path={path} element={element} />
|
||||
))}
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>
|
||||
</MantineProvider>
|
||||
)
|
||||
}
|
||||
|
||||
return render(<Wrapper />)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a minimal SessionProvider-less wrapper that just supplies the
|
||||
* query and router context. Returns the queryClient so tests can prime it.
|
||||
*/
|
||||
export function createTestQueryClient() {
|
||||
return new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user