import React, {StrictMode} from 'react'; import {render, act, waitFor} from '@testing-library/react'; import {renderHook} from '@testing-library/react-hooks'; import {CheckoutFormProvider} from './CheckoutFormProvider'; import {useCheckout} from './CheckoutContext'; import * as mocks from '../../../test/mocks'; import makeDeferred from '../../../test/makeDeferred'; describe('CheckoutFormProvider', () => { let mockStripe: any; let mockStripePromise: any; let mockCheckoutSdk: any; let consoleWarn: any; let consoleError: any; beforeEach(() => { mockCheckoutSdk = mocks.mockCheckoutSdk(); const mockCheckoutActions = mocks.mockCheckoutActions(); mockCheckoutSdk.loadActions.mockResolvedValue({ type: 'success', actions: mockCheckoutActions, }); mockStripe = mocks.mockStripe(); mockStripe.initCheckoutFormSdk.mockReturnValue(mockCheckoutSdk); mockStripePromise = Promise.resolve(mockStripe); jest.spyOn(console, 'error'); jest.spyOn(console, 'warn'); consoleError = console.error; consoleWarn = console.warn; }); afterEach(() => { jest.restoreAllMocks(); }); const fakeClientSecret = 'cs_123'; describe('calls initCheckoutFormSdk (not initCheckoutElementsSdk)', () => { it('calls initCheckoutFormSdk with the provided options', async () => { render(
); await waitFor(() => { expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledTimes(1); expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledWith({ clientSecret: fakeClientSecret, }); expect(mockStripe.initCheckoutElementsSdk).not.toHaveBeenCalled(); }); }); it('calls initCheckoutFormSdk once in StrictMode', async () => { const TestComponent = () => { useCheckout(); return
; }; act(() => { render( ); }); await waitFor(() => expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledTimes(1) ); }); it('calls initCheckoutFormSdk once with stripePromise in StrictMode', async () => { const TestComponent = () => { useCheckout(); return
; }; act(() => { render( ); }); await waitFor(() => expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledTimes(1) ); }); }); describe('useCheckout() works within CheckoutFormProvider', () => { it('provides checkout state through shared context', async () => { const stripe: any = mocks.mockStripe(); const deferred = makeDeferred(); const mockSdk = mocks.mockCheckoutSdk(); const testMockCheckoutActions = mocks.mockCheckoutActions(); const testMockSession = mocks.mockCheckoutSession(); mockSdk.loadActions.mockReturnValue(deferred.promise); stripe.initCheckoutFormSdk.mockReturnValue(mockSdk); const wrapper = ({children}: any) => ( {children} ); const {result} = renderHook(() => useCheckout(), {wrapper}); expect(result.current).toEqual({type: 'loading'}); expect(stripe.initCheckoutFormSdk).toHaveBeenCalledTimes(1); await act(() => deferred.resolve({ type: 'success', actions: testMockCheckoutActions, }) ); const {on: _on, loadActions: _loadActions, ...sdkMethods} = mockSdk; const { getSession: _getSession, ...otherCheckoutActions } = testMockCheckoutActions; expect(result.current).toEqual({ type: 'success', checkout: { ...sdkMethods, ...otherCheckoutActions, ...testMockSession, }, }); }); }); describe('top-level appearance option (not nested under elementsOptions)', () => { it('passes top-level appearance to initCheckoutFormSdk and calls changeAppearance on update', async () => { const result = render( ); await waitFor(() => { expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledWith({ clientSecret: fakeClientSecret, appearance: {theme: 'stripe'}, }); }); act(() => { result.rerender( ); }); await waitFor(() => { expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledTimes(1); expect(mockCheckoutSdk.changeAppearance).toHaveBeenCalledTimes(1); expect(mockCheckoutSdk.changeAppearance).toHaveBeenCalledWith({ theme: 'night', }); }); }); it('allows changes to appearance via changeAppearance in StrictMode', async () => { let result: any; act(() => { result = render( ); }); await waitFor(() => { expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledTimes(1); expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledWith({ clientSecret: fakeClientSecret, appearance: {theme: 'stripe'}, }); }); act(() => { result.rerender( ); }); await waitFor(() => { expect(mockCheckoutSdk.changeAppearance).toHaveBeenCalledTimes(1); expect(mockCheckoutSdk.changeAppearance).toHaveBeenCalledWith({ theme: 'night', }); }); }); }); describe('top-level fonts option (not nested under elementsOptions)', () => { it('calls loadFonts when fonts change', async () => { let result: any; act(() => { result = render( ); }); await waitFor(() => expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledWith({ clientSecret: fakeClientSecret, }) ); act(() => { result.rerender( ); }); await waitFor(() => { expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledTimes(1); expect(mockCheckoutSdk.loadFonts).toHaveBeenCalledTimes(1); expect(mockCheckoutSdk.loadFonts).toHaveBeenCalledWith([ {cssSrc: 'https://example.com/font.css'}, ]); }); }); it('does not call loadFonts again if fonts do not change', async () => { let result: any; act(() => { result = render( ); }); await waitFor(() => expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledWith({ clientSecret: fakeClientSecret, fonts: [{cssSrc: 'https://example.com/font.css'}], }) ); act(() => { result.rerender( ); }); act(() => { result.rerender( ); }); await waitFor(() => { expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledTimes(1); expect(mockCheckoutSdk.loadFonts).toHaveBeenCalledTimes(0); }); }); }); describe('stripe prop validation', () => { it('validates stripe prop type with CheckoutFormProvider-specific message', () => { consoleError.mockImplementation(() => {}); const renderWithProp = (stripeProp: unknown) => () => { render(
); }; expect(renderWithProp(undefined)).toThrow( 'Invalid prop `stripe` supplied to `CheckoutFormProvider`.' ); expect(renderWithProp(false)).toThrow( 'Invalid prop `stripe` supplied to `CheckoutFormProvider`.' ); expect(renderWithProp('foo')).toThrow( 'Invalid prop `stripe` supplied to `CheckoutFormProvider`.' ); }); it('does not allow changes to an already set Stripe object', async () => { consoleWarn.mockImplementation(() => {}); let result: any; act(() => { result = render( ); }); const mockStripe2: any = mocks.mockStripe(); act(() => { result.rerender( ); }); await waitFor(() => { expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledTimes(1); expect(mockStripe2.initCheckoutFormSdk).toHaveBeenCalledTimes(0); expect(consoleWarn).toHaveBeenCalledWith( 'Unsupported prop change on CheckoutFormProvider: You cannot change the `stripe` prop after setting it.' ); }); }); }); it('allows options changes before setting the Stripe object', async () => { const result = render( ); await waitFor(() => expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledTimes(0) ); act(() => { result.rerender( ); }); await waitFor(() => { expect(console.warn).not.toHaveBeenCalled(); expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledTimes(1); expect(mockStripe.initCheckoutFormSdk).toHaveBeenCalledWith({ clientSecret: fakeClientSecret, appearance: {theme: 'stripe'}, }); }); }); });