1- import { act } from '@testing-library/react' ;
2- import { useContext } from 'react' ;
3- import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest' ;
1+ import { beforeEach , describe , expect , it , vi } from 'vitest' ;
42
53import { bindCreateFixtures } from '@/test/create-fixtures' ;
64import { render , screen , waitFor } from '@/test/utils' ;
7- import { useModuleManager , useOptions } from '@/ui/contexts' ;
8- import { useAppearance } from '@/ui/customizables/AppearanceContext' ;
9- import { useRouter } from '@/ui/router' ;
5+ import { useModuleManager } from '@/ui/contexts' ;
6+ import { Box } from '@/ui/customizables' ;
107
11- import { OrganizationProfileContext } from '../../contexts/components/OrganizationProfile' ;
12- import { UserProfileContext } from '../../contexts/components/UserProfile' ;
8+ import { computedColors } from '../../customizables/__tests__/test-utils' ;
139import { clearFetchCache } from '../../hooks' ;
1410import { OrganizationProfileProvider } from '../OrganizationProfile/OrganizationProfileProvider' ;
1511import { fallbackModuleManager } from '../ProfileProviderShell' ;
12+ import { UserProfileAccountPanel } from '../UserProfile/Account' ;
13+ import { UserProfileProfileSection } from '../UserProfile/AccountProfile' ;
1614import { UserProfileSecurityPanel } from '../UserProfile/Security' ;
1715import { UserProfilePasswordSection } from '../UserProfile/SecurityPassword' ;
1816import { UserProfileProvider } from '../UserProfile/UserProfileProvider' ;
@@ -21,51 +19,13 @@ function patchEnvironment(clerk: any, env: any) {
2119 Object . defineProperty ( clerk , '__internal_environment' , { value : env , configurable : true } ) ;
2220}
2321
24- function ModuleManagerProbe ( ) {
25- const mm = useModuleManager ( ) ;
26- return (
27- < div
28- data-testid = 'mm-probe'
29- data-has-mm = { ! ! mm }
30- />
31- ) ;
32- }
33-
34- function RouterProbe ( ) {
35- const router = useRouter ( ) ;
36- return (
37- < div
38- data-testid = 'router-probe'
39- data-has-navigate = { typeof router . navigate === 'function' }
40- data-has-base-navigate = { typeof router . baseNavigate === 'function' }
41- />
42- ) ;
43- }
44-
4522describe ( 'UserProfileProvider wiring' , ( ) => {
4623 const { createFixtures } = bindCreateFixtures ( 'UserProfile' ) ;
4724
4825 beforeEach ( ( ) => {
4926 clearFetchCache ( ) ;
5027 } ) ;
5128
52- it ( "provides the clerk instance's moduleManager to children" , async ( ) => {
53- const { wrapper, fixtures } = await createFixtures ( f => {
54- f . withUser ( { email_addresses : [ 'test@clerk.com' ] , first_name : 'Test' , last_name : 'User' } ) ;
55- } ) ;
56- patchEnvironment ( fixtures . clerk , fixtures . environment ) ;
57-
58- render (
59- < UserProfileProvider >
60- < ModuleManagerProbe />
61- </ UserProfileProvider > ,
62- { wrapper } ,
63- ) ;
64-
65- const probe = screen . getByTestId ( 'mm-probe' ) ;
66- expect ( probe . dataset . hasMm ) . toBe ( 'true' ) ;
67- } ) ;
68-
6929 it ( 'falls back to fallbackModuleManager when the clerk instance exposes no moduleManager' , async ( ) => {
7030 const { wrapper, fixtures } = await createFixtures ( f => {
7131 f . withUser ( { email_addresses : [ 'test@clerk.com' ] , first_name : 'Test' , last_name : 'User' } ) ;
@@ -97,89 +57,6 @@ describe('UserProfileProvider wiring', () => {
9757 expect ( screen . getByTestId ( 'mm-fallback-probe' ) . dataset . isFallback ) . toBe ( 'true' ) ;
9858 } ) ;
9959
100- it ( 'provides a router that delegates to clerk.navigate' , async ( ) => {
101- const { wrapper, fixtures } = await createFixtures ( f => {
102- f . withUser ( { email_addresses : [ 'test@clerk.com' ] , first_name : 'Test' , last_name : 'User' } ) ;
103- } ) ;
104- patchEnvironment ( fixtures . clerk , fixtures . environment ) ;
105-
106- render (
107- < UserProfileProvider >
108- < RouterProbe />
109- </ UserProfileProvider > ,
110- { wrapper } ,
111- ) ;
112-
113- const probe = screen . getByTestId ( 'router-probe' ) ;
114- expect ( probe . dataset . hasNavigate ) . toBe ( 'true' ) ;
115- expect ( probe . dataset . hasBaseNavigate ) . toBe ( 'true' ) ;
116- } ) ;
117-
118- // The shell does NOT track the host app URL. Composed has no Clerk-internal
119- // navigation between sections (consumer remounts <UserProfile.X>), so there's
120- // no meaningful currentPath to snapshot — and observing the consumer's URL
121- // would just trigger spurious navigation-keyed effects.
122- describe ( 'router.currentPath is decoupled from the host URL' , ( ) => {
123- let originalPath : string ;
124-
125- beforeEach ( ( ) => {
126- originalPath = window . location . pathname ;
127- } ) ;
128-
129- afterEach ( ( ) => {
130- window . history . replaceState ( null , '' , originalPath ) ;
131- } ) ;
132-
133- function CurrentPathProbe ( ) {
134- const router = useRouter ( ) ;
135- return (
136- < div
137- data-testid = 'path-probe'
138- data-current = { router . currentPath }
139- />
140- ) ;
141- }
142-
143- it ( 'does not snapshot window.location.pathname into router.currentPath' , async ( ) => {
144- const { wrapper, fixtures } = await createFixtures ( f => {
145- f . withUser ( { email_addresses : [ 'test@clerk.com' ] } ) ;
146- } ) ;
147- patchEnvironment ( fixtures . clerk , fixtures . environment ) ;
148-
149- window . history . replaceState ( null , '' , '/page-a' ) ;
150-
151- render (
152- < UserProfileProvider >
153- < CurrentPathProbe />
154- </ UserProfileProvider > ,
155- { wrapper } ,
156- ) ;
157-
158- expect ( screen . getByTestId ( 'path-probe' ) . dataset . current ) . toBe ( '' ) ;
159- } ) ;
160-
161- it ( 'does not update router.currentPath on popstate' , async ( ) => {
162- const { wrapper, fixtures } = await createFixtures ( f => {
163- f . withUser ( { email_addresses : [ 'test@clerk.com' ] } ) ;
164- } ) ;
165- patchEnvironment ( fixtures . clerk , fixtures . environment ) ;
166-
167- render (
168- < UserProfileProvider >
169- < CurrentPathProbe />
170- </ UserProfileProvider > ,
171- { wrapper } ,
172- ) ;
173-
174- act ( ( ) => {
175- window . history . pushState ( null , '' , '/page-b' ) ;
176- window . dispatchEvent ( new PopStateEvent ( 'popstate' ) ) ;
177- } ) ;
178-
179- expect ( screen . getByTestId ( 'path-probe' ) . dataset . current ) . toBe ( '' ) ;
180- } ) ;
181- } ) ;
182-
18360 // The end-to-end proof that resolution actually feeds a dynamic import: render
18461 // the real composed password section, enable zxcvbn strength, type a password,
18562 // and assert the resolved manager's `import` fires for the zxcvbn module. This
@@ -224,7 +101,7 @@ describe('UserProfileProvider wiring', () => {
224101 const { wrapper, fixtures } = await createFixtures ( ) ;
225102 patchEnvironment ( fixtures . clerk , fixtures . environment ) ;
226103
227- const { container } = render (
104+ render (
228105 < UserProfileProvider >
229106 < div data-testid = 'should-not-render' />
230107 </ UserProfileProvider > ,
@@ -234,81 +111,50 @@ describe('UserProfileProvider wiring', () => {
234111 expect ( screen . queryByTestId ( 'should-not-render' ) ) . not . toBeInTheDocument ( ) ;
235112 } ) ;
236113
237- it ( 'cascades globalAppearance from ClerkProvider into composed theme ' , async ( ) => {
114+ it ( 'cascades globalAppearance from ClerkProvider into rendered composed styles ' , async ( ) => {
238115 const { wrapper, fixtures } = await createFixtures ( f => {
239116 f . withUser ( { email_addresses : [ 'test@clerk.com' ] , first_name : 'Test' , last_name : 'User' } ) ;
240117 } ) ;
241118 patchEnvironment ( fixtures . clerk , fixtures . environment ) ;
242119
243- // Simulate ClerkProvider setting appearance with colorPrimary
244- fixtures . clerk . __internal_getOption = vi . fn ( ( key : string ) => {
245- if ( key === 'appearance' ) {
246- return { variables : { colorPrimary : '#ff0000' } } ;
247- }
248- return undefined ;
249- } ) ;
250-
251- function AppearanceProbe ( ) {
252- const { parsedInternalTheme } = useAppearance ( ) ;
253- return (
254- < div
255- data-testid = 'appearance-probe'
256- data-color-primary = { parsedInternalTheme . colors . $primary500 }
257- />
258- ) ;
259- }
120+ // Simulate ClerkProvider setting appearance with colorPrimary.
121+ fixtures . clerk . __internal_getOption = vi . fn ( ( key : string ) =>
122+ key === 'appearance' ? { variables : { colorPrimary : 'red' } } : undefined ,
123+ ) ;
260124
261125 render (
262126 < UserProfileProvider >
263- < AppearanceProbe />
127+ < Box
128+ data-testid = 'primary-swatch'
129+ sx = { t => ( { backgroundColor : t . colors . $primary500 } ) }
130+ />
264131 </ UserProfileProvider > ,
265132 { wrapper } ,
266133 ) ;
267134
268- const probe = screen . getByTestId ( 'appearance-probe' ) ;
269- // #ff0000 = hsla(0, 100%, 50%, 1) — the global appearance should cascade
270- expect ( probe . dataset . colorPrimary ) . toBe ( 'hsla(0, 100%, 50%, 1)' ) ;
135+ expect ( getComputedStyle ( screen . getByTestId ( 'primary-swatch' ) ) . backgroundColor ) . toBe ( computedColors . red ) ;
271136 } ) ;
272137
273- it ( 'threads localization from ClerkProvider into composed OptionsProvider ' , async ( ) => {
138+ it ( 'threads ClerkProvider localization into rendered composed text ' , async ( ) => {
274139 const { wrapper, fixtures } = await createFixtures ( f => {
275140 f . withUser ( { email_addresses : [ 'test@clerk.com' ] , first_name : 'Test' , last_name : 'User' } ) ;
276141 } ) ;
277142 patchEnvironment ( fixtures . clerk , fixtures . environment ) ;
278143
279- const expectedLocalization = { locale : 'fr-FR' , signIn : { start : { title : 'Bienvenue' } } } ;
280- const expectedSupportEmail = 'help@clerk.dev' ;
281- fixtures . clerk . __internal_getOption = vi . fn ( ( key : string ) => {
282- if ( key === 'localization' ) {
283- return expectedLocalization ;
284- }
285- if ( key === 'supportEmail' ) {
286- return expectedSupportEmail ;
287- }
288- return undefined ;
289- } ) ;
290-
291- function OptionsProbe ( ) {
292- const options = useOptions ( ) ;
293- return (
294- < div
295- data-testid = 'options-probe'
296- data-locale = { ( options . localization as any ) ?. locale ?? '' }
297- data-support-email = { options . supportEmail ?? '' }
298- />
299- ) ;
300- }
144+ fixtures . clerk . __internal_getOption = vi . fn ( ( key : string ) =>
145+ key === 'localization' ? { userProfile : { start : { headerTitle__account : 'Détails du profil' } } } : undefined ,
146+ ) ;
301147
302148 render (
303149 < UserProfileProvider >
304- < OptionsProbe />
150+ < UserProfileAccountPanel >
151+ < UserProfileProfileSection />
152+ </ UserProfileAccountPanel >
305153 </ UserProfileProvider > ,
306154 { wrapper } ,
307155 ) ;
308156
309- const probe = screen . getByTestId ( 'options-probe' ) ;
310- expect ( probe . dataset . locale ) . toBe ( 'fr-FR' ) ;
311- expect ( probe . dataset . supportEmail ) . toBe ( expectedSupportEmail ) ;
157+ await waitFor ( ( ) => screen . getByText ( 'Détails du profil' ) ) ;
312158 } ) ;
313159
314160 it ( 'returns null when environment is missing' , async ( ) => {
@@ -326,32 +172,6 @@ describe('UserProfileProvider wiring', () => {
326172
327173 expect ( screen . queryByTestId ( 'should-not-render' ) ) . not . toBeInTheDocument ( ) ;
328174 } ) ;
329-
330- it ( 'forwards apiKeysProps into the UserProfile context' , async ( ) => {
331- const { wrapper, fixtures } = await createFixtures ( f => {
332- f . withUser ( { email_addresses : [ 'test@clerk.com' ] , first_name : 'Test' , last_name : 'User' } ) ;
333- } ) ;
334- patchEnvironment ( fixtures . clerk , fixtures . environment ) ;
335-
336- function ApiKeysProbe ( ) {
337- const ctx = useContext ( UserProfileContext ) ;
338- return (
339- < div
340- data-testid = 'apikeys-probe'
341- data-api-keys = { JSON . stringify ( ctx ?. apiKeysProps ) }
342- />
343- ) ;
344- }
345-
346- render (
347- < UserProfileProvider apiKeysProps = { { perPage : 5 } } >
348- < ApiKeysProbe />
349- </ UserProfileProvider > ,
350- { wrapper } ,
351- ) ;
352-
353- expect ( JSON . parse ( screen . getByTestId ( 'apikeys-probe' ) . dataset . apiKeys || 'null' ) ) . toEqual ( { perPage : 5 } ) ;
354- } ) ;
355175} ) ;
356176
357177describe ( 'OrganizationProfileProvider wiring' , ( ) => {
@@ -361,30 +181,6 @@ describe('OrganizationProfileProvider wiring', () => {
361181 clearFetchCache ( ) ;
362182 } ) ;
363183
364- it ( "provides the clerk instance's moduleManager to children" , async ( ) => {
365- const { wrapper, fixtures } = await createFixtures ( f => {
366- f . withOrganizations ( ) ;
367- f . withUser ( {
368- email_addresses : [ 'test@clerk.com' ] ,
369- first_name : 'Test' ,
370- last_name : 'User' ,
371- organization_memberships : [ { name : 'TestOrg' } ] ,
372- } ) ;
373- } ) ;
374- patchEnvironment ( fixtures . clerk , fixtures . environment ) ;
375- fixtures . clerk . organization ?. getDomains . mockReturnValue ( Promise . resolve ( { data : [ ] , total_count : 0 } ) ) ;
376-
377- render (
378- < OrganizationProfileProvider >
379- < ModuleManagerProbe />
380- </ OrganizationProfileProvider > ,
381- { wrapper } ,
382- ) ;
383-
384- const probe = screen . getByTestId ( 'mm-probe' ) ;
385- expect ( probe . dataset . hasMm ) . toBe ( 'true' ) ;
386- } ) ;
387-
388184 it ( 'falls back to fallbackModuleManager when the clerk instance exposes no moduleManager' , async ( ) => {
389185 const { wrapper, fixtures } = await createFixtures ( f => {
390186 f . withOrganizations ( ) ;
@@ -428,7 +224,7 @@ describe('OrganizationProfileProvider wiring', () => {
428224 } ) ;
429225 patchEnvironment ( fixtures . clerk , fixtures . environment ) ;
430226
431- const { container } = render (
227+ render (
432228 < OrganizationProfileProvider >
433229 < div data-testid = 'should-not-render' />
434230 </ OrganizationProfileProvider > ,
@@ -437,45 +233,6 @@ describe('OrganizationProfileProvider wiring', () => {
437233
438234 expect ( screen . queryByTestId ( 'should-not-render' ) ) . not . toBeInTheDocument ( ) ;
439235 } ) ;
440-
441- it ( 'forwards afterLeaveOrganizationUrl and apiKeysProps into the OrganizationProfile context' , async ( ) => {
442- const { wrapper, fixtures } = await createFixtures ( f => {
443- f . withOrganizations ( ) ;
444- f . withUser ( {
445- email_addresses : [ 'test@clerk.com' ] ,
446- first_name : 'Test' ,
447- last_name : 'User' ,
448- organization_memberships : [ { name : 'TestOrg' } ] ,
449- } ) ;
450- } ) ;
451- patchEnvironment ( fixtures . clerk , fixtures . environment ) ;
452- fixtures . clerk . organization ?. getDomains . mockReturnValue ( Promise . resolve ( { data : [ ] , total_count : 0 } ) ) ;
453-
454- function OrgProbe ( ) {
455- const ctx = useContext ( OrganizationProfileContext ) ;
456- return (
457- < div
458- data-testid = 'org-probe'
459- data-after-leave = { ctx ?. afterLeaveOrganizationUrl ?? '' }
460- data-api-keys = { JSON . stringify ( ctx ?. apiKeysProps ) }
461- />
462- ) ;
463- }
464-
465- render (
466- < OrganizationProfileProvider
467- afterLeaveOrganizationUrl = '/bye'
468- apiKeysProps = { { showDescription : true } }
469- >
470- < OrgProbe />
471- </ OrganizationProfileProvider > ,
472- { wrapper } ,
473- ) ;
474-
475- const probe = screen . getByTestId ( 'org-probe' ) ;
476- expect ( probe . dataset . afterLeave ) . toBe ( '/bye' ) ;
477- expect ( JSON . parse ( probe . dataset . apiKeys || 'null' ) ) . toEqual ( { showDescription : true } ) ;
478- } ) ;
479236} ) ;
480237
481238// The fallback exists to fail loudly instead of silently resolving `undefined`.
0 commit comments