import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import axios from 'axios';
import App from '../App';
// Mock axios
jest.mock('axios');
const mockedAxios = axios;
// Mock localStorage
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn(),
};
global.localStorage = localStorageMock;
describe('Login Functionality', () => {
beforeEach(() => {
jest.clearAllMocks();
localStorageMock.getItem.mockReturnValue(null);
// Mock axios to handle different endpoints
mockedAxios.get.mockImplementation((url) => {
if (url === '/api/players/names') {
return Promise.resolve({
data: [
{ name: 'gm' },
{ name: 'anders' },
{ name: 'phillip' }
]
});
}
if (url === '/api/missions') {
return Promise.resolve({ data: [] });
}
if (url === '/api/missions/active/current') {
return Promise.resolve({ data: null });
}
if (url === '/api/missions/active/player') {
return Promise.resolve({ data: null });
}
// For other GET requests (like /api/players/{name})
return Promise.resolve({
data: {
name: 'gm',
tabInfo: {
rp: 100,
xp: 1000,
xpSpent: 200,
renown: 'Respected',
charName: 'Game Master'
}
}
});
});
// Default POST mock - will be overridden per test with mockResolvedValueOnce
mockedAxios.post.mockImplementation((url, data) => {
if (url === '/api/players/login') {
return Promise.resolve({
data: {
success: true,
sessionId: 'default_session_id',
player: { name: data.name }
},
status: 200
});
}
if (url === '/api/sessions/validate') {
const sessionId = data?.sessionId || '';
const playerName = sessionId.includes('anders') ? 'anders' : sessionId.includes('gm') ? 'gm' : '';
return Promise.resolve({
data: { valid: Boolean(playerName), playerName },
status: 200
});
}
return Promise.resolve({ status: 200 });
});
});
test('login with GM account', async () => {
mockedAxios.post.mockResolvedValueOnce({
data: {
success: true,
sessionId: 'session_gm_12345',
player: { name: 'gm' },
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString()
},
status: 200
});
render();
// Wait for the login form to appear
await waitFor(() => {
expect(screen.getByPlaceholderText(/username/i)).toBeInTheDocument();
});
const nameInput = screen.getByPlaceholderText(/username/i);
const passwordInput = screen.getByPlaceholderText(/password/i);
const loginButton = screen.getByRole('button', { name: /login/i });
// Fill in login form with GM credentials
fireEvent.change(nameInput, { target: { value: 'gm' } });
fireEvent.change(passwordInput, { target: { value: 'bongo' } });
// Click login button
fireEvent.click(loginButton);
// Verify login was called with correct parameters
await waitFor(() => {
expect(mockedAxios.post).toHaveBeenCalledWith(
'/api/players/login',
{
name: 'gm',
password: 'bongo'
}
);
});
// Verify success message appears
await waitFor(() => {
expect(screen.getByText(/login successful/i)).toBeInTheDocument();
}, { timeout: 3000 });
});
test('login with invalid password fails', async () => {
mockedAxios.post.mockImplementation((url) => {
if (url === '/api/players/login') {
return Promise.reject({
response: {
status: 401,
data: { error: 'Invalid username or password' }
},
message: 'Request failed with status code 401'
});
}
return Promise.resolve({ data: {}, status: 200 });
});
render();
await waitFor(() => {
expect(screen.getByPlaceholderText(/username/i)).toBeInTheDocument();
});
const nameInput = screen.getByPlaceholderText(/username/i);
const passwordInput = screen.getByPlaceholderText(/password/i);
const loginButton = screen.getByRole('button', { name: /login/i });
fireEvent.change(nameInput, { target: { value: 'gm' } });
fireEvent.change(passwordInput, { target: { value: 'wrong_password' } });
fireEvent.click(loginButton);
await waitFor(() => {
expect(screen.getByText(/login failed/i)).toBeInTheDocument();
});
});
test('login with non-existent player fails', async () => {
mockedAxios.post.mockImplementation((url) => {
if (url === '/api/players/login') {
return Promise.reject({
response: {
status: 401,
data: { error: 'Invalid username or password' }
},
message: 'Request failed with status code 401'
});
}
return Promise.resolve({ data: {}, status: 200 });
});
render();
await waitFor(() => {
expect(screen.getByPlaceholderText(/username/i)).toBeInTheDocument();
});
const nameInput = screen.getByPlaceholderText(/username/i);
const passwordInput = screen.getByPlaceholderText(/password/i);
const loginButton = screen.getByRole('button', { name: /login/i });
fireEvent.change(nameInput, { target: { value: 'nonexistent' } });
fireEvent.change(passwordInput, { target: { value: '1234' } });
fireEvent.click(loginButton);
await waitFor(() => {
expect(screen.getByText(/login failed/i)).toBeInTheDocument();
});
});
test('login with player account', async () => {
mockedAxios.post.mockResolvedValueOnce({
data: {
success: true,
sessionId: 'session_anders_12345',
player: { name: 'anders' },
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString()
},
status: 200
});
render();
// Wait for the login form to appear
await waitFor(() => {
expect(screen.getByPlaceholderText(/username/i)).toBeInTheDocument();
});
// Find login inputs
const nameInput = screen.getByPlaceholderText(/username/i);
const passwordInput = screen.getByPlaceholderText(/password/i);
const loginButton = screen.getByRole('button', { name: /login/i });
// Fill in login form with player credentials
fireEvent.change(nameInput, { target: { value: 'anders' } });
fireEvent.change(passwordInput, { target: { value: '1234' } });
// Click login button
fireEvent.click(loginButton);
// Verify login was called with correct parameters
await waitFor(() => {
expect(mockedAxios.post).toHaveBeenCalledWith(
'/api/players/login',
{
name: 'anders',
password: '1234'
}
);
});
// Verify success message appears
await waitFor(() => {
expect(screen.getByText(/login successful/i)).toBeInTheDocument();
}, { timeout: 3000 });
await waitFor(() => {
expect(screen.getAllByText(/active mission/i).length).toBeGreaterThan(0);
});
expect(screen.queryByText(/mission is only accessible to game masters/i)).not.toBeInTheDocument();
});
});