Skip to content

Commit 411d780

Browse files
committed
test(e2e): assert the profile page mirror and navigation mode
`useUserProfilePages` hardcodes the UserProfile's built-in page list as a mirror of clerk-js, and nothing at any level asserted the mirror was accurate. `openOrNavigate` was covered only by asserting `navigate` was called with a string, never that the app landed there.
1 parent 9165597 commit 411d780

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

integration/tests/mosaic-user-button.test.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,41 @@ export default function Page() {
8282
);
8383
}`;
8484

85+
// `pageOrder` names one custom page and nothing else, so `useCustomPages` sends every built-in
86+
// behind it. clerk-js puts any page it was not asked to move ahead of everything it was, which is
87+
// what makes the nav order readable as an assertion about the built-in list itself.
88+
const pageOrderPage = () => `'use client';
89+
import { UserButton } from '@clerk/nextjs/experimental/mosaic';
90+
91+
export default function Page() {
92+
return (
93+
<UserButton
94+
userProfileProps={{
95+
customPages: [
96+
{
97+
path: 'usage',
98+
label: 'Usage',
99+
content: <p data-testid='mosaic-custom-page'>Mosaic custom page</p>,
100+
},
101+
],
102+
pageOrder: ['usage'],
103+
}}
104+
/>
105+
);
106+
}`;
107+
108+
// A URL is the whole opt-in to navigation, so this passes one and nothing else.
109+
const routedPage = () => `'use client';
110+
import { UserButton } from '@clerk/nextjs/experimental/mosaic';
111+
112+
export default function Page() {
113+
return <UserButton userProfileUrl='/account' />;
114+
}`;
115+
116+
const accountPage = () => `export default function Page() {
117+
return <h1>Routed account page</h1>;
118+
}`;
119+
85120
// The shared template's route answers with `userId` alone; the organization assertions need the
86121
// rest of the auth object.
87122
const meRoute = () => `import { auth } from '@clerk/nextjs/server';
@@ -117,6 +152,9 @@ test.describe('Mosaic UserButton @nextjs', () => {
117152
// be enough. The absence of that line is what the last test in this file asserts.
118153
.addFile('src/app/layout.tsx', layout)
119154
.addFile('src/app/mosaic/page.tsx', mosaicPage)
155+
.addFile('src/app/mosaic-page-order/page.tsx', pageOrderPage)
156+
.addFile('src/app/mosaic-routed/page.tsx', routedPage)
157+
.addFile('src/app/account/page.tsx', accountPage)
120158
.addFile('src/app/api/me/route.ts', meRoute)
121159
.commit();
122160
cleanup.push(() => app.teardown());
@@ -315,6 +353,84 @@ test.describe('Mosaic UserButton @nextjs', () => {
315353
await expect(page.getByTestId('mosaic-custom-page')).toBeVisible();
316354
});
317355

356+
/**
357+
* `useUserProfilePages` hardcodes the profile's own page list and says so: it mirrors clerk-js
358+
* rather than reading from it, because the profile is not mounted at the point the order has to be
359+
* decided. Nothing else at any level asserts the mirror is accurate — the vitest suite checks the
360+
* order is forwarded, against a list it also supplies. This is the only place the two can be
361+
* compared, and it catches drift in both directions without knowing the instance's configuration:
362+
*
363+
* - a built-in the mirror does not name is one clerk-js was not asked to move, so it lands ahead
364+
* of the custom page;
365+
* - a built-in the mirror names but clerk-js does not have fails `isValidPageItem`, is dropped,
366+
* and logs `Invalid custom page data`.
367+
*/
368+
test('orders the profile around a custom page, so the built-in list has to match clerk-js', async ({
369+
page,
370+
context,
371+
}) => {
372+
const u = createTestUtils({ app, page, context });
373+
await context.clearCookies();
374+
375+
const invalidPageErrors: string[] = [];
376+
page.on('console', message => {
377+
if (message.type() === 'error' && message.text().includes('Invalid custom page data')) {
378+
invalidPageErrors.push(message.text());
379+
}
380+
});
381+
382+
await u.po.signIn.goTo();
383+
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
384+
await u.po.expect.toBeSignedIn();
385+
386+
await u.page.goToRelative('/mosaic-page-order');
387+
await u.po.mosaicUserButton.waitForMounted();
388+
389+
await u.po.mosaicUserButton.toggleTrigger();
390+
await u.po.mosaicUserButton.waitForPopover();
391+
await u.po.mosaicUserButton.triggerManageAccount(fakeUser.email);
392+
await u.po.userProfile.waitForUserProfileModal();
393+
await u.po.userProfile.waitForMounted();
394+
395+
// Only the named page leads. Anything else here is a page the profile brings that the mirror
396+
// does not know about, so ordering a custom page after a built-in would silently misplace it.
397+
const navItems = page.locator('.cl-navbarButton');
398+
await expect(navItems.first()).toHaveText('Usage');
399+
// More than the custom page, or the built-ins were all dropped and there is nothing to order.
400+
expect(await navItems.count()).toBeGreaterThan(1);
401+
402+
expect(invalidPageErrors, 'the mirror named a page clerk-js does not have').toEqual([]);
403+
});
404+
405+
/**
406+
* `openOrNavigate` decides between a modal and a route for all three profile surfaces, and it is
407+
* the widest configuration branch in the controller. The vitest suite covers every combination by
408+
* asserting `navigate` was called with a string. Nothing asserts the app actually lands there.
409+
*/
410+
test('a userProfileUrl routes to the app page instead of opening the modal', async ({ page, context }) => {
411+
const u = createTestUtils({ app, page, context });
412+
await context.clearCookies();
413+
414+
await u.po.signIn.goTo();
415+
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
416+
await u.po.expect.toBeSignedIn();
417+
418+
await u.page.goToRelative('/mosaic-routed');
419+
await u.po.mosaicUserButton.waitForMounted();
420+
421+
await u.po.mosaicUserButton.toggleTrigger();
422+
await u.po.mosaicUserButton.waitForPopover();
423+
await u.po.mosaicUserButton.triggerManageAccount(fakeUser.email);
424+
425+
await page.waitForURL(new URL('/account', app.serverUrl).toString());
426+
await expect(page.getByRole('heading', { name: 'Routed account page' })).toBeVisible();
427+
428+
// Routing and the modal are the two halves of one decision, so the modal not opening is half the
429+
// assertion. The popover goes with it: it would otherwise sit over the page it just opened.
430+
await expect(page.locator('.cl-modalContent')).toHaveCount(0);
431+
await u.po.mosaicUserButton.waitForPopoverClosed();
432+
});
433+
318434
// The Mosaic bundle is inlined into @clerk/react at build time. If it ever regresses to importing
319435
// @clerk/ui at runtime, every test above still passes here in the monorepo — where @clerk/ui is
320436
// always resolvable — and breaks only for consumers. This is the check that would catch it.

0 commit comments

Comments
 (0)