Skip to content

Commit

Permalink
Handle accountPin correctly and ensure sanitized data
Browse files Browse the repository at this point in the history
  • Loading branch information
timomeh committed Feb 26, 2024
1 parent 904dbfa commit 2a7b4b5
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 36 deletions.
27 changes: 22 additions & 5 deletions lib/PortingEmbed/PortingForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { required, useForm } from '@modular-forms/preact'
import { useSignalEffect } from '@preact/signals'

import { Porting } from '../types'
import { sanitizeSubmitData } from './sanitizeSubmitData'

type Props = {
porting: Porting
onValidationChange?: (event: { isValid: boolean }) => unknown
onSubmit: (data: PortingForm) => unknown
onSubmit: (data: Partial<PortingForm>) => unknown
}

type PortingForm = {
Expand All @@ -21,7 +22,7 @@ export function PortingForm({ porting, onValidationChange, onSubmit }: Props) {
const [portingForm, { Form, Field }] = useForm<PortingForm>({
initialValues: {
accountNumber: porting.accountNumber || '',
accountPin: porting.accountPinExists ? '[***]' : '',
accountPin: '',
birthday: porting.birthday || '',
firstName: porting.firstName || '',
lastName: porting.lastName || '',
Expand All @@ -35,7 +36,15 @@ export function PortingForm({ porting, onValidationChange, onSubmit }: Props) {
})

return (
<Form id="gigsPortingEmbedForm" role="form" onSubmit={onSubmit}>
<Form
id="gigsPortingEmbedForm"
role="form"
onSubmit={(data) => {
const sanitizedData = sanitizeSubmitData(data)
return onSubmit(sanitizedData)
}}
shouldDirty
>
<Field name="accountNumber" validate={[required('Please enter')]}>
{(field, props) => (
<div>
Expand All @@ -51,11 +60,19 @@ export function PortingForm({ porting, onValidationChange, onSubmit }: Props) {
)}
</Field>

<Field name="accountPin" validate={[required('Please enter')]}>
<Field
name="accountPin"
validate={porting.accountPinExists ? [] : [required('Please enter')]}
>
{(field, props) => (
<div>
<label for="accountPin">Account PIN</label>
<input id="accountPin" type="text" value={field.value} {...props} />
<input
id="accountPin"
type="text"
placeholder={porting.accountPinExists ? '••••' : undefined}
{...props}
/>
{field.error && <div>{field.error}</div>}
</div>
)}
Expand Down
85 changes: 85 additions & 0 deletions lib/PortingEmbed/__tests__/PortingForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { render, screen } from '@testing-library/preact'
import userEvent from '@testing-library/user-event'

import { portingFactory } from '@/testing/factories/porting'

import { PortingForm } from '../PortingForm'

const wrapper = ({ children }: { children: React.ReactNode }) => {
return (
<div>
{children}
<button form="gigsPortingEmbedForm">Submit</button>
</div>
)
}

it('can enter and submit', async () => {
const user = userEvent.setup()
const porting = portingFactory.build()
const submit = vi.fn()
render(<PortingForm porting={porting} onSubmit={submit} />, { wrapper })

await user.type(screen.getByLabelText('Account Number'), '1234')
await user.type(screen.getByLabelText('Account PIN'), '0000')
await user.type(screen.getByLabelText('Birthday'), '01.01.1990')
await user.type(screen.getByLabelText('First Name'), 'Jerry')
await user.type(screen.getByLabelText('Last Name'), 'Seinfeld')
await user.click(screen.getByRole('button', { name: 'Submit' }))

expect(submit).toHaveBeenCalledWith({
accountNumber: '1234',
accountPin: '0000',
birthday: '01.01.1990',
firstName: 'Jerry',
lastName: 'Seinfeld',
})
})

describe('with existing porting fields', () => {
it('prefills the inputs', async () => {
const porting = portingFactory.build({
accountNumber: '1234',
accountPinExists: true,
birthday: '01.01.1990',
firstName: 'Jerry',
lastName: 'Seinfeld',
})
const submit = vi.fn()
render(<PortingForm porting={porting} onSubmit={submit} />, { wrapper })

expect(screen.getByLabelText('Account Number')).toHaveValue('1234')
expect(screen.getByLabelText('Birthday')).toHaveValue('01.01.1990')
expect(screen.getByLabelText('First Name')).toHaveValue('Jerry')
expect(screen.getByLabelText('Last Name')).toHaveValue('Seinfeld')

// the account pin cannot be prefilled, and instead indicates it exists with
// a placeholder
expect(screen.getByLabelText('Account PIN')).toHaveValue('')
expect(screen.getByLabelText('Account PIN')).toHaveAttribute(
'placeholder',
'••••',
)
})

it('only submits changed fields', async () => {
const user = userEvent.setup()
const porting = portingFactory.build({
accountNumber: '1234',
accountPinExists: true,
birthday: '01.01.1990',
firstName: 'Jerry',
lastName: 'Seinfeld',
})
const submit = vi.fn()
render(<PortingForm porting={porting} onSubmit={submit} />, { wrapper })

await user.clear(screen.getByLabelText('Account Number'))
await user.type(screen.getByLabelText('Account Number'), '5678')
await user.click(screen.getByRole('button', { name: 'Submit' }))

expect(submit).toHaveBeenCalledWith({
accountNumber: '5678',
})
})
})
31 changes: 0 additions & 31 deletions lib/PortingEmbed/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,34 +222,3 @@ describe('updating a porting', () => {
})
})
})

describe('validationChange event', () => {
it('fires initially always valid', async () => {
const event = vitest.fn()

const csn = await createFixtures()
const embed = await PortingEmbed(csn, { project })
embed.mount('#mount')
embed.on('validationChange', event)

await waitFor(() => expect(event).toHaveBeenCalledWith({ isValid: true }))
})

it('fires with invalid field', async () => {
const event = vitest.fn()
const user = userEvent.setup()

const csn = await createFixtures()
const embed = await PortingEmbed(csn, { project })
embed.mount('#mount')
embed.on('validationChange', event)

await user.type(screen.getByLabelText('Account Number'), '123')
await user.clear(screen.getByLabelText('Account Number'))
await user.click(screen.getByLabelText('Account PIN'))

await waitFor(() =>
expect(event).toHaveBeenLastCalledWith({ isValid: false }),
)
})
})
31 changes: 31 additions & 0 deletions lib/PortingEmbed/__tests__/sanitizeSubmitData.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { sanitizeSubmitData } from '../sanitizeSubmitData'

it('removes any null values', () => {
expect(sanitizeSubmitData({ foo: 'bar', bar: null, baz: null })).toEqual({
foo: 'bar',
})
})

it('removes any empty string values', () => {
expect(sanitizeSubmitData({ foo: 'bar', bar: '', baz: '' })).toEqual({
foo: 'bar',
})
})

it('removes any undefined values', () => {
expect(sanitizeSubmitData({ foo: 'bar', bar: undefined })).toEqual({
foo: 'bar',
})
})

it('keeps any other values', () => {
expect(
sanitizeSubmitData({ foo: 'bar', bar: 1, baz: true, qux: {}, quux: false }),
).toEqual({
foo: 'bar',
bar: 1,
baz: true,
qux: {},
quux: false,
})
})
14 changes: 14 additions & 0 deletions lib/PortingEmbed/sanitizeSubmitData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Removes any empty, null or undefined values from submit data, to make sure
* we never submit any empty data to the API which resets already filled-out
* porting fields.
*/
export function sanitizeSubmitData<T extends Record<string, unknown>>(data: T) {
const sanitizedData = Object.fromEntries(
Object.entries(data).filter(
([_, v]) => v !== '' && v !== null && v !== undefined,
),
)

return sanitizedData
}

0 comments on commit 2a7b4b5

Please sign in to comment.