Skip to content

fix: repair broken test infra masking Real-time Balance Sync and Dark Mode Theme Engine bugs - #1406

Merged
emdevelopa merged 1 commit into
emdevelopa:mainfrom
davedumto:fix/frontend-ux-batch-dumto
Aug 28, 2026
Merged

fix: repair broken test infra masking Real-time Balance Sync and Dark Mode Theme Engine bugs#1406
emdevelopa merged 1 commit into
emdevelopa:mainfrom
davedumto:fix/frontend-ux-batch-dumto

Conversation

@davedumto

Copy link
Copy Markdown
Contributor

Both RealTimeBalanceSync and the Dark Mode Theme Engine (theme-context.tsx, wired via ThemeProvider/CommandPalette) already had solid implementations. The problem in both cases was that their test coverage was broken, which is worse than no coverage — it gave false confidence and masked real, smaller bugs underneath.

#1363 — Real-time Balance Sync accessibility violation

RealTimeBalanceSync.test.tsx's framer-motion mock only stubbed motion.section/button/p/ul/li/span, but the component also uses motion.div and motion.svg — every test in the file crashed with Element type is invalid: expected a string ... but got: undefined before ever rendering. Fixed the mock, then added axe-core scans (already a transitive dependency via @axe-core/cli) across all four component states (loaded, empty, error, loading). Result: zero accessibility violations — the component's aria-live regions, roles, and reduced-motion handling were already correct.

#1362 — Real-time Balance Sync responsive layout issue

A real one: the balance row (flex items-center justify-between gap-4) had no wrap/truncate handling, and its parent <ul> has overflow-hidden. A balance formatted to the component's own max of 7 decimal places (e.g. 1,234,567.1234567) would be silently clipped on narrow viewports instead of visibly reflowing — for a financial app, silently hidden digits are worse than an ugly wrap. Fixed: the row now wraps (flex-wrap) instead of clipping, the balance value breaks onto a new line if needed (break-all) instead of being cut off, and the asset code truncates gracefully if unusually long.

#1361 / #1360 — Dark Mode Theme Engine UI revamp / incorrect state rendering

The actual live dark-mode engine is src/lib/theme-context.tsx (wired into the app via ThemeProviderlayout.tsx, toggled through CommandPalette) — the various theme-engine*.tsx/ThemeToggle*.tsx files elsewhere in src/lib/src/components are unused/orphaned experiments, not wired into any route.

theme-context.test.tsx's "Dark Mode Theme Engine" suite (added for #651) was 26/27 failing, plus 25 more failures across the file's other suites — 0/27 passing overall before this PR. Two compounding causes, both fixed:

  1. Environment bug (root cause, fixed in src/test/setup.ts, affects the whole repo): Node 22.4+/24+ ships a native globalThis.localStorage, but without a --localstorage-file it's a non-functional stub (getItem/setItem/clear all undefined) — only a runtime warning marks it. Vitest's jsdom environment only copies jsdom's real Storage implementation onto a global key when that key isn't already present on global; since the broken native stub already occupies it, jsdom's working implementation is silently never installed. Fixed by re-pointing globalThis.localStorage/sessionStorage at jsdom's real implementation (exposed by Vitest as globalThis.jsdom.window) in test setup. This alone fixed dozens of unrelated failing tests repo-wide (full suite: 242→180 failing tests, 18→13 failing files).

  2. Test-design bug (fixed in theme-context.test.tsx): the suite replaced globalThis.document/globalThis.window wholesale with bare mock objects, but @testing-library/react's render() needs a real document.body to mount into — every test calling render() crashed with Cannot read properties of undefined (reading 'appendChild'). Rewrote to spy on the real document/window instead of replacing them (vi.spyOn on classList.add/remove, document.querySelector, window.matchMedia), which preserves every existing assertion while keeping render() functional. Also added the legacy addListener/removeListener MediaQueryList methods to the matchMedia mock, since next-themes (wrapped internally by ThemeProvider) still calls those directly and none of the mocks provided them. Fixed three more incorrect test assumptions along the way: a synchronous pre-mount-state assertion that isn't observable given React 18 + RTL's act() effect-flushing timing; a matchMedia mock whose matches value never actually changed when simulating a system-preference-change event; and a setItem-call-count assumption (throw only on the 2nd call) that didn't match the component's real single-call-per-setTheme behavior.

Net result: 0/27 → 27/27 in this file, and the underlying theme engine logic (persistence, system-preference resolution, error rollback, forcedTheme override) is now genuinely verified rather than just assumed passing/failing arbitrarily.

Disclosure: the full frontend suite still has 13 failing test files unrelated to this PR (confirmed identical on a clean main checkout) — e.g. SettingsWidget.tsx's hexToRgb throwing on certain inputs. Out of scope here; flagging for visibility.

Closes #1363
Closes #1362
Closes #1361
Closes #1360

… Dark Mode Theme Engine bugs

- Fix Node 22+/24+ native localStorage stub shadowing jsdom's real
  implementation in Vitest (src/test/setup.ts), which silently broke every
  test relying on localStorage across the repo.
- Fix RealTimeBalanceSync.test.tsx's framer-motion mock missing motion.div
  and motion.svg, which crashed every test in the file with
  'Element type is invalid'.
- Add axe-core accessibility scans across all RealTimeBalanceSync states
  (loaded, empty, error, loading) — zero violations found once the test
  could actually render.
- Fix RealTimeBalanceSync balance-row layout: long balance values (up to 7
  decimal places) were silently clipped by the list's overflow-hidden with
  no wrap/truncate handling; now wraps instead of hiding digits.
- Rewrite theme-context.test.tsx's Dark Mode Theme Engine suite: it
  replaced globalThis.document/window wholesale, which broke
  @testing-library/react's render() (no real document.body). Now spies on
  the real document/window so render() keeps working, adds the legacy
  addListener/removeListener MediaQueryList API next-themes needs
  internally, and fixes three incorrect test assumptions (an
  unobservable pre-effect timing check, a mocked matchMedia() that never
  reflected the simulated system-preference change, and a setItem
  call-count assumption that didn't match actual single-call behavior).

Closes emdevelopa#1363
Closes emdevelopa#1362
Closes emdevelopa#1361
Closes emdevelopa#1360
@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown

@davedumto is attempting to deploy a commit to the Emmanuel's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Aug 28, 2026

Copy link
Copy Markdown

@davedumto Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@emdevelopa
emdevelopa merged commit c7d497a into emdevelopa:main Aug 28, 2026
1 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants