/**
* Tests for the LoginPage component.
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from '../utils';
import { LoginPage } from '../../pages/LoginPage';
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';
describe('LoginPage', () => {
beforeEach(() => {
server.use(
http.get('/api/v1/auth/status', () => {
return HttpResponse.json({ auth_enabled: true, requires_setup: false });
})
);
});
describe('rendering', () => {
it('renders the login form', async () => {
render();
await waitFor(() => {
expect(screen.getByRole('heading', { name: /Bambuddy Login/i })).toBeInTheDocument();
});
expect(screen.getByLabelText(/Username/i)).toBeInTheDocument();
expect(screen.getByLabelText(/Password/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Sign in/i })).toBeInTheDocument();
});
it('renders the sign in description', async () => {
render();
await waitFor(() => {
expect(screen.getByText(/Sign in to your account/i)).toBeInTheDocument();
});
});
});
describe('form validation', () => {
it('shows error when submitting empty form', async () => {
const user = userEvent.setup();
render();
await waitFor(() => {
expect(screen.getByRole('button', { name: /Sign in/i })).toBeInTheDocument();
});
await user.click(screen.getByRole('button', { name: /Sign in/i }));
// The form has required fields, so HTML5 validation should prevent submission
// or the component shows a toast
});
it('allows entering username and password', async () => {
const user = userEvent.setup();
render();
await waitFor(() => {
expect(screen.getByLabelText(/Username/i)).toBeInTheDocument();
});
await user.type(screen.getByLabelText(/Username/i), 'testuser');
await user.type(screen.getByLabelText(/Password/i), 'testpassword');
expect(screen.getByLabelText(/Username/i)).toHaveValue('testuser');
expect(screen.getByLabelText(/Password/i)).toHaveValue('testpassword');
});
});
describe('login flow', () => {
it('submits login request with credentials', async () => {
const user = userEvent.setup();
let loginCalled = false;
server.use(
http.post('/api/v1/auth/login', async ({ request }) => {
loginCalled = true;
const body = await request.json() as { username: string; password: string };
if (body.username === 'validuser' && body.password === 'validpass') {
return HttpResponse.json({
access_token: 'test-token',
token_type: 'bearer',
user: {
id: 1,
username: 'validuser',
role: 'admin',
is_active: true,
created_at: new Date().toISOString(),
},
});
}
return HttpResponse.json(
{ detail: 'Incorrect username or password' },
{ status: 401 }
);
})
);
render();
await waitFor(() => {
expect(screen.getByLabelText(/Username/i)).toBeInTheDocument();
});
await user.type(screen.getByLabelText(/Username/i), 'validuser');
await user.type(screen.getByLabelText(/Password/i), 'validpass');
await user.click(screen.getByRole('button', { name: /Sign in/i }));
// Verify the login endpoint was called
await waitFor(() => {
expect(loginCalled).toBe(true);
});
});
it('shows loading state during login', async () => {
const user = userEvent.setup();
let resolveLogin: () => void;
const loginPromise = new Promise(resolve => { resolveLogin = resolve; });
// Slow login endpoint that we control
server.use(
http.post('/api/v1/auth/login', async () => {
await loginPromise;
return HttpResponse.json({
access_token: 'test-token',
token_type: 'bearer',
user: {
id: 1,
username: 'testuser',
role: 'admin',
is_active: true,
created_at: new Date().toISOString(),
},
});
})
);
render();
await waitFor(() => {
expect(screen.getByLabelText(/Username/i)).toBeInTheDocument();
});
await user.type(screen.getByLabelText(/Username/i), 'testuser');
await user.type(screen.getByLabelText(/Password/i), 'testpass');
await user.click(screen.getByRole('button', { name: /Sign in/i }));
// Check for loading state - button text should change to "Logging in..."
await waitFor(() => {
expect(screen.getByRole('button', { name: /Logging in/i })).toBeInTheDocument();
});
// Release the login request
resolveLogin!();
});
});
});