Skip to content

fix(taxonomic-filter): deflake component tests with fake timers - #70307

Merged
pauldambra merged 2 commits into
masterfrom
fix/taxonomic-filter-jest-fake-timers
Jul 12, 2026
Merged

fix(taxonomic-filter): deflake component tests with fake timers#70307
pauldambra merged 2 commits into
masterfrom
fix/taxonomic-filter-jest-fake-timers

Conversation

@pauldambra

Copy link
Copy Markdown
Member

Problem

Trunk quarantined several tests in TaxonomicFilter.test.tsx and PropertyFilters.component.test.tsx as CI timeout flakes. They aren't unstable in outcome, they're just slow: each search interaction pays taxonomicFilterLogic's real breakpoint(500) search debounce (plus a few stacked breakpoint(100)s) on real timers. At ~1.3-1.4s baseline, plus MSW round trips and waitFor polling, they occasionally exceed jest's 5000ms per-test timeout once CI's sharded --maxWorkers=2 runners add contention.

Changes

Removes the wall-clock wait instead of raising the timeout or loosening assertions:

  • A withoutDebounceDelay helper (and an equivalent inline in PropertyFilters.component.test.tsx's shared searchFor) scopes jest.useFakeTimers() around just the search interaction, advances past the debounce, then calls jest.useRealTimers() before the test's own waitFor picks up the resulting MSW round trip.
  • The fake-timer config excludes setImmediate in addition to queueMicrotask (already excluded globally in jest.config.ts for the same reason). setImmediate also drives MSW v2's response-body pump — faking it makes advanceTimersByTime re-run that pump once per virtual ms for every matched row, turning a fast search into a slow one.
  • A few scenarios that also render the SuggestedFilters "reveal barrier" don't tolerate the fake-to-real timer switch: a pending fake timer scheduled during the fake window is dropped rather than carried over when timers switch back to real, so the barrier never opens. Those stay on real timers, each with a comment explaining why — forcing it would have hidden a real functional break behind a "passing" test.
  • Dropped a couple of pre-existing }, 10000) per-test timeout overrides in TaxonomicFilter.test.tsx that are no longer needed once the debounce wait is gone.

No product code changed — this is test-only.

How did you test this code?

Automated only, run in the branch's worktree.

Before/after timing (--maxWorkers=1, per-test):

Test Before After
TaxonomicFilter › search › typing in the search field filters results 1128ms 611ms
TaxonomicFilter › search › returns full unfiltered results when search is cleared 1212ms 884ms
TaxonomicFilter › no results - switch to all › does not offer a jump to a render-backed group with no real matches 1381ms 1078ms
TaxonomicFilter › no results - switch to all › offers a per-category jump when matches live on another tab 1505ms 852ms
TaxonomicFilter › edge cases › renders correctly when search matches no items 1531ms 1001ms
TaxonomicFilter › collapseUrlsToContainsRow › (4 non-SuggestedFilters tests) 590-629ms 66-178ms
PropertyFilters › shortcut group (pageview URL / screen name / email) 918-1168ms 373-538ms
PropertyFilters › selecting a property and completing the value records to recents 1725ms 1163ms
PropertyFilters › recents show/search hint/prefix disappears/searching in recents 932-1326ms 385-732ms
PropertyFilters › multiple selections limited to 3 in suggested filters 3596ms 1354ms

Two tests in no results - switch to all and one in collapseUrlsToContainsRow stay on real timers (see comments) since they hit the SuggestedFilters reveal-barrier incompatibility above — they're unchanged from baseline (~900-1300ms), still comfortably clear of the 5000ms timeout.

Full-file totals: TaxonomicFilter.test.tsx 31.2s → 25.8s (81 tests), PropertyFilters.component.test.tsx 16.6s → 12.5s (13 tests). All 94 tests pass.

Stability loop: both files together, --maxWorkers=2, 24/24 runs green (target was 20).

Cross-file check: Combobox.test.tsx (the file whose existing fake-timer idiom this follows) run once, 38/38 pass, unaffected.

Docs update

