From 242e1a13c14f83b4a945dcccf554e26ead5622ff Mon Sep 17 00:00:00 2001 From: Jack Meyer Date: Thu, 30 Apr 2020 22:44:38 -0500 Subject: [PATCH 1/5] feat(patient): add email and phone number validation --- package.json | 6 +- .../patients/GeneralInformation.test.tsx | 8 ++ .../patients/edit/EditPatient.test.tsx | 2 +- src/__tests__/patients/patient-slice.test.ts | 77 ++++++++++++++++++- .../enUs/translations/patient/index.ts | 2 + src/patients/GeneralInformation.tsx | 4 + src/patients/patient-slice.ts | 15 ++++ 7 files changed, 109 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c23dd474ec..44dd24138a 100644 --- a/package.json +++ b/package.json @@ -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", @@ -33,7 +33,8 @@ "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" }, "repository": { "type": "git", @@ -64,6 +65,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", diff --git a/src/__tests__/patients/GeneralInformation.test.tsx b/src/__tests__/patients/GeneralInformation.test.tsx index 1e8738c0b4..cfd0c525a9 100644 --- a/src/__tests__/patients/GeneralInformation.test.tsx +++ b/src/__tests__/patients/GeneralInformation.test.tsx @@ -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( @@ -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() }) }) diff --git a/src/__tests__/patients/edit/EditPatient.test.tsx b/src/__tests__/patients/edit/EditPatient.test.tsx index 4b0b237d0b..62de9c53a9 100644 --- a/src/__tests__/patients/edit/EditPatient.test.tsx +++ b/src/__tests__/patients/edit/EditPatient.test.tsx @@ -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', diff --git a/src/__tests__/patients/patient-slice.test.ts b/src/__tests__/patients/patient-slice.test.ts index 58a8d1456b..28ed65761c 100644 --- a/src/__tests__/patients/patient-slice.test.ts +++ b/src/__tests__/patients/patient-slice.test.ts @@ -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') @@ -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', () => { diff --git a/src/locales/enUs/translations/patient/index.ts b/src/locales/enUs/translations/patient/index.ts index dd39f9126e..32b6b4b331 100644 --- a/src/locales/enUs/translations/patient/index.ts +++ b/src/locales/enUs/translations/patient/index.ts @@ -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.', }, }, } diff --git a/src/patients/GeneralInformation.tsx b/src/patients/GeneralInformation.tsx index 231b9832ef..6f926a6d2d 100644 --- a/src/patients/GeneralInformation.tsx +++ b/src/patients/GeneralInformation.tsx @@ -211,6 +211,8 @@ const GeneralInformation = (props: Props) => { onChange={(event: React.ChangeEvent) => { onInputElementChange(event, 'phoneNumber') }} + feedback={t(error?.phoneNumber)} + isInvalid={!!error?.phoneNumber} type="tel" /> @@ -225,6 +227,8 @@ const GeneralInformation = (props: Props) => { onInputElementChange(event, 'email') }} type="email" + feedback={t(error?.email)} + isInvalid={!!error?.email} /> diff --git a/src/patients/patient-slice.ts b/src/patients/patient-slice.ts index deb9e2a58d..4488ef5eef 100644 --- a/src/patients/patient-slice.ts +++ b/src/patients/patient-slice.ts @@ -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' @@ -27,6 +28,8 @@ interface Error { message?: string givenName?: string dateOfBirth?: string + email?: string + phoneNumber?: string } interface AddRelatedPersonError { @@ -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 } From e522406bdaa8bd1ea9cd8a99e4c36faf78ad2c7a Mon Sep 17 00:00:00 2001 From: Matteo Vivona Date: Fri, 1 May 2020 12:19:51 +0200 Subject: [PATCH 2/5] chore(deps): bump components --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 44dd24138a..0135e48978 100644 --- a/package.json +++ b/package.json @@ -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", From e972aaebce02badce360645b3e722135b19f3056 Mon Sep 17 00:00:00 2001 From: Matteo Vivona Date: Sat, 2 May 2020 14:31:46 +0200 Subject: [PATCH 3/5] fix(moment): add resolutions key on package.json --- package.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.json b/package.json index 0135e48978..462d62028a 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,12 @@ "typescript": "~3.8.2", "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", From ed67102b8870f9b1313541744112fe98ccc152d1 Mon Sep 17 00:00:00 2001 From: Matteo Vivona Date: Sat, 2 May 2020 14:34:01 +0200 Subject: [PATCH 4/5] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 462d62028a..e7ea8ea201 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "moment": "2.24.0" }, "comments": { - "resolutions": "Added this key as temp fix https://github.com/moment/moment/issues/5484", + "resolutions": "Added this key as temp fix https://github.com/moment/moment/issues/5484" }, "repository": { "type": "git", From 7ddf0bf8a9c504ac812e491e3c4241b04d0cf106 Mon Sep 17 00:00:00 2001 From: Matteo Vivona Date: Sat, 2 May 2020 14:50:10 +0200 Subject: [PATCH 5/5] ci(actions): bump checkout and compressed --- .github/workflows/compressed-size.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compressed-size.yml b/.github/workflows/compressed-size.yml index 3bb9642e2d..b3197c5cc4 100644 --- a/.github/workflows/compressed-size.yml +++ b/.github/workflows/compressed-size.yml @@ -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 }}"