tests: consolidate into tests/ dirs, fix import paths
Move all test files out of source dirs into per-workspace tests/: - shared/tests/ (3 unit test files) - server/tests/ (1 integration test) - src/tests/ (8 characterization + scenario tests + testHelpers) Fix all relative import paths (App, storage, __mocks__, testHelpers). Fix jest.config testMatch globs in shared/ and server/ (rootDir + <rootDir>/tests pattern). Delete scripts/repro-pause-bug.js (debug scratch, superseded by turn.pause-add.test.js). Keep scripts/replay-combat.js + scripts/audit-rotation.js as manual demo/exploratory tools (NOT unit tests, not deterministic). No logic changes. All green: shared 49 + 1 validated RED, server 23, FE 62. Scenario test unchanged (240s timeout, pre-existing slow).
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
// App.characterization.test.js
|
||||
// Characterize App -> Firebase calls. Lock path + payload shape per action.
|
||||
// Mock SDK, render AdminView, fire action, assert recorded calls.
|
||||
// Purpose: refactor (path-shape rewrite) must not change these calls.
|
||||
|
||||
import React from 'react';
|
||||
import { screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { getCalls, MOCK_DB } from '../__mocks__/firebase/_mock-db';
|
||||
import { renderApp, createCampaignViaUI, selectCampaignByName } from './testHelpers';
|
||||
|
||||
function findCall(fn, pathSub) {
|
||||
return getCalls().find(c => c.fn === fn && (pathSub ? c.path.includes(pathSub) : true));
|
||||
}
|
||||
function findCalls(fn, pathSub) {
|
||||
return getCalls().filter(c => c.fn === fn && (pathSub ? c.path.includes(pathSub) : true));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CAMPAIGN GROUP
|
||||
// ============================================================================
|
||||
|
||||
describe('Campaign -> Firebase', () => {
|
||||
test('createCampaign: setDoc with campaign path + payload', async () => {
|
||||
await renderApp();
|
||||
const id = await createCampaignViaUI('Alpha');
|
||||
const call = findCall('setDoc', '/campaigns/');
|
||||
expect(call.path).toMatch(/campaigns\/.+$/);
|
||||
expect(call.data).toMatchObject({
|
||||
name: 'Alpha',
|
||||
playerDisplayBackgroundUrl: '',
|
||||
players: [],
|
||||
});
|
||||
expect(call.data).toHaveProperty('ownerId');
|
||||
expect(call.data).toHaveProperty('createdAt');
|
||||
});
|
||||
|
||||
test('createCampaign: path includes APP_ID namespace', async () => {
|
||||
await renderApp();
|
||||
await createCampaignViaUI('NS Test');
|
||||
const call = findCall('setDoc', '/campaigns/');
|
||||
expect(call.path).toContain('artifacts/');
|
||||
expect(call.path).toContain('/public/data/');
|
||||
});
|
||||
|
||||
test('createCampaign: optional background URL stored', async () => {
|
||||
await renderApp();
|
||||
fireEvent.click(screen.getByRole('button', { name: /Create Campaign/i }));
|
||||
await waitFor(() => screen.getByLabelText(/Campaign Name/i));
|
||||
fireEvent.change(screen.getByLabelText(/Campaign Name/i), { target: { value: 'With BG' } });
|
||||
fireEvent.change(screen.getByLabelText(/Background URL/i), { target: { value: 'https://img.test/bg.png' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: /^Create$/i }));
|
||||
await waitFor(() => findCall('setDoc', '/campaigns/'));
|
||||
const call = findCall('setDoc', '/campaigns/');
|
||||
expect(call.data.playerDisplayBackgroundUrl).toBe('https://img.test/bg.png');
|
||||
});
|
||||
|
||||
test('addCharacter: updateDoc on campaign doc, players array grows', async () => {
|
||||
await renderApp();
|
||||
const cid = await createCampaignViaUI('Roster');
|
||||
await selectCampaignByName('Roster');
|
||||
|
||||
// CharacterManager form
|
||||
fireEvent.change(screen.getByPlaceholderText('Character name'), { target: { value: 'Brog' } });
|
||||
fireEvent.change(screen.getByLabelText(/Default HP/i), { target: { value: '25' } });
|
||||
fireEvent.change(screen.getByLabelText(/Init Mod/i), { target: { value: '3' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: /Add Character/i }));
|
||||
|
||||
await waitFor(() => findCall('updateDoc', '/campaigns/'));
|
||||
const call = findCall('updateDoc', `/campaigns/${cid}`);
|
||||
expect(call.data.players).toHaveLength(1);
|
||||
expect(call.data.players[0]).toMatchObject({
|
||||
name: 'Brog',
|
||||
defaultMaxHp: 25,
|
||||
defaultInitMod: 3,
|
||||
});
|
||||
expect(call.data.players[0]).toHaveProperty('id');
|
||||
});
|
||||
|
||||
test('updateCharacter: updateDoc with updated players array', async () => {
|
||||
await renderApp();
|
||||
const cid = await createCampaignViaUI('EditRoster');
|
||||
await selectCampaignByName('EditRoster');
|
||||
|
||||
// add one first
|
||||
fireEvent.change(screen.getByPlaceholderText('Character name'), { target: { value: 'Old Name' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: /Add Character/i }));
|
||||
await waitFor(() => findCall('updateDoc', '/campaigns/'));
|
||||
|
||||
// click edit
|
||||
const editBtn = await screen.findByRole('button', { name: /Edit character/i });
|
||||
fireEvent.click(editBtn);
|
||||
await waitFor(() => screen.getByDisplayValue('Old Name'));
|
||||
fireEvent.change(screen.getByDisplayValue('Old Name'), { target: { value: 'New Name' } });
|
||||
// Save button is icon-only (no text); submit its form.
|
||||
const form = screen.getByDisplayValue('New Name').closest('form');
|
||||
fireEvent.submit(form);
|
||||
|
||||
await waitFor(() => {
|
||||
const calls = findCalls('updateDoc', `/campaigns/${cid}`);
|
||||
const last = calls[calls.length - 1];
|
||||
expect(last.data.players[0].name).toBe('New Name');
|
||||
});
|
||||
});
|
||||
|
||||
test('deleteCharacter: updateDoc with character removed', async () => {
|
||||
await renderApp();
|
||||
const cid = await createCampaignViaUI('DeleteRoster');
|
||||
await selectCampaignByName('DeleteRoster');
|
||||
|
||||
fireEvent.change(screen.getByPlaceholderText('Character name'), { target: { value: 'Gone' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: /Add Character/i }));
|
||||
await waitFor(() => findCall('updateDoc', '/campaigns/'));
|
||||
|
||||
const delBtn = await screen.findByRole('button', { name: /Delete character/i });
|
||||
fireEvent.click(delBtn);
|
||||
// confirmation modal
|
||||
fireEvent.click(screen.getByRole('button', { name: /Confirm/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
const calls = findCalls('updateDoc', `/campaigns/${cid}`);
|
||||
const last = calls[calls.length - 1];
|
||||
expect(last.data.players).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('deleteCampaign: deletes encounters batch + campaign doc + activeDisplay null', async () => {
|
||||
await renderApp();
|
||||
const cid = await createCampaignViaUI('Doomed');
|
||||
await selectCampaignByName('Doomed');
|
||||
|
||||
// campaign card delete button has no aria-label; find trash by text via grid
|
||||
const allDeletes = screen.getAllByText(/Delete/i);
|
||||
// campaign card Delete is in card grid, last one rendered
|
||||
fireEvent.click(allDeletes[allDeletes.length - 1]);
|
||||
fireEvent.click(await screen.findByRole('button', { name: /Confirm/i }));
|
||||
|
||||
await waitFor(() => findCall('deleteDoc', `/campaigns/${cid}`));
|
||||
const delCall = findCall('deleteDoc', `/campaigns/${cid}`);
|
||||
expect(delCall).toBeDefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user