@@ -200,4 +200,193 @@ describe('isomorphicClerk', () => {
200200 expect ( result ) . toBe ( mockClerkUI ) ;
201201 } ) ;
202202 } ) ;
203+
204+ describe ( 'shouldLoadUi across SDK scenarios' , ( ) => {
205+ // Helper to run getEntryChunks and return what clerk.load was called with
206+ async function runGetEntryChunks ( options : Record < string , any > ) {
207+ const mockLoad = vi . fn ( ) . mockResolvedValue ( undefined ) ;
208+ const mockClerkInstance = options . Clerk || {
209+ load : mockLoad ,
210+ loaded : false ,
211+ } ;
212+ if ( options . Clerk ) {
213+ options . Clerk . load = mockLoad ;
214+ options . Clerk . loaded = false ;
215+ }
216+
217+ ( global as any ) . Clerk = mockClerkInstance ;
218+
219+ const clerk = new IsomorphicClerk ( {
220+ publishableKey : 'pk_test_XXX' ,
221+ ...options ,
222+ } ) ;
223+
224+ await ( clerk as any ) . getEntryChunks ( ) ;
225+
226+ return { mockLoad } ;
227+ }
228+
229+ // ─── @clerk/react, @clerk/nextjs, @clerk/react-router, @clerk/tanstack-react-start ───
230+ // These SDKs: no Clerk prop, no ui prop, standardBrowser omitted (undefined)
231+ // shouldLoadUi = (undefined !== false && !undefined) || !!undefined = (true && true) || false = true
232+ // → loads UI from CDN
233+ it ( 'loads UI from CDN when no Clerk, no ui, standardBrowser omitted (nextjs/react-router/tanstack)' , async ( ) => {
234+ const { mockLoad } = await runGetEntryChunks ( { } ) ;
235+
236+ expect ( loadClerkUIScript ) . toHaveBeenCalled ( ) ;
237+ expect ( mockLoad ) . toHaveBeenCalledWith (
238+ expect . objectContaining ( {
239+ ui : expect . objectContaining ( {
240+ ClerkUI : ( global as any ) . __internal_ClerkUICtor ,
241+ } ) ,
242+ } ) ,
243+ ) ;
244+ } ) ;
245+
246+ // ─── @clerk/react with bundled ui prop (e.g. user passes ui={ui} from @clerk/ui) ───
247+ // These SDKs: no Clerk prop, ui with ClerkUI, standardBrowser omitted
248+ // shouldLoadUi = (true && true) || true = true
249+ // → getClerkUIEntryChunk returns the bundled ClerkUI (no CDN)
250+ it ( 'uses bundled ClerkUI when ui prop is passed without Clerk instance (react with ui prop)' , async ( ) => {
251+ const mockClerkUI = vi . fn ( ) ;
252+ const { mockLoad } = await runGetEntryChunks ( {
253+ ui : { ClerkUI : mockClerkUI } ,
254+ } ) ;
255+
256+ expect ( loadClerkUIScript ) . not . toHaveBeenCalled ( ) ;
257+ expect ( mockLoad ) . toHaveBeenCalledWith (
258+ expect . objectContaining ( {
259+ ui : expect . objectContaining ( {
260+ ClerkUI : mockClerkUI ,
261+ } ) ,
262+ } ) ,
263+ ) ;
264+ } ) ;
265+
266+ // ─── @clerk/expo (native mode) ───
267+ // Expo native: Clerk instance, no ui prop, standardBrowser: false
268+ // shouldLoadUi = (false !== false && ...) || !!undefined = false || false = false
269+ // → no UI loaded (correct: native apps don't render prebuilt UI)
270+ it ( 'does not load UI for Expo native (Clerk instance, no ui, standardBrowser: false)' , async ( ) => {
271+ const mockClerkInstance = { } as any ;
272+ const { mockLoad } = await runGetEntryChunks ( {
273+ Clerk : mockClerkInstance ,
274+ standardBrowser : false ,
275+ } ) ;
276+
277+ expect ( loadClerkUIScript ) . not . toHaveBeenCalled ( ) ;
278+ expect ( mockLoad ) . toHaveBeenCalledWith (
279+ expect . objectContaining ( {
280+ ui : expect . objectContaining ( {
281+ ClerkUI : undefined ,
282+ } ) ,
283+ } ) ,
284+ ) ;
285+ } ) ;
286+
287+ // ─── @clerk/expo (web mode) ───
288+ // Expo web: Clerk is null, no ui prop, standardBrowser: true
289+ // shouldLoadUi = (true !== false && !null) || false = (true && true) || false = true
290+ // → loads UI from CDN (correct: web mode uses normal browser flow)
291+ it ( 'loads UI from CDN for Expo web (Clerk: null, standardBrowser: true)' , async ( ) => {
292+ const { mockLoad } = await runGetEntryChunks ( {
293+ Clerk : null ,
294+ standardBrowser : true ,
295+ } ) ;
296+
297+ expect ( loadClerkUIScript ) . toHaveBeenCalled ( ) ;
298+ expect ( mockLoad ) . toHaveBeenCalledWith (
299+ expect . objectContaining ( {
300+ ui : expect . objectContaining ( {
301+ ClerkUI : ( global as any ) . __internal_ClerkUICtor ,
302+ } ) ,
303+ } ) ,
304+ ) ;
305+ } ) ;
306+
307+ // ─── @clerk/chrome-extension (without syncHost) ───
308+ // No syncHost: Clerk instance, ui with ClerkUI, standardBrowser: true
309+ // shouldLoadUi = (true && !instance) || true = false || true = true
310+ // → uses bundled ClerkUI (no CDN)
311+ it ( 'uses bundled ClerkUI for chrome-extension without syncHost (standardBrowser: true)' , async ( ) => {
312+ const mockClerkUI = vi . fn ( ) ;
313+ const mockClerkInstance = { } as any ;
314+ const { mockLoad } = await runGetEntryChunks ( {
315+ Clerk : mockClerkInstance ,
316+ ui : { ClerkUI : mockClerkUI } ,
317+ standardBrowser : true ,
318+ } ) ;
319+
320+ expect ( loadClerkUIScript ) . not . toHaveBeenCalled ( ) ;
321+ expect ( mockLoad ) . toHaveBeenCalledWith (
322+ expect . objectContaining ( {
323+ ui : expect . objectContaining ( {
324+ ClerkUI : mockClerkUI ,
325+ } ) ,
326+ } ) ,
327+ ) ;
328+ } ) ;
329+
330+ // ─── @clerk/chrome-extension (with syncHost) ───
331+ // With syncHost: Clerk instance, ui with ClerkUI, standardBrowser: false
332+ // shouldLoadUi = (false !== false && ...) || !!ClerkUI = false || true = true
333+ // → uses bundled ClerkUI (no CDN)
334+ it ( 'uses bundled ClerkUI for chrome-extension with syncHost (standardBrowser: false)' , async ( ) => {
335+ const mockClerkUI = vi . fn ( ) ;
336+ const mockClerkInstance = { } as any ;
337+ const { mockLoad } = await runGetEntryChunks ( {
338+ Clerk : mockClerkInstance ,
339+ ui : { ClerkUI : mockClerkUI } ,
340+ standardBrowser : false ,
341+ } ) ;
342+
343+ expect ( loadClerkUIScript ) . not . toHaveBeenCalled ( ) ;
344+ expect ( mockLoad ) . toHaveBeenCalledWith (
345+ expect . objectContaining ( {
346+ ui : expect . objectContaining ( {
347+ ClerkUI : mockClerkUI ,
348+ } ) ,
349+ } ) ,
350+ ) ;
351+ } ) ;
352+
353+ // ─── Clerk instance provided, no ui prop, standardBrowser: true ───
354+ // shouldLoadUi = (true && !instance) || false = false || false = false
355+ // → no UI loaded (correct: Clerk instance without bundled UI, no CDN attempt)
356+ it ( 'does not load UI when Clerk instance provided without ui prop (standardBrowser: true)' , async ( ) => {
357+ const mockClerkInstance = { } as any ;
358+ const { mockLoad } = await runGetEntryChunks ( {
359+ Clerk : mockClerkInstance ,
360+ standardBrowser : true ,
361+ } ) ;
362+
363+ expect ( loadClerkUIScript ) . not . toHaveBeenCalled ( ) ;
364+ expect ( mockLoad ) . toHaveBeenCalledWith (
365+ expect . objectContaining ( {
366+ ui : expect . objectContaining ( {
367+ ClerkUI : undefined ,
368+ } ) ,
369+ } ) ,
370+ ) ;
371+ } ) ;
372+
373+ // ─── ui prop passed as server marker (no ClerkUI), no Clerk instance ───
374+ // RSC react-server export may provide ui without ClerkUI initially
375+ // shouldLoadUi = (true && true) || false = true
376+ // → getClerkUIEntryChunk is called, but uiProp exists without ClerkUI → returns undefined (skips CDN)
377+ it ( 'skips CDN when ui prop exists without ClerkUI (server marker object)' , async ( ) => {
378+ const { mockLoad } = await runGetEntryChunks ( {
379+ ui : { __brand : '__clerkUI' , version : '1.0.0' } ,
380+ } ) ;
381+
382+ expect ( loadClerkUIScript ) . not . toHaveBeenCalled ( ) ;
383+ expect ( mockLoad ) . toHaveBeenCalledWith (
384+ expect . objectContaining ( {
385+ ui : expect . objectContaining ( {
386+ ClerkUI : undefined ,
387+ } ) ,
388+ } ) ,
389+ ) ;
390+ } ) ;
391+ } ) ;
203392} ) ;
0 commit comments