Skip to content

fix: catch AbortErrors in storybook tests and patch @reatom/core#17

Open
Guria wants to merge 6 commits into
mainfrom
fix/abort-error-guard-and-reatom-patch
Open

fix: catch AbortErrors in storybook tests and patch @reatom/core#17
Guria wants to merge 6 commits into
mainfrom
fix/abort-error-guard-and-reatom-patch

Conversation

@Guria
Copy link
Copy Markdown
Owner

@Guria Guria commented May 28, 2026

Problem

Storybook tests produce many AbortError messages in the connect logger output that go undetected. These indicate issues in the routing/async lifecycle that should be tracked.

Additionally, @reatom/core has a performance issue where urlAtom proactively evaluates ALL registered route loaders on every URL change — even for non-matching routes. Combined with isSomeLoaderPending (used by GlobalLoader) reading route.loader.pending() for all routes, this forces every non-matching route loader to evaluate and produce unnecessary unmatch AbortErrors.

Patch: @reatom/core@1001.0.0

Three optimizations in patches/@reatom%2Fcore@1001.0.0.patch:

  1. urlAtom init hook — only trigger routeAtom.loader() when routeAtom() returns truthy (route matches)
  2. urlAtom set handler — same match check in the _enqueue compute loop
  3. isSomeLoaderPending — check route.match() before reading route.loader.pending(), preventing forced loader evaluation for non-matching routes

These changes are safe because:

  • Non-matching route loaders still abort correctly through the computed dependency chain when a route transitions from matching to non-matching
  • The optimizations only skip proactive pre-evaluation of loaders that would immediately abort with "unmatch"

Other fixes

  • AbortError guard (.storybook/abortErrorGuard.ts) — addGlobalExtension + withCallHook on every .onReject action to collect AbortErrors during tests. Route loader aborts are filtered (expected lifecycle).
  • conversationUnreadCountAtom — removed redundant withConnectHook that caused a concurrent abort (double-fetch on first connection)
  • Storybook URL setup — auth set BEFORE urlAtom.go() to prevent concurrent loader re-evaluations
  • Timer accessibility — added aria-label="Custom duration" to timer input
  • Mobile master-detail tests — wait for list visibility after goBack()
  • connectLogger in tests — disabled via per-task VITE_CONNECT_LOGGER=false
  • Vitest detection — fixed broken import.meta.env.VITEST (always undefined in browser context) → use __vitest_worker__

Result

  • 323/323 tests pass
  • Zero AbortErrors in test output
  • Clean ~10-line terminal output (was thousands of lines of connectLogger spam)

Summary by CodeRabbit

  • New Features

    • Added many Storybook integration stories and scenarios (Articles, Chat, Connections, Listings, Detail and direct-URL flows) with attached play/tests and mobile variants.
  • Bug Fixes

    • Route loaders now run only for active routes, reducing unnecessary loader runs.
    • Storybook tests now detect and fail on aborted loader errors to improve test reliability.
  • Accessibility

    • Added an aria-label for the timer custom-duration input.
  • Refactor

    • Simplified unread-count async handling.
  • Tests

    • Added route/loader contract tests and refined test-run configuration.

Copilot AI review requested due to automatic review settings May 28, 2026 23:39
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de5d492a-f6f9-4e79-a1b1-73998a17d7ed

📥 Commits

Reviewing files that changed from the base of the PR and between fac6e93 and 2170bb9.

📒 Files selected for processing (19)
  • .storybook/abortErrorGuard.ts
  • .storybook/preview.tsx
  • src/app/integration/Articles.detail.stories.tsx
  • src/app/integration/Articles.direct-url.stories.tsx
  • src/app/integration/Articles.list-request.stories.tsx
  • src/app/integration/Articles.list.stories.tsx
  • src/app/integration/Articles.navigation.stories.tsx
  • src/app/integration/Articles.stories.tsx
  • src/app/integration/Chat.detail.stories.tsx
  • src/app/integration/Chat.direct-url.stories.tsx
  • src/app/integration/Chat.list-request.stories.tsx
  • src/app/integration/Chat.list.stories.tsx
  • src/app/integration/Chat.stories.tsx
  • src/app/integration/Connections.detail.stories.tsx
  • src/app/integration/Connections.direct-url.stories.tsx
  • src/app/integration/Connections.list-request.stories.tsx
  • src/app/integration/Connections.list.stories.tsx
  • src/app/integration/Connections.navigation.stories.tsx
  • src/app/integration/Connections.stories.tsx
