Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/connected-accounts-reconnect-pending.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@ describe('connected accounts', () => {
await waitFor(() => expect(navigate).toHaveBeenCalledWith('https://accounts.example/authorize'));
});

it('holds the menu open with a spinner on Reconnect until the provider redirect', async () => {
serveFapi(signedIn([disconnectedGoogle]));
const { clerk } = await renderWithClerk(<UserProfileConnectedAccountsSection />);
const navigate = vi.spyOn(clerk, 'navigate').mockImplementation(() => Promise.resolve());
const request = holdRequests('post', '/v1/me/external_accounts');
const user = userEvent.setup();

try {
await startReconnect(user, 'Google');
await waitFor(() => expect(request.requests).toHaveLength(1));
expect(screen.getByRole('menu')).toBeVisible();
expect(screen.getByRole('menuitem', { name: 'Reconnect' })).toHaveAttribute('aria-busy', 'true');
expect(await screen.findByRole('progressbar', { name: 'Reconnect' })).toBeVisible();
} finally {
request.release();
}

await waitFor(() => expect(navigate).toHaveBeenCalledWith('https://accounts.example/authorize'));
});

it('reports an unavailable recovery when the account changes during redirect preparation', async () => {
serveFapi(signedIn([disconnectedGoogle]));
const redirect = deferred<string>();
Expand Down Expand Up @@ -286,6 +306,25 @@ describe('reconnect reverification', () => {
await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());
});

it('holds the Reconnect menu item until session verification opens', async () => {
await renderDisconnectedNeedingReverification();
const start = holdRequests('post', '/v1/client/sessions/:id/verify');
const user = userEvent.setup();

try {
await startReconnect(user, 'Google');
await waitFor(() => expect(start.requests).toHaveLength(1));
expect(screen.getByRole('menu')).toBeVisible();
expect(screen.getByRole('menuitem', { name: 'Reconnect' })).toHaveAttribute('aria-busy', 'true');
expect(screen.queryByText('Enter your current password to continue')).toBeNull();
} finally {
start.release();
}

expect(await screen.findByText('Enter your current password to continue')).toBeVisible();
await waitFor(() => expect(screen.queryByRole('menu')).toBeNull());
});

it('lets the user cancel verification quietly and retry the reconnect', async () => {
const { navigate } = await renderDisconnectedNeedingReverification();
const user = userEvent.setup();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,21 +80,28 @@ export function UserProfileConnectedAccountRowView({
account,
triggerRef,
isDisabled = false,
isReconnecting = false,
onReconnect,
onRemove,
}: {
account: UserProfileConnectedAccount;
triggerRef?: Ref<HTMLButtonElement>;
isDisabled?: boolean;
isReconnecting?: boolean;
onReconnect?: (id: string) => void;
onRemove?: (account: UserProfileConnectedAccount) => void;
}) {
const m = useMessages('userProfileConnectedAccounts');
const actions: ActionMenuAction[] = [];
if ((account.status === 'reconnect' || account.status === 'error') && onReconnect && !isDisabled) {
if (
(account.status === 'reconnect' || account.status === 'error') &&
onReconnect &&
(!isDisabled || isReconnecting)
) {
actions.push({
label: account.status === 'reconnect' ? m.reconnect : m.tryAgain,
onClick: () => onReconnect(account.id),
pending: isReconnecting,
});
}
if (onRemove) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export function UserProfileConnectedAccountsSectionView({
account={account}
triggerRef={registerTrigger(account.id)}
isDisabled={isBusy}
isReconnecting={pendingId === account.id && !reconnectReverification?.visible}
onReconnect={onReconnect}
onRemove={remove ? account => remove.open(account) : undefined}
/>
Expand Down
Loading