@@ -175,3 +175,231 @@ describe('PricingTable - trial info', () => {
175
175
} ) ;
176
176
} ) ;
177
177
} ) ;
178
+
179
+ describe ( 'PricingTable - plans visibility' , ( ) => {
180
+ const testPlan = {
181
+ id : 'plan_test' ,
182
+ name : 'Test Plan' ,
183
+ fee : {
184
+ amount : 1000 ,
185
+ amountFormatted : '10.00' ,
186
+ currencySymbol : '$' ,
187
+ currency : 'USD' ,
188
+ } ,
189
+ annualFee : {
190
+ amount : 10000 ,
191
+ amountFormatted : '100.00' ,
192
+ currencySymbol : '$' ,
193
+ currency : 'USD' ,
194
+ } ,
195
+ annualMonthlyFee : {
196
+ amount : 833 ,
197
+ amountFormatted : '8.33' ,
198
+ currencySymbol : '$' ,
199
+ currency : 'USD' ,
200
+ } ,
201
+ description : 'Test plan description' ,
202
+ hasBaseFee : true ,
203
+ isRecurring : true ,
204
+ isDefault : false ,
205
+ forPayerType : 'user' ,
206
+ publiclyVisible : true ,
207
+ slug : 'test' ,
208
+ avatarUrl : '' ,
209
+ features : [ ] as any [ ] ,
210
+ freeTrialEnabled : false ,
211
+ freeTrialDays : 0 ,
212
+ __internal_toSnapshot : jest . fn ( ) ,
213
+ pathRoot : '' ,
214
+ reload : jest . fn ( ) ,
215
+ } as const ;
216
+
217
+ it ( 'shows no plans when user is signed in but has no subscription' , async ( ) => {
218
+ const { wrapper, fixtures, props } = await createFixtures ( f => {
219
+ f . withUser ( { email_addresses :
[ '[email protected] ' ] } ) ;
220
+ } ) ;
221
+
222
+ // Provide empty props to the PricingTable context
223
+ props . setProps ( { } ) ;
224
+
225
+ fixtures . clerk . billing . getPlans . mockResolvedValue ( { data : [ testPlan as any ] , total_count : 1 } ) ;
226
+ // Mock no subscription for signed-in user - empty subscription object
227
+ fixtures . clerk . billing . getSubscription . mockResolvedValue ( {
228
+ subscriptionItems : [ ] ,
229
+ pathRoot : '' ,
230
+ reload : jest . fn ( ) ,
231
+ } as any ) ;
232
+
233
+ const { queryByRole } = render ( < PricingTable /> , { wrapper } ) ;
234
+
235
+ await waitFor ( ( ) => {
236
+ // Should not show any plans when signed in but no subscription
237
+ expect ( queryByRole ( 'heading' , { name : 'Test Plan' } ) ) . not . toBeInTheDocument ( ) ;
238
+ } ) ;
239
+ } ) ;
240
+
241
+ it ( 'shows plans when user is signed in and has a subscription' , async ( ) => {
242
+ const { wrapper, fixtures, props } = await createFixtures ( f => {
243
+ f . withUser ( { email_addresses :
[ '[email protected] ' ] } ) ;
244
+ } ) ;
245
+
246
+ // Provide empty props to the PricingTable context
247
+ props . setProps ( { } ) ;
248
+
249
+ fixtures . clerk . billing . getPlans . mockResolvedValue ( { data : [ testPlan as any ] , total_count : 1 } ) ;
250
+ // Mock active subscription for signed-in user
251
+ fixtures . clerk . billing . getSubscription . mockResolvedValue ( {
252
+ id : 'sub_active' ,
253
+ status : 'active' ,
254
+ activeAt : new Date ( '2021-01-01' ) ,
255
+ createdAt : new Date ( '2021-01-01' ) ,
256
+ nextPayment : null ,
257
+ pastDueAt : null ,
258
+ updatedAt : null ,
259
+ subscriptionItems : [
260
+ {
261
+ id : 'si_active' ,
262
+ plan : testPlan ,
263
+ createdAt : new Date ( '2021-01-01' ) ,
264
+ paymentSourceId : 'src_1' ,
265
+ pastDueAt : null ,
266
+ canceledAt : null ,
267
+ periodStart : new Date ( '2021-01-01' ) ,
268
+ periodEnd : new Date ( '2021-01-31' ) ,
269
+ planPeriod : 'month' as const ,
270
+ status : 'active' as const ,
271
+ isFreeTrial : false ,
272
+ cancel : jest . fn ( ) ,
273
+ pathRoot : '' ,
274
+ reload : jest . fn ( ) ,
275
+ } ,
276
+ ] ,
277
+ pathRoot : '' ,
278
+ reload : jest . fn ( ) ,
279
+ } ) ;
280
+
281
+ const { getByRole } = render ( < PricingTable /> , { wrapper } ) ;
282
+
283
+ await waitFor ( ( ) => {
284
+ // Should show plans when signed in and has subscription
285
+ expect ( getByRole ( 'heading' , { name : 'Test Plan' } ) ) . toBeVisible ( ) ;
286
+ } ) ;
287
+ } ) ;
288
+
289
+ it ( 'shows plans when user is signed out' , async ( ) => {
290
+ const { wrapper, fixtures, props } = await createFixtures ( ) ;
291
+
292
+ // Provide empty props to the PricingTable context
293
+ props . setProps ( { } ) ;
294
+
295
+ fixtures . clerk . billing . getPlans . mockResolvedValue ( { data : [ testPlan as any ] , total_count : 1 } ) ;
296
+ // When signed out, getSubscription should throw or return empty response
297
+ fixtures . clerk . billing . getSubscription . mockRejectedValue ( new Error ( 'Unauthenticated' ) ) ;
298
+
299
+ const { getByRole } = render ( < PricingTable /> , { wrapper } ) ;
300
+
301
+ await waitFor ( ( ) => {
302
+ // Should show plans when signed out
303
+ expect ( getByRole ( 'heading' , { name : 'Test Plan' } ) ) . toBeVisible ( ) ;
304
+ } ) ;
305
+ } ) ;
306
+
307
+ it ( 'shows no plans when user is signed in but subscription is null' , async ( ) => {
308
+ const { wrapper, fixtures, props } = await createFixtures ( f => {
309
+ f . withUser ( { email_addresses :
[ '[email protected] ' ] } ) ;
310
+ } ) ;
311
+
312
+ // Provide empty props to the PricingTable context
313
+ props . setProps ( { } ) ;
314
+
315
+ fixtures . clerk . billing . getPlans . mockResolvedValue ( { data : [ testPlan as any ] , total_count : 1 } ) ;
316
+ // Mock null subscription response (different from throwing error)
317
+ fixtures . clerk . billing . getSubscription . mockResolvedValue ( null as any ) ;
318
+
319
+ const { queryByRole } = render ( < PricingTable /> , { wrapper } ) ;
320
+
321
+ await waitFor ( ( ) => {
322
+ // Should not show any plans when signed in but subscription is null
323
+ expect ( queryByRole ( 'heading' , { name : 'Test Plan' } ) ) . not . toBeInTheDocument ( ) ;
324
+ } ) ;
325
+ } ) ;
326
+
327
+ it ( 'shows no plans when user is signed in but subscription is undefined' , async ( ) => {
328
+ const { wrapper, fixtures, props } = await createFixtures ( f => {
329
+ f . withUser ( { email_addresses :
[ '[email protected] ' ] } ) ;
330
+ } ) ;
331
+
332
+ // Provide empty props to the PricingTable context
333
+ props . setProps ( { } ) ;
334
+
335
+ fixtures . clerk . billing . getPlans . mockResolvedValue ( { data : [ testPlan as any ] , total_count : 1 } ) ;
336
+ // Mock undefined subscription response (loading state)
337
+ fixtures . clerk . billing . getSubscription . mockResolvedValue ( undefined as any ) ;
338
+
339
+ const { queryByRole } = render ( < PricingTable /> , { wrapper } ) ;
340
+
341
+ await waitFor ( ( ) => {
342
+ // Should not show any plans when signed in but subscription is undefined (loading)
343
+ expect ( queryByRole ( 'heading' , { name : 'Test Plan' } ) ) . not . toBeInTheDocument ( ) ;
344
+ } ) ;
345
+ } ) ;
346
+
347
+ it ( 'prevents flicker by not showing plans while subscription is loading' , async ( ) => {
348
+ const { wrapper, fixtures, props } = await createFixtures ( f => {
349
+ f . withUser ( { email_addresses :
[ '[email protected] ' ] } ) ;
350
+ } ) ;
351
+
352
+ // Provide empty props to the PricingTable context
353
+ props . setProps ( { } ) ;
354
+
355
+ fixtures . clerk . billing . getPlans . mockResolvedValue ( { data : [ testPlan as any ] , total_count : 1 } ) ;
356
+
357
+ // Create a pending promise and capture its resolver
358
+ let resolveSubscription ! : ( value : any ) => void ;
359
+ const pendingSubscriptionPromise = new Promise < any > ( resolve => {
360
+ resolveSubscription = resolve ;
361
+ } ) ;
362
+ fixtures . clerk . billing . getSubscription . mockReturnValue ( pendingSubscriptionPromise ) ;
363
+
364
+ const { queryByRole, findByRole } = render ( < PricingTable /> , { wrapper } ) ;
365
+
366
+ // Assert no plans render while subscription is pending
367
+ await waitFor ( ( ) => {
368
+ expect ( queryByRole ( 'heading' , { name : 'Test Plan' } ) ) . not . toBeInTheDocument ( ) ;
369
+ } ) ;
370
+
371
+ // Resolve the subscription with an active subscription object
372
+ resolveSubscription ( {
373
+ id : 'sub_active' ,
374
+ status : 'active' ,
375
+ activeAt : new Date ( '2021-01-01' ) ,
376
+ createdAt : new Date ( '2021-01-01' ) ,
377
+ nextPayment : null ,
378
+ pastDueAt : null ,
379
+ updatedAt : null ,
380
+ subscriptionItems : [
381
+ {
382
+ id : 'si_active' ,
383
+ plan : testPlan ,
384
+ createdAt : new Date ( '2021-01-01' ) ,
385
+ paymentSourceId : 'src_1' ,
386
+ pastDueAt : null ,
387
+ canceledAt : null ,
388
+ periodStart : new Date ( '2021-01-01' ) ,
389
+ periodEnd : new Date ( '2021-01-31' ) ,
390
+ planPeriod : 'month' as const ,
391
+ status : 'active' as const ,
392
+ isFreeTrial : false ,
393
+ cancel : jest . fn ( ) ,
394
+ pathRoot : '' ,
395
+ reload : jest . fn ( ) ,
396
+ } ,
397
+ ] ,
398
+ pathRoot : '' ,
399
+ reload : jest . fn ( ) ,
400
+ } ) ;
401
+
402
+ // Assert the plan heading appears after subscription resolves
403
+ await findByRole ( 'heading' , { name : 'Test Plan' } ) ;
404
+ } ) ;
405
+ } ) ;
0 commit comments