e2e: research unlocks the gated WEAPONS build via UI + tech state survives restart
This commit is contained in:
+55
-1
@@ -148,7 +148,9 @@ test('SHIP SYSTEMS tab performs an affordable action and reflects it in the card
|
|||||||
// a build costs three parts. Jumping keeps it going if a whole system is exhausted.
|
// a build costs three parts. Jumping keeps it going if a whole system is exhausted.
|
||||||
let ship = (await (await page.request.get('/api/ship')).json()) as ShipState;
|
let ship = (await (await page.request.get('/api/ship')).json()) as ShipState;
|
||||||
const upgradeTarget = () => ship.systems.find((s) => s.built && s.level < 5 && totalQty(ship.inventory, 'resource') >= s.level);
|
const upgradeTarget = () => ship.systems.find((s) => s.built && s.level < 5 && totalQty(ship.inventory, 'resource') >= s.level);
|
||||||
const buildTarget = () => (totalQty(ship.inventory, 'part') >= 3 ? ship.systems.find((s) => !s.built) : undefined);
|
// WEAPONS is gated behind the unresearched WEAPONS SYSTEMS tech — its build path is covered by
|
||||||
|
// the research test below, so this one only targets ungated types.
|
||||||
|
const buildTarget = () => (totalQty(ship.inventory, 'part') >= 3 ? ship.systems.find((s) => !s.built && s.type !== 'WEAPONS') : undefined);
|
||||||
|
|
||||||
let iterations = 0;
|
let iterations = 0;
|
||||||
while (!upgradeTarget() && !buildTarget() && iterations < 60) {
|
while (!upgradeTarget() && !buildTarget() && iterations < 60) {
|
||||||
@@ -204,6 +206,58 @@ test('SHIP SYSTEMS tab performs an affordable action and reflects it in the card
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('SKILL TREE tab researches WEAPONS SYSTEMS and unlocks the gated WEAPONS build', async ({ page }) => {
|
||||||
|
// Research costs two parts, building WEAPONS three — scan until the hold has five. Parts drop on
|
||||||
|
// ~30% of scans, so P(<5 in 80 scans) is negligible; jumping keeps it going if a system exhausts.
|
||||||
|
let ship = (await (await page.request.get('/api/ship')).json()) as ShipState;
|
||||||
|
let iterations = 0;
|
||||||
|
while (totalQty(ship.inventory, 'part') < 5 && iterations < 80) {
|
||||||
|
iterations++;
|
||||||
|
const sys = (await (await page.request.get('/api/system')).json()) as SystemState;
|
||||||
|
const unexplored = sys.planets.filter((p) => !p.explored);
|
||||||
|
if (unexplored.length > 0) {
|
||||||
|
expect((await page.request.post('/api/explore', { data: { planetId: unexplored[0].id } })).status()).toBe(200);
|
||||||
|
} else {
|
||||||
|
const world = (await (await page.request.get('/api/world')).json()) as WorldState;
|
||||||
|
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];
|
||||||
|
expect((await page.request.post('/api/jump', { data: { systemId: targetId } })).status()).toBe(200);
|
||||||
|
}
|
||||||
|
ship = (await (await page.request.get('/api/ship')).json()) as ShipState;
|
||||||
|
}
|
||||||
|
expect(totalQty(ship.inventory, 'part')).toBeGreaterThanOrEqual(5);
|
||||||
|
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
// SHIP SYSTEMS tab: BUILD NEW on WEAPONS is disabled — gated behind the unresearched tech.
|
||||||
|
await page.getByRole('tab', { name: 'SHIP SYSTEMS' }).click();
|
||||||
|
const weaponsCard = page.getByRole('heading', { name: 'WEAPONS' }).locator('xpath=ancestor::div[3]');
|
||||||
|
await expect(weaponsCard.getByText('REQUIRES // WEAPONS SYSTEMS')).toBeVisible();
|
||||||
|
await expect(weaponsCard.getByRole('button', { name: 'BUILD NEW' })).toBeDisabled();
|
||||||
|
|
||||||
|
// SKILL TREE tab: research WEAPONS SYSTEMS (two parts) — the card flips to RESEARCHED.
|
||||||
|
await page.getByRole('tab', { name: 'SKILL TREE' }).click();
|
||||||
|
const techCard = page.getByRole('heading', { name: 'WEAPONS SYSTEMS' }).locator('xpath=ancestor::div[3]');
|
||||||
|
await expect(techCard.getByText('AVAILABLE')).toBeVisible(); // root tech — no prerequisites
|
||||||
|
await expect(techCard.getByRole('button', { name: 'RESEARCH' })).toBeEnabled(); // parts ≥ 2
|
||||||
|
const turnBefore = ship.turn;
|
||||||
|
await techCard.getByRole('button', { name: 'RESEARCH' }).click();
|
||||||
|
await expect(techCard.getByText('RESEARCHED')).toBeVisible();
|
||||||
|
|
||||||
|
const afterResearch = (await (await page.request.get('/api/ship')).json()) as ShipState;
|
||||||
|
expect(afterResearch.turn).toBe(turnBefore + 1); // research costs a turn
|
||||||
|
|
||||||
|
// The gated ship action is now available: BUILD NEW on WEAPONS flips to enabled and builds.
|
||||||
|
await page.getByRole('tab', { name: 'SHIP SYSTEMS' }).click();
|
||||||
|
await expect(weaponsCard.getByRole('button', { name: 'BUILD NEW' })).toBeEnabled();
|
||||||
|
await weaponsCard.getByRole('button', { name: 'BUILD NEW' }).click();
|
||||||
|
|
||||||
|
const afterBuild = (await (await page.request.get('/api/ship')).json()) as ShipState;
|
||||||
|
expect(afterBuild.systems.find((s) => s.type === 'WEAPONS')!.built).toBe(true);
|
||||||
|
await expect(weaponsCard.getByText('LV 1')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
test('GET /api/version reports the app version as JSON', async ({ page }) => {
|
test('GET /api/version reports the app version as JSON', async ({ page }) => {
|
||||||
const res = await page.request.get('/api/version');
|
const res = await page.request.get('/api/version');
|
||||||
expect(res.status()).toBe(200);
|
expect(res.status()).toBe(200);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ interface SystemState { systemId: number; systemName: string; planets: PlanetDto
|
|||||||
interface CrewDto { id: number; name: string; role: string; traitGood: string; traitBad: string; origin: string }
|
interface CrewDto { id: number; name: string; role: string; traitGood: string; traitBad: string; origin: string }
|
||||||
interface ShipSystemDto { id: number; type: string; level: number; capacity: number; built: boolean }
|
interface ShipSystemDto { id: number; type: string; level: number; capacity: number; built: boolean }
|
||||||
interface ShipState { systems: ShipSystemDto[]; inventory: { kind: string; name: string; qty: number }[]; turn: number }
|
interface ShipState { systems: ShipSystemDto[]; inventory: { kind: string; name: string; qty: number }[]; turn: number }
|
||||||
|
interface TechDto { id: number; slug: string; name: string; costKind: string; costQty: number; prereqs: string[]; effect: string; state: string; missingPrereqs: string[] }
|
||||||
|
|
||||||
test('app survives a container restart (stack + volume persistence)', async ({ page }) => {
|
test('app survives a container restart (stack + volume persistence)', async ({ page }) => {
|
||||||
test.setTimeout(180_000);
|
test.setTimeout(180_000);
|
||||||
@@ -58,6 +59,10 @@ test('app survives a container restart (stack + volume persistence)', async ({ p
|
|||||||
const shipBefore = (await (await page.request.get('/api/ship')).json()) as ShipState;
|
const shipBefore = (await (await page.request.get('/api/ship')).json()) as ShipState;
|
||||||
expect(shipBefore.systems.length).toBeGreaterThan(0); // starting ship is always seeded
|
expect(shipBefore.systems.length).toBeGreaterThan(0); // starting ship is always seeded
|
||||||
|
|
||||||
|
// Tech baseline: the researched flags must survive too.
|
||||||
|
const techBefore = (await (await page.request.get('/api/tech')).json()) as TechDto[];
|
||||||
|
expect(techBefore.length).toBeGreaterThan(0); // the tech tree is always seeded
|
||||||
|
|
||||||
// Restart the app container mid-run — the named DB volume must survive it.
|
// Restart the app container mid-run — the named DB volume must survive it.
|
||||||
execSync(`docker compose ${composeArgs} restart space-wanderer`, { cwd: repoRoot, stdio: 'pipe' });
|
execSync(`docker compose ${composeArgs} restart space-wanderer`, { cwd: repoRoot, stdio: 'pipe' });
|
||||||
|
|
||||||
@@ -96,6 +101,10 @@ test('app survives a container restart (stack + volume persistence)', async ({ p
|
|||||||
const shipAfter = (await (await page.request.get('/api/ship')).json()) as ShipState;
|
const shipAfter = (await (await page.request.get('/api/ship')).json()) as ShipState;
|
||||||
expect(shipAfter).toEqual(shipBefore);
|
expect(shipAfter).toEqual(shipBefore);
|
||||||
|
|
||||||
|
// Tech tree survived too: same researched flags (no re-seed on restart).
|
||||||
|
const techAfter = (await (await page.request.get('/api/tech')).json()) as TechDto[];
|
||||||
|
expect(techAfter).toEqual(techBefore);
|
||||||
|
|
||||||
// The page itself serves again with the landing title.
|
// The page itself serves again with the landing title.
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await expect(page.getByRole('heading', { name: 'SpaceWanderer' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'SpaceWanderer' })).toBeVisible();
|
||||||
|
|||||||
Reference in New Issue
Block a user