fix: v0.10.1 — dispatch pickers independent per accepted contract (placeholder default, no first-eligible auto-fallback; stale-select fix via explicit placeholder option)

This commit is contained in:
2026-09-18 06:48:55 +00:00
parent cf811dcf4d
commit 4c06ed26ff
5 changed files with 175 additions and 7 deletions
+82
View File
@@ -261,3 +261,85 @@ test('market buy/sell render with price and payout previews from the catalog in
await expect(page.getByTestId('market-sell-payout')).toContainText(eur(payout));
await expect(page.getByTestId('market-sell')).toBeVisible();
});
test('dispatch pickers are independent per contract — selecting one does not leak into the others (v0.10.1)', async ({ page }) => {
test.setTimeout(240_000); // two short flights at scale 60 plus UI steps
// Self-heal first: a previous interrupted run may have left plane-starter mid-flight or at
// another airport (same pattern as the dispatch-loop test above).
let body = await getState(page);
const starter0 = body.planes.find((p) => p.id === 'plane-starter');
expect(starter0).toBeDefined();
if (starter0!.status === 'flying') {
await postSim(page, { timeScale: SCALE });
await expect
.poll(async () => (await getState(page)).planes.find((p) => p.id === 'plane-starter')?.status === 'available', {
timeout: 90_000,
message: 'waiting for an in-flight plane-starter to land',
})
.toBe(true);
body = await getState(page);
}
if (body.planes.find((p) => p.id === 'plane-starter')!.locationIcao !== HUB) {
const from = body.planes.find((p) => p.id === 'plane-starter')!.locationIcao;
await flyViaApi(page, from, HUB);
}
// Two hook contracts, both originating at EDDF on the shortest route.
const nearest = await nearestToHub(page);
const a = await createContract(page, HUB, nearest.icao);
const b = await createContract(page, HUB, nearest.icao);
await page.goto('/');
// Accept BOTH through the UI buttons.
for (const c of [a, b]) {
const row = page.locator(`li[data-testid="open-contract-${c.id}"]`);
await expect(row).toBeVisible({ timeout: 15_000 });
await page.getByTestId(`accept-${c.id}`).click();
}
const accA = page.getByTestId(`accepted-contract-${a.id}`);
const accB = page.getByTestId(`accepted-contract-${b.id}`);
await expect(accA).toBeVisible({ timeout: 10_000 });
await expect(accB).toBeVisible();
// Both dispatch pickers render with the placeholder default — nothing preselected (v0.10.1)…
const pickerA = page.getByTestId(`dispatch-plane-${a.id}`);
const pickerB = page.getByTestId(`dispatch-plane-${b.id}`);
await expect(pickerA).toBeVisible();
await expect(pickerB).toBeVisible();
await expect(pickerA).toHaveValue('');
await expect(pickerB).toHaveValue('');
await expect(pickerA.locator('option[value=""]')).toHaveText(/select a plane/i);
// …and the Dispatch buttons stay disabled until a valid plane is chosen.
await expect(page.getByTestId(`dispatch-${a.id}`)).toBeDisabled();
await expect(page.getByTestId(`dispatch-${b.id}`)).toBeDisabled();
// Select plane-starter in contract A's picker → contract B's picker must STILL show the
// placeholder (the v0.10.1 regression: selections used to appear shared across rows).
await pickerA.selectOption('plane-starter');
await expect(pickerA).toHaveValue('plane-starter');
await expect(pickerB).toHaveValue('');
// Dispatch A with the selected plane → it goes 'flying' and its row shows the in-flight…
await page.getByTestId(`dispatch-${a.id}`).click();
await expect(page.getByTestId(`contract-inflight-${a.id}`)).toBeVisible({ timeout: 10_000 });
body = await getState(page);
expect(body.planes.find((p) => p.id === 'plane-starter')!.status).toBe('flying');
// …indicator, while B remains accepted with its own independent picker on the placeholder.
await expect(accB).toBeVisible();
await expect(pickerB).toHaveValue('');
await expect(page.getByTestId(`dispatch-${b.id}`)).toBeDisabled();
// Restore: let A's flight finish and fly plane-starter home so later specs start clean.
await postSim(page, { timeScale: SCALE });
await expect
.poll(async () => {
const p = (await getState(page)).planes.find((x) => x.id === 'plane-starter');
return !!p && p.status === 'available' && p.locationIcao === nearest.icao;
}, { timeout: 90_000, message: `waiting for arrival at ${nearest.icao} (scale 60)` })
.toBe(true);
await flyViaApi(page, nearest.icao, HUB);
});