@@ -24,7 +24,7 @@ import { createTestUtils } from '../testUtils';
2424// The template's layout, plus the one line this suite adds. Its appearance options are what the
2525// shared sign-in page object's selectors depend on, so they are carried over verbatim.
2626const layout = ( ) => `import './globals.css';
27- import './ mosaic.css';
27+ import '@clerk/nextjs/experimental/ mosaic/styles .css';
2828import { Inter } from 'next/font/google';
2929import { ClerkProvider } from '@clerk/nextjs';
3030
@@ -52,20 +52,16 @@ export default function RootLayout({ children }: { children: React.ReactNode })
5252 );
5353}` ;
5454
55- // The layered form is what `@clerk/react`'s mosaic entry documents. If Next cannot resolve a bare
56- // specifier through `@import`, the fallback is a plain `import` of the same file from the layout.
57- const mosaicCss = ( ) => `@import '@clerk/nextjs/experimental/mosaic/styles.css' layer(clerk);\n` ;
58-
5955// Both UserButtons mount together while signed in, so every case below runs with the legacy Emotion
6056// tree and the Mosaic tree live at once rather than proving coexistence in one isolated smoke test.
6157const mosaicPage = ( ) => `'use client';
6258import { UserButton } from '@clerk/nextjs/experimental/mosaic';
63- import { SignIn, SignedIn, SignedOut , UserButton as LegacyUserButton } from '@clerk/nextjs';
59+ import { SignIn, Show , UserButton as LegacyUserButton } from '@clerk/nextjs';
6460
6561export default function Page() {
6662 return (
6763 <main>
68- <SignedIn >
64+ <Show when='signed-in' >
6965 <UserButton
7066 userProfileProps={{
7167 customPages: [
@@ -78,10 +74,10 @@ export default function Page() {
7874 }}
7975 />
8076 <LegacyUserButton />
81- </SignedIn >
82- <SignedOut >
77+ </Show >
78+ <Show when='signed-out' >
8379 <SignIn routing='hash' />
84- </SignedOut >
80+ </Show >
8581 </main>
8682 );
8783}` ;
@@ -107,18 +103,23 @@ test.describe('Mosaic UserButton @nextjs', () => {
107103 let fakeUser : FakeUserWithEmail ;
108104 let otherUser : FakeUserWithEmail ;
109105 let fakeOrganization : FakeOrganization ;
106+ // Filled as each resource is created, so a setup that fails halfway still tears down what it made.
107+ const cleanup : ( ( ) => Promise < unknown > ) [ ] = [ ] ;
110108
111109 test . beforeAll ( async ( ) => {
110+ // Installing and booting a fresh app, before a single test runs.
111+ test . setTimeout ( 90_000 ) ;
112+
112113 app = await appConfigs . next . appRouter
113114 . clone ( )
114115 // Deliberately no `.addDependency('@clerk/ui', PKGLAB)`, unlike composed-components.test.ts.
115116 // The Mosaic entry is bundled into @clerk /react at build time, so @clerk/nextjs alone has to
116117 // be enough. The absence of that line is what the last test in this file asserts.
117- . addFile ( 'src/app/mosaic.css' , mosaicCss )
118118 . addFile ( 'src/app/layout.tsx' , layout )
119119 . addFile ( 'src/app/mosaic/page.tsx' , mosaicPage )
120120 . addFile ( 'src/app/api/me/route.ts' , meRoute )
121121 . commit ( ) ;
122+ cleanup . push ( ( ) => app . teardown ( ) ) ;
122123 await app . setup ( ) ;
123124 await app . withEnv ( appConfigs . envs . withEmailCodes ) ;
124125 await app . dev ( ) ;
@@ -136,17 +137,17 @@ test.describe('Mosaic UserButton @nextjs', () => {
136137 fakeUser = createUser ( ) ;
137138 otherUser = createUser ( ) ;
138139 const user = await m . services . users . createBapiUser ( fakeUser ) ;
140+ cleanup . unshift ( ( ) => fakeUser . deleteIfExists ( ) ) ;
139141 await m . services . users . createBapiUser ( otherUser ) ;
142+ cleanup . unshift ( ( ) => otherUser . deleteIfExists ( ) ) ;
140143 fakeOrganization = await m . services . users . createFakeOrganization ( user . id ) ;
144+ cleanup . unshift ( ( ) => fakeOrganization . delete ( ) ) ;
141145 } ) ;
142146
143147 test . afterAll ( async ( ) => {
144- try {
145- await fakeOrganization . delete ( ) ;
146- await otherUser . deleteIfExists ( ) ;
147- await fakeUser . deleteIfExists ( ) ;
148- } finally {
149- await app . teardown ( ) ;
148+ test . setTimeout ( 90_000 ) ;
149+ for ( const teardown of cleanup ) {
150+ await teardown ( ) ;
150151 }
151152 } ) ;
152153
@@ -194,7 +195,7 @@ test.describe('Mosaic UserButton @nextjs', () => {
194195 await expect ( u . po . mosaicUserButton . popup ( ) ) . toHaveCount ( 0 ) ;
195196 } ) ;
196197
197- test ( 'selecting an organization , then the personal workspace , reaches the server' , async ( { page, context } ) => {
198+ test ( 'selecting the personal workspace , then the organization , reaches the server' , async ( { page, context } ) => {
198199 const u = createTestUtils ( { app, page, context } ) ;
199200 await context . clearCookies ( ) ;
200201
@@ -205,26 +206,30 @@ test.describe('Mosaic UserButton @nextjs', () => {
205206 await u . page . goToRelative ( '/mosaic' ) ;
206207 await u . po . mosaicUserButton . waitForMounted ( ) ;
207208
209+ // The only organization is already active on sign-in, so its row is the current item rather than
210+ // a button. Personal is the reachable move first; the organization becomes selectable after it.
211+ await u . po . mosaicUserButton . expectTriggerLabel ( fakeOrganization . name ) ;
212+
208213 await u . po . mosaicUserButton . toggleTrigger ( ) ;
209214 await u . po . mosaicUserButton . waitForPopover ( ) ;
210- await u . po . mosaicUserButton . selectWorkspace ( fakeOrganization . name ) ;
215+ await u . po . mosaicUserButton . selectPersonalWorkspace ( ) ;
211216 await u . po . mosaicUserButton . waitForPopoverClosed ( ) ;
212- await u . po . mosaicUserButton . expectTriggerLabel ( fakeOrganization . name ) ;
213217
214218 await expect ( async ( ) => {
215- const auth = await readAuthState ( page ) ;
216- expect ( auth . orgId ) . toBe ( fakeOrganization . organization . id ) ;
217- expect ( auth . orgRole ) . toBe ( 'org:admin' ) ;
218- expect ( auth . orgSlug ) . toBeTruthy ( ) ;
219+ expect ( ( await readAuthState ( page ) ) . orgId ) . toBeNull ( ) ;
219220 } ) . toPass ( ) ;
220221
221222 await u . po . mosaicUserButton . toggleTrigger ( ) ;
222223 await u . po . mosaicUserButton . waitForPopover ( ) ;
223- await u . po . mosaicUserButton . selectPersonalWorkspace ( ) ;
224+ await u . po . mosaicUserButton . selectWorkspace ( fakeOrganization . name ) ;
224225 await u . po . mosaicUserButton . waitForPopoverClosed ( ) ;
226+ await u . po . mosaicUserButton . expectTriggerLabel ( fakeOrganization . name ) ;
225227
226228 await expect ( async ( ) => {
227- expect ( ( await readAuthState ( page ) ) . orgId ) . toBeNull ( ) ;
229+ const auth = await readAuthState ( page ) ;
230+ expect ( auth . orgId ) . toBe ( fakeOrganization . organization . id ) ;
231+ expect ( auth . orgRole ) . toBe ( 'org:admin' ) ;
232+ expect ( auth . orgSlug ) . toBeTruthy ( ) ;
228233 } ) . toPass ( ) ;
229234 } ) ;
230235
@@ -298,7 +303,7 @@ test.describe('Mosaic UserButton @nextjs', () => {
298303
299304 await u . po . mosaicUserButton . toggleTrigger ( ) ;
300305 await u . po . mosaicUserButton . waitForPopover ( ) ;
301- await u . po . mosaicUserButton . triggerManageAccount ( ) ;
306+ await u . po . mosaicUserButton . triggerManageAccount ( fakeUser . email ) ;
302307
303308 // The modal is rendered by clerk-js, so the legacy page object is the right tool. Reaching it
304309 // from a Mosaic surface is the hand-off under test.
0 commit comments