diff --git a/app/javascript/packages/document-capture/components/in-person-full-address-search.spec.tsx b/app/javascript/packages/document-capture/components/in-person-full-address-search.spec.tsx index 6a85b281693..713fe90b93b 100644 --- a/app/javascript/packages/document-capture/components/in-person-full-address-search.spec.tsx +++ b/app/javascript/packages/document-capture/components/in-person-full-address-search.spec.tsx @@ -10,6 +10,88 @@ import { LOCATIONS_URL } from './in-person-location-post-office-search-step'; describe('FullAddressSearch', () => { const sandbox = useSandbox(); + + context('validates form', () => { + it('displays an error for all required fields when input is empty', async () => { + const handleLocationsFound = sandbox.stub(); + const { findByText, findAllByText } = render( + new Map() }}> + + , + ); + + await userEvent.click( + await findByText('in_person_proofing.body.location.po_search.search_button'), + ); + + const errors = await findAllByText('simple_form.required.text'); + expect(errors).to.have.lengthOf(4); + }); + + it('displays an error for an invalid ZIP code length (length = 1)', async () => { + const handleLocationsFound = sandbox.stub(); + const { findByText, findByLabelText, findAllByText } = render( + new Map() }}> + + , + ); + + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.address_label'), + '200 main', + ); + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.city_label'), + 'Endeavor', + ); + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.state_label'), + 'DE', + ); + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.zipcode_label'), + '1', + ); + await userEvent.click( + await findByText('in_person_proofing.body.location.po_search.search_button'), + ); + + const errors = await findAllByText('idv.errors.pattern_mismatch.zipcode_five'); + expect(errors).to.have.lengthOf(1); + }); + + it('does not display an error for a valid ZIP code length (length = 5)', async () => { + const handleLocationsFound = sandbox.stub(); + const { findByText, findByLabelText, queryByText } = render( + new Map() }}> + + , + ); + + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.address_label'), + '200 main', + ); + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.city_label'), + 'Endeavor', + ); + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.state_label'), + 'DE', + ); + await userEvent.type( + await findByLabelText('in_person_proofing.body.location.po_search.zipcode_label'), + '17201', + ); + await userEvent.click( + await findByText('in_person_proofing.body.location.po_search.search_button'), + ); + + expect(queryByText('idv.errors.pattern_mismatch.zipcode')).to.be.null; + }); + }); + context('when an address is found', () => { let server: SetupServer; before(() => { diff --git a/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx b/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx index 433393984f8..83c91d7c93b 100644 --- a/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx +++ b/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx @@ -50,33 +50,65 @@ function useUspsLocations(locationsURL: string) { const validatedStateFieldRef = useRef(null); const validatedZipCodeFieldRef = useRef(null); - const handleLocationSearch = useCallback( - (event, addressValue, cityValue, stateValue, zipCodeValue) => { - event.preventDefault(); + const checkValidityAndDisplayErrors = (address, city, state, zipCode) => { + let formIsValid = true; + const zipCodeIsValid = zipCode.length === 5 && !!zipCode.match(/\d{5}/); + + if (address.length === 0) { + validatedAddressFieldRef.current?.setCustomValidity(t('simple_form.required.text')); + formIsValid = false; + } else { validatedAddressFieldRef.current?.setCustomValidity(''); - validatedAddressFieldRef.current?.reportValidity(); + } + + if (city.length === 0) { + formIsValid = false; + validatedCityFieldRef.current?.setCustomValidity(t('simple_form.required.text')); + } else { validatedCityFieldRef.current?.setCustomValidity(''); - validatedCityFieldRef.current?.reportValidity(); + } + + if (state.length === 0) { + formIsValid = false; + validatedStateFieldRef.current?.setCustomValidity(t('simple_form.required.text')); + } else { validatedStateFieldRef.current?.setCustomValidity(''); - validatedStateFieldRef.current?.reportValidity(); + } + + if (zipCode.length === 0) { + formIsValid = false; + validatedZipCodeFieldRef.current?.setCustomValidity(t('simple_form.required.text')); + } else { validatedZipCodeFieldRef.current?.setCustomValidity(''); - validatedZipCodeFieldRef.current?.reportValidity(); - - if ( - addressValue.trim().length === 0 || - cityValue.trim().length === 0 || - stateValue.trim().length === 0 || - zipCodeValue.trim().length === 0 - ) { + } + + validatedAddressFieldRef.current?.reportValidity(); + validatedCityFieldRef.current?.reportValidity(); + validatedStateFieldRef.current?.reportValidity(); + validatedZipCodeFieldRef.current?.reportValidity(); + + return formIsValid && zipCodeIsValid; + }; + + const handleLocationSearch = useCallback( + (event, addressValue, cityValue, stateValue, zipCodeValue) => { + event.preventDefault(); + const address = addressValue.trim(); + const city = cityValue.trim(); + const zipCode = zipCodeValue.trim(); + + const formIsValid = checkValidityAndDisplayErrors(address, city, stateValue, zipCode); + + if (!formIsValid) { return; } setLocationQuery({ - address: `${addressValue}, ${cityValue}, ${stateValue} ${zipCodeValue}`, - streetAddress: addressValue, - city: cityValue, + address: `${address}, ${city}, ${stateValue} ${zipCode}`, + streetAddress: address, + city, state: stateValue, - zipCode: zipCodeValue, + zipCode, }); }, [], @@ -147,9 +179,11 @@ function FullAddressSearch({ input(target.value); }; + type SelectChangeEvent = React.ChangeEvent; + const onAddressChange = inputChangeHandler(setAddressValue); const onCityChange = inputChangeHandler(setCityValue); - const onStateChange = inputChangeHandler(setStateValue); + const onStateChange = (e: SelectChangeEvent) => setStateValue(e.target.value); const onZipCodeChange = inputChangeHandler(setZipCodeValue); useEffect(() => { @@ -177,7 +211,12 @@ function FullAddressSearch({ return ( <> - + - + @@ -216,7 +264,12 @@ function FullAddressSearch({ ))} - +
diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml index 4f4146cc122..d331b10ef3d 100644 --- a/config/locales/idv/en.yml +++ b/config/locales/idv/en.yml @@ -56,6 +56,7 @@ en: pattern_mismatch: ssn: 'Enter a nine-digit Social Security number' zipcode: Enter a 5 or 9 digit ZIP Code + zipcode_five: Enter a 5 digit ZIP Code failure: attempts_html: one: For security reasons, you have one attempt remaining to diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml index 753352e6088..6056fcee9f2 100644 --- a/config/locales/idv/es.yml +++ b/config/locales/idv/es.yml @@ -57,6 +57,7 @@ es: pattern_mismatch: ssn: 'Ingrese un número de Seguro Social de nueve dígitos' zipcode: Ingresa un código postal de 5 o 9 dígitos + zipcode_five: Ingresa un código postal de 5 dígitos failure: attempts_html: one: Por motivos de seguridad, le quedan un intento para añadir diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml index 58acedd8f1d..5de4838c0e0 100644 --- a/config/locales/idv/fr.yml +++ b/config/locales/idv/fr.yml @@ -60,6 +60,7 @@ fr: pattern_mismatch: ssn: 'Entrez un numéro de sécurité sociale à neuf chiffres' zipcode: Entrez un code postal à 5 ou 9 chiffres + zipcode_five: Entrez un code postal à 5 chiffres failure: attempts_html: one: Pour des raisons de sécurité, il vous reste une tentative