mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 15:44:21 +00:00
Refactor LoginPage tests
This commit is contained in:
@@ -43,9 +43,15 @@ const render = (ui: React.ReactElement) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('LoginPage', () => {
|
describe('LoginPage', () => {
|
||||||
beforeEach(() => {
|
let mockNotificationShow: ReturnType<typeof vi.fn>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|
||||||
|
// Get the mocked notification function
|
||||||
|
const { notifications } = await import('@mantine/notifications');
|
||||||
|
mockNotificationShow = vi.mocked(notifications.show);
|
||||||
|
|
||||||
// Setup default mock implementations
|
// Setup default mock implementations
|
||||||
mockGetCurrentUser.mockRejectedValue(new Error('No user session'));
|
mockGetCurrentUser.mockRejectedValue(new Error('No user session'));
|
||||||
mockApiLogin.mockResolvedValue({
|
mockApiLogin.mockResolvedValue({
|
||||||
@@ -67,96 +73,68 @@ describe('LoginPage', () => {
|
|||||||
screen.getByText('Please sign in to continue')
|
screen.getByText('Please sign in to continue')
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
|
|
||||||
// Check form fields
|
// Check form fields with correct attributes
|
||||||
expect(screen.getByTestId('email-input')).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId('password-input')).toBeInTheDocument();
|
|
||||||
|
|
||||||
// Check submit button
|
|
||||||
expect(screen.getByTestId('login-button')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders form fields with correct placeholders', () => {
|
|
||||||
render(<LoginPage />);
|
|
||||||
|
|
||||||
const emailInput = screen.getByTestId('email-input');
|
const emailInput = screen.getByTestId('email-input');
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
const passwordInput = screen.getByTestId('password-input');
|
||||||
|
|
||||||
expect(emailInput).toHaveAttribute('placeholder', 'your@email.com');
|
|
||||||
expect(passwordInput).toHaveAttribute('placeholder', 'Your password');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders required fields as required', () => {
|
|
||||||
render(<LoginPage />);
|
|
||||||
|
|
||||||
const emailInput = screen.getByTestId('email-input');
|
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
|
||||||
|
|
||||||
expect(emailInput).toBeRequired();
|
|
||||||
expect(passwordInput).toBeRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('submit button is not loading initially', () => {
|
|
||||||
render(<LoginPage />);
|
|
||||||
|
|
||||||
const submitButton = screen.getByTestId('login-button');
|
const submitButton = screen.getByTestId('login-button');
|
||||||
expect(submitButton).toHaveRole('button');
|
|
||||||
|
expect(emailInput).toBeInTheDocument();
|
||||||
|
expect(emailInput).toHaveAttribute('type', 'email');
|
||||||
|
expect(emailInput).toHaveAttribute('placeholder', 'your@email.com');
|
||||||
|
expect(emailInput).toBeRequired();
|
||||||
|
|
||||||
|
expect(passwordInput).toBeInTheDocument();
|
||||||
|
expect(passwordInput).toHaveAttribute('type', 'password');
|
||||||
|
expect(passwordInput).toHaveAttribute('placeholder', 'Your password');
|
||||||
|
expect(passwordInput).toBeRequired();
|
||||||
|
|
||||||
|
expect(submitButton).toBeInTheDocument();
|
||||||
|
expect(submitButton).toHaveAttribute('type', 'submit');
|
||||||
expect(submitButton).not.toHaveAttribute('data-loading', 'true');
|
expect(submitButton).not.toHaveAttribute('data-loading', 'true');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Form Interaction', () => {
|
describe('Form Interaction', () => {
|
||||||
it('updates email input value when typed', () => {
|
it('updates input values when user types', () => {
|
||||||
render(<LoginPage />);
|
render(<LoginPage />);
|
||||||
|
|
||||||
const emailInput = screen.getByTestId('email-input');
|
const emailInput = screen.getByTestId('email-input');
|
||||||
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
|
|
||||||
|
|
||||||
expect((emailInput as HTMLInputElement).value).toBe('test@example.com');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updates password input value when typed', () => {
|
|
||||||
render(<LoginPage />);
|
|
||||||
|
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
const passwordInput = screen.getByTestId('password-input');
|
||||||
|
|
||||||
|
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
|
||||||
fireEvent.change(passwordInput, { target: { value: 'password123' } });
|
fireEvent.change(passwordInput, { target: { value: 'password123' } });
|
||||||
|
|
||||||
|
expect((emailInput as HTMLInputElement).value).toBe('test@example.com');
|
||||||
expect((passwordInput as HTMLInputElement).value).toBe('password123');
|
expect((passwordInput as HTMLInputElement).value).toBe('password123');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clears form values when inputs are cleared', () => {
|
it('prevents form submission with empty fields due to HTML5 validation', () => {
|
||||||
render(<LoginPage />);
|
render(<LoginPage />);
|
||||||
|
|
||||||
const emailInput = screen.getByTestId('email-input');
|
const submitButton = screen.getByTestId('login-button');
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
// Set values
|
expect(mockApiLogin).not.toHaveBeenCalled();
|
||||||
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
|
|
||||||
fireEvent.change(passwordInput, { target: { value: 'password123' } });
|
|
||||||
|
|
||||||
// Clear values
|
|
||||||
fireEvent.change(emailInput, { target: { value: '' } });
|
|
||||||
fireEvent.change(passwordInput, { target: { value: '' } });
|
|
||||||
|
|
||||||
expect((emailInput as HTMLInputElement).value).toBe('');
|
|
||||||
expect((passwordInput as HTMLInputElement).value).toBe('');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Form Submission', () => {
|
describe('Form Submission', () => {
|
||||||
it('calls login function with correct credentials on form submit', async () => {
|
const fillAndSubmitForm = (email: string, password: string) => {
|
||||||
render(<LoginPage />);
|
|
||||||
|
|
||||||
const emailInput = screen.getByTestId('email-input');
|
const emailInput = screen.getByTestId('email-input');
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
const passwordInput = screen.getByTestId('password-input');
|
||||||
const submitButton = screen.getByTestId('login-button');
|
const submitButton = screen.getByTestId('login-button');
|
||||||
|
|
||||||
// Fill in the form
|
fireEvent.change(emailInput, { target: { value: email } });
|
||||||
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
|
fireEvent.change(passwordInput, { target: { value: password } });
|
||||||
fireEvent.change(passwordInput, { target: { value: 'password123' } });
|
|
||||||
|
|
||||||
// Submit the form
|
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
|
return { emailInput, passwordInput, submitButton };
|
||||||
|
};
|
||||||
|
|
||||||
|
it('calls login function with correct credentials on form submit', async () => {
|
||||||
|
render(<LoginPage />);
|
||||||
|
fillAndSubmitForm('test@example.com', 'password123');
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockApiLogin).toHaveBeenCalledWith(
|
expect(mockApiLogin).toHaveBeenCalledWith(
|
||||||
'test@example.com',
|
'test@example.com',
|
||||||
@@ -165,27 +143,8 @@ describe('LoginPage', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls login function when form is submitted via Enter key', async () => {
|
it('shows loading state during login and resets after completion', async () => {
|
||||||
render(<LoginPage />);
|
// Create a controlled promise for login
|
||||||
|
|
||||||
const form = screen.getByRole('form');
|
|
||||||
const emailInput = screen.getByTestId('email-input');
|
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
|
||||||
|
|
||||||
// Fill in the form
|
|
||||||
fireEvent.change(emailInput, { target: { value: 'user@test.com' } });
|
|
||||||
fireEvent.change(passwordInput, { target: { value: 'testpass' } });
|
|
||||||
|
|
||||||
// Submit via form submission (Enter key)
|
|
||||||
fireEvent.submit(form);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockApiLogin).toHaveBeenCalledWith('user@test.com', 'testpass');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows loading state during login process', async () => {
|
|
||||||
// Create a promise we can control
|
|
||||||
let resolveLogin: () => void;
|
let resolveLogin: () => void;
|
||||||
const loginPromise = new Promise<void>((resolve) => {
|
const loginPromise = new Promise<void>((resolve) => {
|
||||||
resolveLogin = resolve;
|
resolveLogin = resolve;
|
||||||
@@ -193,59 +152,59 @@ describe('LoginPage', () => {
|
|||||||
mockApiLogin.mockReturnValue(loginPromise);
|
mockApiLogin.mockReturnValue(loginPromise);
|
||||||
|
|
||||||
render(<LoginPage />);
|
render(<LoginPage />);
|
||||||
|
const { submitButton } = fillAndSubmitForm(
|
||||||
|
'test@example.com',
|
||||||
|
'password123'
|
||||||
|
);
|
||||||
|
|
||||||
const emailInput = screen.getByTestId('email-input');
|
// Check loading state appears
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
|
||||||
const submitButton = screen.getByTestId('login-button');
|
|
||||||
|
|
||||||
// Fill in the form
|
|
||||||
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
|
|
||||||
fireEvent.change(passwordInput, { target: { value: 'password123' } });
|
|
||||||
|
|
||||||
// Submit the form
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
// Check loading state
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(submitButton).toHaveAttribute('data-loading', 'true');
|
expect(submitButton).toHaveAttribute('data-loading', 'true');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Resolve the login
|
// Resolve the login and check loading state is removed
|
||||||
resolveLogin!();
|
resolveLogin!();
|
||||||
|
|
||||||
// Wait for loading to finish
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(submitButton).not.toHaveAttribute('data-loading', 'true');
|
expect(submitButton).not.toHaveAttribute('data-loading', 'true');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles login errors gracefully', async () => {
|
it('handles login success with notification', async () => {
|
||||||
|
render(<LoginPage />);
|
||||||
|
fillAndSubmitForm('test@example.com', 'password123');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockApiLogin).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify success notification is shown
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockNotificationShow).toHaveBeenCalledWith({
|
||||||
|
title: 'Success',
|
||||||
|
message: 'Logged in successfully',
|
||||||
|
color: 'green',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles login errors gracefully with notification', async () => {
|
||||||
const consoleErrorSpy = vi
|
const consoleErrorSpy = vi
|
||||||
.spyOn(console, 'error')
|
.spyOn(console, 'error')
|
||||||
.mockImplementation(() => {});
|
.mockImplementation(() => {});
|
||||||
mockApiLogin.mockRejectedValue(new Error('Login failed'));
|
const errorMessage = 'Invalid credentials';
|
||||||
|
mockApiLogin.mockRejectedValue(new Error(errorMessage));
|
||||||
|
|
||||||
render(<LoginPage />);
|
render(<LoginPage />);
|
||||||
|
const { submitButton } = fillAndSubmitForm(
|
||||||
const emailInput = screen.getByTestId('email-input');
|
'test@example.com',
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
'wrongpassword'
|
||||||
const submitButton = screen.getByTestId('login-button');
|
);
|
||||||
|
|
||||||
// Fill in the form
|
|
||||||
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
|
|
||||||
fireEvent.change(passwordInput, { target: { value: 'wrongpassword' } });
|
|
||||||
|
|
||||||
// Submit the form
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockApiLogin).toHaveBeenCalledWith(
|
expect(mockApiLogin).toHaveBeenCalled();
|
||||||
'test@example.com',
|
|
||||||
'wrongpassword'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait for error handling
|
// Verify error is logged
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||||
'Login failed:',
|
'Login failed:',
|
||||||
@@ -253,7 +212,16 @@ describe('LoginPage', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Loading state should be reset
|
// Verify error notification is shown
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockNotificationShow).toHaveBeenCalledWith({
|
||||||
|
title: 'Error',
|
||||||
|
message: errorMessage,
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify loading state is reset
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(submitButton).not.toHaveAttribute('data-loading', 'true');
|
expect(submitButton).not.toHaveAttribute('data-loading', 'true');
|
||||||
});
|
});
|
||||||
@@ -261,65 +229,13 @@ describe('LoginPage', () => {
|
|||||||
consoleErrorSpy.mockRestore();
|
consoleErrorSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prevents form submission with empty fields', () => {
|
it('handles special characters in credentials', async () => {
|
||||||
render(<LoginPage />);
|
render(<LoginPage />);
|
||||||
|
|
||||||
const submitButton = screen.getByTestId('login-button');
|
|
||||||
|
|
||||||
// Try to submit without filling fields
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
// Login should not be called due to HTML5 validation
|
|
||||||
expect(mockApiLogin).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('prevents form submission with only email filled', () => {
|
|
||||||
render(<LoginPage />);
|
|
||||||
|
|
||||||
const emailInput = screen.getByTestId('email-input');
|
|
||||||
const submitButton = screen.getByTestId('login-button');
|
|
||||||
|
|
||||||
// Fill only email
|
|
||||||
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
|
|
||||||
|
|
||||||
// Try to submit
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
// Login should not be called due to HTML5 validation
|
|
||||||
expect(mockApiLogin).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('prevents form submission with only password filled', () => {
|
|
||||||
render(<LoginPage />);
|
|
||||||
|
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
|
||||||
const submitButton = screen.getByTestId('login-button');
|
|
||||||
|
|
||||||
// Fill only password
|
|
||||||
fireEvent.change(passwordInput, { target: { value: 'password123' } });
|
|
||||||
|
|
||||||
// Try to submit
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
// Login should not be called due to HTML5 validation
|
|
||||||
expect(mockApiLogin).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Edge Cases', () => {
|
|
||||||
it('handles special characters in email and password', async () => {
|
|
||||||
render(<LoginPage />);
|
|
||||||
|
|
||||||
const emailInput = screen.getByTestId('email-input');
|
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
|
||||||
const submitButton = screen.getByTestId('login-button');
|
|
||||||
|
|
||||||
const specialEmail = 'user+test@example-domain.com';
|
const specialEmail = 'user+test@example-domain.com';
|
||||||
const specialPassword = 'P@ssw0rd!#$%';
|
const specialPassword = 'P@ssw0rd!#$%';
|
||||||
|
|
||||||
fireEvent.change(emailInput, { target: { value: specialEmail } });
|
fillAndSubmitForm(specialEmail, specialPassword);
|
||||||
fireEvent.change(passwordInput, { target: { value: specialPassword } });
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockApiLogin).toHaveBeenCalledWith(
|
expect(mockApiLogin).toHaveBeenCalledWith(
|
||||||
@@ -328,75 +244,5 @@ describe('LoginPage', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles very long email and password values', async () => {
|
|
||||||
render(<LoginPage />);
|
|
||||||
|
|
||||||
const emailInput = screen.getByTestId('email-input');
|
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
|
||||||
const submitButton = screen.getByTestId('login-button');
|
|
||||||
|
|
||||||
const longEmail = 'a'.repeat(100) + '@example.com';
|
|
||||||
const longPassword = 'p'.repeat(200);
|
|
||||||
|
|
||||||
fireEvent.change(emailInput, { target: { value: longEmail } });
|
|
||||||
fireEvent.change(passwordInput, { target: { value: longPassword } });
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockApiLogin).toHaveBeenCalledWith(longEmail, longPassword);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('resets loading state after successful login', async () => {
|
|
||||||
render(<LoginPage />);
|
|
||||||
|
|
||||||
const emailInput = screen.getByTestId('email-input');
|
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
|
||||||
const submitButton = screen.getByTestId('login-button');
|
|
||||||
|
|
||||||
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
|
|
||||||
fireEvent.change(passwordInput, { target: { value: 'password123' } });
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockApiLogin).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(submitButton).not.toHaveAttribute('data-loading', 'true');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Accessibility', () => {
|
|
||||||
it('has proper form structure with labels', () => {
|
|
||||||
render(<LoginPage />);
|
|
||||||
|
|
||||||
const emailInput = screen.getByTestId('email-input');
|
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
|
||||||
|
|
||||||
expect(emailInput).toBeInTheDocument();
|
|
||||||
expect(passwordInput).toBeInTheDocument();
|
|
||||||
expect(emailInput.tagName).toBe('INPUT');
|
|
||||||
expect(passwordInput.tagName).toBe('INPUT');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has proper input types', () => {
|
|
||||||
render(<LoginPage />);
|
|
||||||
|
|
||||||
const emailInput = screen.getByTestId('email-input');
|
|
||||||
const passwordInput = screen.getByTestId('password-input');
|
|
||||||
|
|
||||||
expect(emailInput).toHaveAttribute('type', 'email');
|
|
||||||
expect(passwordInput).toHaveAttribute('type', 'password');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('submit button has proper type', () => {
|
|
||||||
render(<LoginPage />);
|
|
||||||
|
|
||||||
const submitButton = screen.getByTestId('login-button');
|
|
||||||
expect(submitButton).toHaveAttribute('type', 'submit');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user