395 lines
12 KiB
JavaScript
395 lines
12 KiB
JavaScript
import React from 'react';
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
import '@testing-library/jest-dom';
|
|
import axios from 'axios';
|
|
import PlayerManagement from '../components/PlayerManagement';
|
|
|
|
// Mock axios
|
|
jest.mock('axios');
|
|
const mockedAxios = axios;
|
|
|
|
// Mock window.confirm
|
|
const mockConfirm = jest.fn();
|
|
Object.defineProperty(window, 'confirm', {
|
|
value: mockConfirm,
|
|
writable: true,
|
|
});
|
|
|
|
describe('PlayerManagement Component', () => {
|
|
const mockSessionId = 'test-session-123';
|
|
const mockAuthedPlayer = 'gm';
|
|
|
|
// Mock player data
|
|
const mockPlayers = [
|
|
{
|
|
name: 'TestPlayer',
|
|
tabInfo: {
|
|
rp: 50,
|
|
xp: 1000,
|
|
xpSpent: 200,
|
|
renown: 'Respected',
|
|
charName: 'Brother Testicus'
|
|
}
|
|
}
|
|
];
|
|
|
|
beforeEach(() => {
|
|
// Reset mocks
|
|
jest.clearAllMocks();
|
|
mockConfirm.mockReturnValue(true);
|
|
|
|
// Mock successful API responses
|
|
mockedAxios.get.mockResolvedValue({ data: mockPlayers });
|
|
mockedAxios.post.mockResolvedValue({ status: 200, data: { success: true } });
|
|
mockedAxios.delete.mockResolvedValue({ status: 200, data: { success: true } });
|
|
});
|
|
|
|
test('renders access denied for non-GM users', async () => {
|
|
render(<PlayerManagement authedPlayer="player1" sessionId={mockSessionId} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Access Denied')).toBeInTheDocument();
|
|
});
|
|
expect(screen.getByText(/Player Management is only accessible to Game Masters/)).toBeInTheDocument();
|
|
});
|
|
|
|
test('renders player management interface for GM', async () => {
|
|
render(<PlayerManagement authedPlayer={mockAuthedPlayer} sessionId={mockSessionId} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Player Management')).toBeInTheDocument();
|
|
});
|
|
expect(screen.getByText(/Manage player accounts, requisition points/)).toBeInTheDocument();
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('TestPlayer')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
test('fetches and displays players on mount', async () => {
|
|
render(<PlayerManagement authedPlayer={mockAuthedPlayer} sessionId={mockSessionId} />);
|
|
|
|
// Wait for players to load
|
|
await waitFor(() => {
|
|
expect(screen.getByText('TestPlayer')).toBeInTheDocument();
|
|
});
|
|
|
|
// Check that all expected data is rendered somewhere in the document
|
|
const container = screen.getByText(/Player Management/);
|
|
const documentText = container.closest('body').textContent;
|
|
|
|
expect(documentText).toContain('TestPlayer');
|
|
expect(documentText).toContain('1000');
|
|
expect(documentText).toContain('200');
|
|
expect(documentText).toContain('800');
|
|
});
|
|
|
|
test('creates new player', async () => {
|
|
render(<PlayerManagement authedPlayer={mockAuthedPlayer} sessionId={mockSessionId} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('TestPlayer')).toBeInTheDocument();
|
|
});
|
|
|
|
// Fill in new player form
|
|
const nameInput = screen.getByPlaceholderText('Player Name');
|
|
const rpInput = screen.getByPlaceholderText('Requisition Points');
|
|
const pwInput = screen.getByPlaceholderText('Password (use environment default)');
|
|
const addButton = screen.getByText('Add Player');
|
|
|
|
fireEvent.change(nameInput, { target: { value: 'NewPlayer' } });
|
|
fireEvent.change(rpInput, { target: { value: '75' } });
|
|
fireEvent.change(pwInput, { target: { value: 'testpass' } });
|
|
fireEvent.click(addButton);
|
|
|
|
await waitFor(() => {
|
|
expect(mockedAxios.post).toHaveBeenCalledWith(
|
|
'/api/players/gm/add-or-update',
|
|
{
|
|
name: 'NewPlayer',
|
|
requisitionPoints: 75,
|
|
password: 'testpass'
|
|
},
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-session-id': mockSessionId,
|
|
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
|
|
}
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
test('sets requisition points for player', async () => {
|
|
render(<PlayerManagement authedPlayer={mockAuthedPlayer} sessionId={mockSessionId} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('TestPlayer')).toBeInTheDocument();
|
|
});
|
|
|
|
const setButtons = screen.getAllByText('Set');
|
|
fireEvent.click(setButtons[0]);
|
|
|
|
await waitFor(() => {
|
|
expect(mockedAxios.post).toHaveBeenCalledWith(
|
|
'/api/players/gm/set-rp',
|
|
{
|
|
playerName: 'TestPlayer',
|
|
requisitionPoints: 50
|
|
},
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-session-id': mockSessionId,
|
|
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
|
|
}
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
test('sets experience points for player', async () => {
|
|
render(<PlayerManagement authedPlayer={mockAuthedPlayer} sessionId={mockSessionId} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('TestPlayer')).toBeInTheDocument();
|
|
});
|
|
|
|
const setButtons = screen.getAllByText('Set');
|
|
fireEvent.click(setButtons[1]);
|
|
|
|
await waitFor(() => {
|
|
expect(mockedAxios.post).toHaveBeenCalledWith(
|
|
'/api/players/gm/set-xp',
|
|
{
|
|
playerName: 'TestPlayer',
|
|
xp: 1000
|
|
},
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-session-id': mockSessionId,
|
|
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
|
|
}
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
test('sets XP spent for player', async () => {
|
|
render(<PlayerManagement authedPlayer={mockAuthedPlayer} sessionId={mockSessionId} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('TestPlayer')).toBeInTheDocument();
|
|
});
|
|
|
|
const setButtons = screen.getAllByText('Set');
|
|
fireEvent.click(setButtons[2]);
|
|
|
|
await waitFor(() => {
|
|
expect(mockedAxios.post).toHaveBeenCalledWith(
|
|
'/api/players/gm/set-xp-spent',
|
|
{
|
|
playerName: 'TestPlayer',
|
|
xpSpent: 200
|
|
},
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-session-id': mockSessionId,
|
|
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
|
|
}
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
test('sets renown for player', async () => {
|
|
render(<PlayerManagement authedPlayer={mockAuthedPlayer} sessionId={mockSessionId} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('TestPlayer')).toBeInTheDocument();
|
|
});
|
|
|
|
const setButtons = screen.getAllByText('Set');
|
|
fireEvent.click(setButtons[3]);
|
|
|
|
await waitFor(() => {
|
|
expect(mockedAxios.post).toHaveBeenCalledWith(
|
|
'/api/players/gm/set-renown',
|
|
{
|
|
playerName: 'TestPlayer',
|
|
renown: 'Respected'
|
|
},
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-session-id': mockSessionId,
|
|
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
|
|
}
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
test('resets password for player', async () => {
|
|
render(<PlayerManagement authedPlayer={mockAuthedPlayer} sessionId={mockSessionId} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('TestPlayer')).toBeInTheDocument();
|
|
});
|
|
|
|
const passwordInput = screen.getByPlaceholderText('New password (use environment default)');
|
|
const resetButton = screen.getByText('Reset');
|
|
|
|
fireEvent.change(passwordInput, { target: { value: 'newpass123' } });
|
|
fireEvent.click(resetButton);
|
|
|
|
await waitFor(() => {
|
|
expect(mockedAxios.post).toHaveBeenCalledWith(
|
|
'/api/players/gm/reset-password',
|
|
{
|
|
playerName: 'TestPlayer',
|
|
newPassword: 'newpass123'
|
|
},
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-session-id': mockSessionId,
|
|
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
|
|
}
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
test('deletes player with confirmation', async () => {
|
|
render(<PlayerManagement authedPlayer={mockAuthedPlayer} sessionId={mockSessionId} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('TestPlayer')).toBeInTheDocument();
|
|
});
|
|
|
|
const deleteButton = screen.getByText('Delete Player');
|
|
fireEvent.click(deleteButton);
|
|
|
|
expect(mockConfirm).toHaveBeenCalledWith(
|
|
'Are you sure you want to delete player TestPlayer? This cannot be undone.'
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(mockedAxios.delete).toHaveBeenCalledWith(
|
|
'/api/players/gm/delete/TestPlayer',
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-session-id': mockSessionId,
|
|
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
|
|
}
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
test('bulk gives XP to all players', async () => {
|
|
render(<PlayerManagement authedPlayer={mockAuthedPlayer} sessionId={mockSessionId} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('TestPlayer')).toBeInTheDocument();
|
|
});
|
|
|
|
const bulkXPInputs = screen.getAllByDisplayValue('100');
|
|
const giveXPButton = screen.getByText('Give XP to All');
|
|
|
|
fireEvent.change(bulkXPInputs[0], { target: { value: '250' } });
|
|
fireEvent.click(giveXPButton);
|
|
|
|
expect(mockConfirm).toHaveBeenCalledWith('Give 250 XP to all 1 players?');
|
|
|
|
await waitFor(() => {
|
|
expect(mockedAxios.post).toHaveBeenCalledWith(
|
|
'/api/players/gm/set-xp',
|
|
{
|
|
playerName: 'TestPlayer',
|
|
xp: 1250
|
|
},
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-session-id': mockSessionId,
|
|
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
|
|
}
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
test('bulk gives RP to all players', async () => {
|
|
render(<PlayerManagement authedPlayer={mockAuthedPlayer} sessionId={mockSessionId} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('TestPlayer')).toBeInTheDocument();
|
|
});
|
|
|
|
const bulkRPInputs = screen.getAllByDisplayValue('10');
|
|
const giveRPButton = screen.getByText('Give RP to All');
|
|
|
|
fireEvent.change(bulkRPInputs[0], { target: { value: '25' } });
|
|
fireEvent.click(giveRPButton);
|
|
|
|
expect(mockConfirm).toHaveBeenCalledWith('Give 25 RP to all 1 players?');
|
|
|
|
await waitFor(() => {
|
|
expect(mockedAxios.post).toHaveBeenCalledWith(
|
|
'/api/players/gm/set-rp',
|
|
{
|
|
playerName: 'TestPlayer',
|
|
requisitionPoints: 75
|
|
},
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-session-id': mockSessionId,
|
|
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
|
|
}
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
test('handles API errors gracefully', async () => {
|
|
mockedAxios.post.mockRejectedValueOnce({
|
|
response: { data: { message: 'Server error' } }
|
|
});
|
|
|
|
render(<PlayerManagement authedPlayer={mockAuthedPlayer} sessionId={mockSessionId} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('TestPlayer')).toBeInTheDocument();
|
|
});
|
|
|
|
const setButtons = screen.getAllByText('Set');
|
|
fireEvent.click(setButtons[0]);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Failed to set RP for TestPlayer: Server error/)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
test('refreshes players list', async () => {
|
|
render(<PlayerManagement authedPlayer={mockAuthedPlayer} sessionId={mockSessionId} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('TestPlayer')).toBeInTheDocument();
|
|
});
|
|
|
|
const refreshButton = screen.getByText('Refresh Players');
|
|
fireEvent.click(refreshButton);
|
|
|
|
await waitFor(() => {
|
|
expect(mockedAxios.get).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|
|
});
|