diff --git a/packages/core/src/app/shipping/PayPalFastlaneShippingAddress.test.tsx b/packages/core/src/app/shipping/PayPalFastlaneShippingAddress.test.tsx new file mode 100644 index 0000000000..b186a879c9 --- /dev/null +++ b/packages/core/src/app/shipping/PayPalFastlaneShippingAddress.test.tsx @@ -0,0 +1,160 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import {getAddress, getCustomer} from '@bigcommerce/checkout/test-mocks'; +import {CheckoutSelectors, CheckoutService, createCheckoutService} from '@bigcommerce/checkout-sdk'; +import { PayPalFastlaneShippingAddress, PayPalFastlaneShippingAddressProps } from './PayPalFastlaneShippingAddress'; +import { createLocaleContext, LocaleContext, LocaleContextType } from '@bigcommerce/checkout/locale'; +import { CheckoutContext } from '@bigcommerce/checkout/payment-integration-api'; +import { getStoreConfig } from '../config/config.mock'; +import { usePayPalFastlaneAddress } from '@bigcommerce/checkout/paypal-fastlane-integration'; +import { noop } from 'lodash'; +import { Formik } from 'formik'; + +jest.mock('@bigcommerce/checkout/paypal-fastlane-integration', () => ({ + ...jest.requireActual('@bigcommerce/checkout/paypal-fastlane-integration'), + usePayPalFastlaneAddress: jest.fn(() => ({ + isPayPalFastlaneEnabled: false, + paypalFastlaneAddresses: [], + })), +})); + +describe('PayPalFastlaneShippingAddress', () => { + let component; + let defaultProps: PayPalFastlaneShippingAddressProps; + let checkoutService: CheckoutService; + let checkoutState: CheckoutSelectors; + let localeContext: LocaleContextType; + + beforeEach(() => { + localeContext = createLocaleContext(getStoreConfig()); + checkoutService = createCheckoutService(); + checkoutState = checkoutService.getState(); + + const paypalFastlaneAddresses = [{ + ...getAddress(), + address1: 'PP Fastlane address' + }]; + + (usePayPalFastlaneAddress as jest.Mock).mockReturnValue({ + isPayPalFastlaneEnabled: true, + paypalFastlaneAddresses, + shouldShowPayPalFastlaneShippingForm: true, + }); + + defaultProps = { + consignments:[], + countriesWithAutocomplete: [], + handleFieldChange: jest.fn(), + isShippingStepPending: false, + hasRequestedShippingOptions: false, + onUseNewAddress: jest.fn(), + addresses: getCustomer().addresses, + shippingAddress: getCustomer().addresses[0], + formFields: [ + { + custom: false, + default: 'NO PO BOX', + id: 'field_18', + label: 'Address Line 1', + name: 'address1', + required: true, + }, + { + custom: true, + default: '', + id: 'field_19', + label: 'Address Line 2', + name: 'address2', + required: false, + }, + ], + isLoading: false, + methodId: 'paypalcommerceacceleratedcheckout', + deinitialize: jest.fn(), + initialize: jest.fn(), + onAddressSelect: jest.fn(), + onFieldChange: jest.fn(), + onUnhandledError: jest.fn(), + countries: [ + { + code: 'US', + name: 'United States', + hasPostalCodes: true, + requiresState: true, + subdivisions: [ + { code: 'CA', name: 'California' }, + { code: 'TX', name: 'Texas' }, + ], + } + ], + }; + }) + + it('renders PayPalFastlaneShippingAddress edit button', async () => { + component = render( + + + + ); + + + + ); + + const fastlaneEditButton = await screen.findByTestId('step-edit-button'); + + expect(fastlaneEditButton).toBeInTheDocument(); + }); + + it('initializes fastlane shipping strategy', async () => { + const initializeMock = jest.fn(); + + component = render( + + + + ); + + + + ); + + expect(initializeMock).toHaveBeenCalled(); + }); + + it('renders regular shipping form if shouldShowPayPalFastlaneShippingForm false', async () => { + const paypalFastlaneAddresses = [{ + ...getAddress(), + address1: 'PP Fastlane address' + }]; + (usePayPalFastlaneAddress as jest.Mock).mockReturnValue({ + isPayPalFastlaneEnabled: true, + paypalFastlaneAddresses, + shouldShowPayPalFastlaneShippingForm: false, + }); + + component = render( + + + + ); + + + + ); + + try { + await screen.findByTestId('step-edit-button'); + } catch (error) { + expect(error).toBeDefined(); + } + + const countryText = await screen.findByText('United States'); + const stateText = await screen.findByText('California,'); + const streetText = await screen.findByText('12345 Testing Way'); + + expect(countryText).toBeInTheDocument(); + expect(streetText).toBeInTheDocument(); + expect(stateText).toBeInTheDocument(); + }); +}); diff --git a/packages/core/src/app/shipping/PayPalFastlaneShippingAddress.tsx b/packages/core/src/app/shipping/PayPalFastlaneShippingAddress.tsx index 3d058782c8..b7ef33a8a0 100644 --- a/packages/core/src/app/shipping/PayPalFastlaneShippingAddress.tsx +++ b/packages/core/src/app/shipping/PayPalFastlaneShippingAddress.tsx @@ -1,13 +1,41 @@ -import { Address } from '@bigcommerce/checkout-sdk'; -import React, { FC } from 'react'; +import { + Address, + Consignment, + Country, + CustomerAddress, + FormField, + ShippingInitializeOptions +} from '@bigcommerce/checkout-sdk'; +import React, { FC, useEffect, useRef } from 'react'; +import hasSelectedShippingOptions from './hasSelectedShippingOptions'; -import { PayPalFastlaneShippingAddressForm } from '@bigcommerce/checkout/paypal-fastlane-integration'; +import { + isPayPalCommerceFastlaneMethod, + isPayPalFastlaneMethod, + PayPalFastlaneShippingAddressForm, + usePayPalFastlaneAddress, +} from '@bigcommerce/checkout/paypal-fastlane-integration'; import { ShippingAddressProps } from './ShippingAddress'; -interface PayPalFastlaneShippingAddressProps extends ShippingAddressProps { - methodId: string, - shippingAddress: Address, +import ShippingAddressForm from './ShippingAddressForm'; +import { LoadingOverlay } from '../ui/loading'; + +export interface PayPalFastlaneShippingAddressProps extends ShippingAddressProps { + methodId?: string, + shippingAddress?: Address, + isGuest?: boolean, + consignments: Consignment[]; + countries?: Country[]; + countriesWithAutocomplete: string[]; + formFields: FormField[], + googleMapsApiKey?: string; + handleFieldChange(fieldName: string, value: string): void, + onAddressSelect(address: Address): void; +} + +interface PayPalFastlaneAddressComponentRef { + showAddressSelector?: () => Promise; } export const PayPalFastlaneShippingAddress: FC = (props) => { @@ -22,20 +50,110 @@ export const PayPalFastlaneShippingAddress: FC({}); + const paypalCommerceFastlaneOptions = { + paypalcommercefastlane: { + onPayPalFastlaneAddressChange: ( + showPayPalFastlaneAddressSelector: PayPalFastlaneAddressComponentRef['showAddressSelector'], + ) => { + paypalFastlaneShippingComponent.current.showAddressSelector = + showPayPalFastlaneAddressSelector; + }, + }, + }; - return ( - + const braintreeFastlaneOptions = { + braintreefastlane: { + onPayPalFastlaneAddressChange: ( + showPayPalFastlaneAddressSelector: PayPalFastlaneAddressComponentRef['showAddressSelector'], + ) => { + paypalFastlaneShippingComponent.current.showAddressSelector = + showPayPalFastlaneAddressSelector; + }, + }, + }; + + const initializationOptions: ShippingInitializeOptions = isPayPalCommerceFastlaneMethod( + methodId, ) + ? paypalCommerceFastlaneOptions + : braintreeFastlaneOptions; + + const initializeShippingStrategyOrThrow = async () => { + try { + await initialize({ + methodId, + ...initializationOptions, + }); + } catch (error) { + if (typeof onUnhandledError === 'function' && error instanceof Error) { + onUnhandledError(error); + } + } + }; + + const deinitializeShippingStrategyOrThrow = async () => { + try { + await deinitialize({ methodId }); + } catch (error) { + if (typeof onUnhandledError === 'function' && error instanceof Error) { + onUnhandledError(error); + } + } + }; + + useEffect(() => { + void initializeShippingStrategyOrThrow(); + + return () => { + void deinitializeShippingStrategyOrThrow(); + }; + }, []); + + const { isPayPalFastlaneEnabled, paypalFastlaneAddresses, shouldShowPayPalFastlaneShippingForm } = usePayPalFastlaneAddress(); + + const shippingAddresses = isPayPalFastlaneEnabled && isGuest + ? paypalFastlaneAddresses + : addresses; + + return ( + + {methodId && isPayPalFastlaneMethod(methodId) && shippingAddress && shouldShowPayPalFastlaneShippingForm ? ( + + ) : ( + + )} + + ); }; diff --git a/packages/core/src/app/shipping/ShippingAddress.spec.tsx b/packages/core/src/app/shipping/ShippingAddress.spec.tsx index d7bae8e983..d32cfebde7 100644 --- a/packages/core/src/app/shipping/ShippingAddress.spec.tsx +++ b/packages/core/src/app/shipping/ShippingAddress.spec.tsx @@ -1,5 +1,5 @@ import { CheckoutService, createCheckoutService } from '@bigcommerce/checkout-sdk'; -import { mount, ReactWrapper, shallow } from 'enzyme'; +import { mount, ReactWrapper } from 'enzyme'; import { Formik } from 'formik'; import { noop } from 'lodash'; import React, { FunctionComponent } from 'react'; @@ -116,5 +116,19 @@ describe('ShippingAddress Component', () => { expect(component.find(StaticAddressEditable)).toHaveLength(1); }); + + it('calls initialize function when fastlane enabled and shouldShowPayPalFastlaneShippingForm false', () => { + const initializeMock = jest.fn(); + const shippingFormProps = { + ...defaultProps, + initialize: initializeMock, + }; + + mount( + , + ); + + expect(initializeMock).toHaveBeenCalled(); + }); }); }); diff --git a/packages/core/src/app/shipping/ShippingAddress.tsx b/packages/core/src/app/shipping/ShippingAddress.tsx index 2733767eec..348b9c9d83 100644 --- a/packages/core/src/app/shipping/ShippingAddress.tsx +++ b/packages/core/src/app/shipping/ShippingAddress.tsx @@ -10,12 +10,12 @@ import { } from '@bigcommerce/checkout-sdk'; import React, { FunctionComponent, memo, useContext } from 'react'; -import { isPayPalFastlaneMethod, usePayPalFastlaneAddress } from '@bigcommerce/checkout/paypal-fastlane-integration'; import { FormContext } from '@bigcommerce/checkout/ui'; import { AmazonPayShippingAddress } from './AmazonPayShippingAddress'; import { PayPalFastlaneShippingAddress } from './PayPalFastlaneShippingAddress'; -import ShippingAddressForm from './ShippingAddressForm'; +import { isPayPalFastlaneMethod } from '@bigcommerce/checkout/paypal-fastlane-integration'; +import ShippingAddressForm from "./ShippingAddressForm"; export interface ShippingAddressProps { addresses: CustomerAddress[]; @@ -24,6 +24,7 @@ export interface ShippingAddressProps { countriesWithAutocomplete: string[]; formFields: FormField[]; googleMapsApiKey?: string; + isGuest?: boolean; isLoading: boolean; isShippingStepPending: boolean; methodId?: string; @@ -56,9 +57,9 @@ const ShippingAddress: FunctionComponent = (props) => { addresses, shouldShowSaveAddress, isFloatingLabelEnabled, + isGuest, } = props; - const { shouldShowPayPalFastlaneShippingForm } = usePayPalFastlaneAddress(); const { setSubmitted } = useContext(FormContext); const handleFieldChange: (fieldName: string, value: string) => void = (fieldName, value) => { @@ -69,42 +70,42 @@ const ShippingAddress: FunctionComponent = (props) => { onFieldChange(fieldName, value); }; - if (methodId === 'amazonpay' && shippingAddress) { + if (methodId && isPayPalFastlaneMethod(methodId) && shippingAddress) { return ( - - ); + ) } - if (methodId && isPayPalFastlaneMethod(methodId) && shippingAddress && shouldShowPayPalFastlaneShippingForm) { + if (methodId === 'amazonpay' && shippingAddress) { return ( - - ) + ); } - return ( - - ); + return }; export default memo(ShippingAddress); diff --git a/packages/core/src/app/shipping/ShippingForm.spec.tsx b/packages/core/src/app/shipping/ShippingForm.spec.tsx index 97e0170117..8bc6eecd91 100644 --- a/packages/core/src/app/shipping/ShippingForm.spec.tsx +++ b/packages/core/src/app/shipping/ShippingForm.spec.tsx @@ -393,7 +393,7 @@ describe('ShippingForm Component', () => { - + , diff --git a/packages/core/src/app/shipping/ShippingForm.tsx b/packages/core/src/app/shipping/ShippingForm.tsx index b72c37ebfd..7bde4c516d 100644 --- a/packages/core/src/app/shipping/ShippingForm.tsx +++ b/packages/core/src/app/shipping/ShippingForm.tsx @@ -17,12 +17,12 @@ import { import React from 'react'; import { withLanguage, WithLanguageProps } from '@bigcommerce/checkout/locale'; -import { usePayPalFastlaneAddress } from '@bigcommerce/checkout/paypal-fastlane-integration'; import MultiShippingForm, { MultiShippingFormValues } from './MultiShippingForm'; import MultiShippingFormV2 from './MultiShippingFormV2'; import MultiShippingGuestForm from './MultiShippingGuestForm'; import SingleShippingForm, { SingleShippingFormValues } from './SingleShippingForm'; +import { usePayPalFastlaneAddress } from '@bigcommerce/checkout/paypal-fastlane-integration'; export interface ShippingFormProps { addresses: CustomerAddress[]; @@ -153,6 +153,7 @@ const ShippingForm = ({ getMultiShippingForm() ) : (
; deinitialize(options?: ShippingRequestOptions): Promise; initialize(options?: ShippingInitializeOptions): Promise; onAddressSelect(address: Address): void; @@ -42,78 +42,14 @@ export interface PayPalFastlaneAddressComponentRef { const PayPalFastlaneShippingAddressForm = (props: PayPalFastlaneStaticAddressProps) => { const { address: addressWithoutLocalization, - methodId, formFields, isLoading, - initialize, - deinitialize, - onUnhandledError, onFieldChange, countries, + paypalFastlaneShippingComponent } = props; const address = localizeAddress(addressWithoutLocalization, countries); - const paypalFastlaneShippingComponent = useRef({}); - - const paypalCommerceFastlaneOptions = { - paypalcommercefastlane: { - onPayPalFastlaneAddressChange: ( - showPayPalFastlaneAddressSelector: PayPalFastlaneAddressComponentRef['showAddressSelector'], - ) => { - paypalFastlaneShippingComponent.current.showAddressSelector = - showPayPalFastlaneAddressSelector; - }, - }, - }; - - const braintreeFastlaneOptions = { - braintreefastlane: { - onPayPalFastlaneAddressChange: ( - showPayPalFastlaneAddressSelector: PayPalFastlaneAddressComponentRef['showAddressSelector'], - ) => { - paypalFastlaneShippingComponent.current.showAddressSelector = - showPayPalFastlaneAddressSelector; - }, - }, - }; - - const initializationOptions: ShippingInitializeOptions = isPayPalCommerceFastlaneMethod( - methodId, - ) - ? paypalCommerceFastlaneOptions - : braintreeFastlaneOptions; - - const initializeShippingStrategyOrThrow = async () => { - try { - await initialize({ - methodId, - ...initializationOptions, - }); - } catch (error) { - if (typeof onUnhandledError === 'function' && error instanceof Error) { - onUnhandledError(error); - } - } - }; - - const deinitializeShippingStrategyOrThrow = async () => { - try { - await deinitialize({ methodId }); - } catch (error) { - if (typeof onUnhandledError === 'function' && error instanceof Error) { - onUnhandledError(error); - } - } - }; - - useEffect(() => { - void initializeShippingStrategyOrThrow(); - - return () => { - void deinitializeShippingStrategyOrThrow(); - }; - }, []); - const customFormFields = formFields.filter(({ custom }) => custom); const shouldShowCustomFormFields = customFormFields.length > 0;