Skip to content

test(frontend): restore recommendations mock defaults before each test - #1391

Merged
IAmJSD merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:ui/issue-803
Oct 24, 2026
Merged

IAmJSD merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:ui/issue-803

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator

frontend/src/__tests__/recommendations.test.ts failed 16 to 19 of its 420 tests under jest --randomize (seeds 1 to 6), even when run alone, because mock return values set by one test leaked into the next. This PR resets every mock in the file to its factory default before each test, so the file passes in any order. Test-only, no production code changes.

Closes #803

What changed

  • frontend/src/__tests__/recommendations.test.ts (+18 lines): at file load, snapshot the factory default (getMockImplementation()) of every jest mock in the mocked ../api, ../state and ../api/recommendations modules plus mockShowToast. A new file-level beforeEach calls mockReset() on each and restores that default. Describe-level beforeEach hooks run after it, so tests that set their own values are unaffected.

Root cause: setup.ts calls jest.clearAllMocks() before each test, which clears recorded calls but keeps any mockReturnValue/mockResolvedValue/mockImplementation a test installed. A test that set, for example, state.getCostPeriod to 'hourly', a column filter, a selection, the current user or an account-override response leaked it into whichever test ran next (e.g. applyColumnFilters's '>100' savings filter then matched nothing because savings were scaled to hourly). The issue reported 6 failures; the file has grown since.

Commit: cf593904.

Why this approach

It reuses the defaults already declared in the jest.mock factories instead of copying them into a reset list, so a default added to a factory later is restored automatically. Adding afterEach resets test by test would touch dozens of tests and miss future leaks. Switching setup.ts to a global jest.resetAllMocks() would wipe the factory defaults every suite depends on. Module-level state in recommendations.ts (expanded cells, column-filter seeding, and so on) was checked as a second possible leak; once the mock defaults were restored, no failures remained across 29 seeds, so no further resets were added.

Screenshots

No visible change: test-only change.

Verification

Run on this branch's head cf593904 on top of main a14dc8f2, from frontend/:

  • Before: --randomize with seeds 1 to 6 gave 16 to 19 failures out of 420.
  • After: the file alone passes 420/420 in default order and 420/420 under --randomize for each of 29 seeds (1-26, 12345, 803, -42).
  • npm run lint, typecheck and build pass.
  • Full jest (--maxWorkers=2): 109 suites, 3486 passed, 1 skipped. One full randomized run (seed 1510269885): recommendations.test.ts passes; 6 tests fail in users, dashboard, riexchange and inventory.test.ts, which have the same kind of order dependency and are outside this issue.
  • Playwright (e2e-local.sh): 157 passed.
  • Secrets scan clean (planted key flagged); scripts/test-git-secrets-allowlist.sh: 137 PASS.

Notes for review

  • The issue's suggestion to consider enabling randomization in CI is not done: a full randomized run still fails in users, dashboard, riexchange and inventory.test.ts, so a CI-wide randomize: true would go red. Those files need the same treatment first, which could be a follow-up issue.
  • Some older ad-hoc resets in this file (e.g. the loadRecommendations afterEach that resets getRecommendationsColumnFilters) are now redundant but were left in to keep the diff small.
  • The file is still 8,486 lines; splitting it is refactor(frontend): fourteen non-test files in frontend/src exceed the 500-line ceiling, up to 5481 lines #210.

Overlaps

Note for the UI owner

This changes UI code that @cristim owns. IAmJSD reviewed the before/after screenshots and asked for it to be merged; any design point you disagree with can be a follow-up.

🤖 Generated with Claude Code

…each test

recommendations.test.ts failed 16 to 19 tests under jest --randomize
(seeds 1 to 6), even when run alone. setup.ts calls jest.clearAllMocks()
before each test, which clears recorded calls but keeps any
mockReturnValue / mockResolvedValue / mockImplementation a test
installed. So a test that set, for example, state.getCostPeriod to
'hourly', a column filter, a selection or an account override leaked
that value into whichever test ran next. The default order happened to
put tests after harmless overrides; random order did not.

Snapshot each api, state, api/recommendations and showToast mock's
factory default once at file load, and reset every mock to it in a
file-level beforeEach. Describe-level beforeEach hooks still run after
it, so tests that set their own values keep working. This reuses the
defaults already declared in the jest.mock factories instead of
repeating them.

Refs LeanerCloud#803

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added impact/internal Team-internal only triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline effort/s Hours type/bug Defect labels Oct 24, 2026
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Caution

Review failed

The pull request is closed.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE cf593904aeb625e74737ccbff0a274908042866d

Blocking:

  • none
    Non-blocking:
  • frontend/src/tests/recommendations.test.ts:100: the ../utils mock (formatCurrency, escapeHtml, populateAccountFilter) is not in the reset set, so an override installed on it would still leak. None leaks today.
    Attacked: Checked that in Jest 30 getMockImplementation() captures factory defaults set via jest.fn(impl), mockReturnValue and mockResolvedValue. Checked that the file-level beforeEach runs before the describe-level hooks. Ran the file in a scratch worktree under --randomize with seeds 7, 99 and 2024: 420/420 each time. Removed the scratch worktree afterwards.

@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Local verification at cf593904 merged onto current main (5fbedae8; merge-tree 2ad11ccf; Linux, Node, headless Chromium):

  • This branch's own checks passed at this head: lint, typecheck, build, the full jest suite, the full Playwright e2e suite, and the git-secrets checks (see Verification in the PR body).
  • The combined tree of every UI PR in this merge wave (tree 89dd34d8, main plus 46 PRs) passes npm ci, lint, typecheck, build, jest (1 skipped, 3914 passed, 3915 total), and the full Playwright suite (208 passed).
  • CI is green on cf593904 (CI Success, Run pre-commit hooks).
  • The independent review above approves this exact head.
  • Nothing was applied to any cloud.

@IAmJSD
IAmJSD marked this pull request as ready for review October 24, 2026 17:30
@IAmJSD
IAmJSD merged commit 15a0d38 into LeanerCloud:main Oct 24, 2026
29 checks passed
@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Marked ready and merged by IAmJSD's agent, at IAmJSD's request (IAmJSD reviewed the before/after screenshots; cristim is traveling), after the independent review and local verification above, all at cf593904.

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

Labels

effort/s Hours impact/internal Team-internal only priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/bug Defect urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test(frontend): recommendations.test.ts fails 6 tests under random ordering (order-dependent state)

1 participant