tests: Playwright spec for tryEnterPointModeIfNeeded short-circuits (closes #192)#197
tests: Playwright spec for tryEnterPointModeIfNeeded short-circuits (closes #192)#197rdhyee wants to merge 2 commits intoisamplesorg:mainfrom
Conversation
…loses isamplesorg#192) Adds tests/playwright/explorer-helper.spec.js with two passing tests that exercise the helper's deterministically observable invariants plus two `.skip()`d tests documenting paths that need a hookable refactor or headed-mode execution. Passing tests: 1. **boot to cluster mode** — explorer at world altitude reaches the "${n} clusters, ${m} samples." done state. Smoke test for the normal Phase-1 boot path. 2. **source-filter toggle at world altitude does NOT trigger "Fetching sample index…"** — verifies the helper's altitude short-circuit (invariant b). The source-filter handler chases the helper after its own loadRes settles, and at world altitude the chase MUST bail at the altitude check without painting the helper's own loadingMsg. Skipped tests (documented inline why): 3. **deep-link to point altitude reaches a sample-points done message** 4. **source-filter toggle in point mode does NOT trigger "Fetching sample index…"** Both require the Cesium camera at point altitude, which doesn't reproduce reliably in headless chromium — the postRender event that applies the deep-link camera position doesn't drive the camera- changed handler the same way as a real WebGL display, so the camera stays at world altitude and the point-mode logic never fires. The tests are kept (skipped) as living documentation; a future PR can either run them in headed mode (`npm run test:headed`) or expose a small test-only hook on `window` that lets the spec drive `viewer.camera.flyTo()` or set `_globeState.mode` directly. Both passing tests run in <10s combined against `quarto preview explorer.qmd --port 5860`. Local run: ✓ boot to cluster mode reaches a done message with cluster counts (2.1s) ✓ source-filter toggle at world altitude does NOT trigger Fetching sample index (4.0s) - deep-link to point altitude reaches a sample-points done message - source-filter toggle while in point mode does NOT trigger Fetching sample index 2 skipped, 2 passed (6.5s) Closes isamplesorg#192. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… tests (review round 2) Address Codex review of isamplesorg#197: 1. The two `.skip()`d point-altitude tests are now executable. Codex pointed out that Quarto's OJS runtime exposes evaluated cells via `window._ojs.ojsConnector.mainModule.value('viewer')`, which lets tests reach the closure-private `viewer` Cesium widget without adding a production hook. From there, `viewer.camera.flyTo(...)` drives the cluster→point-mode transition deterministically. 2. Fix the headless render-loop suspension that was the *real* reason the deep-link approach didn't work: Cesium's default `requestRenderMode = true` only renders on demand, and a single `setView` + `requestRender` doesn't reliably trigger `camera.changed` in headless chromium. Setting `scene.requestRenderMode = false` keeps the loop active, and an animated `flyTo` produces a stream of `postRender` events that exceed the percentageChanged threshold and fire the camera-changed handler. 3. Tighten the point-mode done-message assertion. The earlier substring match on `"individual samples"` was satisfied by the intermediate `"Loading individual samples..."` loading state. Wait for the count-prefixed terminal form via a regex instead. Coverage matrix is now: ✓ boot to cluster mode reaches a done message with cluster counts ✓ source-filter toggle at world altitude does NOT trigger Fetching sample index (invariant b) ✓ flying camera into point altitude triggers helper and enters point mode (camera-handler call site, end-to-end) ✓ source-filter toggle while in point mode does NOT trigger Fetching sample index (invariant a) 4 passed (30.4s) Closes isamplesorg#192. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codex review round 1 — addressed in 11a06cdBoth blocking findings addressed. Codex's pointer to the OJS runtime works
Confirmed locally — the runtime does expose the Why the previous attempt didn't work (the real root cause)Cesium's default (My earlier debug script "worked" because it ran during the still-warming-up render window. By the time the spec waited for cluster boot, the render loop had suspended.) ResultAll four tests pass: Coverage now meets #192's acceptance: invariants (a) and (b) are deterministically exercised, plus an end-to-end smoke test of the camera-handler call site that drives the helper into a real point-mode entry. Invariant (c) ( Also fixed
Re-requesting review. |
Summary
Closes #192. Adds
tests/playwright/explorer-helper.spec.jscovering the deterministically observable short-circuit invariants oftryEnterPointModeIfNeeded()(introduced in #191).Coverage
boot to cluster mode reaches a done message"${n} clusters, ${m} samples."done state.source-filter toggle at world altitude does NOT trigger Fetching sample indexloadRessettles, and at world altitude the chase MUST bail at the altitude check without painting"Fetching sample index…".deep-link to point altitude reaches a sample-points done messagesource-filter toggle in point mode does NOT trigger Fetching sample indexWhy two tests are skipped
Both require the Cesium camera at point altitude (alt < 120 km). In headless Chromium the
scene.postRenderevent that applies the deep-link camera position doesn't drive the camera-changed handler the same way as a real WebGL display: the camera stays at world altitude and the point-mode logic never fires. (Confirmed during development — visited the alt=62054 deep-link in a headless browser, observed phase-message progression stopped at the cluster-mode done state and never advanced.)The tests are kept (with detailed
.skip()reasons inline) as living documentation. Two paths forward for a follow-up:npm run test:headedagainst the same preview server). Slower, requires display in CI, but the camera flies correctly.vieweronwindow(closure-private currently), so the spec can driveviewer.camera.flyTo()directly and the camera handler fires deterministically. This is a one-line change toexplorer.qmd.Either is out of scope for this PR — the issue's acceptance asks for "at least one Playwright spec covering items 1, 3, and 4," and the altitude-short-circuit test alone already covers an item-1 short-circuit invariant deterministically.
Approach
Cell-internal state (
mode,currentRes,loading,viewer) is closure-private inside an OJS cell and is NOT exposed onwindow. The deterministically observable signal from outside the closure is#phaseMsg'stextContent, whichupdatePhaseMsg(...)rewrites at every state transition. Tests install aMutationObserveron#phaseMsg, capture history during the action, then assert presence/absence of expected loading-message phrases.Test plan
Expected output:
.skip()reason explaining why and how to enableRefs
tryEnterPointModeIfNeeded()🤖 Generated with Claude Code