N/A — test-only change.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Paul directed this from a pre-established diagnosis (already root-caused: real-timer search debounces pushing quarantined tests over CI's per-test timeout) and specified the fix approach (scoped fake timers, following the existing Combobox.test.tsx/recentTaxonomicFiltersLogic.test.ts idiom) plus the validation bar (before/after timing, a 20-run stability loop at --maxWorkers=2).

I'm Claude (Sonnet), working in the target worktree. No skills were invoked — this is a test-timing fix, not new test coverage or a DRF/migration/taxonomic-filter behavior change, so /writing-tests and /modifying-taxonomic-filter didn't apply.

Key decisions along the way:

  • Tried whole-file and whole-describe jest.useFakeTimers() first, per the preferred approach — it deadlocked every test that touched MSW (jest's own per-test 5000ms watchdog killed each one, leaking fake timers into subsequent tests). Root cause: @testing-library/dom's waitFor self-drives fake-timer advancement in a loop, and MSW v2's response pump rides setImmediate, which was being faked and re-entered on every virtual millisecond.
  • Landed on scoping jest.useFakeTimers()/jest.useRealTimers() tightly around just the typing interaction (matching Combobox.test.tsx's existing per-test idiom), excluding setImmediate from the fake set.
  • Found empirically (not guessed) that this still breaks the SuggestedFilters reveal-barrier scenarios specifically — confirmed by testing with a much longer waitFor timeout (4000ms) and observing the assertion still never resolved, i.e. a genuine functional break rather than a timing shortfall. Left those on real timers per the task's explicit fallback instruction rather than forcing a fix that would have masked the break.

Trunk quarantined several TaxonomicFilter/PropertyFilters component tests as
CI timeout flakes. They aren't unstable in outcome, just slow: each search
interaction pays taxonomicFilterLogic's real 500ms search breakpoint (plus
stacked 100ms ones) on real timers, and under CI's sharded, contended
--maxWorkers=2 runners the ~1.3-1.4s baseline occasionally exceeds jest's
5000ms per-test timeout.

Fix removes the wall-clock wait instead of raising the timeout: a scoped
jest.useFakeTimers() around the search interaction, advanced past the
debounce, then jest.useRealTimers() before waiting on the resulting MSW
round trip. Excludes setImmediate from the fake set (like queueMicrotask,
already excluded in jest.config.ts) since it also drives MSW v2's response
pump, and faking it turns a fast search into a slow one under
advanceTimersByTime.

A handful of scenarios that also exercise the SuggestedFilters "reveal
barrier" don't tolerate the fake-to-real timer switch (a pending fake timer
is dropped rather than carried over, and the barrier never opens) — those
stay on real timers with a comment explaining why.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pauldambra pauldambra self-assigned this Jul 12, 2026
@pauldambra
pauldambra marked this pull request as ready for review July 12, 2026 19:08
@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

Bundle size — no change

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 63.03 MiB · no change

No file changed by more than 1000 B.

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.21 MiB · 22 files no change ███░░░░░░░ 28.2% of 4.29 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.09 MiB · 2,967 files no change █████████░ 87.5% of 9.25 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
668 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
278.6 KiB ../node_modules/.pnpm/posthog-js@1.399.2/node_modules/posthog-js/dist/rrweb.js
266.9 KiB ../node_modules/.pnpm/@posthog+icons@0.37.4_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
234.9 KiB src/taxonomy/core-filter-definitions-by-group.json
221.5 KiB ../node_modules/.pnpm/posthog-js@1.399.2/node_modules/posthog-js/dist/module.js
164.0 KiB src/queries/validators.js
154.3 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
105.9 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

Dist folder size — no change

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1267.29 MiB · no change

⚠️ Playwright — 1 failed

🎭 Playwright report · View test results →

1 failed test:

  • Retention calculations, period, breakdown, and chart (chromium)

These issues are not necessarily caused by your changes.
Annoyed by this section? Help fix flakies and failures and it will go green!

@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

🦔 Hogbox preview · ❌ build failed

The preview didn't come up for commit a2d1e2c. See the build log for the failing step. It'll retry on the next push.

@pauldambra pauldambra added the stamphog Request AI approval (no full review) label Jul 12, 2026
stamphog[bot]
stamphog Bot previously approved these changes Jul 12, 2026

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test-only change deflaking two component test files via scoped fake timers; no production code touched, diff matches the description, no risky territory involved.

  • 👍 on the PR from hex-security-app[bot].
Gate mechanics and policy version
Gate Result
prerequisites all clear
deny-list no deny categories matched
size 0L, 0F substantive, 87L/2F incl. docs/generated/snapshots — within ceiling
tier T0 auto-approve: T0-deterministic (87L, 2F, single-area, fix)
stamphog 2.0.0b3 .stamphog/policy.yml @ 59f1d23 · reviewed head 29bff02

github-actions[bot]
github-actions Bot previously approved these changes Jul 12, 2026
…nfig

Faking it deadlocks any test awaiting an MSW v2 response under fake
timers (the interceptor response pump rides setImmediate), which is
upstream's documented stance rather than a bug (mswjs/msw#1830). With
the global doNotFake covering it, a bare jest.useFakeTimers() is safe
against MSW everywhere instead of each file rediscovering the recipe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pauldambra

Copy link
Copy Markdown
Member Author

Added a follow-up commit putting setImmediate in the global fakeTimers.doNotFake in frontend/jest.config.ts (alongside the existing queueMicrotask exclusion). Upstream considers the fake-timers/MSW deadlock a configuration matter, not a bug (mswjs/msw#1830 closed with this recipe; we're on a current 2.x so there's no fixing upgrade) — with the global exclusion, a bare jest.useFakeTimers() is MSW-safe everywhere and future test authors don't have to rediscover the per-call doNotFake incantation. Verified 142 tests across the four fake-timer-using suites (including slackIntegrationLogic.test.ts, whose explicit per-test timer list overrides the global config and is unaffected).

@trunk-io

trunk-io Bot commented Jul 12, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

@github-actions
github-actions Bot dismissed stale reviews from stamphog[bot] and themself July 12, 2026 19:13

New commits pushed (delta classified non_trivial_delta) — stamphog approval dismissed; re-review running automatically.

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test-only deflaking change (scoped fake timers + jest config tweak), no product code touched; author has strong familiarity with these exact files and the approach is well-documented including its known limitations.

  • Author wrote 79% of the modified lines and has 625 merged PRs in these paths (familiarity STRONG).
  • 👍 on the PR from hex-security-app[bot].
Gate mechanics and policy version
Gate Result
prerequisites all clear
deny-list no deny categories matched
size 4L, 1F substantive, 91L/3F incl. docs/generated/snapshots — within ceiling
tier T1-agent / T1b-small (91L, 3F, single-area, fix)
stamphog 2.0.0b3 .stamphog/policy.yml @ 59f1d23 · reviewed head a2d1e2c

@pauldambra
pauldambra enabled auto-merge (squash) July 12, 2026 19:16
@pauldambra
pauldambra merged commit 386e5d0 into master Jul 12, 2026
196 of 197 checks passed
@pauldambra
pauldambra deleted the fix/taxonomic-filter-jest-fake-timers branch July 12, 2026 19:39
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 12, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-12 20:03 UTC Run
prod-us ✅ Deployed 2026-07-12 20:15 UTC Run
prod-eu ✅ Deployed 2026-07-12 20:20 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stamphog Request AI approval (no full review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant