Skip to content

Commit 38c4df7

Browse files
committed
test(mosaic): add openDialog helper that waits for initial focus
1 parent 8376701 commit 38c4df7

8 files changed

Lines changed: 63 additions & 44 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { screen, waitFor } from '@testing-library/react';
2+
import type { UserEvent } from '@testing-library/user-event';
3+
import { expect } from 'vitest';
4+
5+
export async function openDialog(
6+
user: UserEvent,
7+
trigger: HTMLElement,
8+
options: { role?: 'dialog' | 'alertdialog'; name?: string } = {},
9+
) {
10+
await user.click(trigger);
11+
const dialog = await screen.findByRole(options.role ?? 'dialog', { name: options.name });
12+
await waitFor(() => expect(dialog.contains(document.activeElement)).toBe(true));
13+
return dialog;
14+
}

‎packages/mosaic/src/blocks/destructive/__tests__/destructive.feature.test.tsx‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event';
44
import { http, HttpResponse } from 'msw';
55
import { describe, expect, it, vi } from 'vitest';
66

7+
import { openDialog } from '../../../__tests__/feature/dialog';
78
import { fapiUrl, serveFapi, worker } from '../../../__tests__/feature/fake-fapi';
89
import { fapiClient, fapiSession, fapiUser } from '../../../__tests__/feature/fapi';
910
import { renderWithClerk } from '../../../__tests__/feature/render';
@@ -71,9 +72,7 @@ function Host({
7172
}
7273

7374
async function typeConfirmation(user: ReturnType<typeof userEvent.setup>) {
74-
await user.click(screen.getByRole('button', { name: 'Open' }));
75-
const dialog = await screen.findByRole('dialog');
76-
await waitFor(() => expect(dialog).toContainElement(document.activeElement as HTMLElement));
75+
await openDialog(user, screen.getByRole('button', { name: 'Open' }));
7776
await user.type(screen.getByRole('textbox'), 'Delete');
7877
}
7978

‎packages/mosaic/src/features/api-keys/__tests__/api-keys-table.feature.test.tsx‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event';
44
import { http, HttpResponse } from 'msw';
55
import { describe, expect, it, onTestFinished, vi } from 'vitest';
66

7+
import { openDialog } from '../../../__tests__/feature/dialog';
78
import { type FakeFapiSeed, fapiUrl, holdRequests, serveFapi, worker } from '../../../__tests__/feature/fake-fapi';
89
import {
910
fapiApiKey,
@@ -72,8 +73,9 @@ async function expectFallback(seed: FakeFapiSeed, props: Partial<APIKeysTablePro
7273
}
7374

7475
async function openCreate(user: User) {
75-
await user.click(await screen.findByRole('button', { name: 'Create API key' }));
76-
return screen.findByRole('dialog', { name: 'Add new API key' });
76+
return openDialog(user, await screen.findByRole('button', { name: 'Create API key' }), {
77+
name: 'Add new API key',
78+
});
7779
}
7880

7981
async function fillCreate(user: User, dialog: HTMLElement, name: string, expiration: string) {
@@ -84,8 +86,9 @@ async function fillCreate(user: User, dialog: HTMLElement, name: string, expirat
8486

8587
async function startRevoke(user: User, name: string) {
8688
await user.click(await screen.findByRole('button', { name: `Manage ${name}` }));
87-
await user.click(await screen.findByRole('menuitem', { name: 'Revoke key' }));
88-
return screen.findByRole('dialog', { name: `Revoke ${name}?` });
89+
return openDialog(user, await screen.findByRole('menuitem', { name: 'Revoke key' }), {
90+
name: `Revoke ${name}?`,
91+
});
8992
}
9093

9194
async function openRevoke(user: User, name: string) {

‎packages/mosaic/src/features/organization-profile/organization-profile-general-panel.feature.test.tsx‎

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { act, screen, waitFor, within } from '@testing-library/react';
33
import userEvent from '@testing-library/user-event';
44
import { describe, expect, it } from 'vitest';
55

6+
import { openDialog } from '../../__tests__/feature/dialog';
67
import { type FakeFapiSeed, holdRequests, serveFapi } from '../../__tests__/feature/fake-fapi';
78
import {
89
fapiClient,
@@ -81,8 +82,7 @@ describe('OrganizationProfileGeneralPanel', () => {
8182
it('updates only the changed name and shows the refreshed value', async () => {
8283
const { fapi } = await renderPanel();
8384
const user = userEvent.setup();
84-
await user.click(await screen.findByRole('button', { name: 'Edit name' }));
85-
const dialog = screen.getByRole('dialog');
85+
const dialog = await openDialog(user, await screen.findByRole('button', { name: 'Edit name' }));
8686
await user.clear(within(dialog).getByRole('textbox', { name: 'Name' }));
8787
await user.type(within(dialog).getByRole('textbox', { name: 'Name' }), 'Acme Inc');
8888
const update = holdRequests('post', '/v1/organizations/:organizationId');
@@ -100,8 +100,8 @@ describe('OrganizationProfileGeneralPanel', () => {
100100
const other = fapiOrganization({ id: 'org_2', name: 'Other' });
101101
const { clerk } = await renderPanel(signedIn({ organizations: [acme, other] }));
102102
const user = userEvent.setup();
103-
await user.click(await screen.findByRole('button', { name: 'Edit name' }));
104-
await user.type(within(screen.getByRole('dialog')).getByRole('textbox', { name: 'Name' }), ' changed');
103+
const dialog = await openDialog(user, await screen.findByRole('button', { name: 'Edit name' }));
104+
await user.type(within(dialog).getByRole('textbox', { name: 'Name' }), ' changed');
105105
await act(() => clerk.setActive({ organization: other.id }));
106106
await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());
107107
await user.click(await screen.findByRole('button', { name: 'Edit name' }));
@@ -111,8 +111,7 @@ describe('OrganizationProfileGeneralPanel', () => {
111111
it('keeps a failed name draft and a field error, then resets after cancel', async () => {
112112
await renderPanel();
113113
const user = userEvent.setup();
114-
await user.click(await screen.findByRole('button', { name: 'Edit name' }));
115-
const dialog = screen.getByRole('dialog');
114+
const dialog = await openDialog(user, await screen.findByRole('button', { name: 'Edit name' }));
116115
const field = within(dialog).getByRole('textbox', { name: 'Name' });
117116
await user.clear(field);
118117
await user.type(field, 'Rejected');
@@ -128,12 +127,12 @@ describe('OrganizationProfileGeneralPanel', () => {
128127
expect(field).toHaveValue('Rejected');
129128
await user.click(within(dialog).getByRole('button', { name: 'Cancel' }));
130129
await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());
131-
await user.click(screen.getByRole('button', { name: 'Edit name' }));
132-
expect(within(screen.getByRole('dialog')).getByRole('textbox', { name: 'Name' })).toHaveValue('Acme');
133-
await user.clear(within(screen.getByRole('dialog')).getByRole('textbox', { name: 'Name' }));
134-
await user.type(within(screen.getByRole('dialog')).getByRole('textbox', { name: 'Name' }), 'Acme retry');
130+
const reopened = await openDialog(user, screen.getByRole('button', { name: 'Edit name' }));
131+
expect(within(reopened).getByRole('textbox', { name: 'Name' })).toHaveValue('Acme');
132+
await user.clear(within(reopened).getByRole('textbox', { name: 'Name' }));
133+
await user.type(within(reopened).getByRole('textbox', { name: 'Name' }), 'Acme retry');
135134
const retry = serveFapi(signedIn());
136-
await user.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Save changes' }));
135+
await user.click(within(reopened).getByRole('button', { name: 'Save changes' }));
137136
await waitFor(() => expect(retry.memberships[0]?.organization.name).toBe('Acme retry'));
138137
});
139138

@@ -155,8 +154,9 @@ describe('OrganizationProfileGeneralPanel', () => {
155154
it('requires a nonblank changed name before saving', async () => {
156155
await renderPanel();
157156
const user = userEvent.setup();
158-
await user.click(await screen.findByRole('button', { name: 'Edit name' }));
159-
const dialog = screen.getByRole('dialog', { name: 'Edit name' });
157+
const dialog = await openDialog(user, await screen.findByRole('button', { name: 'Edit name' }), {
158+
name: 'Edit name',
159+
});
160160
const field = within(dialog).getByRole('textbox', { name: 'Name' });
161161
await user.clear(field);
162162
await user.type(field, ' ');
@@ -166,8 +166,9 @@ describe('OrganizationProfileGeneralPanel', () => {
166166
it('shows the generic fallback for an unknown name error', async () => {
167167
await renderPanel();
168168
const user = userEvent.setup();
169-
await user.click(await screen.findByRole('button', { name: 'Edit name' }));
170-
const dialog = screen.getByRole('dialog', { name: 'Edit name' });
169+
const dialog = await openDialog(user, await screen.findByRole('button', { name: 'Edit name' }), {
170+
name: 'Edit name',
171+
});
171172
const field = within(dialog).getByRole('textbox', { name: 'Name' });
172173
await user.type(field, ' changed');
173174
const update = holdRequests('post', '/v1/organizations/:organizationId');
@@ -185,8 +186,7 @@ describe('OrganizationProfileGeneralPanel', () => {
185186
it('clears the slug while preserving the current name', async () => {
186187
const { fapi } = await renderPanel();
187188
const user = userEvent.setup();
188-
await user.click(await screen.findByRole('button', { name: 'Edit slug' }));
189-
const dialog = screen.getByRole('dialog');
189+
const dialog = await openDialog(user, await screen.findByRole('button', { name: 'Edit slug' }));
190190
await user.clear(within(dialog).getByRole('textbox', { name: 'Slug' }));
191191
const update = holdRequests('post', '/v1/organizations/:organizationId');
192192
await user.click(within(dialog).getByRole('button', { name: 'Save changes' }));
@@ -226,8 +226,9 @@ describe('OrganizationProfileGeneralPanel', () => {
226226
it('shows a slug-specific FAPI error at the field and keeps the draft', async () => {
227227
await renderPanel();
228228
const user = userEvent.setup();
229-
await user.click(await screen.findByRole('button', { name: 'Edit slug' }));
230-
const dialog = screen.getByRole('dialog', { name: 'Edit slug' });
229+
const dialog = await openDialog(user, await screen.findByRole('button', { name: 'Edit slug' }), {
230+
name: 'Edit slug',
231+
});
231232
const field = within(dialog).getByRole('textbox', { name: 'Slug' });
232233
await user.type(field, '-new');
233234
const update = holdRequests('post', '/v1/organizations/:organizationId');
@@ -249,8 +250,7 @@ describe('OrganizationProfileGeneralPanel', () => {
249250
</MosaicProvider>,
250251
);
251252
const user = userEvent.setup();
252-
await user.click(await screen.findByRole('button', { name: 'Edit slug' }));
253-
const dialog = screen.getByRole('dialog');
253+
const dialog = await openDialog(user, await screen.findByRole('button', { name: 'Edit slug' }));
254254
const field = within(dialog).getByRole('textbox', { name: 'Slug' });
255255
await user.clear(field);
256256
await user.type(field, 'new-slug');

‎packages/mosaic/src/features/user-profile/user-profile-passkeys-section/__tests__/user-profile-passkeys-interactions.feature.test.tsx‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { act, screen, waitFor } from '@testing-library/react';
1+
import { act, screen, waitFor, within } from '@testing-library/react';
22
import userEvent from '@testing-library/user-event';
33
import { afterEach, describe, expect, it, vi } from 'vitest';
44

5+
import { openDialog } from '../../../../__tests__/feature/dialog';
56
import { holdRequests, serveFapi } from '../../../../__tests__/feature/fake-fapi';
67
import { fapiClient, fapiEnvironment, fapiPasskey, fapiSession, fapiUser } from '../../../../__tests__/feature/fapi';
78
import { renderWithClerk } from '../../../../__tests__/feature/render';
@@ -35,10 +36,8 @@ function accounts() {
3536
async function openRename() {
3637
const user = userEvent.setup();
3738
await user.click(screen.getByRole('button', { name: 'Manage Alice laptop' }));
38-
await user.click(screen.getByRole('menuitem', { name: 'Rename' }));
39-
const input = screen.getByRole('textbox', { name: 'Passkey name' });
40-
await waitFor(() => expect(input).toHaveFocus());
41-
return { user, input };
39+
const dialog = await openDialog(user, screen.getByRole('menuitem', { name: 'Rename' }));
40+
return { user, input: within(dialog).getByRole('textbox', { name: 'Passkey name' }) };
4241
}
4342

4443
describe('Passkey interactions across resource changes', () => {

‎packages/mosaic/src/features/user-profile/user-profile-passkeys-section/__tests__/user-profile-passkeys-lifecycle.feature.test.tsx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { act, screen, waitFor, within } from '@testing-library/react';
22
import userEvent from '@testing-library/user-event';
33
import { afterEach, describe, expect, it, vi } from 'vitest';
44

5+
import { openDialog } from '../../../../__tests__/feature/dialog';
56
import { holdRequests, serveFapi } from '../../../../__tests__/feature/fake-fapi';
67
import {
78
fapiClient,
@@ -380,8 +381,8 @@ describe('Passkey identity and policy at action time', () => {
380381
const { clerk } = await renderWithClerk(<UserProfilePasskeysSection />);
381382
const user = userEvent.setup();
382383
await user.click(screen.getByRole('button', { name: 'Manage Alice laptop' }));
383-
await user.click(screen.getByRole('menuitem', { name: 'Rename' }));
384-
const input = screen.getByRole('textbox', { name: 'Passkey name' });
384+
const dialog = await openDialog(user, screen.getByRole('menuitem', { name: 'Rename' }));
385+
const input = within(dialog).getByRole('textbox', { name: 'Passkey name' });
385386
await user.clear(input);
386387
await user.type(input, 'Blocked rename');
387388
const environment = clerk.__internal_environment;
@@ -446,8 +447,8 @@ describe('Replacing instance policy while editing', () => {
446447
);
447448
const user = userEvent.setup();
448449
await user.click(screen.getByRole('button', { name: 'Manage Alice laptop' }));
449-
await user.click(screen.getByRole('menuitem', { name: 'Rename' }));
450-
const input = screen.getByRole('textbox', { name: 'Passkey name' });
450+
const dialog = await openDialog(user, screen.getByRole('menuitem', { name: 'Rename' }));
451+
const input = within(dialog).getByRole('textbox', { name: 'Passkey name' });
451452
await user.clear(input);
452453
await user.type(input, 'Blocked rename');
453454
const nextEnvironment = fapiEnvironment();

‎packages/mosaic/src/features/user-profile/user-profile-passkeys-section/__tests__/user-profile-passkeys-validation.feature.test.tsx‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { screen, waitFor } from '@testing-library/react';
1+
import { screen, waitFor, within } from '@testing-library/react';
22
import userEvent from '@testing-library/user-event';
33
import { http, HttpResponse } from 'msw';
44
import { afterEach, describe, expect, it, vi } from 'vitest';
55

6+
import { openDialog } from '../../../../__tests__/feature/dialog';
67
import { fapiUrl, serveFapi, worker } from '../../../../__tests__/feature/fake-fapi';
78
import { fapiClient, fapiEnvironment, fapiPasskey, fapiSession, fapiUser } from '../../../../__tests__/feature/fapi';
89
import { renderWithClerk } from '../../../../__tests__/feature/render';
@@ -37,8 +38,8 @@ describe('Validating a passkey name', () => {
3738
vi.spyOn(passkey, 'update').mockRejectedValue(new Error('Private native failure'));
3839
const user = userEvent.setup();
3940
await user.click(screen.getByRole('button', { name: 'Manage Laptop' }));
40-
await user.click(screen.getByRole('menuitem', { name: 'Rename' }));
41-
const input = screen.getByRole('textbox', { name: 'Passkey name' });
41+
const dialog = await openDialog(user, screen.getByRole('menuitem', { name: 'Rename' }));
42+
const input = within(dialog).getByRole('textbox', { name: 'Passkey name' });
4243
await user.type(input, ' renamed');
4344
await user.click(screen.getByRole('button', { name: 'Save' }));
4445
await waitFor(() => expect(screen.getByRole('alert')).toHaveTextContent('Impossible de sauvegarder.'));
@@ -65,8 +66,8 @@ describe('Validating a passkey name', () => {
6566
);
6667
const user = userEvent.setup();
6768
await user.click(screen.getByRole('button', { name: 'Manage Laptop' }));
68-
await user.click(screen.getByRole('menuitem', { name: 'Rename' }));
69-
const input = screen.getByRole('textbox', { name: 'Passkey name' });
69+
const dialog = await openDialog(user, screen.getByRole('menuitem', { name: 'Rename' }));
70+
const input = within(dialog).getByRole('textbox', { name: 'Passkey name' });
7071
await user.type(input, ' modifié');
7172
await user.click(screen.getByRole('button', { name: 'Save' }));
7273
await waitFor(() => expect(screen.getByText('Ce nom est invalide.')).toBeVisible());
@@ -89,8 +90,8 @@ describe('Validating a passkey name', () => {
8990
await renderWithClerk(<UserProfilePasskeysSection />);
9091
const user = userEvent.setup();
9192
await user.click(screen.getByRole('button', { name: 'Manage Laptop' }));
92-
await user.click(screen.getByRole('menuitem', { name: 'Rename' }));
93-
const input = screen.getByRole('textbox', { name: 'Passkey name' });
93+
const dialog = await openDialog(user, screen.getByRole('menuitem', { name: 'Rename' }));
94+
const input = within(dialog).getByRole('textbox', { name: 'Passkey name' });
9495
const save = screen.getByRole('button', { name: 'Save' });
9596
await user.clear(input);
9697
await user.type(input, '😀'.repeat(65));

0 commit comments

Comments
 (0)