Skip to content
Open
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
Expand Up @@ -18,6 +18,9 @@ import userEvent from '@testing-library/user-event'
import { http, HttpResponse } from 'msw'
import { beforeEach, describe, expect, it, vi } from 'vitest'

import { i18n } from '@sebt/design-system/client'

import enCoStepUpProcessing from '@/content/locales/en/co/step-upProcessing.json'
import enDcValidation from '@/content/locales/en/dc/validation.json'
import { server } from '@/mocks/server'

Expand Down Expand Up @@ -1097,6 +1100,102 @@ describe('IdProofingForm', () => {
})
})

it('renders the titled LoadingInterstitial when step-upProcessing copy is present in the locale bundle', async () => {
// Forward-compatibility guard: when a state's content sheet ships
// step-upProcessing.title/body upstream, the form must pick it up and
// render the titled interstitial without any code change. We simulate
// that by adding the CO bundle for the duration of this test.
i18n.addResourceBundle('en', 'step-upProcessing', enCoStepUpProcessing, true, true)
try {
let resolveResponse: () => void = () => {}
const responsePromise = new Promise<void>((resolve) => {
resolveResponse = resolve
})

server.use(
http.post('/api/id-proofing', async () => {
await responsePromise
return HttpResponse.json({ result: 'matched' })
})
)

const user = userEvent.setup()
renderWithProviders(
<IdProofingForm
idOptions={TEST_ID_OPTIONS}
contactLink={TEST_CONTACT_LINK}
/>
)

await user.selectOptions(screen.getByRole('combobox', { name: /month/i }), '03')
await user.type(screen.getByRole('textbox', { name: INPUT_LABEL_DAY }), '10')
await user.type(screen.getByRole('textbox', { name: INPUT_LABEL_YEAR }), '1990')
await user.click(screen.getByRole('radio', { name: LABEL_SSN }))
await user.type(await screen.findByRole('textbox', { name: INPUT_LABEL_SSN }), '999999999')
await user.click(screen.getByRole('button', { name: /continue/i }))

await waitFor(() => {
expect(screen.getByRole('status')).toBeInTheDocument()
})
expect(screen.getByText(enCoStepUpProcessing.title)).toBeInTheDocument()
expect(screen.getByText(enCoStepUpProcessing.body)).toBeInTheDocument()

resolveResponse()
await waitFor(() => {
expect(mockPush).toHaveBeenCalledWith('/dashboard')
})
} finally {
i18n.removeResourceBundle('en', 'step-upProcessing')
}
})

it('renders a spinner-only status region when step-upProcessing copy is missing from the active locale bundle', async () => {
// DC's content sheet currently marks S10 - Step-up Processing rows as
// !N/A!, so the step-upProcessing namespace is not registered for DC. If
// the form rendered LoadingInterstitial with tProcessing('title') /
// tProcessing('body'), i18next would fall back to the literal key names
// β€” DC users would see "body" in a box. The loading state falls back to a
// spinner-only status region whenever the copy is missing, regardless of
// which state is active.
let resolveResponse: () => void = () => {}
const responsePromise = new Promise<void>((resolve) => {
resolveResponse = resolve
})

server.use(
http.post('/api/id-proofing', async () => {
await responsePromise
return HttpResponse.json({ result: 'matched' })
})
)

const user = userEvent.setup()
renderWithProviders(
<IdProofingForm
idOptions={TEST_ID_OPTIONS}
contactLink={TEST_CONTACT_LINK}
/>
)

await user.selectOptions(screen.getByRole('combobox', { name: /month/i }), '03')
await user.type(screen.getByRole('textbox', { name: INPUT_LABEL_DAY }), '10')
await user.type(screen.getByRole('textbox', { name: INPUT_LABEL_YEAR }), '1990')
await user.click(screen.getByRole('radio', { name: LABEL_SSN }))
await user.type(await screen.findByRole('textbox', { name: INPUT_LABEL_SSN }), '999999999')
await user.click(screen.getByRole('button', { name: /continue/i }))

await waitFor(() => {
expect(screen.getByRole('status')).toBeInTheDocument()
})
expect(screen.queryByText(/^title$/)).not.toBeInTheDocument()
expect(screen.queryByText(/^body$/)).not.toBeInTheDocument()

resolveResponse()
await waitFor(() => {
expect(mockPush).toHaveBeenCalledWith('/dashboard')
})
})

it('restores the form and shows the submit error alert when the mutation throws', async () => {
// The error path must NOT leave the loading interstitial stuck on screen β€”
// the user needs to see the alert above the form so they can retry.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,12 +296,36 @@ export function IdProofingForm({ idOptions, contactLink, getDiToken }: IdProofin
// see the submit button text change to "Continue..." and any eventual outcome
// (off-boarding navigation or an inline error) reads as "we just got an error
// after waiting."
//
// The titled interstitial only renders when the active locale bundle has
// step-upProcessing copy. States whose content sheet omits those rows (DC
// marks them !N/A!) would otherwise see i18next leak the literal key names
// "title"/"body" through the fallback chain β€” they fall back to a spinner-only
// status region. Adding the copy upstream is enough to switch in the titled
// interstitial; no code change needed.
if (isProcessing || submitIdProofing.isPending) {
const hasInterstitialCopy =
i18n.exists('step-upProcessing:title') && i18n.exists('step-upProcessing:body')
if (hasInterstitialCopy) {
return (
<LoadingInterstitial
title={tProcessing('title')}
message={tProcessing('body')}
/>
)
}
return (
<LoadingInterstitial
title={tProcessing('title')}
message={tProcessing('body')}
/>
<div
className="padding-y-4 text-center"
role="status"
aria-busy="true"
aria-live="polite"
>
<span
className="usa-spinner"
aria-hidden="true"
/>
</div>
)
}

Expand Down
Loading