diff --git a/.changeset/healthy-drinks-smell.md b/.changeset/healthy-drinks-smell.md new file mode 100644 index 0000000000..d2298c087a --- /dev/null +++ b/.changeset/healthy-drinks-smell.md @@ -0,0 +1,5 @@ +--- +"@adyen/adyen-web": patch +--- + +Fixes `props.holderName` not being used in stored cards as `paymetMethod.holderName`. \ No newline at end of file diff --git a/packages/lib/config/setupTests.ts b/packages/lib/config/setupTests.ts index d96f6e8965..727154d458 100644 --- a/packages/lib/config/setupTests.ts +++ b/packages/lib/config/setupTests.ts @@ -9,5 +9,6 @@ import './testMocks/core.mock'; import './testMocks/analyticsMock'; // eslint-disable-next-line import/no-extraneous-dependencies import 'whatwg-fetch'; +import './testMocks/srPanelMock'; configure({ adapter: new Adapter() }); diff --git a/packages/lib/config/testMocks/srPanelMock.ts b/packages/lib/config/testMocks/srPanelMock.ts new file mode 100644 index 0000000000..e05e57e421 --- /dev/null +++ b/packages/lib/config/testMocks/srPanelMock.ts @@ -0,0 +1,11 @@ +import { mock } from 'jest-mock-extended'; +import { SRPanel } from '../../src/core/Errors/SRPanel'; + +function setupSRPanelMock() { + const srPanel = mock({ + moveFocus: true + }); + return srPanel; +} + +global.srPanel = setupSRPanelMock(); diff --git a/packages/lib/src/components/Card/Card.test.tsx b/packages/lib/src/components/Card/Card.test.tsx index f10ee0c774..2b5dad3a34 100644 --- a/packages/lib/src/components/Card/Card.test.tsx +++ b/packages/lib/src/components/Card/Card.test.tsx @@ -1,6 +1,6 @@ import { h } from 'preact'; import { CardElement } from './Card'; -import { render, screen } from '@testing-library/preact'; +import { render, screen, waitFor } from '@testing-library/preact'; import { CoreProvider } from '../../core/Context/CoreProvider'; describe('Card', () => { @@ -157,6 +157,61 @@ describe('Card', () => { }); }); + describe('formatData', () => { + const i18n = global.i18n; + const resources = global.resources; + const srPanel = global.srPanel; + + const props = { loadingContext: 'test', i18n, modules: { resources, srPanel } }; + const storedCardProps = { supportedShopperInteractions: ['Ecommerce'], storedPaymentMethodId: 'xxx' }; + + test('should echo back holderName if is a stored card', () => { + const card = new CardElement(global.core, { ...props, ...storedCardProps, holderName: 'Test Holder' }); + render(card.render()); + + expect(card.formatData().paymentMethod.holderName).toContain('Test Holder'); + }); + + test('should NOT echo back holderName from data if is a stored card', () => { + const card = new CardElement(global.core, { ...props, ...storedCardProps, data: { holderName: 'Test Holder' } }); + render(card.render()); + + expect(card.formatData().paymentMethod.holderName).toContain(''); + }); + + test('if no holderName specificed and is stored card, holder name should be empty string', () => { + const card = new CardElement(global.core, { ...props, ...storedCardProps }); + + expect(card.formatData().paymentMethod.holderName).toContain(''); + }); + + test('if no holderName specificed and is not stored card, holder name should be empty string', () => { + const card = new CardElement(global.core, { ...props, ...storedCardProps }); + + expect(card.formatData().paymentMethod.holderName).toContain(''); + }); + + test('should NOT echo back holderName if is not a stored card', () => { + const card = new CardElement(global.core, { ...props, holderName: 'Test Holder' }); + render(card.render()); + expect(card.formatData().paymentMethod.holderName).toContain(''); + }); + + test('should set holderName if passed via data', async () => { + const card = new CardElement(global.core, { ...props, hasHolderName: true, data: { holderName: 'Test Holder' } }); + render(card.render()); + // we need to wait here for the screen to render / hook to trigger + await waitFor(() => expect(card.formatData().paymentMethod.holderName).toContain('Test Holder')); + }); + + test('should have empty holderName by default', async () => { + const card = new CardElement(global.core, { ...props }); + render(card.render()); + // we need to wait here for the screen to render / hook to trigger + await waitFor(() => expect(card.formatData().paymentMethod.holderName).toContain('')); + }); + }); + describe('isValid', () => { test('returns false if there is no state', () => { const card = new CardElement(global.core); diff --git a/packages/lib/src/components/Card/Card.tsx b/packages/lib/src/components/Card/Card.tsx index 55969b49fe..4639dcdc45 100644 --- a/packages/lib/src/components/Card/Card.tsx +++ b/packages/lib/src/components/Card/Card.tsx @@ -149,7 +149,10 @@ export class CardElement extends UIElement { paymentMethod: { type: CardElement.type, ...this.state.data, - ...(this.props.storedPaymentMethodId && { storedPaymentMethodId: this.props.storedPaymentMethodId }), + ...(this.props.storedPaymentMethodId && { + storedPaymentMethodId: this.props.storedPaymentMethodId, + holderName: this.props.holderName ?? '' + }), ...(cardBrand && { brand: cardBrand }), ...(this.props.fundingSource && { fundingSource: this.props.fundingSource }) }, diff --git a/packages/lib/src/components/Card/components/CardInput/types.ts b/packages/lib/src/components/Card/components/CardInput/types.ts index a95940f09e..5075f1f1dd 100644 --- a/packages/lib/src/components/Card/components/CardInput/types.ts +++ b/packages/lib/src/components/Card/components/CardInput/types.ts @@ -86,6 +86,7 @@ export interface CardInputProps { fundingSource?: 'debit' | 'credit'; hasCVC?: boolean; hasHolderName?: boolean; + holderName?: string; holderNameRequired?: boolean; i18n?: Language; implementationType?: string; diff --git a/packages/lib/src/components/Card/types.ts b/packages/lib/src/components/Card/types.ts index b8717c68a7..f3a352d3e4 100644 --- a/packages/lib/src/components/Card/types.ts +++ b/packages/lib/src/components/Card/types.ts @@ -190,6 +190,12 @@ export interface CardConfiguration extends UIElementProps { */ hasHolderName?: boolean; + /** + * holderName coming from a stored card in /paymentMethods response + * @internal + */ + holderName?: string; + /** * Show/hide the Security Code field * - merchant set config option