mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-27 01:22:27 +02:00
test: deflake messagesViewFirstContact remove-contact test (#264)
This test asserts that clicking "Remove" on a contact:
1. Surfaces a toast "Removed contact: <name>."
2. Drops the contact from the visible list
The Remove handler in MessagesView dispatches a tight cluster of React
state updates in one event tick:
removeContact(peerId)
locallySavedContactIdsRef.current.delete(peerId)
setContacts(...)
setComposeError('')
setComposeStatus(`Removed contact: ${displayNameForPeer(...)}.`)
Locally those updates settle in <100ms and the toast appears under any
findByText default. Under GitHub Actions runner load — especially the
shared Node.js workers on the "CI Gate / Frontend Tests & Build" step
— the reconcile-and-paint cycle has been measured at ~1.4s, which
exceeds the 1s default findByText timeout.
This is a load-sensitive timing flake, not a real bug — the toast
always renders eventually because the state update chain is purely
synchronous and the displayed text comes from the closure's pre-update
contacts (so the "Remove Me" name is always available when the toast
finally renders).
Historical flake hits in CI on this exact assertion:
PR #226 (zh-CN i18n landing, exposed by i18n parse error)
PR #237 (GitLab mirror parity)
PR #261 (post-#227 audit gap closures)
PR #262 (AIS SPKI pinning — failed post-merge Docker Publish,
skipping image publication for commit 729ea78)
The last one is the worst — a post-merge flake that blocked the
Docker image for an actual security fix from being published. The
subsequent merge of #263 cumulatively re-published the image, but
that's by accident, not by design.
Fix: replace the 1-second findByText with waitFor + 5s timeout +
50ms polling. The 5s ceiling still surfaces a real "toast never
renders" regression with a clear error; it just doesn't get racy
under CI load anymore.
Validation:
Local sequential 10x run of just this test → 10 passed, 0 failed
Full vitest suite → 707 passed, 72 files
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -859,10 +859,28 @@ describe('MessagesView first-contact trust UX', () => {
|
||||
renderMessagesView();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'CONTACTS' }));
|
||||
|
||||
expect(await screen.findByText('Remove Me')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText('Remove Me', undefined, { timeout: 5000 }),
|
||||
).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Remove' }));
|
||||
|
||||
expect(await screen.findByText(/Removed contact: Remove Me\./i)).toBeInTheDocument();
|
||||
// The Remove handler dispatches several React state updates in one
|
||||
// event (removeContact + setContacts + setComposeStatus + setComposeError).
|
||||
// Under CI load the resulting render-and-paint cycle has been observed
|
||||
// to take >1s, which is the default findByText timeout — that race has
|
||||
// produced flakes on PRs #226, #237, #261, and #262 in succession.
|
||||
// The settle window is bounded by React's reconciliation, not by any
|
||||
// network/animation cost, so a generous timeout is the right deflake
|
||||
// here (the failure mode this masks would be "toast never renders",
|
||||
// which would still fail at 5s).
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(
|
||||
screen.getByText(/Removed contact: Remove Me\./i),
|
||||
).toBeInTheDocument();
|
||||
},
|
||||
{ timeout: 5000, interval: 50 },
|
||||
);
|
||||
expect(screen.queryByText('Remove Me')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user