MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-1736

Vitest flake (recurring, 2 PRs): ProjectRoadmapCanvas.test.tsx:128 — the auto-reset assertion races the auto-layout effect instead of awaiting it

Done
Description

Symptom

A Vitest shard fails on tests/components/ProjectRoadmapCanvas.test.tsx > ProjectRoadmapCanvas > "auto-resets a level when its auto-laid node set changes (a re-plan)":

AssertionError: expected "vi.fn()" to be called with arguments: [ ArrayContaining ["A", "C"] ]
Number of calls: 0
 ❯ tests/components/ProjectRoadmapCanvas.test.tsx:128:30

1 failed / 195 passed test files. Passes 25/25 locally, re-run 3× on the exact failing commit. gh run rerun --failed clears it.

Recurrence (diff-unrelated every time — the tell it's the test, not the diff)

  • 2026-07-07, PR #1560Vitest (integration + coverage).
  • 2026-07-27, PR #1620Vitest (3/3), on an E2E-test-ONLY diff (tests/e2e/**, which Vitest does not even run). The failing file imports only ProjectRoadmapCanvas + projectCanvasModel — nothing the diff touches. Every other check green, including the acceptance-video lane that actually runs the new spec.

Root cause

The test is a race, not a timing-margin problem — the classic shape of notes.html #37 / CLAUDE.md § E2E tests wait on the AUTHORITATIVE signal, here at the component altitude:

await screen.findByText('c');                                        // :127
expect(onResetPositions).toHaveBeenCalledWith(expect.arrayContaining(['A', 'C'])); // :128

The findByText('c') DOES resolve — so the level re-rendered with the new node set — but that is the wrong signal: it proves the render landed, not that the auto-layout effect that calls onResetPositions has flushed. expect(...).toHaveBeenCalledWith(...) is a synchronous, non-retrying assertion, so on a loaded CI runner it samples the mock before the effect fires and reads Number of calls: 0. Locally the effect wins the race every time.

A flaky Vitest test is not a private cost: PR CI checks out the branch merged with main, so it intermittently red-lights every open PR.

Fix

Wait on the authoritative signal — the callback itself — instead of a proxy for it:

await waitFor(() =>
  expect(onResetPositions).toHaveBeenCalledWith(expect.arrayContaining(['A', 'C'])),
);

Then sweep the file for siblings of the same shape: any expect(someMock).toHaveBeenCalled… that follows a findBy*/fireEvent and asserts an effect-driven callback. A bigger timeout fixes nothing here — there is no timeout in the failing line.

Acceptance criteria

  • The named test awaits onResetPositions authoritatively (waitFor / expect.poll), not a render proxy.
  • Every other assertion in ProjectRoadmapCanvas.test.tsx that asserts an effect-driven mock after a render is converted the same way (or shown to already await one).
  • The file passes 25/25 under repeat load locally (e.g. vitest run --repeat 5, or run under CPU contention), not just once.
  • No production component change — this is a test-side race.

Notes

Logged in the auto-flake-log across both occurrences; filed at Yue's request rather than waiting for a 3rd. Same family as MOTIR-1699 (bulk-4 waitForResponse) and MOTIR-1679 (bulk-shard webServer), and the same lesson as MOTIR-671 (bug-e2e-suite-flaky-specs) one altitude down. Surfaced while shipping MOTIR-1733 (PR #1620), whose own diff is innocent.