Skip to content
Merged
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/mosaic-user-button-feature-tests.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
@@ -1,19 +1,12 @@
'use client';

import { MosaicProvider, UserButton } from '@clerk/mosaic';
import { useState } from 'react';

export default function Page() {
const [actionCount, setActionCount] = useState(0);

return (
<MosaicProvider>
<p>custom-action-count-{actionCount}</p>
<UserButton
customMenuItems={[
{ id: 'custom-link', label: 'Custom link', href: '/custom/link-target' },
{ id: 'custom-action', label: 'Custom action', onClick: () => setActionCount(count => count + 1) },
]}
customMenuItems={[{ id: 'custom-link', label: 'Custom link', href: '/custom/link-target' }]}
userProfileProps={{
customPages: [{ label: 'Custom page', path: 'custom-page', content: <p>custom-page-content</p> }],
}}
Expand Down
84 changes: 17 additions & 67 deletions integration/tests/mosaic/user-button.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,23 @@ testAgainstRunningApps({ withPattern: ['next.appRouterMosaic.*'] })('Mosaic User
test.describe.configure({ mode: 'serial' });

let fakeUser: FakeUser;
let otherFakeUser: FakeUser;
let organizations: Organization[] = [];
let organization: Organization;

test.beforeAll(async () => {
const u = createTestUtils({ app });
fakeUser = u.services.users.createFakeUser(test);
otherFakeUser = u.services.users.createFakeUser(test);
const [user] = await Promise.all([
u.services.users.createBapiUser(fakeUser),
u.services.users.createBapiUser(otherFakeUser),
]);
const suffix = Date.now();
organizations = await Promise.all(
['Alpha', 'Beta'].map(name =>
u.services.clerk.organizations.createOrganization({ name: `Mosaic ${name} ${suffix}`, createdBy: user.id }),
),
);
const user = await u.services.users.createBapiUser(fakeUser);
organization = await u.services.clerk.organizations.createOrganization({
name: `Mosaic ${Date.now()}`,
createdBy: user.id,
});
});

test.afterAll(async () => {
const u = createTestUtils({ app });
const results = await Promise.allSettled([
...organizations.map(({ id }) => u.services.clerk.organizations.deleteOrganization(id)),
u.services.clerk.organizations.deleteOrganization(organization.id),
fakeUser.deleteIfExists(),
otherFakeUser.deleteIfExists(),
]);
await app.teardown();
const failures = results.flatMap(result => (result.status === 'rejected' ? [result.reason] : []));
Expand Down Expand Up @@ -62,37 +54,23 @@ testAgainstRunningApps({ withPattern: ['next.appRouterMosaic.*'] })('Mosaic User
await page.getByRole('menuitem', { name: label }).click();
}

test('renders nothing while signed out', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.page.goToAppHome();
await u.page.waitForClerkJsLoaded();
await u.po.expect.toBeSignedOut();

await expect(page.getByText('signed-out-state')).toBeVisible();
await expect(trigger(page)).toHaveCount(0);
});

test('switches the active organization', async ({ page, context }) => {
await signIn({ page, context });
test('switches the active organization and keeps it across a reload', async ({ page, context }) => {
const u = await signIn({ page, context });
const { id, name } = organization;

await trigger(page).click();
await popup(page).getByRole('button', { name: 'Personal account' }).click();
await page.waitForFunction(() => window.Clerk?.organization === null);
await page.keyboard.press('Escape');
await expect(popup(page)).toBeHidden();

for (const { id, name } of organizations) {
await trigger(page).click();
await popup(page).getByRole('button', { name }).click();

await page.waitForFunction(orgId => window.Clerk?.organization?.id === orgId, id);
await page.keyboard.press('Escape');
await expect(popup(page)).toBeHidden();
}

await trigger(page).click();
await popup(page).getByRole('button', { name: 'Personal account' }).click();
await page.waitForFunction(() => window.Clerk?.organization === null);
await popup(page).getByRole('button', { name }).click();
await page.waitForFunction(orgId => window.Clerk?.organization?.id === orgId, id);

await u.page.reload();
await u.page.waitForClerkJsLoaded();
await page.waitForFunction(orgId => window.Clerk?.organization?.id === orgId, id);
});

test('signs out', async ({ page, context }) => {
Expand All @@ -114,14 +92,9 @@ testAgainstRunningApps({ withPattern: ['next.appRouterMosaic.*'] })('Mosaic User
await page.waitForURL(url => url.pathname.startsWith('/sign-in'));
});

test('runs custom menu items', async ({ page, context }) => {
test('follows a custom menu link', async ({ page, context }) => {
await signIn({ page, context }, '/custom');

await trigger(page).click();
await popup(page).getByRole('button', { name: 'Custom action' }).click();
await expect(page.getByText('custom-action-count-1')).toBeVisible();
await expect(popup(page)).toBeHidden();

await trigger(page).click();
await popup(page).getByRole('link', { name: 'Custom link' }).click();
await expect(page.getByText('custom-link-target')).toBeVisible();
Expand All @@ -137,27 +110,4 @@ testAgainstRunningApps({ withPattern: ['next.appRouterMosaic.*'] })('Mosaic User
await page.locator('.cl-userProfile-root').getByText('Custom page').click();
await expect(page.getByText('custom-page-content')).toBeVisible();
});

test('switches to another signed-in account', async ({ page, context }) => {
const u = await signIn({ page, context });
await u.po.signIn.goTo();
await u.po.signIn.setIdentifier(otherFakeUser.email);
await u.po.signIn.continue();
await u.po.signIn.setPassword(otherFakeUser.password);
await u.po.signIn.continue();
await page.waitForFunction(
email => window.Clerk?.user?.primaryEmailAddress?.emailAddress === email,
otherFakeUser.email,
);

await u.page.goToAppHome();
await trigger(page).click();
await popup(page).getByRole('button', { name: 'Switch account' }).click();
await page.getByRole('menuitem', { name: fakeUser.email }).click();

await page.waitForFunction(
email => window.Clerk?.user?.primaryEmailAddress?.emailAddress === email,
fakeUser.email,
);
});
});
11 changes: 10 additions & 1 deletion packages/mosaic/src/__tests__/feature/fake-fapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ import type {
import { http, HttpResponse, type JsonBodyType } from 'msw';
import { setupWorker } from 'msw/browser';

import { fapiClient, type FapiEnvironment, fapiEnvironment, fapiPage, fapiToken } from './fapi';
import {
fapiClient,
type FapiEnvironment,
fapiEnvironment,
fapiMembership,
fapiOrganization,
fapiPage,
fapiToken,
} from './fapi';

export const PUBLISHABLE_KEY = 'pk_live_Y2xlcmsuYWJjZWYuMTIzNDUucHJvZC5sY2xjbGVyay5jb20k';
const FAPI = 'https://clerk.abcef.12345.prod.lclclerk.com';
Expand Down Expand Up @@ -141,6 +149,7 @@ export function serveFapi(seed: FakeFapiSeed = {}): FakeFapiState {
}
const accepted = { ...invitation, status: 'accepted' as const };
state.invitations = state.invitations.map(i => (i.id === accepted.id ? accepted : i));
state.memberships = [...state.memberships, fapiMembership(fapiOrganization(invitation.public_organization_data))];
return envelope(accepted, state.client);
}),
http.post(fapiUrl('/v1/me/organization_suggestions/:id/accept'), ({ params }) => {
Expand Down
Loading
Loading