diff --git a/.changeset/cold-jeans-yawn.md b/.changeset/cold-jeans-yawn.md new file mode 100644 index 0000000000..0aabaac2fa --- /dev/null +++ b/.changeset/cold-jeans-yawn.md @@ -0,0 +1,5 @@ +--- +"@adyen/adyen-web": patch +--- + +Fix address lookup reseting state field after country change diff --git a/packages/lib/src/components/internal/Address/Address.test.tsx b/packages/lib/src/components/internal/Address/Address.test.tsx index 500d93b482..2fc4121da9 100644 --- a/packages/lib/src/components/internal/Address/Address.test.tsx +++ b/packages/lib/src/components/internal/Address/Address.test.tsx @@ -6,9 +6,31 @@ import { AddressData } from '../../../types'; import { FALLBACK_VALUE } from './constants'; import { render, screen } from '@testing-library/preact'; import { CoreProvider } from '../../../core/Context/CoreProvider'; +import userEvent from '@testing-library/user-event'; jest.mock('../../../core/Services/get-dataset'); -(getDataset as jest.Mock).mockImplementation(jest.fn(() => Promise.resolve([{ id: 'NL', name: 'Netherlands' }]))); +(getDataset as jest.Mock).mockImplementation( + jest.fn(dataset => { + switch (dataset) { + case 'countries': + return Promise.resolve([ + { id: 'US', name: 'United States' }, + { id: 'CA', name: 'Canada' }, + { id: 'NL', name: 'Netherlands' } + ]); + case 'states/US': + return Promise.resolve([ + { id: 'AR', name: 'Arkansas' }, + { id: 'CA', name: 'California' } + ]); + case 'states/CA': + return Promise.resolve([ + { id: 'AB', name: 'Alberta' }, + { id: 'BC', name: 'British Columbia' } + ]); + } + }) +); describe('Address', () => { const addressSpecificationsMock: AddressSpecifications = { @@ -165,7 +187,7 @@ describe('Address', () => { expect(receivedData.country).toBe(data.country); }); - test('should not include fields without a value in the data object', () => { + test('should not include fields without a value in the data object', () => { const data: AddressData = { country: 'NL' }; const onChangeMock = jest.fn(); @@ -203,4 +225,199 @@ describe('Address', () => { const receivedData = lastOnChangeCall[0].data; expect(receivedData.stateOrProvince).toBe(undefined); }); + + describe('Country and State or Province', () => { + const user = userEvent.setup(); + const onChangeMock = jest.fn(); + test('should set the stateOrProvince value to empty and show options when country changes', async () => { + const data: AddressData = { country: 'US' }; + + customRender(
); + + const countrySearch = await screen.findByLabelText('Country/Region'); + await user.click(countrySearch); + // write in the searchbar + await user.keyboard('Canada'); + // select one option + await user.keyboard('[ArrowDown][Enter]'); + const stateSearch = await screen.findByLabelText('Province or Territory'); + expect(stateSearch).toBeInTheDocument(); + // open the state selector + await user.click(stateSearch); + // check if options are avaliable + expect(screen.getByText('Alberta')).toBeVisible(); + }); + + test('should reset the stateOrProvince value when country changes', async () => { + const data: AddressData = { country: 'US', stateOrProvince: 'CA' }; + + customRender(); + + // check if US values for state or province are set + expect(await screen.findByDisplayValue('United States')).toBeVisible(); + expect(await screen.findByDisplayValue('California')).toBeVisible(); + + // search for CountryLabel and region, choose Canada + const countrySearch = await screen.findByLabelText('Country/Region'); + await user.click(countrySearch); + // write in the searchbar + await user.keyboard('Canada'); + // select one option + await user.keyboard('[ArrowDown][Enter]'); + + // Check if the state has reset to empty value + const stateSearch = await screen.findByLabelText('Province or Territory'); + expect(stateSearch).toBeInTheDocument(); + expect(stateSearch).toHaveValue(''); + }); + + test('should trigger postal code validation on country change and show error message', async () => { + const data: AddressData = { country: 'US', stateOrProvince: 'CA', postalCode: '90000' }; + + customRender(); + + // search for CountryLabel and region, choose Canada + const countrySearch = await screen.findByLabelText('Country/Region'); + await user.click(countrySearch); + // write in the searchbar + await user.keyboard('Canada'); + // select one option + await user.keyboard('[ArrowDown][Enter]'); + + // Check if the state has reset to empty value + const postalCodeField = await screen.findByRole('textbox', { name: 'Postal code' }); + expect(postalCodeField).toBeInTheDocument(); + expect(postalCodeField).toHaveValue('90000'); + expect(screen.getByText('Invalid format. Expected format: A9A 9A9 or A9A9A9')).toBeVisible(); + }); + }); + + describe('AddressSearch in Address', () => { + // 0. delay the test since it rellies on user input + // there's probably a performance optimisation here, but delay was the simples and most reliable way to fix it + const user = userEvent.setup({ delay: 100 }); + const onChangeMock = jest.fn(); + + test('should fill the stateOrProvince field for countries who support state', async () => { + const data: AddressData = {}; + + // 1. setup the test + // 1a. create mock for this tests + const addressMock = { + id: 1, + name: '1000 Test Road, California', + street: '1000 Test Road', + city: 'Los Santos', + //houseNumberOrName: '', + postalCode: '90000', + country: 'US', + stateOrProvince: 'CA' + }; + + // 1b. pass the mock to the the mock functions so we get it as the search result + const onAdressSearchMock = jest.fn(async (value, { resolve }) => { + await resolve([addressMock]); + }); + const onAddressSelectedMock = jest.fn(async (value, { resolve }) => { + await resolve(addressMock); + }); + + // 2. render and intereact + customRender( + + ); + const searchBar = screen.getByRole('combobox'); + await user.click(searchBar); + // write in the searchbar + await user.keyboard('mock'); + // select one option + await user.keyboard('[ArrowDown][Enter]'); + + // 3. check filled values are correct + expect(screen.getByDisplayValue('1000 Test Road')).toBeInTheDocument(); + expect(screen.getByDisplayValue('90000')).toBeInTheDocument(); + expect(screen.getByDisplayValue('California')).toBeInTheDocument(); + }); + + test('should fill the stateOrProvince field for countries who support state', async () => { + const data: AddressData = { country: 'CA' }; + + // 1. setup the test + // 1a. create mock for this tests + const addressMock = { + id: 1, + name: '1000 Test Road, California', + street: '1000 Test Road', + city: 'Los Santos', + //houseNumberOrName: '', + postalCode: '90000', + country: 'US', + stateOrProvince: 'CA' + }; + + // 1b. pass the mock to the the mock functions so we get it as the search result + const onAdressSearchMock = jest.fn(async (value, { resolve }) => { + await resolve([addressMock]); + }); + const onAddressSelectedMock = jest.fn(async (value, { resolve }) => { + await resolve(addressMock); + }); + + // 2. render and intereact + customRender( + + ); + const searchBar = screen.getByRole('combobox'); + await user.click(searchBar); + // write in the searchbar + await user.keyboard('mock'); + // select one option + await user.keyboard('[ArrowDown][Enter]'); + + // 3. check filled values are correct + expect(screen.getByDisplayValue('1000 Test Road')).toBeInTheDocument(); + expect(screen.getByDisplayValue('90000')).toBeInTheDocument(); + expect(screen.getByDisplayValue('California')).toBeInTheDocument(); + }); + + test('should trigger field validation after address is selected', async () => { + const data: AddressData = {}; + + // 1. setup the test + // 1a. create mock for this tests + const incorrectPostalCodeAddressMock = { + id: 1, + name: '1000 Test Road, California', + street: '1000 Test Road', + city: 'Los Santos', + //houseNumberOrName: '', + postalCode: '9000 AA', + country: 'US', + stateOrProvince: 'CA' + }; + + // 1b. pass the mock to the the mock functions so we get it as the search result + const onAdressSearchMock = jest.fn(async (value, { resolve }) => { + await resolve([incorrectPostalCodeAddressMock]); + }); + const onAddressSelectedMock = jest.fn(async (value, { resolve }) => { + await resolve(incorrectPostalCodeAddressMock); + }); + + // 2. render and intereact + customRender( + + ); + const searchBar = screen.getByRole('combobox'); + await user.click(searchBar); + // write in the searchbar + await user.keyboard('mock'); + // select one option + await user.keyboard('[ArrowDown][Enter]'); + + // 3. check filled values are correct and error state is triggered + expect(screen.getByRole('textbox', { name: 'Zip code' })).toHaveValue('9000 AA'); + expect(screen.getByText('Invalid format. Expected format: 99999 or 99999-9999')).toBeVisible(); + }); + }); }); diff --git a/packages/lib/src/components/internal/Address/Address.tsx b/packages/lib/src/components/internal/Address/Address.tsx index 0b57bcb025..69ac737a91 100644 --- a/packages/lib/src/components/internal/Address/Address.tsx +++ b/packages/lib/src/components/internal/Address/Address.tsx @@ -39,9 +39,11 @@ export default function Address(props: AddressProps) { const showAddressSearch = !!props.onAddressLookup; + const [ignoreCountryChange, setIgnoreCountryChange] = useState(false); + const showAddressFields = props.onAddressLookup ? hasSelectedAddress || useManualAddress : true; - const { data, errors, valid, isValid, handleChangeFor, triggerValidation, setData } = useForm