Skip to content

test(ui): add AccountButton connected integration test#9191

Open
alexcarpenter wants to merge 11 commits into
carp/account-button-controllerfrom
carp/account-button-integration
Open

test(ui): add AccountButton connected integration test#9191
alexcarpenter wants to merge 11 commits into
carp/account-button-controllerfrom
carp/account-button-integration

Conversation

@alexcarpenter

@alexcarpenter alexcarpenter commented Jul 17, 2026

Copy link
Copy Markdown
Member

Stacked on #9185. Adds busy/loading UX to the connected AccountButton + an end-to-end integration test.

Changes

Busy/loading states (controller + container)

  • Container tracks single in-flight action via pendingKey. Clicked affordance spins, others disable (blocks double-submit). Popover closes only on success; busy clears on rejection so UI never hangs.
  • Skeleton trigger while controller loads.
  • Async callbacks typed void | Promise<unknown> to satisfy no-misused-promises; fire-and-forget navigations void-wrapped.

Spin-delay (useSpinDelay hook + tests)

  • Leaner rework of smeijer/spin-delay around one nullable T | null flag. Fast actions resolve before delay → no spinner flash; any spinner shown held for min duration → no flicker. Re-entry still guarded on immediate pendingKey; only visuals delayed.

View decomposition (1032 → ~600 lines)

  • Slot recipe → account-button.recipe.ts, data-contract types + accountBusyKeysaccount-button.types.ts.
  • BusyButton primitive replaces 4 hand-wired busy buttons (inline join/accept, hover sign-out, header actions, footer sign-out-all).
  • View props renamed AccountButtonViewProps; dead status field dropped. No behavior change.

Spinner Mosaic primitive.

Integration test (14 tests)

Renders real view through real controller against mocked @clerk/shared/react, drives real popover + rows/actions, asserts Clerk calls + close-on-success.

  • Nothing rendered while loading / no active user
  • Trigger renders, popover opens on click
  • Select org / personal → setActive(...) + close
  • Switch account → setActive({session}) + close
  • Sign out session / all → signOut(...) + close
  • Accept invitation / suggestion → accept() + revalidate() + close
  • Manage org / account, Create org → navigate(...), popover stays open

Covers every closeOnSuccess callback + every navigation through real DOM.

Not re-tested here (owned elsewhere): a11y/focus-trap (popover primitive suite), data derivation/gating (controller.test.tsx). Per-row sign-out is hover-revealed (pointer-events:none), so that one uses fireEvent.click (noted inline).

Parity gaps (tracked separately, not test gaps): no hidePersonal, force-org-selection, or trigger pending-counter badge yet.

Changeset

Empty — feature ships under #9184 / #9185, no user-facing change here.

@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
swingset Ready Ready Preview, Comment Jul 20, 2026 9:23pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
clerk-js-sandbox Skipped Skipped Jul 20, 2026 9:23pm

Request Review

@changeset-bot

changeset-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 22e6748

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a7f3fe3-0f9b-4531-87b3-6dfbb33b079e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jul 17, 2026
@macroscopeapp

macroscopeapp Bot commented Jul 17, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved

This PR adds integration tests for the AccountButton component with no production code changes. The empty changeset confirms no packages are affected, making this a low-risk test-only addition.

You can customize Macroscope's approvability policy. Learn more.

@macroscopeapp
macroscopeapp Bot dismissed their stale review July 17, 2026 16:06

Dismissing prior approval to re-evaluate 088fc5a

…ccountButton

Gate add-account and sign-out-of-all on singleSessionMode in the controller, and render pending invitations/suggestions even with no organization memberships.
… suggestions

- add afterSelectOrganizationUrl/afterSelectPersonalUrl props (mirroring
  OrganizationSwitcher) instead of the wrong afterCreateOrganizationUrl redirect
- restore sign-out redirect URLs via buildAfterSignOutUrl /
  buildAfterMultiSessionSingleSignOutUrl
- render accepted suggestions as a non-actionable 'Pending approval' label
Track the single in-flight action in the connected container: the clicked
affordance shows a spinner while others disable to block double-submits, the
popover closes only on success, and busy state clears on rejection so the UI
never hangs. Add a Mosaic Spinner primitive and a skeleton trigger shown while
the controller loads.
…mises

The controller's setActive/signOut/navigate callbacks return promises but were declared () => void, tripping no-misused-promises. Declare the container-awaited actions as void | Promise<unknown> and void-wrap the fire-and-forget navigations; the view keeps its () => void contract.
… quick actions

Add a useSpinDelay hook (a leaner rework of smeijer/spin-delay around a single
visible flag) and gate the AccountButton popover spinner through it. Fast actions
that resolve before the delay never flash a spinner, and any spinner shown is held
for a minimum duration so it can't flicker. The container still guards re-entry on
the immediate pendingKey; only the visual feedback is delayed.
Split the 1032-line account-button.view.tsx into focused modules: the slot
recipe (+ registry augmentation) moves to account-button.recipe.ts and the
data-contract types + accountBusyKeys to account-button.types.ts, leaving the
view at ~600 lines of pure components.

Generalize useSpinDelay from a boolean to a nullable value (T | null) so it
carries the last non-null value through the minDuration hold itself. The
container drops its during-render ref and derives the delayed pending key in
one line. No behavior change.
- Rename the view's props type to AccountButtonViewProps so AccountButtonProps
  is no longer exported with two different meanings across sibling files
- Drop the dead status field from AccountButtonData; the view only ever renders
  ready data, and the controller/container handshake keeps its own discriminant
- Extract a single BusyButton primitive to replace four hand-wired busy buttons
  (inline join/accept, hover sign-out, header actions, footer sign-out-all),
  removing the duplicated disabled/spinner idiom

No behavior change; 38 account-button tests pass, tsc and eslint clean.
Route the controller through useOrganizationListInView so memberships,
suggestions, and invitations page in on scroll instead of showing only
the first page. Expose an opaque loadMoreRef plus hasMoreRows/isFetchingRows
on the ready contract; the view renders a bounded scroll container with a
sentinel and end-of-list spinner. Accounts are unaffected (not paginated).

41 account-button tests pass; tsc and eslint clean.
The swingset AccountButton story imported the removed AccountButtonProps
type and passed status='ready', both of which broke after the view API
changed (AccountButtonViewProps; status now lives on the controller, not
the presentational view). Update the story to match.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant