Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TextInput } from '@18f/identity-components';
import { useState, useRef, useEffect, useCallback } from 'react';
import { TextInput, SelectInput } from '@18f/identity-components';
import { useState, useRef, useEffect, useCallback, useContext } from 'react';
import { t } from '@18f/identity-i18n';
import { request } from '@18f/identity-request';
import ValidatedField from '@18f/identity-validated-field/validated-field';
Expand All @@ -15,6 +15,7 @@ import {
LOCATIONS_URL,
PostOffice,
} from '@18f/identity-address-search';
import { InPersonContext } from '../context';

const formatLocations = (postOffices: PostOffice[]): FormattedLocation[] =>
postOffices.map((po: PostOffice, index) => ({
Expand Down Expand Up @@ -46,7 +47,7 @@ function useUspsLocations() {
const validatedZipCodeFieldRef = useRef<HTMLFormElement>(null);

const handleLocationSearch = useCallback(
(event, addressInput, cityInput, stateInput, zipCodeInput) => {
(event, addressValue, cityValue, stateValue, zipCodeValue) => {
event.preventDefault();
validatedAddressFieldRef.current?.setCustomValidity('');
validatedAddressFieldRef.current?.reportValidity();
Expand All @@ -58,20 +59,20 @@ function useUspsLocations() {
validatedZipCodeFieldRef.current?.reportValidity();

if (
addressInput.trim().length === 0 ||
cityInput.trim().length === 0 ||
stateInput.trim().length === 0 ||
zipCodeInput.trim().length === 0
addressValue.trim().length === 0 ||
cityValue.trim().length === 0 ||
stateValue.trim().length === 0 ||
zipCodeValue.trim().length === 0
) {
return;
}

setLocationQuery({
address: `${addressInput}, ${cityInput}, ${stateInput} ${zipCodeInput}`,
streetAddress: addressInput,
city: cityInput,
state: stateInput,
zipCode: zipCodeInput,
address: `${addressValue}, ${cityValue}, ${stateValue} ${zipCodeValue}`,
streetAddress: addressValue,
city: cityValue,
state: stateValue,
zipCode: zipCodeValue,
});
},
[],
Expand Down Expand Up @@ -114,12 +115,11 @@ function FullAddressSearch({
onError = () => undefined,
disabled = false,
}: FullAddressSearchProps) {
// todo: should we get rid of verbose 'input' word?
const spinnerButtonRef = useRef<SpinnerButtonRefHandle>(null);
const [addressInput, setAddressInput] = useState('');
const [cityInput, setCityInput] = useState('');
const [stateInput, setStateInput] = useState('');
const [zipCodeInput, setZipCodeInput] = useState('');
const [addressValue, setAddressValue] = useState('');
const [cityValue, setCityValue] = useState('');
const [stateValue, setStateValue] = useState('');
const [zipCodeValue, setZipCodeValue] = useState('');
const {
locationQuery,
locationResults,
Expand All @@ -132,15 +132,17 @@ function FullAddressSearch({
validatedZipCodeFieldRef,
} = useUspsLocations();

const textInputChangeHandler = (input) => (event: React.ChangeEvent<HTMLInputElement>) => {
const { target } = event;
input(target.value);
};
const inputChangeHandler =
<T extends HTMLElement & { value: string }>(input) =>
(event: React.ChangeEvent<T>) => {
const { target } = event;
input(target.value);
};

const onAddressChange = textInputChangeHandler(setAddressInput);
const onCityChange = textInputChangeHandler(setCityInput);
const onStateChange = textInputChangeHandler(setStateInput);
const onZipCodeChange = textInputChangeHandler(setZipCodeInput);
const onAddressChange = inputChangeHandler(setAddressValue);
const onCityChange = inputChangeHandler(setCityValue);
const onStateChange = inputChangeHandler(setStateValue);
const onZipCodeChange = inputChangeHandler(setZipCodeValue);

useEffect(() => {
spinnerButtonRef.current?.toggleSpinner(isLoading);
Expand All @@ -158,18 +160,20 @@ function FullAddressSearch({
const handleSearch = useCallback(
(event) => {
onError(null);
onSearch(event, addressInput, cityInput, stateInput, zipCodeInput);
onSearch(event, addressValue, cityValue, stateValue, zipCodeValue);
},
[addressInput, cityInput, stateInput, zipCodeInput],
[addressValue, cityValue, stateValue, zipCodeValue],
);

const { usStatesTerritories } = useContext(InPersonContext);

return (
<>
<ValidatedField ref={validatedAddressFieldRef}>
<TextInput
required
ref={registerField('address')}
value={addressInput}
value={addressValue}
onChange={onAddressChange}
label={t('in_person_proofing.body.location.po_search.address_label')}
disabled={disabled}
Expand All @@ -179,28 +183,37 @@ function FullAddressSearch({
<TextInput
required
ref={registerField('city')}
value={cityInput}
value={cityValue}
onChange={onCityChange}
label={t('in_person_proofing.body.location.po_search.city_label')}
disabled={disabled}
/>
</ValidatedField>
<ValidatedField ref={validatedStateFieldRef}>
<TextInput
<SelectInput
required
ref={registerField('state')}
value={stateInput}
value={stateValue}
onChange={onStateChange}
label={t('in_person_proofing.body.location.po_search.state_label')}
disabled={disabled}
/>
>
<option key="select" value="" disabled>
{t('in_person_proofing.form.address.state_prompt')}
</option>
{usStatesTerritories.map(([name, abbr]) => (
<option key={abbr} value={abbr}>
{name}
</option>
))}
</SelectInput>
</ValidatedField>
<ValidatedField ref={validatedZipCodeFieldRef}>
<TextInput
required
className="tablet:grid-col-5"
ref={registerField('zip_code')}
value={zipCodeInput}
value={zipCodeValue}
onChange={onZipCodeChange}
label={t('in_person_proofing.body.location.po_search.zipcode_label')}
disabled={disabled}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { I18n } from '@18f/identity-i18n';
import { setupServer } from 'msw/node';
import type { SetupServer } from 'msw/node';
import { rest } from 'msw';
import { SWRConfig } from 'swr';
import { I18nContext } from '@18f/identity-react-i18n';
import { ComponentType } from 'react';
import { LOCATIONS_URL } from '@18f/identity-address-search';
import { InPersonContext } from '../context';
import InPersonLocationFullAddressEntryPostOfficeSearchStep from './in-person-location-full-address-entry-post-office-search-step';

const USPS_RESPONSE = [
Expand Down Expand Up @@ -67,10 +69,17 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {

it('renders the step', () => {
const { getByRole } = render(
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
{
wrapper,
},
<InPersonContext.Provider
value={{
inPersonOutageMessageEnabled: false,
inPersonOutageExpectedUpdateDate: 'January 1, 2024',
inPersonFullAddressEntryEnabled: true,
usStatesTerritories: [['Delaware', 'DE']],
}}
>
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
</InPersonContext.Provider>,
{ wrapper },
);

expect(getByRole('heading', { name: 'in_person_proofing.headings.po_search.location' }));
Expand All @@ -83,7 +92,16 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {

it('displays a try again error message', async () => {
const { findByText, findByLabelText } = render(
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
<InPersonContext.Provider
value={{
inPersonOutageMessageEnabled: false,
inPersonOutageExpectedUpdateDate: 'January 1, 2024',
inPersonFullAddressEntryEnabled: true,
usStatesTerritories: [['Delaware', 'DE']],
}}
>
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
</InPersonContext.Provider>,
{ wrapper },
);

Expand All @@ -95,7 +113,7 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {
await findByLabelText('in_person_proofing.body.location.po_search.city_label'),
'Endeavor',
);
await userEvent.type(
await userEvent.selectOptions(
await findByLabelText('in_person_proofing.body.location.po_search.state_label'),
'DE',
);
Expand All @@ -115,10 +133,17 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {

it('displays validation error messages to the user if fields are empty', async () => {
const { findAllByText, findByText } = render(
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
{
wrapper,
},
<InPersonContext.Provider
value={{
inPersonOutageMessageEnabled: false,
inPersonOutageExpectedUpdateDate: 'January 1, 2024',
inPersonFullAddressEntryEnabled: true,
usStatesTerritories: [['Delaware', 'DE']],
}}
>
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
</InPersonContext.Provider>,
{ wrapper },
);

await userEvent.click(
Expand All @@ -131,7 +156,16 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {

it('displays no post office results if a successful search is followed by an unsuccessful search', async () => {
const { findByText, findByLabelText, queryByRole } = render(
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
<InPersonContext.Provider
value={{
inPersonOutageMessageEnabled: false,
inPersonOutageExpectedUpdateDate: 'January 1, 2024',
inPersonFullAddressEntryEnabled: true,
usStatesTerritories: [['Delaware', 'DE']],
}}
>
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
</InPersonContext.Provider>,
{ wrapper },
);

Expand All @@ -143,7 +177,7 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {
await findByLabelText('in_person_proofing.body.location.po_search.city_label'),
'Endeavor',
);
await userEvent.type(
await userEvent.selectOptions(
await findByLabelText('in_person_proofing.body.location.po_search.state_label'),
'DE',
);
Expand Down Expand Up @@ -171,7 +205,16 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {

it('clicking search again after first results do not clear results', async () => {
const { findAllByText, findByText, findByLabelText } = render(
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
<InPersonContext.Provider
value={{
inPersonOutageMessageEnabled: false,
inPersonOutageExpectedUpdateDate: 'January 1, 2024',
inPersonFullAddressEntryEnabled: true,
usStatesTerritories: [['Delaware', 'DE']],
}}
>
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
</InPersonContext.Provider>,
{ wrapper },
);

Expand All @@ -183,7 +226,7 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {
await findByLabelText('in_person_proofing.body.location.po_search.city_label'),
'Endeavor',
);
await userEvent.type(
await userEvent.selectOptions(
await findByLabelText('in_person_proofing.body.location.po_search.state_label'),
'DE',
);
Expand Down Expand Up @@ -215,7 +258,17 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {
})
}
>
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />
<InPersonContext.Provider
value={{
inPersonOutageMessageEnabled: false,
inPersonOutageExpectedUpdateDate: 'January 1, 2024',
inPersonFullAddressEntryEnabled: true,
usStatesTerritories: [['Delaware', 'DE']],
}}
>
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
</InPersonContext.Provider>
,
</I18nContext.Provider>,
{ wrapper },
);
Expand All @@ -227,7 +280,7 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {
await findByLabelText('in_person_proofing.body.location.po_search.city_label'),
'Endeavor',
);
await userEvent.type(
await userEvent.selectOptions(
await findByLabelText('in_person_proofing.body.location.po_search.state_label'),
'DE',
);
Expand Down Expand Up @@ -266,7 +319,17 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {
})
}
>
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />
<InPersonContext.Provider
value={{
inPersonOutageMessageEnabled: false,
inPersonOutageExpectedUpdateDate: 'January 1, 2024',
inPersonFullAddressEntryEnabled: true,
usStatesTerritories: [['Delaware', 'DE']],
}}
>
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
</InPersonContext.Provider>
,
</I18nContext.Provider>,
{ wrapper },
);
Expand All @@ -279,7 +342,7 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {
await findByLabelText('in_person_proofing.body.location.po_search.city_label'),
'Endeavor',
);
await userEvent.type(
await userEvent.selectOptions(
await findByLabelText('in_person_proofing.body.location.po_search.state_label'),
'DE',
);
Expand All @@ -303,7 +366,16 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {

it('allows user to select a location', async () => {
const { findAllByText, findByLabelText, findByText, queryByText } = render(
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
<InPersonContext.Provider
value={{
inPersonOutageMessageEnabled: false,
inPersonOutageExpectedUpdateDate: 'January 1, 2024',
inPersonFullAddressEntryEnabled: true,
usStatesTerritories: [['Delaware', 'DE']],
}}
>
<InPersonLocationFullAddressEntryPostOfficeSearchStep {...DEFAULT_PROPS} />,
</InPersonContext.Provider>,
{ wrapper },
);
await userEvent.type(
Expand All @@ -314,7 +386,7 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {
await findByLabelText('in_person_proofing.body.location.po_search.city_label'),
'Endeavor',
);
await userEvent.type(
await userEvent.selectOptions(
await findByLabelText('in_person_proofing.body.location.po_search.state_label'),
'DE',
);
Expand All @@ -333,9 +405,6 @@ describe('InPersonLocationFullAddressEntryPostOfficeSearchStep', () => {
await userEvent.clear(
await findByLabelText('in_person_proofing.body.location.po_search.city_label'),
);
await userEvent.clear(
await findByLabelText('in_person_proofing.body.location.po_search.state_label'),
);
await userEvent.clear(
await findByLabelText('in_person_proofing.body.location.po_search.zipcode_label'),
);
Expand Down
Loading