136 lines
4.0 KiB
TypeScript
136 lines
4.0 KiB
TypeScript
import { act } from 'react';
|
|
import { createRoot, type Root } from 'react-dom/client';
|
|
import { createMemoryRouter, RouterProvider, type RouteObject } from 'react-router-dom';
|
|
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
|
|
|
|
const { useSessionMock } = vi.hoisted(() => ({
|
|
useSessionMock: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('@/lib/auth-client', () => ({
|
|
useSession: useSessionMock,
|
|
}));
|
|
|
|
import { AuthGuard, GuestGuard } from './guards';
|
|
|
|
interface RenderedRouter {
|
|
container: HTMLDivElement;
|
|
router: ReturnType<typeof createMemoryRouter>;
|
|
}
|
|
|
|
const mountedRoots: Root[] = [];
|
|
|
|
beforeAll(() => {
|
|
Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', {
|
|
configurable: true,
|
|
value: true,
|
|
});
|
|
});
|
|
|
|
afterAll(() => {
|
|
Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT');
|
|
});
|
|
|
|
async function renderRouter(
|
|
routeObjects: RouteObject[],
|
|
initialEntry: string,
|
|
): Promise<RenderedRouter> {
|
|
const container = document.createElement('div');
|
|
document.body.append(container);
|
|
const router = createMemoryRouter(routeObjects, { initialEntries: [initialEntry] });
|
|
const root = createRoot(container);
|
|
mountedRoots.push(root);
|
|
|
|
await act(async () => {
|
|
root.render(<RouterProvider router={router} />);
|
|
});
|
|
|
|
return { container, router };
|
|
}
|
|
|
|
afterEach(async () => {
|
|
for (const root of mountedRoots.splice(0)) {
|
|
await act(async () => {
|
|
root.unmount();
|
|
});
|
|
}
|
|
document.body.replaceChildren();
|
|
useSessionMock.mockReset();
|
|
});
|
|
|
|
const guestRoutes: RouteObject[] = [
|
|
{
|
|
path: '/login',
|
|
element: <GuestGuard />,
|
|
children: [{ index: true, element: <p>Guest page</p> }],
|
|
},
|
|
{ path: '/chat', element: <p>Chat page</p> },
|
|
];
|
|
|
|
const authenticatedRoutes: RouteObject[] = [
|
|
{
|
|
path: '/chat',
|
|
element: <AuthGuard />,
|
|
children: [{ index: true, element: <p>Private page</p> }],
|
|
},
|
|
{ path: '/login', element: <p>Login page</p> },
|
|
];
|
|
|
|
describe('GuestGuard', () => {
|
|
it('renders the guest outlet while session lookup is pending', async () => {
|
|
useSessionMock.mockReturnValue({ data: null, isPending: true });
|
|
|
|
const view = await renderRouter(guestRoutes, '/login');
|
|
|
|
expect(view.container.textContent).toContain('Guest page');
|
|
expect(view.router.state.location.pathname).toBe('/login');
|
|
});
|
|
|
|
it('renders the guest outlet when no session exists', async () => {
|
|
useSessionMock.mockReturnValue({ data: null, isPending: false });
|
|
|
|
const view = await renderRouter(guestRoutes, '/login');
|
|
|
|
expect(view.container.textContent).toContain('Guest page');
|
|
expect(view.router.state.location.pathname).toBe('/login');
|
|
});
|
|
|
|
it('redirects an authenticated session to chat', async () => {
|
|
useSessionMock.mockReturnValue({ data: { user: { id: 'user-1' } }, isPending: false });
|
|
|
|
const view = await renderRouter(guestRoutes, '/login');
|
|
|
|
expect(view.container.textContent).toContain('Chat page');
|
|
expect(view.router.state.location.pathname).toBe('/chat');
|
|
});
|
|
});
|
|
|
|
describe('AuthGuard', () => {
|
|
it('renders the existing loading treatment while session lookup is pending', async () => {
|
|
useSessionMock.mockReturnValue({ data: null, isPending: true });
|
|
|
|
const view = await renderRouter(authenticatedRoutes, '/chat');
|
|
|
|
expect(view.container.textContent).toContain('Loading...');
|
|
expect(view.router.state.location.pathname).toBe('/chat');
|
|
});
|
|
|
|
it('redirects an unauthenticated visitor to login', async () => {
|
|
useSessionMock.mockReturnValue({ data: null, isPending: false });
|
|
|
|
const view = await renderRouter(authenticatedRoutes, '/chat');
|
|
|
|
expect(view.container.textContent).toContain('Login page');
|
|
expect(view.router.state.location.pathname).toBe('/login');
|
|
});
|
|
|
|
it('renders the authenticated outlet when a session exists', async () => {
|
|
useSessionMock.mockReturnValue({ data: { user: { id: 'user-1' } }, isPending: false });
|
|
|
|
const view = await renderRouter(authenticatedRoutes, '/chat');
|
|
|
|
expect(view.container.textContent).toContain('Private page');
|
|
expect(view.router.state.location.pathname).toBe('/chat');
|
|
});
|
|
});
|