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 Response | Promise>) { 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( {ui} , ); } describe('LoginPage', () => { it('renders email and password fields and a submit button', async () => { renderWithProvider(); 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(); 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(); 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(); }); });