-
Notifications
You must be signed in to change notification settings - Fork 358
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(customer): CHECKOUT-4223 Add customer step component
- Loading branch information
Showing
12 changed files
with
1,225 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Response } from '@bigcommerce/request-sender'; | ||
|
||
export function getResponse<T>(body: T, headers = {}, status = 200, statusText = 'OK'): Response<T> { | ||
return { | ||
body, | ||
status, | ||
statusText, | ||
headers: { | ||
'content-type': 'application/json', | ||
...headers, | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { mount } from 'enzyme'; | ||
import { noop } from 'lodash'; | ||
import React from 'react'; | ||
|
||
import CheckoutButton from './CheckoutButton'; | ||
|
||
describe('CheckoutButton', () => { | ||
it('initializes button when component is mounted', () => { | ||
const initialize = jest.fn(); | ||
const onError = jest.fn(); | ||
|
||
mount( | ||
<CheckoutButton | ||
containerId="foobarContainer" | ||
methodId="foobar" | ||
deinitialize={ noop } | ||
initialize={ initialize } | ||
onError={ onError } | ||
/> | ||
); | ||
|
||
expect(initialize) | ||
.toHaveBeenCalledWith({ | ||
methodId: 'foobar', | ||
foobar: { | ||
container: 'foobarContainer', | ||
onError, | ||
}, | ||
}); | ||
}); | ||
|
||
it('deinitializes button when component unmounts', () => { | ||
const deinitialize = jest.fn(); | ||
const onError = jest.fn(); | ||
|
||
const component = mount( | ||
<CheckoutButton | ||
containerId="foobarContainer" | ||
methodId="foobar" | ||
deinitialize={ deinitialize } | ||
initialize={ noop } | ||
onError={ onError } | ||
/> | ||
); | ||
|
||
component.unmount(); | ||
|
||
expect(deinitialize) | ||
.toHaveBeenCalled(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { CustomerInitializeOptions, CustomerRequestOptions } from '@bigcommerce/checkout-sdk'; | ||
import React, { Component } from 'react'; | ||
|
||
export interface CheckoutButtonProps { | ||
containerId: string; | ||
methodId: string; | ||
deinitialize(options: CustomerRequestOptions): void; | ||
initialize(options: CustomerInitializeOptions): void; | ||
onError?(error: Error): void; | ||
} | ||
|
||
export default class CheckoutButton extends Component<CheckoutButtonProps> { | ||
componentDidMount() { | ||
const { | ||
containerId, | ||
initialize, | ||
methodId, | ||
onError, | ||
} = this.props; | ||
|
||
initialize({ | ||
methodId, | ||
[methodId]: { | ||
container: containerId, | ||
onError, | ||
}, | ||
}); | ||
} | ||
|
||
componentWillUnmount() { | ||
const { | ||
deinitialize, | ||
methodId, | ||
} = this.props; | ||
|
||
deinitialize({ methodId }); | ||
} | ||
|
||
render() { | ||
const { containerId } = this.props; | ||
|
||
return ( | ||
<div id={ containerId } /> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { mount, render } from 'enzyme'; | ||
import { noop } from 'lodash'; | ||
import React from 'react'; | ||
|
||
import { getStoreConfig } from '../config/config.mock'; | ||
import { createLocaleContext, LocaleContext, LocaleContextType } from '../locale'; | ||
|
||
import CheckoutButton from './CheckoutButton'; | ||
import CheckoutButtonList from './CheckoutButtonList'; | ||
|
||
describe('CheckoutButtonList', () => { | ||
let localeContext: LocaleContextType; | ||
|
||
beforeEach(() => { | ||
localeContext = createLocaleContext(getStoreConfig()); | ||
}); | ||
|
||
it('matches snapshot', () => { | ||
const component = render( | ||
<LocaleContext.Provider value={ localeContext }> | ||
<CheckoutButtonList | ||
methodIds={ ['amazon', 'braintreevisacheckout'] } | ||
deinitialize={ noop } | ||
initialize={ noop } | ||
/> | ||
</LocaleContext.Provider> | ||
); | ||
|
||
expect(component) | ||
.toMatchSnapshot(); | ||
}); | ||
|
||
it('filters out unsupported methods', () => { | ||
const component = mount( | ||
<LocaleContext.Provider value={ localeContext }> | ||
<CheckoutButtonList | ||
methodIds={ ['amazon', 'braintreevisacheckout', 'foobar'] } | ||
deinitialize={ noop } | ||
initialize={ noop } | ||
/> | ||
</LocaleContext.Provider> | ||
); | ||
|
||
expect(component.find(CheckoutButton)) | ||
.toHaveLength(2); | ||
}); | ||
|
||
it('does not render if there are no supported methods', () => { | ||
const component = mount( | ||
<LocaleContext.Provider value={ localeContext }> | ||
<CheckoutButtonList | ||
methodIds={ ['foobar'] } | ||
deinitialize={ noop } | ||
initialize={ noop } | ||
/> | ||
</LocaleContext.Provider> | ||
); | ||
|
||
expect(component.html()) | ||
.toEqual(null); | ||
}); | ||
|
||
it('passes data to every checkout button', () => { | ||
const deinitialize = jest.fn(); | ||
const initialize = jest.fn(); | ||
const component = mount( | ||
<LocaleContext.Provider value={ localeContext }> | ||
<CheckoutButtonList | ||
methodIds={ ['amazon', 'braintreevisacheckout'] } | ||
deinitialize={ deinitialize } | ||
initialize={ initialize } | ||
/> | ||
</LocaleContext.Provider> | ||
); | ||
|
||
expect(component.find(CheckoutButton).at(0).props()) | ||
.toEqual({ | ||
containerId: 'amazonCheckoutButton', | ||
methodId: 'amazon', | ||
deinitialize, | ||
initialize, | ||
}); | ||
}); | ||
|
||
it('notifies parent if methods are incompatible with Embedded Checkout', () => { | ||
const methodIds = ['amazon', 'braintreevisacheckout']; | ||
const onError = jest.fn(); | ||
const checkEmbeddedSupport = jest.fn(() => { throw new Error(); }); | ||
|
||
render( | ||
<LocaleContext.Provider value={ localeContext }> | ||
<CheckoutButtonList | ||
methodIds={ methodIds } | ||
checkEmbeddedSupport={ checkEmbeddedSupport } | ||
deinitialize={ noop } | ||
initialize={ noop } | ||
onError={ onError } | ||
/> | ||
</LocaleContext.Provider> | ||
); | ||
|
||
expect(checkEmbeddedSupport) | ||
.toHaveBeenCalledWith(methodIds); | ||
|
||
expect(onError) | ||
.toHaveBeenCalledWith(expect.any(Error)); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { CustomerInitializeOptions, CustomerRequestOptions } from '@bigcommerce/checkout-sdk'; | ||
import React, { Fragment, FunctionComponent } from 'react'; | ||
|
||
import { TranslatedString } from '../language'; | ||
|
||
import CheckoutButton from './CheckoutButton'; | ||
|
||
// TODO: The API should tell UI which payment method offers its own checkout button | ||
export const SUPPORTED_METHODS: string[] = [ | ||
'amazon', | ||
'braintreevisacheckout', | ||
'chasepay', | ||
'masterpass', | ||
'googlepaybraintree', | ||
'googlepaystripe', | ||
]; | ||
|
||
export interface CheckoutButtonListProps { | ||
methodIds: string[]; | ||
checkEmbeddedSupport?(methodIds: string[]): void; | ||
deinitialize(options: CustomerRequestOptions): void; | ||
initialize(options: CustomerInitializeOptions): void; | ||
onError?(error: Error): void; | ||
} | ||
|
||
const CheckoutButtonList: FunctionComponent<CheckoutButtonListProps> = ({ | ||
checkEmbeddedSupport, | ||
onError, | ||
methodIds, | ||
...rest | ||
}) => { | ||
const supportedMethodIds = methodIds | ||
.filter(methodId => SUPPORTED_METHODS.indexOf(methodId) !== -1); | ||
|
||
if (supportedMethodIds.length === 0) { | ||
return null; | ||
} | ||
|
||
if (checkEmbeddedSupport) { | ||
try { | ||
checkEmbeddedSupport(supportedMethodIds); | ||
} catch (error) { | ||
if (onError) { | ||
onError(error); | ||
} else { | ||
throw error; | ||
} | ||
|
||
return null; | ||
} | ||
} | ||
|
||
return ( | ||
<Fragment> | ||
<p><TranslatedString id="remote.continue_with_text" /></p> | ||
|
||
<div className="checkoutRemote"> | ||
{ supportedMethodIds.map(methodId => | ||
<CheckoutButton | ||
containerId={ `${methodId}CheckoutButton` } | ||
key={ methodId } | ||
methodId={ methodId } | ||
onError={ onError } | ||
{ ...rest } | ||
/> | ||
) } | ||
</div> | ||
</Fragment> | ||
); | ||
}; | ||
|
||
export default CheckoutButtonList; |
Oops, something went wrong.