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

Commit

Permalink
Merge pull request #2030 from HospitalRun/input-validation
Browse files Browse the repository at this point in the history
feat(patient): add email and phone number validation
  • Loading branch information
matteovivona committed May 2, 2020
2 parents 1693e5f + 7ddf0bf commit 63f0706
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 8 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/compressed-size.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2-beta
- uses: actions/checkout@v2
with:
fetch-depth: 1
- uses: preactjs/compressed-size-action@v1
- uses: preactjs/compressed-size-action@v2
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
14 changes: 11 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"private": false,
"license": "MIT",
"dependencies": {
"@hospitalrun/components": "^1.3.0",
"@hospitalrun/components": "^1.4.0",
"@reduxjs/toolkit": "~1.3.0",
"@types/pouchdb-find": "~6.3.4",
"bootstrap": "~4.4.1",
Expand All @@ -14,8 +14,8 @@
"i18next": "~19.4.0",
"i18next-browser-languagedetector": "~4.1.0",
"i18next-xhr-backend": "~3.2.2",
"node-sass": "~4.14.0",
"lodash": "^4.17.15",
"node-sass": "~4.14.0",
"pouchdb": "~7.2.1",
"pouchdb-adapter-memory": "~7.2.1",
"pouchdb-find": "~7.2.1",
Expand All @@ -33,7 +33,14 @@
"redux-thunk": "~2.3.0",
"shortid": "^2.2.15",
"typescript": "~3.8.2",
"uuid": "^8.0.0"
"uuid": "^8.0.0",
"validator": "^13.0.0"
},
"resolutions": {
"moment": "2.24.0"
},
"comments": {
"resolutions": "Added this key as temp fix https://github.com/moment/moment/issues/5484"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -64,6 +71,7 @@
"@types/redux-mock-store": "~1.0.1",
"@types/shortid": "^0.0.29",
"@types/uuid": "^7.0.0",
"@types/validator": "~13.0.0",
"@typescript-eslint/eslint-plugin": "~2.30.0",
"@typescript-eslint/parser": "~2.30.0",
"commitizen": "~4.0.3",
Expand Down
8 changes: 8 additions & 0 deletions src/__tests__/patients/GeneralInformation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ describe('Error handling', () => {
message: 'some message',
givenName: 'given name message',
dateOfBirth: 'date of birth message',
phoneNumber: 'phone number message',
email: 'email message',
}
const history = createMemoryHistory()
const wrapper = mount(
Expand All @@ -27,12 +29,18 @@ describe('Error handling', () => {
const errorMessage = wrapper.find(Alert)
const givenNameInput = wrapper.findWhere((w: any) => w.prop('name') === 'givenName')
const dateOfBirthInput = wrapper.findWhere((w: any) => w.prop('name') === 'dateOfBirth')
const emailInput = wrapper.findWhere((w: any) => w.prop('name') === 'email')
const phoneNumberInput = wrapper.findWhere((w: any) => w.prop('name') === 'phoneNumber')
expect(errorMessage).toBeTruthy()
expect(errorMessage.prop('message')).toMatch(error.message)
expect(givenNameInput.prop('isInvalid')).toBeTruthy()
expect(givenNameInput.prop('feedback')).toEqual(error.givenName)
expect(dateOfBirthInput.prop('isInvalid')).toBeTruthy()
expect(dateOfBirthInput.prop('feedback')).toEqual(error.dateOfBirth)
expect(emailInput.prop('feedback')).toEqual(error.email)
expect(emailInput.prop('isInvalid')).toBeTruthy()
expect(phoneNumberInput.prop('feedback')).toEqual(error.phoneNumber)
expect(phoneNumberInput.prop('isInvalid')).toBeTruthy()
})
})

Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/patients/edit/EditPatient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('Edit Patient', () => {
type: 'charity',
occupation: 'occupation',
preferredLanguage: 'preferredLanguage',
phoneNumber: 'phoneNumber',
phoneNumber: '123456789',
email: 'email@email.com',
address: 'address',
code: 'P00001',
Expand Down
77 changes: 75 additions & 2 deletions src/__tests__/patients/patient-slice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,12 @@ describe('patients slice', () => {
expect(onSuccessSpy).toHaveBeenCalledWith(expectedPatient)
})

it('should validate the patient', async () => {
it('should validate the patient required fields', async () => {
const store = mockStore()
const expectedPatientId = 'sliceId10'
const expectedPatient = {
id: expectedPatientId,
givenName: undefined,
dateOfBirth: addDays(new Date(), 4).toISOString(),
} as Patient
const saveOrUpdateSpy = jest
.spyOn(PatientRepository, 'saveOrUpdate')
Expand All @@ -218,10 +217,84 @@ describe('patients slice', () => {
createPatientError({
message: 'patient.errors.createPatientError',
givenName: 'patient.errors.patientGivenNameFeedback',
}),
)
})

it('should validate that the patient birthday is not a future date', async () => {
const store = mockStore()
const expectedPatientId = 'sliceId10'
const expectedPatient = {
id: expectedPatientId,
givenName: 'some given name',
dateOfBirth: addDays(new Date(), 4).toISOString(),
} as Patient
const saveOrUpdateSpy = jest
.spyOn(PatientRepository, 'saveOrUpdate')
.mockResolvedValue(expectedPatient)
const onSuccessSpy = jest.fn()

await store.dispatch(createPatient(expectedPatient, onSuccessSpy))

expect(onSuccessSpy).not.toHaveBeenCalled()
expect(saveOrUpdateSpy).not.toHaveBeenCalled()
expect(store.getActions()[1]).toEqual(
createPatientError({
message: 'patient.errors.createPatientError',
dateOfBirth: 'patient.errors.patientDateOfBirthFeedback',
}),
)
})

it('should validate that the patient email is a valid email', async () => {
const store = mockStore()
const expectedPatientId = 'sliceId10'
const expectedPatient = {
id: expectedPatientId,
givenName: 'some given name',
phoneNumber: 'not a phone number',
} as Patient
const saveOrUpdateSpy = jest
.spyOn(PatientRepository, 'saveOrUpdate')
.mockResolvedValue(expectedPatient)
const onSuccessSpy = jest.fn()

await store.dispatch(createPatient(expectedPatient, onSuccessSpy))

expect(onSuccessSpy).not.toHaveBeenCalled()
expect(saveOrUpdateSpy).not.toHaveBeenCalled()
expect(store.getActions()[1]).toEqual(
createPatientError({
message: 'patient.errors.createPatientError',
phoneNumber: 'patient.errors.invalidPhoneNumber',
}),
)
})

it('should validate that the patient phone number is a valid phone number', async () => {
const store = mockStore()
const expectedPatientId = 'sliceId10'
const expectedPatient = {
id: expectedPatientId,
givenName: 'some given name',
phoneNumber: 'not a phone number',
} as Patient
const saveOrUpdateSpy = jest
.spyOn(PatientRepository, 'saveOrUpdate')
.mockResolvedValue(expectedPatient)
const onSuccessSpy = jest.fn()

await store.dispatch(createPatient(expectedPatient, onSuccessSpy))

expect(onSuccessSpy).not.toHaveBeenCalled()
expect(saveOrUpdateSpy).not.toHaveBeenCalled()
expect(store.getActions()[1]).toEqual(
createPatientError({
message: 'patient.errors.createPatientError',
phoneNumber: 'patient.errors.invalidPhoneNumber',
}),
)
})
})

describe('fetch patient', () => {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/enUs/translations/patient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ export default {
updatePatientError: 'Could not update patient.',
patientGivenNameFeedback: 'Given Name is required.',
patientDateOfBirthFeedback: 'Date of Birth can not be greater than today',
invalidEmail: 'Must be a valid email.',
invalidPhoneNumber: 'Must be a valid phone number.',
},
},
}
4 changes: 4 additions & 0 deletions src/patients/GeneralInformation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ const GeneralInformation = (props: Props) => {
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
onInputElementChange(event, 'phoneNumber')
}}
feedback={t(error?.phoneNumber)}
isInvalid={!!error?.phoneNumber}
type="tel"
/>
</div>
Expand All @@ -225,6 +227,8 @@ const GeneralInformation = (props: Props) => {
onInputElementChange(event, 'email')
}}
type="email"
feedback={t(error?.email)}
isInvalid={!!error?.email}
/>
</div>
</div>
Expand Down
15 changes: 15 additions & 0 deletions src/patients/patient-slice.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { isAfter, parseISO } from 'date-fns'
import _ from 'lodash'
import validator from 'validator'
import { uuid } from '../util/uuid'
import Patient from '../model/Patient'
import PatientRepository from '../clients/db/PatientRepository'
Expand All @@ -27,6 +28,8 @@ interface Error {
message?: string
givenName?: string
dateOfBirth?: string
email?: string
phoneNumber?: string
}

interface AddRelatedPersonError {
Expand Down Expand Up @@ -150,6 +153,18 @@ function validatePatient(patient: Patient) {
}
}

if (patient.email) {
if (!validator.isEmail(patient.email)) {
error.email = 'patient.errors.invalidEmail'
}
}

if (patient.phoneNumber) {
if (!validator.isMobilePhone(patient.phoneNumber)) {
error.phoneNumber = 'patient.errors.invalidPhoneNumber'
}
}

return error
}

Expand Down

1 comment on commit 63f0706

@vercel
Copy link

@vercel vercel bot commented on 63f0706 May 2, 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.