Skip to content

Commit 85fef05

Browse files
test(mosaic): restore mocks through config and run select layout tests in Chromium (#10454)
1 parent eec2699 commit 85fef05

62 files changed

Lines changed: 334 additions & 542 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.changeset/mosaic-mock-hygiene.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

‎.github/workflows/ci.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,9 @@ jobs:
428428
- name: Run feature tests
429429
run: pnpm --filter @clerk/mosaic test:feature
430430

431+
- name: Run browser tests
432+
run: pnpm --filter @clerk/mosaic test:browser
433+
431434
- name: Upload failure artifacts
432435
if: ${{ cancelled() || failure() }}
433436
uses: actions/upload-artifact@v4.6.2

‎packages/mosaic/docs/testing.md‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,18 @@ Existing tests that break these rules move when their folder next changes.
9595
pnpm --filter @clerk/mosaic test # unit tests (jsdom and primitives)
9696
pnpm --filter @clerk/mosaic test:feature # feature tests (Chromium)
9797
pnpm --filter @clerk/mosaic test:feature user-button # one feature
98+
pnpm --filter @clerk/mosaic test:browser # layout tests (Chromium)
9899
pnpm --filter @clerk/mosaic exec vitest run # every project
99100
```
100101

101102
CI runs feature tests in their own `Mosaic Feature Tests` job, so the repo-wide
102103
unit test job never needs a browser.
103104

104-
Feature tests need Chromium: run `pnpm playwright install chromium` once. They
105+
jsdom and happy-dom have no layout engine, so every rect is 0 and a test that
106+
asserts on position, size or scrolling always passes there. Name such a test
107+
`*.browser.test.tsx` so it runs in Chromium, in the same CI job.
108+
109+
Feature and browser tests need Chromium: run `pnpm playwright install chromium` once. They
105110
import the built `@clerk/clerk-js`, so run `pnpm turbo build --filter=@clerk/clerk-js`
106111
after changing it. Failure screenshots land in `packages/mosaic/.vitest/`.
107112
Console output from passing tests only shows with `--reporter=verbose`.

‎packages/mosaic/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"lint:attw": "attw --pack . --exclude-entrypoints styles.css --profile esm-only",
5959
"lint:publint": "publint",
6060
"test": "vitest run --project primitives --project mosaic",
61+
"test:browser": "vitest run --project browser",
6162
"test:ci": "vitest run --project primitives --project mosaic --maxWorkers=70%",
6263
"test:feature": "vitest run --project feature",
6364
"test:webkit": "vitest run --project webkit",

‎packages/mosaic/src/blocks/confirm/__tests__/confirm.controller.test.ts‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ClerkRuntimeError } from '@clerk/shared/error';
22
import { act, renderHook, waitFor } from '@testing-library/react';
3-
import { afterEach, describe, expect, it, vi } from 'vitest';
3+
import { describe, expect, it, vi } from 'vitest';
44

55
import { deferred } from '../../../__tests__/async';
66
import { createActor } from '../../../machine/create-actor';
@@ -9,9 +9,6 @@ import { confirmMachine, useConfirmController } from '../confirm.controller';
99

1010
const blocked = new SaveError({ global: { code: 'action_blocked', message: 'Raw server sentence.' } });
1111
const cancelled = new ClerkRuntimeError('cancelled', { code: 'reverification_cancelled' });
12-
afterEach(() => {
13-
vi.restoreAllMocks();
14-
});
1512

1613
function start() {
1714
const actor = createActor(confirmMachine).start();

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { reverificationError } from '@clerk/shared/authorization-errors';
22
import { act, screen, waitFor } from '@testing-library/react';
33
import userEvent from '@testing-library/user-event';
44
import type { ReactNode } from 'react';
5-
import { afterEach, describe, expect, it, vi } from 'vitest';
5+
import { describe, expect, it, vi } from 'vitest';
66

77
import { deferred } from '../../../__tests__/async';
88
import { clerkApiError } from '../../../__tests__/clerk-errors';
@@ -25,10 +25,6 @@ const session = fapiSession({ id: 'sess_1', user: fapiUser({ id: 'user_1' }) });
2525
const VERIFY = '/v1/client/sessions/sess_1/verify';
2626
const TITLE = 'Remove connected account';
2727

28-
afterEach(() => {
29-
vi.restoreAllMocks();
30-
});
31-
3228
function Host<Payload>({
3329
options,
3430
children,

‎packages/mosaic/src/components/action-bar/action-bar.test.tsx‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as stylex from '@stylexjs/stylex';
22
import { render, screen, waitFor } from '@testing-library/react';
33
import userEvent from '@testing-library/user-event';
44
import React from 'react';
5-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5+
import { beforeEach, describe, expect, it, vi } from 'vitest';
66

77
import { PortalProvider } from '../../primitives/portal';
88
import { Dialog } from '../dialog';
@@ -65,10 +65,6 @@ beforeEach(() => {
6565
vi.spyOn(document.documentElement, 'clientHeight', 'get').mockReturnValue(768);
6666
});
6767

68-
afterEach(() => {
69-
vi.restoreAllMocks();
70-
});
71-
7268
describe('Mosaic ActionBar', () => {
7369
it('renders a labelled toolbar tied to its table and described by its count', () => {
7470
render(<BulkActions />);

‎packages/mosaic/src/components/avatar/avatar.test.tsx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { render, screen, waitFor } from '@testing-library/react';
33
import React from 'react';
44
import { flushSync } from 'react-dom';
55
import { createRoot } from 'react-dom/client';
6-
import { afterEach, describe, expect, it, vi } from 'vitest';
6+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
77

88
import { Pen } from '../../icons/glyphs/pen';
99
import { Icon } from '../icon';
@@ -45,7 +45,9 @@ class MockImage {
4545
}
4646
}
4747

48-
vi.stubGlobal('Image', MockImage);
48+
beforeEach(() => {
49+
vi.stubGlobal('Image', MockImage);
50+
});
4951

5052
afterEach(() => {
5153
outcomes = {};
@@ -365,9 +367,8 @@ describe('Mosaic Avatar', () => {
365367
});
366368

367369
it('throws when a part is rendered outside <Avatar.Root>', () => {
368-
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
370+
vi.spyOn(console, 'error').mockImplementation(() => {});
369371
expect(() => render(<Avatar.Fallback>CN</Avatar.Fallback>)).toThrow(/must be rendered inside <Avatar.Root>/);
370372
expect(() => render(<Avatar.Icon />)).toThrow(/must be rendered inside <Avatar.Root>/);
371-
spy.mockRestore();
372373
});
373374
});

‎packages/mosaic/src/components/dialog/__tests__/alert-dialog.test.tsx‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ describe('role="alertdialog"', () => {
8383

8484
expect(document.querySelector('.cl-dialog-popup')).toHaveAttribute('data-variant', 'card');
8585
expect(warn).not.toHaveBeenCalled();
86-
warn.mockRestore();
8786
});
8887

8988
// A corner X answers the question by leaving, which is the one thing an alert must not offer.
@@ -276,7 +275,6 @@ describe('dev warnings', () => {
276275

277276
expect(warn).toHaveBeenCalledWith(expect.stringContaining('no description'));
278277
expect(warn).toHaveBeenCalledWith(expect.stringContaining('<Card.Description>'));
279-
warn.mockRestore();
280278
});
281279

282280
it('does not ask a plain dialog for a description', async () => {
@@ -296,7 +294,6 @@ describe('dev warnings', () => {
296294
await settle();
297295

298296
expect(warn).not.toHaveBeenCalled();
299-
warn.mockRestore();
300297
});
301298

302299
// The name warning skipped any role but `dialog` before alert dialogs existed, which would have
@@ -322,7 +319,6 @@ describe('dev warnings', () => {
322319

323320
expect(warn).toHaveBeenCalledWith(expect.stringContaining('no accessible name'));
324321
expect(warn).toHaveBeenCalledWith(expect.stringContaining('<Card.Title>'));
325-
warn.mockRestore();
326322
});
327323

328324
it('stays quiet when both are supplied', async () => {
@@ -332,7 +328,6 @@ describe('dev warnings', () => {
332328
await settle();
333329

334330
expect(warn).not.toHaveBeenCalled();
335-
warn.mockRestore();
336331
});
337332
});
338333

@@ -371,7 +366,6 @@ describe('stacked on another dialog', () => {
371366
expect(popups[0]).toHaveAttribute('data-stack-base', '');
372367
expect(popups[1]).toHaveAttribute('data-stacked', '');
373368
expect(warn).not.toHaveBeenCalled();
374-
warn.mockRestore();
375369
});
376370
});
377371

@@ -454,6 +448,5 @@ describe('showCloseButton', () => {
454448
await settle();
455449

456450
expect(warn).not.toHaveBeenCalled();
457-
warn.mockRestore();
458451
});
459452
});

‎packages/mosaic/src/components/dialog/__tests__/dialog.test.tsx‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,6 @@ describe('nested Mosaic Dialogs', () => {
310310
await user.click(screen.getByRole('button', { name: 'Add email' }));
311311

312312
expect(warn).toHaveBeenCalledWith(expect.stringContaining('variant="profile"'));
313-
warn.mockRestore();
314313
});
315314

316315
it('warns when a fullscreen profile opens inside another dialog', async () => {
@@ -321,7 +320,6 @@ describe('nested Mosaic Dialogs', () => {
321320
await user.click(screen.getByRole('button', { name: 'Add email' }));
322321

323322
expect(warn).toHaveBeenCalledWith(expect.stringContaining('variant="fullscreen"'));
324-
warn.mockRestore();
325323
});
326324

327325
// A card over a profile is the delete-account confirmation: a `Card` inside a `card` dialog.
@@ -333,7 +331,6 @@ describe('nested Mosaic Dialogs', () => {
333331
await user.click(screen.getByRole('button', { name: 'Add email' }));
334332

335333
expect(warn).not.toHaveBeenCalled();
336-
warn.mockRestore();
337334
});
338335
});
339336

@@ -463,7 +460,6 @@ describe('Dialog.CloseButton', () => {
463460
);
464461

465462
expect(warn).toHaveBeenCalledWith(expect.stringContaining('alert dialog'));
466-
warn.mockRestore();
467463
});
468464
});
469465

@@ -665,15 +661,13 @@ describe('compactPlacement', () => {
665661

666662
expect(classesOf('.cl-dialog-popup')).not.toEqual(expect.arrayContaining(atomFor(probe.anchored)));
667663
expect(warn).toHaveBeenCalledWith(expect.stringContaining('takes no placement'));
668-
warn.mockRestore();
669664
});
670665

671666
it('warns when a fullscreen profile is given a placement', () => {
672667
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
673668
renderPlacement('sheet', 'fullscreen');
674669

675670
expect(warn).toHaveBeenCalledWith(expect.stringContaining('variant="fullscreen"'));
676-
warn.mockRestore();
677671
});
678672

679673
it('keeps a profile centered by default without warning', () => {
@@ -682,7 +676,6 @@ describe('compactPlacement', () => {
682676

683677
expect(classesOf('.cl-dialog-popup')).not.toEqual(expect.arrayContaining(atomFor(probe.anchored)));
684678
expect(warn).not.toHaveBeenCalledWith(expect.stringContaining('takes no placement'));
685-
warn.mockRestore();
686679
});
687680
});
688681

@@ -764,7 +757,6 @@ describe('accessible name warning', () => {
764757

765758
expect(warn).toHaveBeenCalledWith(expect.stringContaining('no accessible name'));
766759
expect(warn).toHaveBeenCalledWith(expect.stringContaining(`<${titlePart}>`));
767-
warn.mockRestore();
768760
});
769761

770762
it('does not warn when the card inside it supplies the name', async () => {
@@ -780,7 +772,6 @@ describe('accessible name warning', () => {
780772
await settle();
781773

782774
expect(warn).not.toHaveBeenCalled();
783-
warn.mockRestore();
784775
});
785776

786777
// The name can come from the consumer instead of from a Title, and an `aria-label` on the popup
@@ -796,6 +787,5 @@ describe('accessible name warning', () => {
796787
await settle();
797788

798789
expect(warn).not.toHaveBeenCalled();
799-
warn.mockRestore();
800790
});
801791
});

0 commit comments

Comments
 (0)