💤 Files with no reviewable changes (3)
  • src/app/integration/Articles.stories.tsx
  • src/app/integration/Chat.stories.tsx
  • src/app/integration/Connections.stories.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/app/integration/Connections.list.stories.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • .storybook/preview.tsx

Walkthrough

This PR enhances test infrastructure and Storybook reliability: it isolates the connect logger from tests, captures and reports abort errors during Storybook tests, adds a pre-navigation callback, gates route loader execution to active routes, hardens mobile test interactions and story metadata, and simplifies an unread-count atom.

Changes

Test Infrastructure and Component Reliability

Layer / File(s) Summary
Connect logger test isolation
.config/mise/conf.d/tasks-test.toml, src/setup.ts
Test tasks set VITE_CONNECT_LOGGER = "false", and setup conditionally starts connectLogger only when appropriate for dev.
Reatom route loader conditional execution
patches/@reatom%2Fcore@1001.0.0.patch, package.json
Patch ensures route loaders run only for active routes and isSomeLoaderPending considers route.match() when reporting pending loaders; package.json adds msw patched dependency entry.
Storybook abort error capture and reporting
.storybook/abortErrorGuard.ts, .storybook/preview.tsx
Add an abort-error collector with clearAbortErrors()/drainAbortErrors()/formatAbortErrors() and wire preview beforeEach to clear before tests, conditionally set viewport in Vitest, and fail tests on drained abort errors.
Storybook navigation setup callback
.storybook/setupStorybookUrl.ts, .storybook/preview.tsx
setupStorybookUrl accepts an optional beforeNavigate callback invoked before urlAtom.go; ReatomDecorator uses it to set authenticated test state and ensures cleanup clears abort errors.
Test projects and route-contract tests
vitest.config.ts, src/shared/test/reatomRouteContracts.test.ts
Add a unit Vitest project and a test suite verifying that non-matching route loaders are not executed and pending checks ignore them.
New Storybook integration tests
src/app/integration/* (Articles, Chat, Connections)
Add many integration story modules covering list/detail flows, direct URLs, error/retry, loading states, mobile variants, and attached play/tests.
Mobile test interaction reliability and story metadata formatting
src/app/integration/*.stories.tsx (multiple files)
Mobile stories wait for list elements after back navigation; many Storybook parameters objects reformatted to multiline explicit objects.
Timer input accessibility and test locator
src/pages/timer/testing.ts, src/pages/timer/ui/TimerPage.tsx
Add aria-label="Custom duration" to the input and tighten the test locator to role('textbox', 'Custom duration').
Conversation unread count atom cleanup
src/entities/conversation/model/unreadCount.ts, src/pages/chat/ui/ChatNavItem.tsx
Remove withConnectHook and keep only extend(withAsyncData()) for the unread-count atom; derive route-specific unread only when chatRoute.match() is true; fallback unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Guria/modern-stack#5: Modifies Storybook runtime and Reatom setup; overlaps with setup and unread-count changes.
  • Guria/modern-stack#4: Previous edits to .config/mise/conf.d/tasks-test.toml; related test task env adjustments.

Poem

🐰 I cleared the logs so tests sleep tight,

I gathered aborts before they bite,
Routes wake only when they should,
Labels and waits make tests behave good,
Hoppity, the suite sleeps through the night.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: catching AbortErrors in Storybook tests and patching @reatom/core. It directly relates to the core objectives of fixing undetected AbortError messages and route loader evaluation issues.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/abort-error-guard-and-reatom-patch

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 28, 2026

Fallow audit report

Found 1 finding.

Details
Severity Rule Location Description
minor fallow/high-crap-score .storybook/abortErrorGuard.ts:76 '<arrow>' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)

Generated by fallow.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR reduces noisy AbortError output in Storybook browser tests and improves routing/loader performance by patching @reatom/core to avoid evaluating non-matching route loaders on URL changes.

Changes:

  • Add a Storybook test-time guard to collect and fail on unexpected Reatom AbortErrors, and fix Vitest detection in the browser context.
  • Patch @reatom/core@1001.0.0 to avoid proactively evaluating loaders for non-matching routes and to avoid forcing loader evaluation in isSomeLoaderPending.
  • Apply several test/stability/accessibility tweaks (timer input labeling, mobile master-detail navigation waits, disable connect logger in test tasks, simplify unread count atom).

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/setup.ts Enables connectLogger by default in dev unless explicitly disabled.
src/pages/timer/ui/TimerPage.tsx Adds accessible label for the custom duration input.
src/pages/timer/testing.ts Updates test locator to target the labeled timer input.
src/entities/conversation/model/unreadCount.ts Removes redundant connect hook from unread count async atom.
src/app/integration/Connections.stories.tsx Stabilizes mobile navigation test by waiting for list visibility after back navigation.
src/app/integration/Articles.stories.tsx Stabilizes mobile navigation test by waiting for list visibility after back navigation.
.storybook/setupStorybookUrl.ts Adds a pre-navigation hook so auth/setup runs before urlAtom.go().
.storybook/preview.tsx Integrates abort error guard + fixes Vitest detection + adds per-story abort error assertion.
.storybook/abortErrorGuard.ts Adds global onReject call hook to collect unexpected abort errors during Storybook tests.
patches/@reatom%2Fcore@1001.0.0.patch Patches Reatom routing internals to avoid non-matching loader evaluation and pending checks.
package.json Registers the @reatom/core patch via patchedDependencies.
bun.lock Records patchedDependencies entry for Bun.
.config/mise/conf.d/tasks-test.toml Disables connect logger in test-related tasks to reduce noisy output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .storybook/abortErrorGuard.ts Outdated
Comment on lines +28 to +30
// Reatom routing control flow — urlAtom proactively triggers all
// registered route loaders on every URL change, and nested loaders
// await their parents which can cause concurrent re-evaluation.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a12c6cf666

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (url !== newUrl) {
_enqueue(() => {
- for (const [, routeAtom] of Object.entries(urlAtom.routes)) routeAtom.loader();
+ for (const [, routeAtom] of Object.entries(urlAtom.routes)) if (routeAtom()) routeAtom.loader();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve loader aborts when routes stop matching

When navigating away from a route with an in-flight loader, this guard skips calling that route's loader after routeAtom() becomes null. That removes the proactive recomputation that used to enter the loader, see the unmatch state, and trigger the withAbort/reject path; with the new isSomeLoaderPending short-circuit, unmatched loaders are also no longer kept subscribed via pending(). As a result, slow requests for the previous route can continue and fulfill into route.loader.data() after the route is no longer active instead of being aborted on navigation.

Useful? React with 👍 / 👎.

@Guria Guria force-pushed the fix/abort-error-guard-and-reatom-patch branch 2 times, most recently from e9c0966 to 6598873 Compare May 28, 2026 23:52
Add abort error detection guard to storybook test harness that fails
tests when non-route-loader AbortErrors are detected. Route loader
aborts (unmatch/concurrent) are expected Reatom routing lifecycle and
are filtered.

Patch @reatom/core to reduce unnecessary route loader evaluations:
- urlAtom init/set loops now only trigger loaders for matching routes
- isSomeLoaderPending only checks pending state for matching routes

These optimizations prevent isSomeLoaderPending (used by GlobalLoader)
from forcing ALL route loaders to evaluate, which was causing every
non-matching route to produce an unmatch AbortError on every URL change.

Other fixes:
- Remove redundant withConnectHook from conversationUnreadCountAtom
  that caused a concurrent abort (double-fetch on first connection)
- Set auth before URL navigation in storybook setup to prevent
  concurrent loader re-evaluations from auth state changes
- Fix timer input accessibility (add aria-label)
- Fix mobile master-detail tests to wait for list after goBack
- Disable connectLogger during test runs (VITE_CONNECT_LOGGER per-task)
- Fix broken import.meta.env.VITEST detection in browser tests
@Guria Guria force-pushed the fix/abort-error-guard-and-reatom-patch branch from 6598873 to 878a1f4 Compare May 29, 2026 00:00
Guria added 2 commits May 31, 2026 01:06
 - split Connections, Chat, and Articles integration stories by route mode
- keep Storybook AbortError guard strict by default
- add explicit helper for expected matched-route loader teardown aborts
- restore isolated multi-navigation coverage for Articles
- reduce cross-story/test abort attribution noise without hiding real loader misuse
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants