Skip to content

Commit 526d505

Browse files
authored
chore(clerk-js): Add unit tests for checkout completed screen (#6763)
1 parent 1b2a021 commit 526d505

File tree

2 files changed

+192
-9
lines changed

2 files changed

+192
-9
lines changed

.changeset/lucky-readers-bathe.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx

Lines changed: 190 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('Checkout', () => {
3131
});
3232

3333
// Mock billing to prevent actual API calls and stay in loading state
34-
fixtures.clerk.billing.startCheckout.mockImplementation(() => new Promise(() => {}));
34+
fixtures.clerk.billing.startCheckout.mockResolvedValue({} as any);
3535

3636
const { baseElement } = render(
3737
<Drawer.Root
@@ -67,7 +67,7 @@ describe('Checkout', () => {
6767
});
6868

6969
// Mock billing to prevent actual API calls
70-
fixtures.clerk.billing.startCheckout.mockImplementation(() => new Promise(() => {}));
70+
fixtures.clerk.billing.startCheckout.mockResolvedValue({} as any);
7171

7272
const { baseElement, getByRole } = render(
7373
<Drawer.Root
@@ -135,7 +135,7 @@ describe('Checkout', () => {
135135
});
136136

137137
// Mock billing to stay in loading state
138-
fixtures.clerk.billing.startCheckout.mockImplementation(() => new Promise(() => {}));
138+
fixtures.clerk.billing.startCheckout.mockResolvedValue({} as any);
139139

140140
const { baseElement } = render(
141141
<Drawer.Root
@@ -167,7 +167,7 @@ describe('Checkout', () => {
167167
f.withBilling();
168168
});
169169

170-
fixtures.clerk.billing.startCheckout.mockImplementation(() => new Promise(() => {}));
170+
fixtures.clerk.billing.startCheckout.mockResolvedValue({} as any);
171171

172172
const { baseElement } = render(
173173
<Drawer.Root
@@ -209,7 +209,7 @@ describe('Checkout', () => {
209209
});
210210

211211
// Mock billing to prevent actual API calls
212-
fixtures.clerk.billing.startCheckout.mockImplementation(() => new Promise(() => {}));
212+
fixtures.clerk.billing.startCheckout.mockResolvedValue({} as any);
213213

214214
const { baseElement } = render(
215215
<Drawer.Root
@@ -235,7 +235,7 @@ describe('Checkout', () => {
235235
f.withUser({ email_addresses: ['[email protected]'] });
236236
f.withBilling();
237237
});
238-
fixtures.clerk.billing.startCheckout.mockImplementation(() => new Promise(() => {}));
238+
fixtures.clerk.billing.startCheckout.mockResolvedValue({} as any);
239239
const { baseElement } = render(
240240
<Drawer.Root
241241
open
@@ -257,7 +257,7 @@ describe('Checkout', () => {
257257
const { wrapper, fixtures } = await createFixtures(f => {
258258
f.withUser({ email_addresses: ['[email protected]'] });
259259
});
260-
fixtures.clerk.billing.startCheckout.mockImplementation(() => new Promise(() => {}));
260+
fixtures.clerk.billing.startCheckout.mockResolvedValue({} as any);
261261
const { baseElement } = render(
262262
<Drawer.Root
263263
open
@@ -281,7 +281,7 @@ describe('Checkout', () => {
281281
f.withBilling();
282282
});
283283

284-
fixtures.clerk.billing.startCheckout.mockImplementation(() => new Promise(() => {}));
284+
fixtures.clerk.billing.startCheckout.mockResolvedValue({} as any);
285285

286286
const { baseElement } = render(
287287
<Drawer.Root
@@ -316,7 +316,6 @@ describe('Checkout', () => {
316316
});
317317

318318
const freeTrialEndsAt = new Date('2025-08-19');
319-
320319
fixtures.clerk.billing.startCheckout.mockResolvedValue({
321320
id: 'chk_trial_1',
322321
status: 'needs_confirmation',
@@ -475,6 +474,188 @@ describe('Checkout', () => {
475474
});
476475
});
477476

477+
it('renders subscription start data on completed stage for downgrades', async () => {
478+
const { wrapper, fixtures } = await createFixtures(f => {
479+
f.withUser({ email_addresses: ['[email protected]'] });
480+
f.withBilling();
481+
});
482+
483+
fixtures.clerk.billing.startCheckout.mockResolvedValue({
484+
id: 'chk_payment_method_2',
485+
status: 'completed',
486+
externalClientSecret: 'cs_test_payment_method_2',
487+
externalGatewayId: 'gw_test',
488+
totals: {
489+
subtotal: { amount: 1000, amountFormatted: '10.00', currency: 'USD', currencySymbol: '$' },
490+
grandTotal: { amount: 1000, amountFormatted: '10.00', currency: 'USD', currencySymbol: '$' },
491+
taxTotal: { amount: 0, amountFormatted: '0.00', currency: 'USD', currencySymbol: '$' },
492+
credit: { amount: 0, amountFormatted: '0.00', currency: 'USD', currencySymbol: '$' },
493+
pastDue: { amount: 0, amountFormatted: '0.00', currency: 'USD', currencySymbol: '$' },
494+
totalDueNow: { amount: 0, amountFormatted: '0.00', currency: 'USD', currencySymbol: '$' },
495+
},
496+
isImmediatePlanChange: true,
497+
planPeriod: 'month',
498+
plan: {
499+
id: 'plan_trial',
500+
name: 'Pro',
501+
description: 'Pro plan',
502+
features: [],
503+
fee: {
504+
amount: 1000,
505+
amountFormatted: '10.00',
506+
currency: 'USD',
507+
currencySymbol: '$',
508+
},
509+
annualFee: {
510+
amount: 12000,
511+
amountFormatted: '120.00',
512+
currency: 'USD',
513+
currencySymbol: '$',
514+
},
515+
annualMonthlyFee: {
516+
amount: 1000,
517+
amountFormatted: '10.00',
518+
currency: 'USD',
519+
currencySymbol: '$',
520+
},
521+
slug: 'pro',
522+
avatarUrl: '',
523+
publiclyVisible: true,
524+
isDefault: true,
525+
isRecurring: true,
526+
hasBaseFee: false,
527+
forPayerType: 'user',
528+
freeTrialDays: 7,
529+
freeTrialEnabled: true,
530+
},
531+
paymentSource: {
532+
id: 'pm_test_visa',
533+
last4: '4242',
534+
paymentMethod: 'card',
535+
cardType: 'visa',
536+
isDefault: true,
537+
isRemovable: true,
538+
status: 'active',
539+
walletType: undefined,
540+
remove: jest.fn(),
541+
makeDefault: jest.fn(),
542+
pathRoot: '/',
543+
reload: jest.fn(),
544+
},
545+
planPeriodStart: new Date('2025-08-19'),
546+
confirm: jest.fn(),
547+
freeTrialEndsAt: null,
548+
} as any);
549+
550+
const { getByText } = render(
551+
<Drawer.Root
552+
open
553+
onOpenChange={() => {}}
554+
>
555+
<Checkout
556+
planId='plan_payment_method_complete'
557+
planPeriod='month'
558+
/>
559+
</Drawer.Root>,
560+
{ wrapper },
561+
);
562+
563+
await waitFor(() => {
564+
expect(getByText('Subscription begins')).toBeVisible();
565+
expect(getByText('August 19, 2025')).toBeVisible();
566+
});
567+
});
568+
569+
it('renders payment method on completed stage', async () => {
570+
const { wrapper, fixtures } = await createFixtures(f => {
571+
f.withUser({ email_addresses: ['[email protected]'] });
572+
f.withBilling();
573+
});
574+
575+
fixtures.clerk.billing.startCheckout.mockResolvedValue({
576+
id: 'chk_payment_method_2',
577+
status: 'completed',
578+
externalClientSecret: 'cs_test_payment_method_2',
579+
externalGatewayId: 'gw_test',
580+
totals: {
581+
subtotal: { amount: 1000, amountFormatted: '10.00', currency: 'USD', currencySymbol: '$' },
582+
grandTotal: { amount: 1000, amountFormatted: '10.00', currency: 'USD', currencySymbol: '$' },
583+
taxTotal: { amount: 0, amountFormatted: '0.00', currency: 'USD', currencySymbol: '$' },
584+
credit: { amount: 0, amountFormatted: '0.00', currency: 'USD', currencySymbol: '$' },
585+
pastDue: { amount: 0, amountFormatted: '0.00', currency: 'USD', currencySymbol: '$' },
586+
totalDueNow: { amount: 1000, amountFormatted: '10.00', currency: 'USD', currencySymbol: '$' },
587+
},
588+
isImmediatePlanChange: true,
589+
planPeriod: 'month',
590+
plan: {
591+
id: 'plan_trial',
592+
name: 'Pro',
593+
description: 'Pro plan',
594+
features: [],
595+
fee: {
596+
amount: 1000,
597+
amountFormatted: '10.00',
598+
currency: 'USD',
599+
currencySymbol: '$',
600+
},
601+
annualFee: {
602+
amount: 12000,
603+
amountFormatted: '120.00',
604+
currency: 'USD',
605+
currencySymbol: '$',
606+
},
607+
annualMonthlyFee: {
608+
amount: 1000,
609+
amountFormatted: '10.00',
610+
currency: 'USD',
611+
currencySymbol: '$',
612+
},
613+
slug: 'pro',
614+
avatarUrl: '',
615+
publiclyVisible: true,
616+
isDefault: true,
617+
isRecurring: true,
618+
hasBaseFee: false,
619+
forPayerType: 'user',
620+
freeTrialDays: 7,
621+
freeTrialEnabled: true,
622+
},
623+
paymentSource: {
624+
id: 'pm_test_visa',
625+
last4: '4242',
626+
paymentMethod: 'card',
627+
cardType: 'visa',
628+
isDefault: true,
629+
isRemovable: true,
630+
status: 'active',
631+
walletType: undefined,
632+
remove: jest.fn(),
633+
makeDefault: jest.fn(),
634+
pathRoot: '/',
635+
reload: jest.fn(),
636+
},
637+
confirm: jest.fn(),
638+
freeTrialEndsAt: null,
639+
} as any);
640+
641+
const { getByText } = render(
642+
<Drawer.Root
643+
open
644+
onOpenChange={() => {}}
645+
>
646+
<Checkout
647+
planId='plan_payment_method_complete'
648+
planPeriod='month'
649+
/>
650+
</Drawer.Root>,
651+
{ wrapper },
652+
);
653+
654+
await waitFor(() => {
655+
expect(getByText('Visa ⋯ 4242')).toBeVisible();
656+
});
657+
});
658+
478659
it('renders existing payment sources during checkout confirmation', async () => {
479660
const { wrapper, fixtures } = await createFixtures(f => {
480661
f.withUser({ email_addresses: ['[email protected]'] });

0 commit comments

Comments
 (0)