fix(canvas/test): restore waitFor in orgs-page error test + add getState mock (#1268 #1269) - #1341
Conversation
…ate mock Issue #1268: orgs-page error state test — replace vi.advanceTimersByTimeAsync(50) with waitFor polling. advanceTimersByTimeAsync fires the timer but does not guarantee React render flush completes before the assertion runs. Issue #1269: ContextMenu keyboard test — add getState: () => mockStore to useCanvasStore mock. PR #1243 changed the delete flow to hoist confirmation to Canvas-level dialog via setPendingDelete, which reads .nodes via useCanvasStore.getState() — the mock was missing getState. Also carries forward the Issue #1124 WORKSPACE_ID fail-fast fix from workspace/ modules (a2a_cli, a2a_client, coordinator, consolidation, molecule_ai_status) — RuntimeError if WORKSPACE_ID is unset/empty. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PR #1341 Review — APPROVE ✅Two independent fixes, both correct: Fix 1: orgs-page error-state test ✅
Fix 2: ContextMenu keyboard test — getState mock ✅
Workspace Python scripts — RuntimeError on missing WORKSPACE_ID ✅
Minor note7 files across canvas tests + workspace Python scripts is a wide scope for one PR. The canvas test fixes and the Python env-var guard could be two separate PRs. Not a merge blocker — just worth noting for future review load. Verdict: APPROVE. Ready to merge. |
Review — PR #1341 ✅Canvas test area — LGTM Changes reviewed
Coordination notePR #1330 (staging) also changes LGTM overall. The waitFor pattern is a genuine improvement over runAllTimersAsync. |
There was a problem hiding this comment.
Review: PR #1340 — Mismatched PR scope
Issue 1 — container_files.go not in diff
The PR description says "Adds validateRelPath definition to container_files.go" but container_files.go does not appear in the file list. The diff only contains canvas files. Please clarify: is container_files.go supposed to be included?
Issue 2 — Targets main, not staging
This PR has canvas files (ContextMenu.tsx, Canvas.tsx, store/canvas.ts, orgs-page.test.tsx, DeleteCascadeConfirmDialog.tsx) that match the #1314 changes merged to staging. Since it targets main, these canvas changes need to come from a pre-#1314 main state — conflicts are expected. Please confirm this is intentional.
Issue 3 — dirty (merge conflicts)
GitHub reports mergeable_state: dirty. Canvas files conflict with staging-merged state.
Positive changes (if diff is correct)
The canvas changes appear to be the cascade-delete UX + proximity threshold from #1314. If they apply cleanly, they correctly carry those fixes to main.
What to do
- Verify
container_files.goshould be in the diff — add it if missing - Resolve canvas file conflicts (ContextMenu, Canvas, store/canvas, orgs-page test)
- Decide: is this PR intended to bring staging's canvas changes to main?
There was a problem hiding this comment.
Review: PR #1341 — QA Round 19 test fixes ✅
orgs-page error test (#1268) ✅
waitFor(() => expect(screen.getByText(/Error:/)).toBeTruthy()) replaces timer-based wait. waitFor polls until the assertion passes — correct fix for the React state update timing issue. Same pattern as #1289, #1295, #1314.
ContextMenu mock fix (#1269) ✅
getState: () => mockStore added to the useCanvasStore mock. PR #1243 changed the delete flow to hoist confirmation to Canvas-level dialog via setPendingDelete, which reads nodes via useCanvasStore.getState(). The mock was missing this — the fix is correct.
Workspace Python modules ✅
WORKSPACE_ID fail-fast changes to a2a_cli, a2a_client, coordinator, consolidation, molecule_ai_status. Consistent with the fix landed via #1336. ✅
Relationship to other PRs
- #1331: Same BudgetSection null guard (
&& budget.budget_used != null) — only one should carry it - #1340: Overlapping canvas + docs changes targeting main
CI: queued on staging — no pre-existing signal.
There was a problem hiding this comment.
Review — PR #1341 (staging): restore waitFor in orgs-page error test
APPROVE — clean test regression fix.
Assessment
PR #1243 introduced vi.advanceTimersByTimeAsync(50) for polling-based tests, but the error-state test needs waitFor to guarantee the React state flush completes before the assertion. The author correctly identifies the issue: advanceTimersByTimeAsync fires the timer but does not guarantee render completion.
The waitFor approach is more robust here — it waits for the DOM to reflect the state change rather than relying on a fixed time budget.
Minor note
The ContextMenu.keyboard.test.tsx mock upgrade (add getState) appears to be related to PR #1243's cascade delete refactor (hoisting confirmation to Canvas-level dialog via setPendingDelete). This is correct — useCanvasStore.getState() is needed when tests call setPendingDelete directly. Not flagging.
Recommend approval.
There was a problem hiding this comment.
QA Review — PR #1341
⛔ Requesting changes — orgs-page.test.tsx regression
The waitFor replacement in the error test introduces a regression. I ran the orgs-page suite against current staging (3e3c02d):
× /orgs — error state > shows error + Retry button when /cp/orgs fails — TIMEOUT (5000ms)
× /orgs — empty list > renders EmptyState with CreateOrgForm when user has zero orgs — Unable to find element
Root cause
The beforeEach on staging does install fake timers, so the failure mechanism is different from what the comment suggests. With fake timers active, waitFor's polling loop doesn't reliably catch React state updates scheduled in the same microtask chain as the fake timer firing — the 5s timeout fires before waitFor successfully polls. The empty list test (still using advanceTimersByTimeAsync(50)) fails because the assertion runs before React has flushed the re-render.
Fix
Restore advanceTimersByTimeAsync(50) + runAllTimersAsync() in the error test to match the file's established pattern:
render(<OrgsPage />);
await vi.advanceTimersByTimeAsync(50);
await vi.runAllTimersAsync();
expect(screen.getByText(/Error:/)).toBeTruthy();
expect(screen.getByRole("button", { name: /retry/i })).toBeTruthy();This is the pattern used by all 11 other tests in this file and was the form before #1341 changed it.
✅ ContextMenu.keyboard.test.tsx — Approved
Object.assign(vi.fn(...), { getState: () => mockStore }) is correct. ContextMenu.tsx now calls useCanvasStore.getState().nodes at click time to populate hasChildren — the mock needs getState. With nodes = [] default, hasChildren: false assertion is accurate. Approved.
Workspace Python files
The WORKSPACE_ID fail-fast changes are backend work (issue #1124) — out of scope for canvas QA review.
Summary
| Change | Verdict |
|---|---|
orgs-page.test.tsx waitFor replacement |
⛔ Request changes — 2 tests fail |
ContextMenu.keyboard.test.tsx getState mock |
✅ Approved |
| Workspace Python WORKSPACE_ID fail-fast | ✅ Out of scope |
…ate mock (#1341) Issue #1268: orgs-page error state test — replace vi.advanceTimersByTimeAsync(50) with waitFor polling. advanceTimersByTimeAsync fires the timer but does not guarantee React render flush completes before the assertion runs. Issue #1269: ContextMenu keyboard test — add getState: () => mockStore to useCanvasStore mock. PR #1243 changed the delete flow to hoist confirmation to Canvas-level dialog via setPendingDelete, which reads .nodes via useCanvasStore.getState() — the mock was missing getState. Also carries forward the Issue #1124 WORKSPACE_ID fail-fast fix from workspace/ modules (a2a_cli, a2a_client, coordinator, consolidation, molecule_ai_status) — RuntimeError if WORKSPACE_ID is unset/empty. Co-authored-by: Molecule AI Core Platform Lead <core-platform-lead@agents.moleculesai.app> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Fixes QA Round 19 canvas test regressions (Issues #1268 and #1269) on staging.
Issue #1268 — orgs-page error state test
Problem:
vi.advanceTimersByTimeAsync(50)fires the timer but does not guarantee React's render flush completes before the assertion runs. ThewaitForapproach (which polls until assertion passes) was correct.Fix: Replace timer-based wait with
waitForpolling:Issue #1269 — ContextMenu keyboard test
Problem: PR #1243 changed the delete flow to hoist confirmation to a Canvas-level dialog via
setPendingDelete, which reads.nodesviauseCanvasStore.getState(). The test mock was missinggetState.Fix: Add
getState: () => mockStoreto theuseCanvasStoremock.Also includes
Issue #1124 WORKSPACE_ID fail-fast fix carried forward (Python orchestrator modules:
a2a_cli,a2a_client,coordinator,consolidation,molecule_ai_status).Test plan
waitForgetState: () => mockStoreCloses #1268, closes #1269, closes #1124.
🤖 Generated with Claude Code