diff --git a/app/javascript/packages/address-search/README.md b/app/javascript/packages/address-search/README.md index 39213a0f503..10ee4ef05e0 100644 --- a/app/javascript/packages/address-search/README.md +++ b/app/javascript/packages/address-search/README.md @@ -30,6 +30,7 @@ return( disabled={disabledAddressSearchCallback} handleLocationSelect={handleLocationSelect} locationsURL={LOCATIONS_URL} + noInPersonLocationsDisplay={noInPersonLocationsDisplay} onFoundLocations={setLocationResultsCallback} registerField={registerFieldCallback} resultsHeaderComponent={resultsHeaderComponent} diff --git a/app/javascript/packages/address-search/components/address-search.tsx b/app/javascript/packages/address-search/components/address-search.tsx index 4d116f48705..e69cce8a817 100644 --- a/app/javascript/packages/address-search/components/address-search.tsx +++ b/app/javascript/packages/address-search/components/address-search.tsx @@ -4,12 +4,14 @@ import { t } from '@18f/identity-i18n'; import InPersonLocations from './in-person-locations'; import AddressInput from './address-input'; import type { AddressSearchProps, LocationQuery, FormattedLocation } from '../types'; +import NoInPersonLocationsDisplay from './no-in-person-locations-display'; function AddressSearch({ addressSearchURL, disabled, handleLocationSelect, locationsURL, + noInPersonLocationsDisplay = NoInPersonLocationsDisplay, onFoundLocations, registerField, resultsHeaderComponent, @@ -46,6 +48,7 @@ function AddressSearch({ {locationResults && foundAddress && !isLoadingLocations && ( +

No PO found

+

{address}

+ + ); +} + describe('InPersonLocations', () => { const locations: FormattedLocation[] = [ { - formattedCityStateZip: 'one', - distance: 'one', + name: 'test name', + streetAddress: '123 Test Address', + formattedCityStateZip: 'City, State 12345-1234', + distance: '0.2 miles', + weekdayHours: '9 AM - 5 PM', + saturdayHours: '9 AM - 6 PM', + sundayHours: 'Closed', id: 1, - name: 'one', - saturdayHours: 'one', - streetAddress: 'one', - sundayHours: 'one', - weekdayHours: 'one', isPilot: false, }, { - formattedCityStateZip: 'two', - distance: 'two', - id: 2, - name: 'two', - saturdayHours: 'two', - streetAddress: 'two', - sundayHours: 'two', - weekdayHours: 'two', + name: 'test name', + streetAddress: '456 Test Address', + formattedCityStateZip: 'City, State 12345-1234', + distance: '2.1 miles', + weekdayHours: '8 AM - 5 PM', + saturdayHours: '10 AM - 5 PM', + sundayHours: 'Closed', + id: 1, isPilot: false, }, ]; @@ -43,6 +54,7 @@ describe('InPersonLocations', () => { resultsHeaderComponent={alertComponent} locations={locations} onSelect={onSelect} + noInPersonLocationsDisplay={NoLocationsViewMock} />, ); @@ -52,7 +64,12 @@ describe('InPersonLocations', () => { it('renders results instructions when onSelect is passed', () => { const { getByText } = render( - , + , ); expect(getByText('in_person_proofing.body.location.po_search.results_instructions')).to.exist(); @@ -60,11 +77,68 @@ describe('InPersonLocations', () => { it('does not render results instructions when onSelect is not passed', () => { const { queryByText } = render( - , + , ); expect( queryByText('in_person_proofing.body.location.po_search.results_instructions'), ).to.not.exist(); }); + + context('when no locations are found', () => { + it('renders the passed in noLocations component w/ address', () => { + const onClick = sinon.stub(); + const { getByText } = render( + , + ); + + expect(getByText('No PO found')).to.exist(); + expect(getByText(address)).to.exist(); + expect(screen.getByTestId('no-results-found')).to.exist(); + }); + + it('does not render Post Office results', () => { + const onClick = sinon.stub(); + const { queryByText } = render( + , + ); + + expect(queryByText('in_person_proofing.body.location.po_search.results_instructions')).to.be + .null; + expect(queryByText('in_person_proofing.body.location.retail_hours_heading')).not.to.exist(); + }); + }); + + context('when at least 1 location is found', () => { + it('renders a list of Post Offices and does not render the passed in noInPersonLocationsDisplay component', () => { + const onClick = sinon.stub(); + const { queryByText } = render( + , + ); + + expect(queryByText('123 Test Address')).to.exist(); + expect(queryByText('456 Test Address')).to.exist(); + expect(queryByText('No PO found')).to.be.null; + }); + }); }); diff --git a/app/javascript/packages/address-search/components/in-person-locations.tsx b/app/javascript/packages/address-search/components/in-person-locations.tsx index 737f1d79adc..8873e76f134 100644 --- a/app/javascript/packages/address-search/components/in-person-locations.tsx +++ b/app/javascript/packages/address-search/components/in-person-locations.tsx @@ -2,7 +2,6 @@ import { ComponentType } from 'react'; import { t } from '@18f/identity-i18n'; import LocationCollection from './location-collection'; import LocationCollectionItem from './location-collection-item'; -import NoInPersonLocationsDisplay from './no-in-person-locations-display'; export interface FormattedLocation { formattedCityStateZip: string; @@ -20,6 +19,7 @@ interface InPersonLocationsProps { locations: FormattedLocation[] | null | undefined; onSelect; address: string; + noInPersonLocationsDisplay: ComponentType<{ address: string }>; resultsHeaderComponent?: ComponentType; } @@ -27,6 +27,7 @@ function InPersonLocations({ locations, onSelect, address, + noInPersonLocationsDisplay: NoInPersonLocationsDisplay, resultsHeaderComponent: HeaderComponent, }: InPersonLocationsProps) { const isPilot = locations?.some((l) => l.isPilot); diff --git a/app/javascript/packages/address-search/components/no-in-person-locations-display.tsx b/app/javascript/packages/address-search/components/no-in-person-locations-display.tsx index 82c3ce8b1b7..9513f3666b9 100644 --- a/app/javascript/packages/address-search/components/no-in-person-locations-display.tsx +++ b/app/javascript/packages/address-search/components/no-in-person-locations-display.tsx @@ -1,14 +1,18 @@ import { getAssetPath } from '@18f/identity-assets'; import { t } from '@18f/identity-i18n'; -function NoInPersonLocationsDisplay({ address }) { +interface NoInPersonLocationsDisplayProps { + address: string; +} + +function NoInPersonLocationsDisplay({ address }: NoInPersonLocationsDisplayProps) { return (
{t('image_description.info_pin_map')}
diff --git a/app/javascript/packages/address-search/package.json b/app/javascript/packages/address-search/package.json index 585cbf93bfb..3f4a30d7355 100644 --- a/app/javascript/packages/address-search/package.json +++ b/app/javascript/packages/address-search/package.json @@ -1,6 +1,6 @@ { "name": "@18f/identity-address-search", - "version": "2.2.0", + "version": "2.3.0", "type": "module", "private": false, "files": [ diff --git a/app/javascript/packages/address-search/types.d.ts b/app/javascript/packages/address-search/types.d.ts index 66e4b583aa7..9656c055113 100644 --- a/app/javascript/packages/address-search/types.d.ts +++ b/app/javascript/packages/address-search/types.d.ts @@ -58,6 +58,7 @@ interface AddressSearchProps { addressSearchURL: string; disabled: boolean; handleLocationSelect: ((e: any, id: number) => Promise) | null | undefined; + noInPersonLocationsDisplay?: ComponentType<{ address: string }>; resultsHeaderComponent?: ComponentType; locationsURL: string; onFoundLocations: Dispatch>; 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 1cbd826ed70..fbe11b674d1 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 @@ -1,7 +1,7 @@ import { Alert, PageHeading } from '@18f/identity-components'; import { useState } from 'react'; import { t } from '@18f/identity-i18n'; -import { InPersonLocations } from '@18f/identity-address-search'; +import { InPersonLocations, NoInPersonLocationsDisplay } from '@18f/identity-address-search'; import type { LocationQuery, FormattedLocation } from '@18f/identity-address-search/types'; import FullAddressSearchInput from './full-address-search-input'; @@ -11,6 +11,7 @@ function FullAddressSearch({ handleLocationSelect, disabled, onFoundLocations, + noInPersonLocationsDisplay = NoInPersonLocationsDisplay, }) { const [apiError, setApiError] = useState(null); const [foundAddress, setFoundAddress] = useState(null); @@ -48,6 +49,7 @@ function FullAddressSearch({ locations={locationResults} onSelect={handleLocationSelect} address={foundAddress.address || ''} + noInPersonLocationsDisplay={noInPersonLocationsDisplay} /> )} diff --git a/config/locales/in_person_proofing/en.yml b/config/locales/in_person_proofing/en.yml index 35f2574846d..0978956cad4 100644 --- a/config/locales/in_person_proofing/en.yml +++ b/config/locales/in_person_proofing/en.yml @@ -47,7 +47,7 @@ en: city_label: City is_searching_message: Searching for Post Office locations… none_found: Sorry, there are no participating Post Offices within 50 miles of - %{address}. + %{address} none_found_tip: You can search using a different address, or add photos of your ID to try and verify your identity online again. po_search_about: You can verify your identity in person at a local participating diff --git a/config/locales/in_person_proofing/es.yml b/config/locales/in_person_proofing/es.yml index 52c592f0445..e4e96b00edc 100644 --- a/config/locales/in_person_proofing/es.yml +++ b/config/locales/in_person_proofing/es.yml @@ -51,7 +51,7 @@ es: city_label: Ciudad is_searching_message: Buscando oficinas de correos… none_found: Lo sentimos, no hay Oficinas de Correos participantes en un radio de - 50 millas de la %{address}. + 50 millas de la %{address} none_found_tip: Puede buscar utilizando una dirección diferente, o añadir fotos de su documento de identidad para intentar verificar su identidad en línea de nuevo.