Files
tower/apps/web/app/login/page.test.tsx
T
2026-06-09 02:02:40 +05:30

89 lines
3.4 KiB
TypeScript

import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import LoginPage from './page';
import { AuthProvider } from '../_lib/auth-context';
import { SuperAdminProvider } from '../_lib/super-admin-context';
const pushMock = jest.fn();
const refreshMock = jest.fn();
jest.mock('next/navigation', () => ({
useRouter: () => ({ push: pushMock, refresh: refreshMock, replace: jest.fn() }),
useSearchParams: () => new URLSearchParams(),
}));
function authResponse(body: unknown, status = 200) {
return new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } });
}
const UNAUTHORIZED = () => Promise.resolve(new Response('Unauthorized', { status: 401 }));
function mockFetch(responses: Record<string, (url: string, init?: RequestInit) => Response | Promise<Response>>) {
fetchSpy.mockImplementation((url: string, init?: RequestInit) => {
const handler = responses[url];
if (handler) return handler(url, init);
return responses['default']?.(url, init) ?? UNAUTHORIZED();
});
}
beforeEach(() => {
jest.clearAllMocks();
fetchSpy = jest.spyOn(global, 'fetch');
mockFetch({ default: UNAUTHORIZED });
});
afterEach(() => {
jest.restoreAllMocks();
});
function renderWithProvider(ui: React.ReactElement) {
return render(
<AuthProvider>
<SuperAdminProvider>{ui}</SuperAdminProvider>
</AuthProvider>,
);
}
describe('LoginPage', () => {
it('renders email and password fields and a submit button', async () => {
renderWithProvider(<LoginPage />);
expect(await screen.findByLabelText(/email/i)).toBeInTheDocument();
expect(await screen.findByLabelText(/password/i)).toBeInTheDocument();
expect(await screen.findByRole('button', { name: /sign in/i })).toBeInTheDocument();
});
it('posts credentials to /api/auth/login and redirects on success', async () => {
mockFetch({
'/api/auth/login': () => authResponse({ admin: { id: 'a-1', email: 'me@x.com' } }),
'/api/auth/me': () => authResponse({ admin: { id: 'a-1', email: 'me@x.com', role: 'OWNER', tenantId: 't1', tenantSlug: 'default', tenantName: 'Default' } }),
});
renderWithProvider(<LoginPage />);
await screen.findByLabelText(/email/i);
fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'me@x.com' } });
fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'secret' } });
fireEvent.click(screen.getByRole('button', { name: /sign in/i }));
await waitFor(() =>
expect(fetchSpy).toHaveBeenCalledWith(
'/api/auth/login',
expect.objectContaining({
method: 'POST',
body: JSON.stringify({ email: 'me@x.com', password: 'secret' }),
}),
),
);
expect(pushMock).toHaveBeenCalledWith('/');
});
it('shows an error message on 401', async () => {
mockFetch({
'/api/auth/login': () => authResponse({ message: 'Invalid email or password' }, 401),
});
renderWithProvider(<LoginPage />);
await screen.findByLabelText(/email/i);
fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'me@x.com' } });
fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'wrong' } });
fireEvent.click(screen.getByRole('button', { name: /sign in/i }));
expect(await screen.findByRole('alert')).toHaveTextContent(/invalid email or password/i);
expect(pushMock).not.toHaveBeenCalled();
});
});