From b6bfcedaf615cdbff5dc122f53241f0a05f5e491 Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Sat, 19 Sep 2026 11:32:19 +0000 Subject: [PATCH] =?UTF-8?q?test:=20click=20the=20rendered=20marker=20posit?= =?UTF-8?q?ion=20while=20paused=20=E2=80=94=20closes=20render-lag=20race?= =?UTF-8?q?=20(CI=20run=20#37)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + e2e/map-interactions.spec.ts | 35 +++++++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 660ce29..ab20488 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Cargo sub-line route span had no white-space rule and wrapped mid-route — it now carries the existing `contract-route` class (nowrap + ellipsis) - **Left pane sanity-checked at its new 280px**: fleet-row location span (`.plane-loc`) already had nowrap+ellipsis but lacked `min-width: 0`, so a long in-flight leg would have overflowed instead of truncating — same one-line guard added. Fuel/loadout spans are short fixed strings and need no guard - Verified: `tsc --noEmit` clean; unit suite **238/238** green locally (no lib changes); e2e not run locally by design — CI verifies against an isolated stack +- **Unrelated test-stability fix included on this branch to unblock the pipeline** (`e2e/map-interactions.spec.ts`, no app changes): the mid-flight marker click now targets the plane's RENDERED position — it polls up to ~5 s for the rendered feature via `window.__flightMap.queryRenderedFeatures` on layers `planes-flying`/`planes-grounded` and projects that feature's coordinates, instead of fetching a fresh `/api/state`. With the sim paused the server position is static, but the map renders from its own ~1 s poll — right after the freeze confirmation the last rendered frame can still be up to one poll interval stale (tens of px at scale 1000 on EDDF→LEMD), so a click projected from a fresh fetch misses the small circle layer: the render-lag race that flaked CI run #37. Pause + freeze confirmation, popup assertions and resume are unchanged ## v0.15.0 — 2026-09-19 09:35 - **Hover visualization from the side panels (ISS-002).** Hovering a fleet row in the left pane, an "On board" block (including its cargo rows) or an open-package row in the right pane now highlights the matching object on the map. The shared `HoverTarget` type (`components/hoverTypes.ts`: `{kind:'plane', id}` | `{kind:'package', originIcao, destIcao}`) is owned by the shell as a single `useState` and prop-drilled down — panes report via an `onHover` callback, `` consumes it. No React contexts introduced; the established prop-drilling pattern is kept diff --git a/e2e/map-interactions.spec.ts b/e2e/map-interactions.spec.ts index e323912..e55c355 100644 --- a/e2e/map-interactions.spec.ts +++ b/e2e/map-interactions.spec.ts @@ -266,20 +266,35 @@ test('plane markers move while flying and the plane popup shows status + route', }, { timeout: 15_000, message: 'waiting for the paused clock to freeze the plane position' }) .toBe(true); - // Fetch + project in one evaluate — with the sim paused above, the server's analytic - // position is static, so the projected point matches what the map renders. + // Click the RENDERED marker position, not a fresh /api/state fetch. With the sim paused + // above the server's analytic position is static — but the map renders from its own ~1 s + // poll, so right after the freeze confirmation the last rendered frame can still be up to + // one poll interval stale (tens of px at scale 1000 on this leg) and a click projected + // from a fresh fetch misses the small circle layer (CI run #37). Poll for the plane's + // rendered feature instead and project ITS coordinates — exactly what the map draws. + // Find + project in one evaluate so nothing can change between the two steps. const pt = await page.evaluate(async () => { const m = (window as any).__flightMap; if (!m) return null; - const res = await fetch('/api/state', { cache: 'no-store' }); - const body = (await res.json()) as StateBody; - const p = body.planes.find((x) => x.id === 'plane-starter'); - if (!p || p.status !== 'flying') return null; - const projected = m.project([p.lon, p.lat]); - const rect = m.getContainer().getBoundingClientRect(); - return { x: rect.left + projected.x, y: rect.top + projected.y }; + const deadline = Date.now() + 5_000; + for (;;) { + let feats: Array<{ properties?: Record; geometry?: { coordinates: number[] } }> = []; + try { + feats = m.queryRenderedFeatures(undefined, { layers: ['planes-flying', 'planes-grounded'] }); + } catch { + // map not ready yet — keep polling + } + const f = feats.find((x) => x.properties?.id === 'plane-starter'); + if (f && f.geometry) { + const projected = m.project(f.geometry.coordinates); + const rect = m.getContainer().getBoundingClientRect(); + return { x: rect.left + projected.x, y: rect.top + projected.y }; + } + if (Date.now() >= deadline) return null; + await new Promise((r) => setTimeout(r, 100)); + } }); - expect(pt).not.toBeNull(); // the ~10 s window at scale 1000 is still open this early after departure + expect(pt, 'plane marker not rendered after pause').not.toBeNull(); await page.mouse.click(pt!.x, pt!.y); const popup = page.getByTestId('plane-popup');