-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(react): add NricField component (#2715)
- Loading branch information
Showing
4 changed files
with
262 additions
and
0 deletions.
There are no files selected for viewing
100 changes: 100 additions & 0 deletions
100
frontend/src/templates/Field/Nric/NricField.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import { useEffect, useState } from 'react' | ||
import { FormProvider, useForm } from 'react-hook-form' | ||
import { Text } from '@chakra-ui/react' | ||
import { Meta, Story } from '@storybook/react' | ||
|
||
import { BasicField } from '~shared/types/field' | ||
|
||
import Button from '~components/Button' | ||
|
||
import { | ||
NricField as NricFieldComponent, | ||
NricFieldProps, | ||
NricFieldSchema, | ||
} from './NricField' | ||
|
||
export default { | ||
title: 'Templates/Field/NricField', | ||
component: NricFieldComponent, | ||
decorators: [], | ||
parameters: { | ||
docs: { | ||
// Required in this story due to react-hook-form conflicting with | ||
// Storybook somehow. | ||
// See https://github.com/storybookjs/storybook/issues/12747. | ||
source: { | ||
type: 'code', | ||
}, | ||
}, | ||
}, | ||
} as Meta | ||
|
||
const baseSchema: NricFieldSchema = { | ||
title: 'NRIC/FIN', | ||
description: 'Lorem ipsum what is your NRIC', | ||
required: true, | ||
disabled: false, | ||
fieldType: BasicField.Nric, | ||
_id: '611b94dfbb9e300012f702a7', | ||
} | ||
|
||
interface StoryNricFieldProps extends NricFieldProps { | ||
defaultValue?: string | ||
} | ||
|
||
const Template: Story<StoryNricFieldProps> = ({ defaultValue, ...args }) => { | ||
const formMethods = useForm({ | ||
defaultValues: { | ||
[args.schema._id]: defaultValue, | ||
}, | ||
}) | ||
|
||
const [submitValues, setSubmitValues] = useState<string>() | ||
|
||
const onSubmit = (values: Record<string, string>) => { | ||
setSubmitValues(values[args.schema._id] || 'Nothing was selected') | ||
} | ||
|
||
useEffect(() => { | ||
formMethods.trigger() | ||
}, []) | ||
|
||
return ( | ||
<FormProvider {...formMethods}> | ||
<form onSubmit={formMethods.handleSubmit(onSubmit)} noValidate> | ||
<NricFieldComponent {...args} /> | ||
<Button | ||
mt="1rem" | ||
type="submit" | ||
isLoading={formMethods.formState.isSubmitting} | ||
loadingText="Submitting" | ||
> | ||
Submit | ||
</Button> | ||
{submitValues && <Text>You have submitted: {submitValues}</Text>} | ||
</form> | ||
</FormProvider> | ||
) | ||
} | ||
|
||
export const ValidationRequired = Template.bind({}) | ||
ValidationRequired.args = { | ||
schema: baseSchema, | ||
} | ||
|
||
export const ValidationOptional = Template.bind({}) | ||
ValidationOptional.args = { | ||
schema: { ...baseSchema, required: false }, | ||
} | ||
|
||
export const ValidationInvalidNric = Template.bind({}) | ||
ValidationInvalidNric.args = { | ||
schema: baseSchema, | ||
defaultValue: 'S0000002Z', | ||
} | ||
|
||
export const ValidationValidNric = Template.bind({}) | ||
ValidationValidNric.args = { | ||
schema: baseSchema, | ||
defaultValue: 'S0000001I', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { composeStories } from '@storybook/testing-react' | ||
import { render, screen, waitFor } from '@testing-library/react' | ||
import userEvent from '@testing-library/user-event' | ||
|
||
import { REQUIRED_ERROR } from '~constants/validation' | ||
|
||
import * as stories from './NricField.stories' | ||
|
||
const { ValidationRequired, ValidationOptional } = composeStories(stories) | ||
|
||
describe('validation required', () => { | ||
it('renders error when field is not filled before submitting', async () => { | ||
// Arrange | ||
render(<ValidationRequired />) | ||
const submitButton = screen.getByText('Submit') | ||
|
||
// Act | ||
userEvent.click(submitButton) | ||
await waitFor(() => submitButton.textContent !== 'Submitting') | ||
|
||
// Assert | ||
// Should show error message. | ||
const error = screen.getByText(REQUIRED_ERROR) | ||
expect(error).not.toBeNull() | ||
}) | ||
|
||
it('renders success when field has valid NRIC when submitted', async () => { | ||
// Arrange | ||
const schema = ValidationRequired.args?.schema | ||
render(<ValidationRequired />) | ||
const input = screen.getByLabelText(schema!.title) as HTMLInputElement | ||
const submitButton = screen.getByText('Submit') | ||
|
||
expect(input.value).toBe('') | ||
|
||
// Act | ||
// Valid NRIC | ||
userEvent.type(input, 'S0000002G') | ||
userEvent.click(submitButton) | ||
await waitFor(() => submitButton.textContent !== 'Submitting') | ||
|
||
// Assert | ||
// Should show success message. | ||
const success = screen.getByText('You have submitted: S0000002G') | ||
expect(success).not.toBeNull() | ||
const error = screen.queryByText('Please fill in required field') | ||
expect(error).toBeNull() | ||
}) | ||
}) | ||
|
||
describe('validation optional', () => { | ||
it('renders success even when field is empty before submitting', async () => { | ||
// Arrange | ||
render(<ValidationOptional />) | ||
const submitButton = screen.getByText('Submit') | ||
|
||
// Act | ||
userEvent.click(submitButton) | ||
await waitFor(() => submitButton.textContent !== 'Submitting') | ||
|
||
// Assert | ||
// Should show success message. | ||
const success = screen.getByText('You have submitted: Nothing was selected') | ||
expect(success).not.toBeNull() | ||
}) | ||
|
||
it('renders success when field has valid NRIC when submitted', async () => { | ||
// Arrange | ||
const schema = ValidationOptional.args?.schema | ||
render(<ValidationOptional />) | ||
const input = screen.getByLabelText(schema!.title) as HTMLInputElement | ||
const submitButton = screen.getByText('Submit') | ||
|
||
expect(input.value).toBe('') | ||
|
||
// Act | ||
userEvent.type(input, 'S0000001I') | ||
userEvent.click(submitButton) | ||
await waitFor(() => submitButton.textContent !== 'Submitting') | ||
|
||
// Assert | ||
// Should show success message. | ||
const success = screen.getByText('You have submitted: S0000001I') | ||
expect(success).not.toBeNull() | ||
const error = screen.queryByText(REQUIRED_ERROR) | ||
expect(error).toBeNull() | ||
}) | ||
}) | ||
|
||
describe('NRIC validation', () => { | ||
it('renders error when invalid NRIC is submitted', async () => { | ||
// Arrange | ||
const schema = ValidationOptional.args?.schema | ||
render(<ValidationOptional />) | ||
const input = screen.getByLabelText(schema!.title) as HTMLInputElement | ||
const submitButton = screen.getByText('Submit') | ||
|
||
expect(input.value).toBe('') | ||
|
||
// Act | ||
userEvent.type(input, 'S0000001B') | ||
userEvent.click(submitButton) | ||
await waitFor(() => submitButton.textContent !== 'Submitting') | ||
|
||
// Assert | ||
// Should show error message. | ||
const error = screen.getByText('Please enter a valid NRIC') | ||
expect(error).not.toBeNull() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/** | ||
* @precondition Must have a parent `react-hook-form#FormProvider` component. | ||
*/ | ||
import { useMemo } from 'react' | ||
import { useFormContext } from 'react-hook-form' | ||
|
||
import { FormFieldWithId, NricFieldBase } from '~shared/types/field' | ||
|
||
import { createNricValidationRules } from '~utils/fieldValidation' | ||
import Input from '~components/Input' | ||
|
||
import { BaseFieldProps, FieldContainer } from '../FieldContainer' | ||
|
||
export type NricFieldSchema = FormFieldWithId<NricFieldBase> | ||
export interface NricFieldProps extends BaseFieldProps { | ||
schema: NricFieldSchema | ||
} | ||
|
||
export const NricField = ({ | ||
schema, | ||
questionNumber, | ||
}: NricFieldProps): JSX.Element => { | ||
const validationRules = useMemo( | ||
() => createNricValidationRules(schema), | ||
[schema], | ||
) | ||
|
||
const { register } = useFormContext() | ||
|
||
return ( | ||
<FieldContainer schema={schema} questionNumber={questionNumber}> | ||
<Input | ||
aria-label={schema.title} | ||
{...register(schema._id, validationRules)} | ||
/> | ||
</FieldContainer> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters