test(e2e): pin NEXT TURN full-width and no-overlap with the active-tasks strip (#52)

This commit is contained in:
2026-10-06 12:51:11 +00:00
parent bb44530649
commit ca590c7b53
+44
View File
@@ -1046,3 +1046,47 @@ test('NEXT TURN with an empty queue is a free skip — +1 turn, the strip stays
expect(after.turn).toBe(T + 1);
expect(after.tasks.length).toBe(0);
});
test('NEXT TURN button is full-width in the right pane and not covered by the task strip (#52)', async ({ page }) => {
await drainQueue(page);
const world = (await (await page.request.get('/api/world')).json()) as WorldStateQ;
const currentId = world.systems.find((s) => s.current)!.id;
const edge = world.edges.find(([a, b]) => a === currentId || b === currentId)!;
const targetId = edge[0] === currentId ? edge[1] : edge[0];
const { crew } = (await (await page.request.get('/api/crew')).json()) as { crew: CrewDto[]; tasks: QueueTaskDto[] };
expect(crew.length).toBeGreaterThanOrEqual(2);
// Queue a one-person JUMP and deliberately do not tick it (the #44 jump scenarios' pattern,
// the in-transit 409 one leaves it that way on purpose): the strip renders from the shell's
// system snapshot — the same queue — so a non-empty queue keeps ACTIVE TASKS visible all test.
const res = await page.request.post('/api/jump', { data: { systemId: targetId, leadCrewId: crew[0].id, helperCrewIds: [] } });
expect(res.status()).toBe(200);
const queued = (await res.json()) as WorldStateQ;
expect(queued.tasks.find((t) => t.type === 'JUMP')!.status).toBe('ACTIVE');
await page.goto('/');
await expect(page.getByText('ACTIVE TASKS', { exact: true })).toBeVisible(); // the strip is up
// Two rects intersect when their x AND y ranges overlap (touching edges do not count).
type Box = { x: number; y: number; width: number; height: number };
const intersects = (a: Box, b: Box) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
const buttonBox = await page.getByRole('button', { name: 'NEXT TURN' }).boundingBox();
expect(buttonBox).not.toBeNull();
// The strip's root div is the parent of its 'ACTIVE TASKS' label (components/task-strip.tsx).
const stripBox = await page.getByText('ACTIVE TASKS', { exact: true }).locator('xpath=..').boundingBox();
expect(stripBox).not.toBeNull();
// The CARGO HOLD panel: the Panel root is the h3 heading's grandparent div (components/ui/panel.tsx).
const panelBox = await page.getByRole('heading', { name: 'CARGO HOLD // INVENTORY' }).locator('xpath=ancestor::div[2]').boundingBox();
expect(panelBox).not.toBeNull();
// (a) The button spans the right pane's content width — >= 280 on its own, and within ±8px of
// the CARGO HOLD panel, a sibling full-width element in the same pane column.
expect(buttonBox!.width).toBeGreaterThanOrEqual(280);
expect(Math.abs(buttonBox!.width - panelBox!.width)).toBeLessThanOrEqual(8);
// (b) The button's box does not intersect the active-tasks strip's box.
expect(intersects(buttonBox!, stripBox!)).toBe(false);
await drainQueue(page); // leave the queue clean for the next scenario
});