test(e2e): mission test — match dotted ACTIVE badge, idempotent across retries
CI / build + unit + e2e (pull_request) Successful in 2m25s
CI / build + unit + e2e (pull_request) Successful in 2m25s
CI run 358: the ACTIVE assertion used exact text but badge.tsx renders a '●' dot prefix for ACTIVE variants (text is '●ACTIVE'), so it could never match. And attempt 1's accept persisted an ACTIVE mission, leaving every ACCEPT button disabled on retry. Drop exact matching and, when a leftover active mission exists, assert the UI state and complete that one instead of re-accepting.
This commit is contained in:
+35
-19
@@ -290,30 +290,46 @@ test('MISSIONS tab accepts a contract and completing it grants the reward', asyn
|
||||
const board = (await (await page.request.get('/api/missions')).json()) as MissionDto[];
|
||||
expect(board.filter((m) => m.state === 'AVAILABLE').length).toBeGreaterThanOrEqual(3); // the board is always topped up
|
||||
|
||||
// Prefer a scan-completable contract — fresh boards are all EXPLORE/FIND (DELIVER only appears once systems are discovered).
|
||||
const picked = board.find((m) => m.state === 'AVAILABLE' && m.type !== 'DELIVER') ?? board.find((m) => m.state === 'AVAILABLE')!;
|
||||
const idx = board.indexOf(picked);
|
||||
await page.goto('/');
|
||||
await page.getByRole('tab', { name: 'MISSIONS' }).click();
|
||||
const acceptButtons = page.getByRole('button', { name: 'ACCEPT' });
|
||||
const panel = page.getByRole('tabpanel');
|
||||
|
||||
// Idempotent across retries sharing a DB: if a previous attempt already accepted a mission, it's still
|
||||
// ACTIVE and every ACCEPT button is disabled — complete that one instead of accepting again.
|
||||
const leftover = board.find((m) => m.state === 'ACTIVE');
|
||||
let picked: MissionDto;
|
||||
|
||||
if (leftover) {
|
||||
picked = leftover;
|
||||
// No exact match — ACTIVE badges render a "●" dot prefix (components/ui/badge.tsx), so their text is "●ACTIVE".
|
||||
await expect(panel.getByText('ACTIVE')).toBeVisible();
|
||||
for (const button of await acceptButtons.all()) {
|
||||
await expect(button).toBeDisabled();
|
||||
}
|
||||
} else {
|
||||
// Prefer a scan-completable contract — fresh boards are all EXPLORE/FIND (DELIVER only appears once systems are discovered).
|
||||
picked = board.find((m) => m.state === 'AVAILABLE' && m.type !== 'DELIVER') ?? board.find((m) => m.state === 'AVAILABLE')!;
|
||||
const idx = board.indexOf(picked);
|
||||
|
||||
// One ACCEPT button per card, in board order — the picked contract's is enabled (AVAILABLE, nothing active).
|
||||
await expect(acceptButtons.nth(idx)).toBeEnabled();
|
||||
await acceptButtons.nth(idx).click();
|
||||
|
||||
// UI flipped: exactly one ACTIVE badge on the board and the accepted card's button is now disabled.
|
||||
// No exact match — ACTIVE badges render a "●" dot prefix (components/ui/badge.tsx), so their text is "●ACTIVE".
|
||||
await expect(panel.getByText('ACTIVE')).toBeVisible();
|
||||
await expect(acceptButtons.nth(idx)).toBeDisabled();
|
||||
|
||||
// API confirms it: exactly one ACTIVE mission, the picked one (accepting cost a turn).
|
||||
const boardAfterAccept = (await (await page.request.get('/api/missions')).json()) as MissionDto[];
|
||||
expect(boardAfterAccept.filter((m) => m.state === 'ACTIVE')).toEqual([boardAfterAccept.find((m) => m.id === picked.id)!]);
|
||||
}
|
||||
|
||||
// Baseline of the reward kind in the hold — scans during completion may add more, so assert >=.
|
||||
const shipBefore = (await (await page.request.get('/api/ship')).json()) as ShipState;
|
||||
const baseline = totalQty(shipBefore.inventory, picked.rewardKind);
|
||||
|
||||
await page.goto('/');
|
||||
await page.getByRole('tab', { name: 'MISSIONS' }).click();
|
||||
// One ACCEPT button per card, in board order — the picked contract's is enabled (AVAILABLE, nothing active).
|
||||
const acceptButtons = page.getByRole('button', { name: 'ACCEPT' });
|
||||
await expect(acceptButtons.nth(idx)).toBeEnabled();
|
||||
await acceptButtons.nth(idx).click();
|
||||
|
||||
// UI flipped: exactly one ACTIVE badge on the board and the accepted card's button is now disabled.
|
||||
const panel = page.getByRole('tabpanel');
|
||||
await expect(panel.getByText('ACTIVE', { exact: true })).toBeVisible();
|
||||
await expect(acceptButtons.nth(idx)).toBeDisabled();
|
||||
|
||||
// API confirms it: exactly one ACTIVE mission, the picked one (accepting cost a turn).
|
||||
const boardAfterAccept = (await (await page.request.get('/api/missions')).json()) as MissionDto[];
|
||||
expect(boardAfterAccept.filter((m) => m.state === 'ACTIVE')).toEqual([boardAfterAccept.find((m) => m.id === picked.id)!]);
|
||||
|
||||
// Complete it. EXPLORE/FIND: scan until done, jumping when a system exhausts (parts drop on ~30% of
|
||||
// scans, so P(a FIND-part misses in 80 scans) ≈ 0.7^80 — negligible). DELIVER: follow the shortest
|
||||
// jump path to its target (edges only ever get added, so the precomputed path stays valid).
|
||||
|
||||
Reference in New Issue
Block a user