Skip to content
This repository has been archived by the owner on Jan 9, 2023. It is now read-only.

Commit

Permalink
fix(patient): Input validation and error feedback (#1977)
Browse files Browse the repository at this point in the history
  • Loading branch information
martimfj committed Apr 12, 2020
1 parent b816ebf commit 992e58d
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 39 deletions.
66 changes: 66 additions & 0 deletions src/__tests__/patients/edit/EditPatient.test.tsx
Expand Up @@ -9,6 +9,7 @@ import { act } from 'react-dom/test-utils'
import configureMockStore, { MockStore } from 'redux-mock-store'
import thunk from 'redux-thunk'
import { Button } from '@hospitalrun/components'
import { addDays, endOfToday } from 'date-fns'
import EditPatient from '../../../patients/edit/EditPatient'
import GeneralInformation from '../../../patients/GeneralInformation'
import Patient from '../../../model/Patient'
Expand Down Expand Up @@ -119,6 +120,71 @@ describe('Edit Patient', () => {
expect(store.getActions()).toContainEqual(patientSlice.updatePatientSuccess(patient))
})

it('should pass no given name error when form doesnt contain a given name on save button click', async () => {
let wrapper: any
await act(async () => {
wrapper = await setup()
})

const givenName = wrapper.findWhere((w: any) => w.prop('name') === 'givenName')
expect(givenName.prop('value')).toBe('')

const generalInformationForm = wrapper.find(GeneralInformation)
expect(generalInformationForm.prop('errorMessage')).toBe('')

const saveButton = wrapper.find(Button).at(0)
const onClick = saveButton.prop('onClick') as any
expect(saveButton.text().trim()).toEqual('actions.save')

act(() => {
onClick()
})

wrapper.update()
expect(wrapper.find(GeneralInformation).prop('errorMessage')).toMatch(
'patient.errors.updatePatientError',
)
expect(wrapper.find(GeneralInformation).prop('feedbackFields').givenName).toMatch(
'patient.errors.patientGivenNameFeedback',
)
expect(wrapper.update.isInvalid === true)
})

it('should pass invalid date of birth error when input date is grater than today on save button click', async () => {
let wrapper: any
await act(async () => {
wrapper = await setup()
})

const generalInformationForm = wrapper.find(GeneralInformation)

act(() => {
generalInformationForm.prop('onFieldChange')(
'dateOfBirth',
addDays(endOfToday(), 10).toISOString(),
)
})

wrapper.update()

const saveButton = wrapper.find(Button).at(0)
const onClick = saveButton.prop('onClick') as any
expect(saveButton.text().trim()).toEqual('actions.save')

await act(async () => {
await onClick()
})

wrapper.update()
expect(wrapper.find(GeneralInformation).prop('errorMessage')).toMatch(
'patient.errors.updatePatientError',
)
expect(wrapper.find(GeneralInformation).prop('feedbackFields').dateOfBirth).toMatch(
'patient.errors.patientDateOfBirthFeedback',
)
expect(wrapper.update.isInvalid === true)
})

it('should navigate to /patients/:id when cancel is clicked', async () => {
let wrapper: any
await act(async () => {
Expand Down
41 changes: 40 additions & 1 deletion src/__tests__/patients/new/NewPatient.test.tsx
Expand Up @@ -10,6 +10,7 @@ import configureMockStore, { MockStore } from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as components from '@hospitalrun/components'

import { addDays, endOfToday } from 'date-fns'
import NewPatient from '../../../patients/new/NewPatient'
import GeneralInformation from '../../../patients/GeneralInformation'
import Patient from '../../../model/Patient'
Expand Down Expand Up @@ -95,7 +96,45 @@ describe('New Patient', () => {

wrapper.update()
expect(wrapper.find(GeneralInformation).prop('errorMessage')).toMatch(
'patient.errors.patientGivenNameRequiredOnCreate',
'patient.errors.createPatientError',
)
expect(wrapper.find(GeneralInformation).prop('feedbackFields').givenName).toMatch(
'patient.errors.patientGivenNameFeedback',
)
expect(wrapper.update.isInvalid === true)
})

it('should pass invalid date of birth error when input date is grater than today on save button click', async () => {
let wrapper: any
await act(async () => {
wrapper = await setup()
})

const generalInformationForm = wrapper.find(GeneralInformation)

act(() => {
generalInformationForm.prop('onFieldChange')(
'dateOfBirth',
addDays(endOfToday(), 10).toISOString(),
)
})

wrapper.update()

const saveButton = wrapper.find(components.Button).at(0)
const onClick = saveButton.prop('onClick') as any
expect(saveButton.text().trim()).toEqual('actions.save')

await act(async () => {
await onClick()
})

wrapper.update()
expect(wrapper.find(GeneralInformation).prop('errorMessage')).toMatch(
'patient.errors.createPatientError',
)
expect(wrapper.find(GeneralInformation).prop('feedbackFields').dateOfBirth).toMatch(
'patient.errors.patientDateOfBirthFeedback',
)
expect(wrapper.update.isInvalid === true)
})
Expand Down
6 changes: 5 additions & 1 deletion src/components/input/DatePickerWithLabelFormGroup.tsx
Expand Up @@ -8,10 +8,12 @@ interface Props {
isEditable?: boolean
onChange?: (date: Date) => void
isRequired?: boolean
feedback?: string
isInvalid?: boolean
}

const DatePickerWithLabelFormGroup = (props: Props) => {
const { onChange, label, name, isEditable, value, isRequired } = props
const { onChange, label, name, isEditable, value, isRequired, feedback, isInvalid } = props
const id = `${name}DatePicker`
return (
<div className="form-group">
Expand All @@ -24,6 +26,8 @@ const DatePickerWithLabelFormGroup = (props: Props) => {
timeIntervals={30}
withPortal={false}
disabled={!isEditable}
feedback={feedback}
isInvalid={isInvalid}
onChange={(inputDate) => {
if (onChange) {
onChange(inputDate)
Expand Down
6 changes: 3 additions & 3 deletions src/locales/enUs/translations/patient/index.ts
@@ -1,6 +1,5 @@
export default {
patient: {
code: 'Code',
firstName: 'First Name',
lastName: 'Last Name',
suffix: 'Suffix',
Expand Down Expand Up @@ -85,9 +84,10 @@ export default {
private: 'Private',
},
errors: {
patientGivenNameRequiredOnCreate: 'Could not create new patient.',
patientGivenNameRequiredOnUpdate: 'Could not update patient.',
createPatientError: 'Could not create new patient.',
updatePatientError: 'Could not update patient.',
patientGivenNameFeedback: 'Given Name is required.',
patientDateOfBirthFeedback: 'Date of Birth can not be greater than today',
},
},
}
8 changes: 4 additions & 4 deletions src/locales/ptBr/translations/patient/index.ts
Expand Up @@ -64,10 +64,10 @@ export default {
private: 'Particular',
},
errors: {
patientGivenNameRequiredOnCreate: 'Nome do Paciente é necessário.',
// todo Portuguese translation
patientGivenNameRequiredOnUpdate: '',
patientGivenNameFeedback: '',
createPatientError: 'Não foi possível criar um paciente.',
updatePatientError: 'Não foi possível atualizar o paciente',
patientGivenNameFeedback: 'Nome do Paciente é necessário.',
patientDateOfBirthFeedback: 'Data de Nascimento não pode ser maior que hoje.',
},
},
}
29 changes: 17 additions & 12 deletions src/patients/GeneralInformation.tsx
Expand Up @@ -9,25 +9,28 @@ import TextInputWithLabelFormGroup from '../components/input/TextInputWithLabelF
import SelectWithLabelFormGroup from '../components/input/SelectWithLableFormGroup'
import DatePickerWithLabelFormGroup from '../components/input/DatePickerWithLabelFormGroup'

interface Feedback {
givenName: string
dateOfBirth: string
}

interface InvalidFields {
givenName: boolean
dateOfBirth: boolean
}

interface Props {
patient: Patient
isEditable?: boolean
errorMessage?: string
onFieldChange?: (key: string, value: string | boolean) => void
isInvalid?: boolean
patientGivenNameFeedback?: string
invalidFields?: InvalidFields
feedbackFields?: Feedback
}

const GeneralInformation = (props: Props) => {
const { t } = useTranslation()
const {
patient,
isEditable,
onFieldChange,
errorMessage,
isInvalid,
patientGivenNameFeedback,
} = props
const { patient, isEditable, onFieldChange, errorMessage, invalidFields, feedbackFields } = props

const onSelectChange = (event: React.ChangeEvent<HTMLSelectElement>, fieldName: string) =>
onFieldChange && onFieldChange(fieldName, event.target.value)
Expand Down Expand Up @@ -81,8 +84,8 @@ const GeneralInformation = (props: Props) => {
onInputElementChange(event, 'givenName')
}}
isRequired
isInvalid={isInvalid}
feedback={patientGivenNameFeedback}
isInvalid={invalidFields?.givenName}
feedback={feedbackFields?.givenName}
/>
</div>
<div className="col-md-4">
Expand Down Expand Up @@ -163,6 +166,8 @@ const GeneralInformation = (props: Props) => {
? new Date(patient.dateOfBirth)
: undefined
}
isInvalid={invalidFields?.dateOfBirth}
feedback={feedbackFields?.dateOfBirth}
onChange={(date: Date) => {
onDateOfBirthChange(date)
}}
Expand Down
53 changes: 44 additions & 9 deletions src/patients/edit/EditPatient.tsx
Expand Up @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { Spinner, Button, Toast } from '@hospitalrun/components'

import { parseISO } from 'date-fns'
import GeneralInformation from '../GeneralInformation'
import useTitle from '../../page-header/useTitle'
import Patient from '../../model/Patient'
Expand All @@ -27,8 +28,14 @@ const EditPatient = () => {

const [patient, setPatient] = useState({} as Patient)
const [errorMessage, setErrorMessage] = useState('')
const [isInvalid, setIsInvalid] = useState(false)
const [patientGivenNameFeedback, setPatientGivenNameFeedback] = useState('')
const [invalidFields, setInvalidFields] = useState({
givenName: false,
dateOfBirth: false,
})
const [feedbackFields, setFeedbackFields] = useState({
givenName: '',
dateOfBirth: '',
})
const { patient: reduxPatient, isLoading } = useSelector((state: RootState) => state.patient)

useTitle(
Expand Down Expand Up @@ -68,12 +75,40 @@ const EditPatient = () => {
)
}

const onSave = () => {
const validateInput = () => {
let inputIsValid = true

if (!patient.givenName) {
setErrorMessage(t('patient.errors.patientGivenNameRequiredOnUpdate'))
setIsInvalid(true)
setPatientGivenNameFeedback(t('patient.errors.patientGivenNameFeedback'))
} else {
inputIsValid = false
setErrorMessage(t('patient.errors.updatePatientError'))
setInvalidFields((prevState) => ({
...prevState,
givenName: true,
}))
setFeedbackFields((prevState) => ({
...prevState,
givenName: t('patient.errors.patientGivenNameFeedback'),
}))
}
if (patient.dateOfBirth) {
if (parseISO(patient.dateOfBirth) > new Date(Date.now())) {
inputIsValid = false
setErrorMessage(t('patient.errors.updatePatientError'))
setInvalidFields((prevState) => ({
...prevState,
dateOfBirth: true,
}))
setFeedbackFields((prevState) => ({
...prevState,
dateOfBirth: t('patient.errors.patientDateOfBirthFeedback'),
}))
}
}
return inputIsValid
}

const onSave = () => {
if (validateInput()) {
dispatch(
updatePatient(
{
Expand Down Expand Up @@ -104,8 +139,8 @@ const EditPatient = () => {
patient={patient}
onFieldChange={onFieldChange}
errorMessage={errorMessage}
isInvalid={isInvalid}
patientGivenNameFeedback={patientGivenNameFeedback}
invalidFields={invalidFields}
feedbackFields={feedbackFields}
/>
<div className="row float-right">
<div className="btn-group btn-group-lg">
Expand Down

1 comment on commit 992e58d

@vercel
Copy link

@vercel vercel bot commented on 992e58d Apr 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.