Skip to content

Commit

Permalink
feat(payment): PAYPAL-4827 fixed fastlane shipping strategy bug
Browse files Browse the repository at this point in the history
  • Loading branch information
andriiVitvitskyi1990 committed Nov 15, 2024
1 parent 7952168 commit 5d57641
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 117 deletions.
160 changes: 160 additions & 0 deletions packages/core/src/app/shipping/PayPalFastlaneShippingAddress.test.tsx
Original file line number Diff line number Diff line change
@@ -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(
<Formik initialValues={{}} onSubmit={noop}>
<LocaleContext.Provider value={localeContext}>
<CheckoutContext.Provider value={{ checkoutState, checkoutService }}>
<PayPalFastlaneShippingAddress {...defaultProps} />);
</CheckoutContext.Provider>
</LocaleContext.Provider>
</Formik>
);

const fastlaneEditButton = await screen.findByTestId('step-edit-button');

expect(fastlaneEditButton).toBeInTheDocument();
});

it('initializes fastlane shipping strategy', async () => {
const initializeMock = jest.fn();

component = render(
<Formik initialValues={{}} onSubmit={noop}>
<LocaleContext.Provider value={localeContext}>
<CheckoutContext.Provider value={{ checkoutState, checkoutService }}>
<PayPalFastlaneShippingAddress {...defaultProps} initialize={initializeMock} />);
</CheckoutContext.Provider>
</LocaleContext.Provider>
</Formik>
);

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(
<Formik initialValues={{}} onSubmit={noop}>
<LocaleContext.Provider value={localeContext}>
<CheckoutContext.Provider value={{ checkoutState, checkoutService }}>
<PayPalFastlaneShippingAddress {...defaultProps} />);
</CheckoutContext.Provider>
</LocaleContext.Provider>
</Formik>
);

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();
});
});
156 changes: 137 additions & 19 deletions packages/core/src/app/shipping/PayPalFastlaneShippingAddress.tsx
Original file line number Diff line number Diff line change
@@ -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<CustomerAddress | undefined>;
}

export const PayPalFastlaneShippingAddress: FC<PayPalFastlaneShippingAddressProps> = (props) => {
Expand All @@ -22,20 +50,110 @@ export const PayPalFastlaneShippingAddress: FC<PayPalFastlaneShippingAddressProp
deinitialize,
isLoading,
shippingAddress,
addresses,
isGuest,
handleFieldChange,
consignments
} = props;
const paypalFastlaneShippingComponent = useRef<PayPalFastlaneAddressComponentRef>({});
const paypalCommerceFastlaneOptions = {
paypalcommercefastlane: {
onPayPalFastlaneAddressChange: (
showPayPalFastlaneAddressSelector: PayPalFastlaneAddressComponentRef['showAddressSelector'],
) => {
paypalFastlaneShippingComponent.current.showAddressSelector =
showPayPalFastlaneAddressSelector;
},
},
};

return (
<PayPalFastlaneShippingAddressForm
address={shippingAddress}
countries={countries}
deinitialize={deinitialize}
formFields={formFields}
initialize={initialize}
isLoading={isLoading}
methodId={methodId}
onAddressSelect={onAddressSelect}
onFieldChange={onFieldChange}
onUnhandledError={onUnhandledError}
/>
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 (
<LoadingOverlay hideContentWhenLoading isLoading={!hasSelectedShippingOptions(consignments)}>
{methodId && isPayPalFastlaneMethod(methodId) && shippingAddress && shouldShowPayPalFastlaneShippingForm ? (
<PayPalFastlaneShippingAddressForm
address={shippingAddress}
countries={countries}
deinitialize={deinitialize}
formFields={formFields}
initialize={initialize}
isLoading={isLoading}
methodId={methodId}
onAddressSelect={onAddressSelect}
onFieldChange={onFieldChange}
onUnhandledError={onUnhandledError}
paypalFastlaneShippingComponent={paypalFastlaneShippingComponent}
/>
) : (
<ShippingAddressForm
address={shippingAddress}
addresses={shippingAddresses}
consignments={props.consignments}
countries={countries}
countriesWithAutocomplete={props.countriesWithAutocomplete}
formFields={formFields}
googleMapsApiKey={props.googleMapsApiKey}
isFloatingLabelEnabled={props.isFloatingLabelEnabled}
isLoading={isLoading}
onAddressSelect={onAddressSelect}
onFieldChange={handleFieldChange}
onUseNewAddress={props.onUseNewAddress}
shouldShowSaveAddress={props.shouldShowSaveAddress}
/>
)}
</LoadingOverlay>
);
};
16 changes: 15 additions & 1 deletion packages/core/src/app/shipping/ShippingAddress.spec.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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(
<TestComponent {...shippingFormProps} methodId="braintreeacceleratedcheckout" />,
);

expect(initializeMock).toHaveBeenCalled();
});
});
});
Loading

0 comments on commit 5d57641

Please sign in to comment.