From 13654baa779b1e4906690decdf0fab198e63e7c5 Mon Sep 17 00:00:00 2001 From: Tomas Nygren Date: Thu, 24 Sep 2020 12:34:03 +1000 Subject: [PATCH 1/4] refactor(patients): use usePatient (#2411) --- src/__tests__/patients/Patients.test.tsx | 14 ++-- .../patients/edit/EditPatient.test.tsx | 9 +-- src/__tests__/patients/patient-slice.test.ts | 67 ------------------- ...name-util.test.ts => patient-util.test.ts} | 36 ++++++++-- .../patients/view/ViewPatient.test.tsx | 3 - .../appointments/AppointmentsList.tsx | 4 +- src/patients/edit/EditPatient.tsx | 39 ++++------- src/patients/patient-slice.ts | 13 ---- .../{patient-name-util.ts => patient-util.ts} | 10 ++- src/patients/util/set-patient-helper.ts | 2 +- src/patients/view/ViewPatient.tsx | 33 +++------ 11 files changed, 77 insertions(+), 153 deletions(-) rename src/__tests__/patients/util/{patient-name-util.test.ts => patient-util.test.ts} (66%) rename src/patients/util/{patient-name-util.ts => patient-util.ts} (77%) diff --git a/src/__tests__/patients/Patients.test.tsx b/src/__tests__/patients/Patients.test.tsx index d153419d28..4067fc00a3 100644 --- a/src/__tests__/patients/Patients.test.tsx +++ b/src/__tests__/patients/Patients.test.tsx @@ -12,6 +12,7 @@ import { addBreadcrumbs } from '../../page-header/breadcrumbs/breadcrumbs-slice' import * as titleUtil from '../../page-header/title/TitleContext' import EditPatient from '../../patients/edit/EditPatient' import NewPatient from '../../patients/new/NewPatient' +import * as patientNameUtil from '../../patients/util/patient-util' import ViewPatient from '../../patients/view/ViewPatient' import PatientRepository from '../../shared/db/PatientRepository' import Patient from '../../shared/model/Patient' @@ -27,8 +28,8 @@ describe('/patients/new', () => { const store = mockStore({ title: 'test', user: { user: { id: '123' }, permissions: [Permissions.WritePatients] }, - patient: {}, breadcrumbs: { breadcrumbs: [] }, + patient: {}, components: { sidebarCollapsed: false }, } as any) @@ -46,6 +47,8 @@ describe('/patients/new', () => { ) }) + wrapper.update() + expect(wrapper.find(NewPatient)).toHaveLength(1) expect(store.getActions()).toContainEqual( @@ -91,6 +94,9 @@ describe('/patients/edit/:id', () => { } as Patient jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) + jest + .spyOn(patientNameUtil, 'getPatientFullName') + .mockReturnValue(`${patient.prefix} ${patient.givenName} ${patient.familyName}`) const store = mockStore({ title: 'test', @@ -115,14 +121,14 @@ describe('/patients/edit/:id', () => { expect(wrapper.find(EditPatient)).toHaveLength(1) - expect(store.getActions()).toContainEqual( - addBreadcrumbs([ + expect(store.getActions()).toContainEqual({ + ...addBreadcrumbs([ { i18nKey: 'patients.label', location: '/patients' }, { text: 'test test test', location: `/patients/${patient.id}` }, { i18nKey: 'patients.editPatient', location: `/patients/${patient.id}/edit` }, { i18nKey: 'dashboard.label', location: '/' }, ]), - ) + }) }) it('should render the Dashboard when the user does not have read patient privileges', () => { diff --git a/src/__tests__/patients/edit/EditPatient.test.tsx b/src/__tests__/patients/edit/EditPatient.test.tsx index a49d95261c..d7d12f0b0a 100644 --- a/src/__tests__/patients/edit/EditPatient.test.tsx +++ b/src/__tests__/patients/edit/EditPatient.test.tsx @@ -11,7 +11,6 @@ import thunk from 'redux-thunk' import * as titleUtil from '../../../page-header/title/TitleContext' import EditPatient from '../../../patients/edit/EditPatient' import GeneralInformation from '../../../patients/GeneralInformation' -import * as patientSlice from '../../../patients/patient-slice' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' import { RootState } from '../../../shared/store' @@ -63,7 +62,9 @@ describe('Edit Patient', () => { ) wrapper.find(EditPatient).props().updateTitle = jest.fn() + wrapper.update() + return wrapper } @@ -87,14 +88,12 @@ describe('Edit Patient', () => { expect(wrapper.find(GeneralInformation)).toHaveLength(1) }) - it('should dispatch fetchPatient when component loads', async () => { + it('should load a Patient when component loads', async () => { await act(async () => { await setup() }) expect(PatientRepository.find).toHaveBeenCalledWith(patient.id) - expect(store.getActions()).toContainEqual(patientSlice.fetchPatientStart()) - expect(store.getActions()).toContainEqual(patientSlice.fetchPatientSuccess(patient)) }) it('should dispatch updatePatient when save button is clicked', async () => { @@ -114,8 +113,6 @@ describe('Edit Patient', () => { }) expect(PatientRepository.saveOrUpdate).toHaveBeenCalledWith(patient) - expect(store.getActions()).toContainEqual(patientSlice.updatePatientStart()) - expect(store.getActions()).toContainEqual(patientSlice.updatePatientSuccess(patient)) }) it('should navigate to /patients/:id when cancel is clicked', async () => { diff --git a/src/__tests__/patients/patient-slice.test.ts b/src/__tests__/patients/patient-slice.test.ts index fa68233f53..c99e5df924 100644 --- a/src/__tests__/patients/patient-slice.test.ts +++ b/src/__tests__/patients/patient-slice.test.ts @@ -10,9 +10,6 @@ import patient, { createPatientError, createPatientStart, createPatientSuccess, - fetchPatient, - fetchPatientStart, - fetchPatientSuccess, updatePatient, updatePatientError, updatePatientStart, @@ -38,34 +35,6 @@ describe('patients slice', () => { expect(patientStore.patient).toEqual({}) }) - it('should handle the FETCH_PATIENT_START action', () => { - const patientStore = patient(undefined, { - type: fetchPatientStart.type, - }) - - expect(patientStore.status).toEqual('loading') - }) - - it('should handle the FETCH_PATIENT_SUCCESS actions', () => { - const expectedPatient = { - id: '123', - rev: '123', - sex: 'male', - dateOfBirth: new Date().toISOString(), - givenName: 'test', - familyName: 'test', - } - const patientStore = patient(undefined, { - type: fetchPatientSuccess.type, - payload: { - ...expectedPatient, - }, - }) - - expect(patientStore.status).toEqual('completed') - expect(patientStore.patient).toEqual(expectedPatient) - }) - it('should handle the CREATE_PATIENT_START action', () => { const patientsStore = patient(undefined, { type: createPatientStart.type, @@ -321,42 +290,6 @@ describe('patients slice', () => { ) }) }) - - describe('fetch patient', () => { - it('should dispatch the FETCH_PATIENT_START action', async () => { - const store = mockStore() - const expectedPatientId = 'sliceId6' - const expectedPatient = { id: expectedPatientId, givenName: 'some name' } as Patient - jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) - - await store.dispatch(fetchPatient(expectedPatientId)) - - expect(store.getActions()[0]).toEqual(fetchPatientStart()) - }) - - it('should call the PatientRepository find method with the correct patient id', async () => { - const store = mockStore() - const expectedPatientId = 'sliceId6' - const expectedPatient = { id: expectedPatientId, givenName: 'some name' } as Patient - jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) - - await store.dispatch(fetchPatient(expectedPatientId)) - - expect(PatientRepository.find).toHaveBeenCalledWith(expectedPatientId) - }) - - it('should dispatch the FETCH_PATIENT_SUCCESS action with the correct data', async () => { - const store = mockStore() - const expectedPatientId = 'sliceId6' - const expectedPatient = { id: expectedPatientId, givenName: 'some name' } as Patient - jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) - - await store.dispatch(fetchPatient(expectedPatientId)) - - expect(store.getActions()[1]).toEqual(fetchPatientSuccess(expectedPatient)) - }) - }) - describe('update patient', () => { it('should dispatch the UPDATE_PATIENT_START action', async () => { const store = mockStore() diff --git a/src/__tests__/patients/util/patient-name-util.test.ts b/src/__tests__/patients/util/patient-util.test.ts similarity index 66% rename from src/__tests__/patients/util/patient-name-util.test.ts rename to src/__tests__/patients/util/patient-util.test.ts index f0fd700634..03455e5a11 100644 --- a/src/__tests__/patients/util/patient-name-util.test.ts +++ b/src/__tests__/patients/util/patient-util.test.ts @@ -1,7 +1,11 @@ -import { getPatientFullName, getPatientName } from '../../../patients/util/patient-name-util' +import { + getPatientCode, + getPatientFullName, + getPatientName, +} from '../../../patients/util/patient-util' import Patient from '../../../shared/model/Patient' -describe('patient name util', () => { +describe('patient util', () => { describe('getPatientName', () => { it('should build the patients name when three different type of names are passed in', () => { const expectedGiven = 'given' @@ -56,10 +60,32 @@ describe('patient name util', () => { suffix: 'suffix', } as Patient - const expectedFullName = `${patient.givenName} ${patient.familyName} ${patient.suffix}` + it('should return the patients name given a patient', () => { + const expectedFullName = `${patient.givenName} ${patient.familyName} ${patient.suffix}` - const actualFullName = getPatientFullName(patient) + const actualFullName = getPatientFullName(patient) - expect(actualFullName).toEqual(expectedFullName) + expect(actualFullName).toEqual(expectedFullName) + }) + + it('should return a empty string given undefined', () => { + expect(getPatientFullName(undefined)).toEqual('') + }) + }) + + describe('getPatientCode', () => { + const patient = { + code: 'code', + } as Patient + + it('should return the patients code given a patient', () => { + const actualFullName = getPatientCode(patient) + + expect(actualFullName).toEqual(patient.code) + }) + + it('should return a empty string given undefined', () => { + expect(getPatientCode(undefined)).toEqual('') + }) }) }) diff --git a/src/__tests__/patients/view/ViewPatient.test.tsx b/src/__tests__/patients/view/ViewPatient.test.tsx index 80713efce7..dcc2f9884b 100644 --- a/src/__tests__/patients/view/ViewPatient.test.tsx +++ b/src/__tests__/patients/view/ViewPatient.test.tsx @@ -18,7 +18,6 @@ import Diagnoses from '../../../patients/diagnoses/Diagnoses' import GeneralInformation from '../../../patients/GeneralInformation' import Labs from '../../../patients/labs/Labs' import NotesTab from '../../../patients/notes/NoteTab' -import * as patientSlice from '../../../patients/patient-slice' import RelatedPersonTab from '../../../patients/related-persons/RelatedPersonTab' import ViewPatient from '../../../patients/view/ViewPatient' import PatientRepository from '../../../shared/db/PatientRepository' @@ -93,8 +92,6 @@ describe('ViewPatient', () => { await setup() expect(PatientRepository.find).toHaveBeenCalledWith(patient.id) - expect(store.getActions()).toContainEqual(patientSlice.fetchPatientStart()) - expect(store.getActions()).toContainEqual(patientSlice.fetchPatientSuccess(patient)) }) it('should have called useUpdateTitle hook', async () => { diff --git a/src/patients/appointments/AppointmentsList.tsx b/src/patients/appointments/AppointmentsList.tsx index 5536c3449c..4bf4855c89 100644 --- a/src/patients/appointments/AppointmentsList.tsx +++ b/src/patients/appointments/AppointmentsList.tsx @@ -12,12 +12,10 @@ interface Props { patientId: string } -const AppointmentsList = (props: Props) => { +const AppointmentsList = ({ patientId }: Props) => { const history = useHistory() const { t } = useTranslator() - const { patientId } = props - const { data, status } = usePatientsAppointments(patientId) const breadcrumbs = [ diff --git a/src/patients/edit/EditPatient.tsx b/src/patients/edit/EditPatient.tsx index 933f9fd0fc..520561e356 100644 --- a/src/patients/edit/EditPatient.tsx +++ b/src/patients/edit/EditPatient.tsx @@ -9,52 +9,39 @@ import useTranslator from '../../shared/hooks/useTranslator' import Patient from '../../shared/model/Patient' import { RootState } from '../../shared/store' import GeneralInformation from '../GeneralInformation' -import { updatePatient, fetchPatient } from '../patient-slice' -import { getPatientFullName } from '../util/patient-name-util' - -const getPatientCode = (p: Patient): string => { - if (p) { - return p.code - } - - return '' -} +import usePatient from '../hooks/usePatient' +import { updatePatient } from '../patient-slice' +import { getPatientCode, getPatientFullName } from '../util/patient-util' const EditPatient = () => { const { t } = useTranslator() const history = useHistory() const dispatch = useDispatch() + const { id } = useParams() + const { data: givenPatient, status } = usePatient(id) const [patient, setPatient] = useState({} as Patient) - const { patient: reduxPatient, status, updateError } = useSelector( - (state: RootState) => state.patient, - ) + const { updateError } = useSelector((state: RootState) => state.patient) const updateTitle = useUpdateTitle() + updateTitle( - `${t('patients.editPatient')}: ${getPatientFullName(reduxPatient)} (${getPatientCode( - reduxPatient, + `${t('patients.editPatient')}: ${getPatientFullName(givenPatient)} (${getPatientCode( + givenPatient, )})`, ) const breadcrumbs = [ { i18nKey: 'patients.label', location: '/patients' }, - { text: getPatientFullName(reduxPatient), location: `/patients/${reduxPatient.id}` }, - { i18nKey: 'patients.editPatient', location: `/patients/${reduxPatient.id}/edit` }, + { text: getPatientFullName(givenPatient), location: `/patients/${id}` }, + { i18nKey: 'patients.editPatient', location: `/patients/${id}/edit` }, ] useAddBreadcrumbs(breadcrumbs, true) useEffect(() => { - setPatient(reduxPatient) - }, [reduxPatient]) - - const { id } = useParams() - useEffect(() => { - if (id) { - dispatch(fetchPatient(id)) - } - }, [id, dispatch]) + setPatient(givenPatient || ({} as Patient)) + }, [givenPatient]) const onCancel = () => { history.push(`/patients/${patient.id}`) diff --git a/src/patients/patient-slice.ts b/src/patients/patient-slice.ts index 3119407c02..608bed9a44 100644 --- a/src/patients/patient-slice.ts +++ b/src/patients/patient-slice.ts @@ -72,11 +72,6 @@ const patientSlice = createSlice({ name: 'patient', initialState, reducers: { - fetchPatientStart: start, - fetchPatientSuccess(state, { payload }: PayloadAction) { - state.status = 'completed' - state.patient = payload - }, createPatientStart: start, createPatientSuccess(state) { state.status = 'completed' @@ -102,8 +97,6 @@ const patientSlice = createSlice({ }) export const { - fetchPatientStart, - fetchPatientSuccess, createPatientStart, createPatientSuccess, createPatientError, @@ -113,12 +106,6 @@ export const { addDiagnosisError, } = patientSlice.actions -export const fetchPatient = (id: string): AppThunk => async (dispatch) => { - dispatch(fetchPatientStart()) - const patient = await PatientRepository.find(id) - dispatch(fetchPatientSuccess(patient)) -} - function validatePatient(patient: Patient) { const error: Error = {} diff --git a/src/patients/util/patient-name-util.ts b/src/patients/util/patient-util.ts similarity index 77% rename from src/patients/util/patient-name-util.ts rename to src/patients/util/patient-util.ts index e4dee31524..38b93d386b 100644 --- a/src/patients/util/patient-name-util.ts +++ b/src/patients/util/patient-util.ts @@ -19,7 +19,15 @@ export function getPatientName(givenName?: string, familyName?: string, suffix?: return name.trim() } -export function getPatientFullName(patient: Patient): string { +export const getPatientCode = (p: Patient | undefined): string => { + if (p) { + return p.code + } + + return '' +} + +export function getPatientFullName(patient: Patient | undefined): string { if (!patient) { return '' } diff --git a/src/patients/util/set-patient-helper.ts b/src/patients/util/set-patient-helper.ts index 87af353bd8..1ee03e9440 100644 --- a/src/patients/util/set-patient-helper.ts +++ b/src/patients/util/set-patient-helper.ts @@ -1,5 +1,5 @@ import Patient from '../../shared/model/Patient' -import { getPatientName } from './patient-name-util' +import { getPatientName } from './patient-util' /** * Add full name. Get rid of empty phone numbers, emails, and addresses. diff --git a/src/patients/view/ViewPatient.tsx b/src/patients/view/ViewPatient.tsx index 11c7f72ef0..3b622bd7dd 100644 --- a/src/patients/view/ViewPatient.tsx +++ b/src/patients/view/ViewPatient.tsx @@ -1,6 +1,6 @@ import { Panel, Spinner, TabsHeader, Tab, Button } from '@hospitalrun/components' import React, { useEffect } from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { useSelector } from 'react-redux' import { useParams, withRouter, @@ -14,7 +14,6 @@ import useAddBreadcrumbs from '../../page-header/breadcrumbs/useAddBreadcrumbs' import { useButtonToolbarSetter } from '../../page-header/button-toolbar/ButtonBarProvider' import { useUpdateTitle } from '../../page-header/title/TitleContext' import useTranslator from '../../shared/hooks/useTranslator' -import Patient from '../../shared/model/Patient' import Permissions from '../../shared/model/Permissions' import { RootState } from '../../shared/store' import Allergies from '../allergies/Allergies' @@ -23,48 +22,34 @@ import CareGoalTab from '../care-goals/CareGoalTab' import CarePlanTab from '../care-plans/CarePlanTab' import Diagnoses from '../diagnoses/Diagnoses' import GeneralInformation from '../GeneralInformation' +import usePatient from '../hooks/usePatient' import Labs from '../labs/Labs' import Note from '../notes/NoteTab' -import { fetchPatient } from '../patient-slice' import RelatedPerson from '../related-persons/RelatedPersonTab' -import { getPatientFullName } from '../util/patient-name-util' +import { getPatientCode, getPatientFullName } from '../util/patient-util' import VisitTab from '../visits/VisitTab' -const getPatientCode = (p: Patient): string => { - if (p) { - return p.code - } - - return '' -} - const ViewPatient = () => { const { t } = useTranslator() const history = useHistory() - const dispatch = useDispatch() const location = useLocation() const { path } = useRouteMatch() + const setButtonToolBar = useButtonToolbarSetter() - const { patient, status } = useSelector((state: RootState) => state.patient) + const { id } = useParams() const { permissions } = useSelector((state: RootState) => state.user) + const { data: patient, status } = usePatient(id) const updateTitle = useUpdateTitle() updateTitle(`${getPatientFullName(patient)} (${getPatientCode(patient)})`) - const setButtonToolBar = useButtonToolbarSetter() - const breadcrumbs = [ { i18nKey: 'patients.label', location: '/patients' }, - { text: getPatientFullName(patient), location: `/patients/${patient.id}` }, + { text: getPatientFullName(patient), location: `/patients/${id}` }, ] useAddBreadcrumbs(breadcrumbs, true) - const { id } = useParams() useEffect(() => { - if (id) { - dispatch(fetchPatient(id)) - } - const buttons = [] if (permissions.includes(Permissions.WritePatients)) { buttons.push( @@ -74,7 +59,7 @@ const ViewPatient = () => { icon="edit" outlined onClick={() => { - history.push(`/patients/edit/${patient.id}`) + history.push(`/patients/edit/${id}`) }} > {t('actions.edit')} @@ -87,7 +72,7 @@ const ViewPatient = () => { return () => { setButtonToolBar([]) } - }, [dispatch, id, setButtonToolBar, history, patient.id, permissions, t]) + }, [setButtonToolBar, history, id, permissions, t]) if (status === 'loading' || !patient) { return From 6647e379f2f664cd354ebff98615f85631c03271 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 26 Sep 2020 01:17:51 +0000 Subject: [PATCH 2/4] build(deps-dev): bump eslint-config-prettier from 6.11.0 to 6.12.0 (#2416) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 83ae8ec69c..fab897a905 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "enzyme-adapter-react-16": "~1.15.2", "eslint": "~6.8.0", "eslint-config-airbnb": "~18.2.0", - "eslint-config-prettier": "~6.11.0", + "eslint-config-prettier": "~6.12.0", "eslint-import-resolver-typescript": "~2.3.0", "eslint-plugin-import": "~2.22.0", "eslint-plugin-jest": "~24.0.0", From c1b850ed2109cc753548dda7a837b3de772f38f9 Mon Sep 17 00:00:00 2001 From: Rafael Sousa Date: Sat, 26 Sep 2020 01:56:16 -0300 Subject: [PATCH 3/4] refactor(labs): refactor labs module to use react query (#2410) --- src/__tests__/labs/Labs.test.tsx | 51 +-- src/__tests__/labs/ViewLab.test.tsx | 92 ++-- src/__tests__/labs/ViewLabs.test.tsx | 175 ++++---- src/__tests__/labs/hooks/useCancelLab.test.ts | 33 ++ .../labs/hooks/useCompleteLab.test.ts | 58 +++ src/__tests__/labs/hooks/useLab.test.ts | 29 ++ .../labs/hooks/useLabsSearch.test.ts | 65 +++ .../labs/hooks/useRequestLab.test.ts | 59 +++ src/__tests__/labs/hooks/useUpdateLab.test.ts | 27 ++ src/__tests__/labs/lab-slice.test.ts | 406 ------------------ src/__tests__/labs/labs.slice.test.ts | 140 ------ .../labs/requests/NewLabRequest.test.tsx | 67 +-- src/__tests__/labs/utils/validate-lab.test.ts | 60 +++ .../patients/care-goals/CareGoalTab.test.tsx | 6 +- .../patients/care-plans/CarePlanTab.test.tsx | 6 +- .../patients/view/ViewPatient.test.tsx | 11 +- src/labs/ViewLab.tsx | 75 ++-- src/labs/ViewLabs.tsx | 21 +- src/labs/hooks/useCancelLab.ts | 18 + src/labs/hooks/useCompleteLab.ts | 32 ++ src/labs/hooks/useLab.ts | 12 + src/labs/hooks/useLabsSearch.ts | 30 ++ src/labs/hooks/useRequestLab.ts | 26 ++ src/labs/hooks/useUpdateLab.ts | 17 + src/labs/lab-slice.ts | 193 --------- src/labs/labs-slice.ts | 66 --- src/labs/model/LabSearchRequest.ts | 6 + src/labs/requests/NewLabRequest.tsx | 43 +- src/labs/utils/validate-lab.ts | 49 +++ src/patients/hooks/usePatient.tsx | 8 +- .../locales/enUs/translations/labs/index.ts | 1 + src/shared/store/index.ts | 4 - 32 files changed, 821 insertions(+), 1065 deletions(-) create mode 100644 src/__tests__/labs/hooks/useCancelLab.test.ts create mode 100644 src/__tests__/labs/hooks/useCompleteLab.test.ts create mode 100644 src/__tests__/labs/hooks/useLab.test.ts create mode 100644 src/__tests__/labs/hooks/useLabsSearch.test.ts create mode 100644 src/__tests__/labs/hooks/useRequestLab.test.ts create mode 100644 src/__tests__/labs/hooks/useUpdateLab.test.ts delete mode 100644 src/__tests__/labs/lab-slice.test.ts delete mode 100644 src/__tests__/labs/labs.slice.test.ts create mode 100644 src/__tests__/labs/utils/validate-lab.test.ts create mode 100644 src/labs/hooks/useCancelLab.ts create mode 100644 src/labs/hooks/useCompleteLab.ts create mode 100644 src/labs/hooks/useLab.ts create mode 100644 src/labs/hooks/useLabsSearch.ts create mode 100644 src/labs/hooks/useRequestLab.ts create mode 100644 src/labs/hooks/useUpdateLab.ts delete mode 100644 src/labs/lab-slice.ts delete mode 100644 src/labs/labs-slice.ts create mode 100644 src/labs/model/LabSearchRequest.ts create mode 100644 src/labs/utils/validate-lab.ts diff --git a/src/__tests__/labs/Labs.test.tsx b/src/__tests__/labs/Labs.test.tsx index e8254db294..19077a05a5 100644 --- a/src/__tests__/labs/Labs.test.tsx +++ b/src/__tests__/labs/Labs.test.tsx @@ -1,3 +1,4 @@ +import { act } from '@testing-library/react' import { mount, ReactWrapper } from 'enzyme' import React from 'react' import { Provider } from 'react-redux' @@ -28,46 +29,39 @@ describe('Labs', () => { jest .spyOn(PatientRepository, 'find') .mockResolvedValue({ id: '12345', fullName: 'test test' } as Patient) - const setup = (route: string, permissions: Permissions[] = []) => { + + const setup = async (initialEntry: string, permissions: Permissions[] = []) => { const store = mockStore({ user: { permissions }, - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - lab: { - lab: ({ - id: '1234', - patientId: 'patientId', - requestedOn: new Date().toISOString(), - } as unknown) as Lab, - patient: { id: 'patientId', fullName: 'some name' }, - error: {}, - }, } as any) - const wrapper = mount( - - - - - - - , - ) + let wrapper: any + await act(async () => { + wrapper = await mount( + + + + + + + , + ) + }) + wrapper.update() return { wrapper: wrapper as ReactWrapper } } describe('routing', () => { describe('/labs/new', () => { - it('should render the new lab request screen when /labs/new is accessed', () => { - const permissions: Permissions[] = [Permissions.RequestLab] - const { wrapper } = setup('/labs/new', permissions) + it('should render the new lab request screen when /labs/new is accessed', async () => { + const { wrapper } = await setup('/labs/new', [Permissions.RequestLab]) expect(wrapper.find(NewLabRequest)).toHaveLength(1) }) - it('should not navigate to /labs/new if the user does not have RequestLab permissions', () => { - const { wrapper } = setup('/labs/new') + it('should not navigate to /labs/new if the user does not have RequestLab permissions', async () => { + const { wrapper } = await setup('/labs/new') expect(wrapper.find(NewLabRequest)).toHaveLength(0) }) @@ -75,15 +69,14 @@ describe('Labs', () => { describe('/labs/:id', () => { it('should render the view lab screen when /labs/:id is accessed', async () => { - const permissions: Permissions[] = [Permissions.ViewLab] - const { wrapper } = setup('/labs/1234', permissions) + const { wrapper } = await setup('/labs/1234', [Permissions.ViewLab]) expect(wrapper.find(ViewLab)).toHaveLength(1) }) }) it('should not navigate to /labs/:id if the user does not have ViewLab permissions', async () => { - const { wrapper } = setup('/labs/1234') + const { wrapper } = await setup('/labs/1234') expect(wrapper.find(ViewLab)).toHaveLength(0) }) diff --git a/src/__tests__/labs/ViewLab.test.tsx b/src/__tests__/labs/ViewLab.test.tsx index baa9019ac5..42b9b1be3b 100644 --- a/src/__tests__/labs/ViewLab.test.tsx +++ b/src/__tests__/labs/ViewLab.test.tsx @@ -9,6 +9,8 @@ import { Router, Route } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' +import * as validateUtil from '../../labs/utils/validate-lab' +import { LabError } from '../../labs/utils/validate-lab' import ViewLab from '../../labs/ViewLab' import * as ButtonBarProvider from '../../page-header/button-toolbar/ButtonBarProvider' import * as titleUtil from '../../page-header/title/TitleContext' @@ -38,15 +40,16 @@ describe('View Lab', () => { let setButtonToolBarSpy: any let labRepositorySaveSpy: any const expectedDate = new Date() + const setup = async (lab: Lab, permissions: Permissions[], error = {}) => { jest.resetAllMocks() Date.now = jest.fn(() => expectedDate.valueOf()) setButtonToolBarSpy = jest.fn() jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) - jest.spyOn(LabRepository, 'find').mockResolvedValue(lab) labRepositorySaveSpy = jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(mockLab) jest.spyOn(PatientRepository, 'find').mockResolvedValue(mockPatient as Patient) + jest.spyOn(LabRepository, 'find').mockResolvedValue(lab) history = createMemoryHistory() history.push(`labs/${lab.id}`) @@ -80,7 +83,7 @@ describe('View Lab', () => { }) wrapper.find(ViewLab).props().updateTitle = jest.fn() wrapper.update() - return wrapper + return { wrapper: wrapper as ReactWrapper } } describe('title', () => { @@ -94,8 +97,9 @@ describe('View Lab', () => { describe('page content', () => { it('should display the patient full name for the for', async () => { const expectedLab = { ...mockLab } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const forPatientDiv = wrapper.find('.for-patient') + expect(forPatientDiv.find('h4').text().trim()).toEqual('labs.lab.for') expect(forPatientDiv.find('h5').text().trim()).toEqual(mockPatient.fullName) @@ -103,7 +107,7 @@ describe('View Lab', () => { it('should display the lab type for type', async () => { const expectedLab = { ...mockLab, type: 'expected type' } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const labTypeDiv = wrapper.find('.lab-type') expect(labTypeDiv.find('h4').text().trim()).toEqual('labs.lab.type') @@ -112,7 +116,7 @@ describe('View Lab', () => { it('should display the requested on date', async () => { const expectedLab = { ...mockLab, requestedOn: '2020-03-30T04:43:20.102Z' } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const requestedOnDiv = wrapper.find('.requested-on') expect(requestedOnDiv.find('h4').text().trim()).toEqual('labs.lab.requestedOn') @@ -123,7 +127,7 @@ describe('View Lab', () => { it('should not display the completed date if the lab is not completed', async () => { const expectedLab = { ...mockLab } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const completedOnDiv = wrapper.find('.completed-on') expect(completedOnDiv).toHaveLength(0) @@ -131,7 +135,7 @@ describe('View Lab', () => { it('should not display the canceled date if the lab is not canceled', async () => { const expectedLab = { ...mockLab } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const completedOnDiv = wrapper.find('.canceled-on') expect(completedOnDiv).toHaveLength(0) @@ -142,7 +146,7 @@ describe('View Lab', () => { ...mockLab, result: 'expected results', } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) @@ -154,9 +158,9 @@ describe('View Lab', () => { it('should display the past notes', async () => { const expectedNotes = 'expected notes' const expectedLab = { ...mockLab, notes: [expectedNotes] } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - const notes = wrapper.find('[data-test="note"]') + const notes = wrapper.findWhere((w) => w.prop('data-test') === 'note') const pastNotesIndex = notes.reduce( (result: number, item: ReactWrapper, index: number) => item.text().trim() === expectedNotes ? index : result, @@ -164,13 +168,22 @@ describe('View Lab', () => { ) expect(pastNotesIndex).not.toBe(-1) - expect(notes.length).toBe(1) + expect(notes).toHaveLength(1) + }) + + it('should not display past notes if there is not', async () => { + const expectedLab = { ...mockLab, notes: undefined } as Lab + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) + + const notes = wrapper.findWhere((w) => w.prop('data-test') === 'note') + + expect(notes).toHaveLength(0) }) it('should display the notes text field empty', async () => { const expectedNotes = 'expected notes' const expectedLab = { ...mockLab, notes: [expectedNotes] } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const notesTextField = wrapper.find(TextFieldWithLabelFormGroup).at(1) @@ -180,8 +193,17 @@ describe('View Lab', () => { it('should display errors', async () => { const expectedLab = { ...mockLab, status: 'requested' } as Lab - const expectedError = { message: 'some message', result: 'some result feedback' } - const wrapper = await setup(expectedLab, [Permissions.ViewLab], expectedError) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab, Permissions.CompleteLab]) + + const expectedError = { message: 'some message', result: 'some result feedback' } as LabError + jest.spyOn(validateUtil, 'validateLabComplete').mockReturnValue(expectedError) + + const completeButton = wrapper.find(Button).at(1) + await act(async () => { + const onClick = completeButton.prop('onClick') as any + await onClick() + }) + wrapper.update() const alert = wrapper.find(Alert) const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) @@ -195,7 +217,7 @@ describe('View Lab', () => { describe('requested lab request', () => { it('should display a warning badge if the status is requested', async () => { const expectedLab = { ...mockLab, status: 'requested' } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const labStatusDiv = wrapper.find('.lab-status') const badge = labStatusDiv.find(Badge) expect(labStatusDiv.find('h4').text().trim()).toEqual('labs.lab.status') @@ -205,7 +227,7 @@ describe('View Lab', () => { }) it('should display a update lab, complete lab, and cancel lab button if the lab is in a requested state', async () => { - const wrapper = await setup(mockLab, [ + const { wrapper } = await setup(mockLab, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, @@ -223,7 +245,7 @@ describe('View Lab', () => { describe('canceled lab request', () => { it('should display a danger badge if the status is canceled', async () => { const expectedLab = { ...mockLab, status: 'canceled' } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const labStatusDiv = wrapper.find('.lab-status') const badge = labStatusDiv.find(Badge) @@ -239,7 +261,7 @@ describe('View Lab', () => { status: 'canceled', canceledOn: '2020-03-30T04:45:20.102Z', } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const canceledOnDiv = wrapper.find('.canceled-on') expect(canceledOnDiv.find('h4').text().trim()).toEqual('labs.lab.canceledOn') @@ -252,7 +274,7 @@ describe('View Lab', () => { it('should not display update, complete, and cancel button if the lab is canceled', async () => { const expectedLab = { ...mockLab, status: 'canceled' } as Lab - const wrapper = await setup(expectedLab, [ + const { wrapper } = await setup(expectedLab, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, @@ -264,7 +286,7 @@ describe('View Lab', () => { it('should not display an update button if the lab is canceled', async () => { const expectedLab = { ...mockLab, status: 'canceled' } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const updateButton = wrapper.find(Button) expect(updateButton).toHaveLength(0) @@ -273,7 +295,7 @@ describe('View Lab', () => { it('should not display notes text field if the status is canceled', async () => { const expectedLab = { ...mockLab, status: 'canceled' } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const textsField = wrapper.find(TextFieldWithLabelFormGroup) const notesTextField = wrapper.find('notesTextField') @@ -287,7 +309,7 @@ describe('View Lab', () => { it('should display a primary badge if the status is completed', async () => { jest.resetAllMocks() const expectedLab = { ...mockLab, status: 'completed' } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const labStatusDiv = wrapper.find('.lab-status') const badge = labStatusDiv.find(Badge) expect(labStatusDiv.find('h4').text().trim()).toEqual('labs.lab.status') @@ -302,7 +324,7 @@ describe('View Lab', () => { status: 'completed', completedOn: '2020-03-30T04:44:20.102Z', } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const completedOnDiv = wrapper.find('.completed-on') expect(completedOnDiv.find('h4').text().trim()).toEqual('labs.lab.completedOn') @@ -315,7 +337,7 @@ describe('View Lab', () => { it('should not display update, complete, and cancel buttons if the lab is completed', async () => { const expectedLab = { ...mockLab, status: 'completed' } as Lab - const wrapper = await setup(expectedLab, [ + const { wrapper } = await setup(expectedLab, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, @@ -328,7 +350,7 @@ describe('View Lab', () => { it('should not display notes text field if the status is completed', async () => { const expectedLab = { ...mockLab, status: 'completed' } as Lab - const wrapper = await setup(expectedLab, [ + const { wrapper } = await setup(expectedLab, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, @@ -345,26 +367,26 @@ describe('View Lab', () => { describe('on update', () => { it('should update the lab with the new information', async () => { - const wrapper = await setup(mockLab, [Permissions.ViewLab]) + const { wrapper } = await setup(mockLab, [Permissions.ViewLab]) const expectedResult = 'expected result' const newNotes = 'expected notes' const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) act(() => { - const onChange = resultTextField.prop('onChange') + const onChange = resultTextField.prop('onChange') as any onChange({ currentTarget: { value: expectedResult } }) }) wrapper.update() const notesTextField = wrapper.find(TextFieldWithLabelFormGroup).at(1) act(() => { - const onChange = notesTextField.prop('onChange') + const onChange = notesTextField.prop('onChange') as any onChange({ currentTarget: { value: newNotes } }) }) wrapper.update() const updateButton = wrapper.find(Button) await act(async () => { - const onClick = updateButton.prop('onClick') + const onClick = updateButton.prop('onClick') as any onClick() }) @@ -380,7 +402,7 @@ describe('View Lab', () => { describe('on complete', () => { it('should mark the status as completed and fill in the completed date with the current time', async () => { - const wrapper = await setup(mockLab, [ + const { wrapper } = await setup(mockLab, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, @@ -389,14 +411,14 @@ describe('View Lab', () => { const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) await act(async () => { - const onChange = resultTextField.prop('onChange') + const onChange = resultTextField.prop('onChange') as any await onChange({ currentTarget: { value: expectedResult } }) }) wrapper.update() const completeButton = wrapper.find(Button).at(1) await act(async () => { - const onClick = completeButton.prop('onClick') + const onClick = completeButton.prop('onClick') as any await onClick() }) wrapper.update() @@ -416,7 +438,7 @@ describe('View Lab', () => { describe('on cancel', () => { it('should mark the status as canceled and fill in the cancelled on date with the current time', async () => { - const wrapper = await setup(mockLab, [ + const { wrapper } = await setup(mockLab, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, @@ -425,14 +447,14 @@ describe('View Lab', () => { const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) await act(async () => { - const onChange = resultTextField.prop('onChange') + const onChange = resultTextField.prop('onChange') as any await onChange({ currentTarget: { value: expectedResult } }) }) wrapper.update() const cancelButton = wrapper.find(Button).at(2) await act(async () => { - const onClick = cancelButton.prop('onClick') + const onClick = cancelButton.prop('onClick') as any await onClick() }) wrapper.update() diff --git a/src/__tests__/labs/ViewLabs.test.tsx b/src/__tests__/labs/ViewLabs.test.tsx index 9a14691b4b..483f111d52 100644 --- a/src/__tests__/labs/ViewLabs.test.tsx +++ b/src/__tests__/labs/ViewLabs.test.tsx @@ -1,4 +1,4 @@ -import { Select, Table } from '@hospitalrun/components' +import { Select, Table, TextInput } from '@hospitalrun/components' import { act } from '@testing-library/react' import { mount, ReactWrapper } from 'enzyme' import { createMemoryHistory } from 'history' @@ -8,7 +8,6 @@ import { Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' -import * as labsSlice from '../../labs/labs-slice' import ViewLabs from '../../labs/ViewLabs' import * as ButtonBarProvider from '../../page-header/button-toolbar/ButtonBarProvider' import * as titleUtil from '../../page-header/title/TitleContext' @@ -19,65 +18,63 @@ import { RootState } from '../../shared/store' const mockStore = createMockStore([thunk]) -let history: any -const expectedLab = { - code: 'L-1234', - id: '1234', - type: 'lab type', - patient: 'patientId', - status: 'requested', - requestedOn: new Date().toISOString(), -} as Lab - -const setup = (permissions: Permissions[] = [Permissions.ViewLabs, Permissions.RequestLab]) => { - const store = mockStore({ - user: { permissions }, - labs: { labs: [expectedLab] }, - } as any) - history = createMemoryHistory() - - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(LabRepository, 'findAll').mockResolvedValue([expectedLab]) - - const wrapper = mount( - - - - - - - , - ) - - wrapper.find(ViewLabs).props().updateTitle = jest.fn() - wrapper.update() - return { wrapper: wrapper as ReactWrapper } -} - -describe('title', () => { - it('should have called the useUpdateTitle hook', async () => { - setup() - expect(titleUtil.useUpdateTitle).toHaveBeenCalled() +describe('View Labs', () => { + let history: any + const setButtonToolBarSpy = jest.fn() + jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) + + const setup = async (permissions: Permissions[] = []) => { + history = createMemoryHistory() + jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) + + const store = mockStore({ + title: '', + user: { + permissions, + }, + } as any) + + let wrapper: any + await act(async () => { + wrapper = await mount( + + + + + + + + + , + ) + }) + + wrapper.find(ViewLabs).props().updateTitle = jest.fn() + wrapper.update() + return { wrapper: wrapper as ReactWrapper } + } + + describe('title', () => { + it('should have called the useUpdateTitle hook', async () => { + await setup() + expect(titleUtil.useUpdateTitle).toHaveBeenCalled() + }) }) -}) -describe('View Labs', () => { describe('button bar', () => { + beforeEach(() => { + setButtonToolBarSpy.mockReset() + }) + it('should display button to add new lab request', async () => { - const setButtonToolBarSpy = jest.fn() - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) - jest.spyOn(LabRepository, 'findAll').mockResolvedValue([]) - setup() + await setup([Permissions.ViewLab, Permissions.RequestLab]) const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] expect((actualButtons[0] as any).props.children).toEqual('labs.requests.new') }) it('should not display button to add new lab request if the user does not have permissions', async () => { - const setButtonToolBarSpy = jest.fn() - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) - jest.spyOn(LabRepository, 'findAll').mockResolvedValue([]) - setup([]) + await setup([Permissions.ViewLabs]) const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] expect(actualButtons).toEqual([]) @@ -85,8 +82,20 @@ describe('View Labs', () => { }) describe('table', () => { - it('should render a table with data', () => { - const { wrapper } = setup() + const expectedLab = { + code: 'L-1234', + id: '1234', + type: 'lab type', + patient: 'patientId', + status: 'requested', + requestedOn: '2020-03-30T04:43:20.102Z', + } as Lab + + jest.spyOn(LabRepository, 'findAll').mockResolvedValue([expectedLab]) + + it('should render a table with data', async () => { + const { wrapper } = await setup([Permissions.ViewLabs, Permissions.RequestLab]) + const table = wrapper.find(Table) const columns = table.prop('columns') const actions = table.prop('actions') as any @@ -104,8 +113,8 @@ describe('View Labs', () => { expect(table.prop('data')).toEqual([expectedLab]) }) - it('should navigate to the lab when the view button is clicked', () => { - const { wrapper } = setup() + it('should navigate to the lab when the view button is clicked', async () => { + const { wrapper } = await setup([Permissions.ViewLabs, Permissions.RequestLab]) const tr = wrapper.find('tr').at(1) act(() => { @@ -117,55 +126,59 @@ describe('View Labs', () => { }) describe('dropdown', () => { - it('should search for labs when dropdown changes', () => { - const searchLabsSpy = jest.spyOn(labsSlice, 'searchLabs') - const { wrapper } = setup() + const searchLabsSpy = jest.spyOn(LabRepository, 'search') + beforeEach(() => { searchLabsSpy.mockClear() + }) - act(() => { + it('should search for labs when dropdown changes', async () => { + const expectedStatus = 'requested' + const { wrapper } = await setup([Permissions.ViewLabs]) + + await act(async () => { const onChange = wrapper.find(Select).prop('onChange') as any - onChange({ - target: { - value: 'requested', - }, - preventDefault: jest.fn(), - }) + await onChange([expectedStatus]) }) - wrapper.update() expect(searchLabsSpy).toHaveBeenCalledTimes(1) + expect(searchLabsSpy).toHaveBeenCalledWith( + expect.objectContaining({ status: expectedStatus }), + ) }) }) -}) - -describe('search functionality', () => { - beforeEach(() => jest.useFakeTimers()) - - afterEach(() => jest.useRealTimers()) - it('should search for labs after the search text has not changed for 500 milliseconds', async () => { - const searchLabsSpy = jest.spyOn(labsSlice, 'searchLabs') + describe('search functionality', () => { + const searchLabsSpy = jest.spyOn(LabRepository, 'search') - searchLabsSpy.mockClear() + beforeEach(() => { + searchLabsSpy.mockClear() + }) - beforeEach(async () => { - const { wrapper } = setup() + it('should search for labs after the search text has not changed for 500 milliseconds', async () => { + jest.useFakeTimers() + const { wrapper } = await setup([Permissions.ViewLabs]) - searchLabsSpy.mockClear() + const expectedSearchText = 'search text' act(() => { - const onChange = wrapper.find(Select).prop('onChange') as any + const onChange = wrapper.find(TextInput).prop('onChange') as any onChange({ target: { - value: 'requested', + value: expectedSearchText, }, preventDefault: jest.fn(), }) }) - wrapper.update() + act(() => { + jest.advanceTimersByTime(500) + }) + expect(searchLabsSpy).toHaveBeenCalledTimes(1) + expect(searchLabsSpy).toHaveBeenCalledWith( + expect.objectContaining({ text: expectedSearchText }), + ) }) }) }) diff --git a/src/__tests__/labs/hooks/useCancelLab.test.ts b/src/__tests__/labs/hooks/useCancelLab.test.ts new file mode 100644 index 0000000000..e73e9750b6 --- /dev/null +++ b/src/__tests__/labs/hooks/useCancelLab.test.ts @@ -0,0 +1,33 @@ +import { act } from '@testing-library/react-hooks' + +import useCancelLab from '../../../labs/hooks/useCancelLab' +import LabRepository from '../../../shared/db/LabRepository' +import Lab from '../../../shared/model/Lab' +import executeMutation from '../../test-utils/use-mutation.util' + +describe('Use Cancel Lab', () => { + const expectedCanceledOnDate = new Date() + const lab = { + id: 'id lab', + status: 'requested', + } as Lab + const expectedCanceledLab = { + ...lab, + status: 'canceled', + canceledOn: expectedCanceledOnDate.toISOString(), + } as Lab + + Date.now = jest.fn(() => expectedCanceledOnDate.valueOf()) + jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(expectedCanceledLab) + + it('should cancel a lab', async () => { + let actualData: any + await act(async () => { + actualData = await executeMutation(() => useCancelLab(), lab) + }) + + expect(LabRepository.saveOrUpdate).toHaveBeenCalledTimes(1) + expect(LabRepository.saveOrUpdate).toHaveBeenCalledWith(lab) + expect(actualData).toEqual(expectedCanceledLab) + }) +}) diff --git a/src/__tests__/labs/hooks/useCompleteLab.test.ts b/src/__tests__/labs/hooks/useCompleteLab.test.ts new file mode 100644 index 0000000000..b275422f04 --- /dev/null +++ b/src/__tests__/labs/hooks/useCompleteLab.test.ts @@ -0,0 +1,58 @@ +import { act } from '@testing-library/react-hooks' + +import useCompleteLab from '../../../labs/hooks/useCompleteLab' +import { LabError } from '../../../labs/utils/validate-lab' +import * as validateLabUtils from '../../../labs/utils/validate-lab' +import LabRepository from '../../../shared/db/LabRepository' +import Lab from '../../../shared/model/Lab' +import executeMutation from '../../test-utils/use-mutation.util' + +describe('Use Complete lab', () => { + const expectedCompletedOnDate = new Date() + const lab = { + type: 'test', + result: 'some result', + } as Lab + const expectedCompletedLab = { + ...lab, + completedOn: expectedCompletedOnDate.toISOString(), + status: 'completed', + } as Lab + + Date.now = jest.fn(() => expectedCompletedOnDate.valueOf()) + jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(expectedCompletedLab) + + beforeEach(() => { + jest.clearAllMocks() + }) + + it('should save lab as complete', async () => { + let actualData: any + await act(async () => { + actualData = await executeMutation(() => useCompleteLab(), lab) + }) + + expect(LabRepository.saveOrUpdate).toHaveBeenCalledTimes(1) + expect(LabRepository.saveOrUpdate).toHaveBeenCalledWith(expectedCompletedLab) + expect(actualData).toEqual(expectedCompletedLab) + }) + + it('should throw errors', async () => { + expect.hasAssertions() + + const expectedLabError = { + result: 'some result error message', + } as LabError + + jest.spyOn(validateLabUtils, 'validateLabComplete').mockReturnValue(expectedLabError) + + await act(async () => { + try { + await executeMutation(() => useCompleteLab(), lab) + } catch (e) { + expect(e).toEqual(expectedLabError) + expect(LabRepository.saveOrUpdate).not.toHaveBeenCalled() + } + }) + }) +}) diff --git a/src/__tests__/labs/hooks/useLab.test.ts b/src/__tests__/labs/hooks/useLab.test.ts new file mode 100644 index 0000000000..cd98fa268d --- /dev/null +++ b/src/__tests__/labs/hooks/useLab.test.ts @@ -0,0 +1,29 @@ +import { act, renderHook } from '@testing-library/react-hooks' + +import useLab from '../../../labs/hooks/useLab' +import LabRepository from '../../../shared/db/LabRepository' +import Lab from '../../../shared/model/Lab' +import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util' + +describe('Use lab', () => { + const expectedLabId = 'lab id' + const expectedLab = { + id: expectedLabId, + } as Lab + + jest.spyOn(LabRepository, 'find').mockResolvedValue(expectedLab) + + it('should get a lab by id', async () => { + let actualData: any + await act(async () => { + const renderHookResult = renderHook(() => useLab(expectedLabId)) + const { result } = renderHookResult + await waitUntilQueryIsSuccessful(renderHookResult) + actualData = result.current.data + }) + + expect(LabRepository.find).toHaveBeenCalledTimes(1) + expect(LabRepository.find).toHaveBeenCalledWith(expectedLabId) + expect(actualData).toEqual(expectedLab) + }) +}) diff --git a/src/__tests__/labs/hooks/useLabsSearch.test.ts b/src/__tests__/labs/hooks/useLabsSearch.test.ts new file mode 100644 index 0000000000..c0bff21528 --- /dev/null +++ b/src/__tests__/labs/hooks/useLabsSearch.test.ts @@ -0,0 +1,65 @@ +import { act, renderHook } from '@testing-library/react-hooks' + +import useLabsSearch from '../../../labs/hooks/useLabsSearch' +import LabSearchRequest from '../../../labs/model/LabSearchRequest' +import LabRepository from '../../../shared/db/LabRepository' +import Lab from '../../../shared/model/Lab' +import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util' + +describe('Use Labs Search', () => { + const expectedLabs = [ + { + id: 'lab id', + }, + ] as Lab[] + + const labRepositoryFindAllSpy = jest + .spyOn(LabRepository, 'findAll') + .mockResolvedValue(expectedLabs) + const labRepositorySearchSpy = jest.spyOn(LabRepository, 'search').mockResolvedValue(expectedLabs) + + beforeEach(() => { + labRepositoryFindAllSpy.mockClear() + }) + + it('should return all labs', async () => { + const expectedLabsSearchRequest = { + text: '', + status: 'all', + } as LabSearchRequest + + let actualData: any + await act(async () => { + const renderHookResult = renderHook(() => useLabsSearch(expectedLabsSearchRequest)) + const { result } = renderHookResult + await waitUntilQueryIsSuccessful(renderHookResult) + actualData = result.current.data + }) + + expect(labRepositoryFindAllSpy).toHaveBeenCalledTimes(1) + expect(labRepositorySearchSpy).not.toHaveBeenCalled() + expect(actualData).toEqual(expectedLabs) + }) + + it('should search for labs', async () => { + const expectedLabsSearchRequest = { + text: 'search text', + status: 'all', + } as LabSearchRequest + + let actualData: any + await act(async () => { + const renderHookResult = renderHook(() => useLabsSearch(expectedLabsSearchRequest)) + const { result } = renderHookResult + await waitUntilQueryIsSuccessful(renderHookResult) + actualData = result.current.data + }) + + expect(labRepositoryFindAllSpy).not.toHaveBeenCalled() + expect(labRepositorySearchSpy).toHaveBeenCalledTimes(1) + expect(labRepositorySearchSpy).toHaveBeenCalledWith( + expect.objectContaining(expectedLabsSearchRequest), + ) + expect(actualData).toEqual(expectedLabs) + }) +}) diff --git a/src/__tests__/labs/hooks/useRequestLab.test.ts b/src/__tests__/labs/hooks/useRequestLab.test.ts new file mode 100644 index 0000000000..fc83aa805c --- /dev/null +++ b/src/__tests__/labs/hooks/useRequestLab.test.ts @@ -0,0 +1,59 @@ +import { act } from '@testing-library/react-hooks' + +import useRequestLab from '../../../labs/hooks/useRequestLab' +import * as validateLabRequest from '../../../labs/utils/validate-lab' +import { LabError } from '../../../labs/utils/validate-lab' +import LabRepository from '../../../shared/db/LabRepository' +import Lab from '../../../shared/model/Lab' +import executeMutation from '../../test-utils/use-mutation.util' + +describe('Use Request lab', () => { + const expectedRequestedOnDate = new Date() + const lab = { + type: 'test', + patient: '123', + } as Lab + const expectedRequestedLab = { + ...lab, + requestedOn: expectedRequestedOnDate.toISOString(), + } as Lab + + Date.now = jest.fn(() => expectedRequestedOnDate.valueOf()) + jest.spyOn(LabRepository, 'save').mockResolvedValue(expectedRequestedLab) + + beforeEach(() => { + jest.clearAllMocks() + }) + + it('should save new request lab', async () => { + let actualData: any + await act(async () => { + actualData = await executeMutation(() => useRequestLab(), lab) + }) + + expect(LabRepository.save).toHaveBeenCalledTimes(1) + expect(LabRepository.save).toHaveBeenCalledWith(lab) + expect(actualData).toEqual(expectedRequestedLab) + }) + + it('should return errors', async () => { + expect.hasAssertions() + + const expectedError = { + message: 'error message', + patient: 'error patient', + type: 'error type', + } as LabError + + jest.spyOn(validateLabRequest, 'validateLabRequest').mockReturnValue(expectedError) + + await act(async () => { + try { + await executeMutation(() => useRequestLab(), lab) + } catch (e) { + expect(e).toEqual(expectedError) + expect(LabRepository.save).not.toHaveBeenCalled() + } + }) + }) +}) diff --git a/src/__tests__/labs/hooks/useUpdateLab.test.ts b/src/__tests__/labs/hooks/useUpdateLab.test.ts new file mode 100644 index 0000000000..6ab9840284 --- /dev/null +++ b/src/__tests__/labs/hooks/useUpdateLab.test.ts @@ -0,0 +1,27 @@ +import { act } from '@testing-library/react-hooks' + +import useUpdateLab from '../../../labs/hooks/useUpdateLab' +import LabRepository from '../../../shared/db/LabRepository' +import Lab from '../../../shared/model/Lab' +import executeMutation from '../../test-utils/use-mutation.util' + +describe('Use update lab', () => { + const expectedLab = { + type: 'some type', + notes: ['some note'], + } as Lab + + jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(expectedLab) + + it('should update lab', async () => { + let actualData: any + + await act(async () => { + actualData = await executeMutation(() => useUpdateLab(), expectedLab) + }) + + expect(LabRepository.saveOrUpdate).toHaveBeenCalledTimes(1) + expect(LabRepository.saveOrUpdate).toHaveBeenCalledWith(expectedLab) + expect(actualData).toEqual(expectedLab) + }) +}) diff --git a/src/__tests__/labs/lab-slice.test.ts b/src/__tests__/labs/lab-slice.test.ts deleted file mode 100644 index 45449b3aea..0000000000 --- a/src/__tests__/labs/lab-slice.test.ts +++ /dev/null @@ -1,406 +0,0 @@ -import createMockStore from 'redux-mock-store' -import thunk from 'redux-thunk' - -import labSlice, { - requestLab, - fetchLabStart, - fetchLabSuccess, - updateLabStart, - updateLabSuccess, - requestLabStart, - requestLabSuccess, - completeLabStart, - completeLabSuccess, - cancelLabStart, - cancelLabSuccess, - fetchLab, - cancelLab, - completeLab, - completeLabError, - requestLabError, - updateLab, -} from '../../labs/lab-slice' -import LabRepository from '../../shared/db/LabRepository' -import PatientRepository from '../../shared/db/PatientRepository' -import Lab from '../../shared/model/Lab' -import Patient from '../../shared/model/Patient' -import { RootState } from '../../shared/store' - -const mockStore = createMockStore([thunk]) - -describe('lab slice', () => { - describe('reducers', () => { - describe('fetchLabStart', () => { - it('should set status to loading', async () => { - const labStore = labSlice(undefined, fetchLabStart()) - - expect(labStore.status).toEqual('loading') - }) - }) - - describe('fetchLabSuccess', () => { - it('should set the lab, patient, and status to success', () => { - const expectedLab = { id: 'labId' } as Lab - const expectedPatient = { id: 'patient' } as Patient - - const labStore = labSlice( - undefined, - fetchLabSuccess({ lab: expectedLab, patient: expectedPatient }), - ) - - expect(labStore.status).toEqual('completed') - expect(labStore.lab).toEqual(expectedLab) - expect(labStore.patient).toEqual(expectedPatient) - }) - }) - - describe('updateLabStart', () => { - it('should set status to loading', async () => { - const labStore = labSlice(undefined, updateLabStart()) - - expect(labStore.status).toEqual('loading') - }) - }) - - describe('updateLabSuccess', () => { - it('should set the lab and status to success', () => { - const expectedLab = { id: 'labId' } as Lab - - const labStore = labSlice(undefined, updateLabSuccess(expectedLab)) - - expect(labStore.status).toEqual('completed') - expect(labStore.lab).toEqual(expectedLab) - }) - }) - - describe('requestLabStart', () => { - it('should set status to loading', async () => { - const labStore = labSlice(undefined, requestLabStart()) - - expect(labStore.status).toEqual('loading') - }) - }) - - describe('requestLabSuccess', () => { - it('should set the lab and status to success', () => { - const expectedLab = { id: 'labId' } as Lab - - const labStore = labSlice(undefined, requestLabSuccess(expectedLab)) - - expect(labStore.status).toEqual('completed') - expect(labStore.lab).toEqual(expectedLab) - }) - }) - - describe('requestLabError', () => { - const expectedError = { message: 'some message', result: 'some result error' } - - const labStore = labSlice(undefined, requestLabError(expectedError)) - - expect(labStore.status).toEqual('error') - expect(labStore.error).toEqual(expectedError) - }) - - describe('completeLabStart', () => { - it('should set status to loading', async () => { - const labStore = labSlice(undefined, completeLabStart()) - - expect(labStore.status).toEqual('loading') - }) - }) - - describe('completeLabSuccess', () => { - it('should set the lab and status to success', () => { - const expectedLab = { id: 'labId' } as Lab - - const labStore = labSlice(undefined, completeLabSuccess(expectedLab)) - - expect(labStore.status).toEqual('completed') - expect(labStore.lab).toEqual(expectedLab) - }) - }) - - describe('completeLabError', () => { - const expectedError = { message: 'some message', result: 'some result error' } - - const labStore = labSlice(undefined, completeLabError(expectedError)) - - expect(labStore.status).toEqual('error') - expect(labStore.error).toEqual(expectedError) - }) - - describe('cancelLabStart', () => { - it('should set status to loading', async () => { - const labStore = labSlice(undefined, cancelLabStart()) - - expect(labStore.status).toEqual('loading') - }) - }) - - describe('cancelLabSuccess', () => { - it('should set the lab and status to success', () => { - const expectedLab = { id: 'labId' } as Lab - - const labStore = labSlice(undefined, cancelLabSuccess(expectedLab)) - - expect(labStore.status).toEqual('completed') - expect(labStore.lab).toEqual(expectedLab) - }) - }) - }) - - describe('fetch lab', () => { - let patientRepositorySpy: any - let labRepositoryFindSpy: any - - const mockLab = { - id: 'labId', - patient: 'patient', - } as Lab - - const mockPatient = { - id: 'patient', - } as Patient - - beforeEach(() => { - patientRepositorySpy = jest.spyOn(PatientRepository, 'find').mockResolvedValue(mockPatient) - labRepositoryFindSpy = jest.spyOn(LabRepository, 'find').mockResolvedValue(mockLab) - }) - - it('should fetch the lab and patient', async () => { - const store = mockStore() - - await store.dispatch(fetchLab(mockLab.id)) - const actions = store.getActions() - - expect(actions[0]).toEqual(fetchLabStart()) - expect(labRepositoryFindSpy).toHaveBeenCalledWith(mockLab.id) - expect(patientRepositorySpy).toHaveBeenCalledWith(mockLab.patient) - expect(actions[1]).toEqual(fetchLabSuccess({ lab: mockLab, patient: mockPatient })) - }) - }) - - describe('cancel lab', () => { - const mockLab = { - id: 'labId', - patient: 'patient', - } as Lab - let labRepositorySaveOrUpdateSpy: any - - beforeEach(() => { - Date.now = jest.fn().mockReturnValue(new Date().valueOf()) - labRepositorySaveOrUpdateSpy = jest - .spyOn(LabRepository, 'saveOrUpdate') - .mockResolvedValue(mockLab) - }) - - it('should cancel a lab', async () => { - const expectedCanceledLab = { - ...mockLab, - canceledOn: new Date(Date.now()).toISOString(), - status: 'canceled', - } as Lab - - const store = mockStore() - - await store.dispatch(cancelLab(mockLab)) - const actions = store.getActions() - - expect(actions[0]).toEqual(cancelLabStart()) - expect(labRepositorySaveOrUpdateSpy).toHaveBeenCalledWith(expectedCanceledLab) - expect(actions[1]).toEqual(cancelLabSuccess(expectedCanceledLab)) - }) - - it('should call on success callback if provided', async () => { - const expectedCanceledLab = { - ...mockLab, - canceledOn: new Date(Date.now()).toISOString(), - status: 'canceled', - } as Lab - - const store = mockStore() - const onSuccessSpy = jest.fn() - await store.dispatch(cancelLab(mockLab, onSuccessSpy)) - - expect(onSuccessSpy).toHaveBeenCalledWith(expectedCanceledLab) - }) - }) - - describe('complete lab', () => { - const mockLab = { - id: 'labId', - patient: 'patient', - result: 'lab result', - } as Lab - let labRepositorySaveOrUpdateSpy: any - - beforeEach(() => { - Date.now = jest.fn().mockReturnValue(new Date().valueOf()) - labRepositorySaveOrUpdateSpy = jest - .spyOn(LabRepository, 'saveOrUpdate') - .mockResolvedValue(mockLab) - }) - - it('should complete a lab', async () => { - const expectedCompletedLab = { - ...mockLab, - completedOn: new Date(Date.now()).toISOString(), - status: 'completed', - result: 'lab result', - } as Lab - - const store = mockStore() - - await store.dispatch(completeLab(mockLab)) - const actions = store.getActions() - - expect(actions[0]).toEqual(completeLabStart()) - expect(labRepositorySaveOrUpdateSpy).toHaveBeenCalledWith(expectedCompletedLab) - expect(actions[1]).toEqual(completeLabSuccess(expectedCompletedLab)) - }) - - it('should call on success callback if provided', async () => { - const expectedCompletedLab = { - ...mockLab, - completedOn: new Date(Date.now()).toISOString(), - status: 'completed', - } as Lab - - const store = mockStore() - const onSuccessSpy = jest.fn() - await store.dispatch(completeLab(mockLab, onSuccessSpy)) - - expect(onSuccessSpy).toHaveBeenCalledWith(expectedCompletedLab) - }) - - it('should validate that the lab can be completed', async () => { - const store = mockStore() - const onSuccessSpy = jest.fn() - await store.dispatch(completeLab({ id: 'labId' } as Lab, onSuccessSpy)) - const actions = store.getActions() - - expect(actions[1]).toEqual( - completeLabError({ - result: 'labs.requests.error.resultRequiredToComplete', - message: 'labs.requests.error.unableToComplete', - }), - ) - expect(onSuccessSpy).not.toHaveBeenCalled() - }) - }) - - describe('request lab', () => { - const mockLab = { - id: 'labId', - type: 'labType', - patient: 'patient', - } as Lab - let labRepositorySaveSpy: any - - beforeEach(() => { - jest.restoreAllMocks() - Date.now = jest.fn().mockReturnValue(new Date().valueOf()) - labRepositorySaveSpy = jest.spyOn(LabRepository, 'save').mockResolvedValue(mockLab) - }) - - it('should request a new lab', async () => { - const store = mockStore({ - user: { - user: { - id: 'fake id', - }, - }, - } as any) - - const expectedRequestedLab = { - ...mockLab, - requestedOn: new Date(Date.now()).toISOString(), - status: 'requested', - requestedBy: store.getState().user.user.id, - } as Lab - - await store.dispatch(requestLab(mockLab)) - - const actions = store.getActions() - - expect(actions[0]).toEqual(requestLabStart()) - expect(labRepositorySaveSpy).toHaveBeenCalledWith(expectedRequestedLab) - expect(actions[1]).toEqual(requestLabSuccess(expectedRequestedLab)) - }) - - it('should execute the onSuccess callback if provided', async () => { - const store = mockStore({ - user: { - user: { - id: 'fake id', - }, - }, - } as any) - const onSuccessSpy = jest.fn() - - await store.dispatch(requestLab(mockLab, onSuccessSpy)) - - expect(onSuccessSpy).toHaveBeenCalledWith(mockLab) - }) - - it('should validate that the lab can be requested', async () => { - const store = mockStore() - const onSuccessSpy = jest.fn() - await store.dispatch(requestLab({} as Lab, onSuccessSpy)) - - const actions = store.getActions() - - expect(actions[0]).toEqual(requestLabStart()) - expect(actions[1]).toEqual( - requestLabError({ - patient: 'labs.requests.error.patientRequired', - type: 'labs.requests.error.typeRequired', - message: 'labs.requests.error.unableToRequest', - }), - ) - expect(labRepositorySaveSpy).not.toHaveBeenCalled() - expect(onSuccessSpy).not.toHaveBeenCalled() - }) - }) - - describe('update lab', () => { - const mockLab = { - id: 'labId', - patient: 'patient', - result: 'lab result', - } as Lab - let labRepositorySaveOrUpdateSpy: any - - const expectedUpdatedLab = { - ...mockLab, - type: 'some other type', - } - - beforeEach(() => { - Date.now = jest.fn().mockReturnValue(new Date().valueOf()) - labRepositorySaveOrUpdateSpy = jest - .spyOn(LabRepository, 'saveOrUpdate') - .mockResolvedValue(expectedUpdatedLab) - }) - - it('should update the lab', async () => { - const store = mockStore() - - await store.dispatch(updateLab(expectedUpdatedLab)) - const actions = store.getActions() - - expect(actions[0]).toEqual(updateLabStart()) - expect(labRepositorySaveOrUpdateSpy).toHaveBeenCalledWith(expectedUpdatedLab) - expect(actions[1]).toEqual(updateLabSuccess(expectedUpdatedLab)) - }) - - it('should call the onSuccess callback if successful', async () => { - const store = mockStore() - const onSuccessSpy = jest.fn() - - await store.dispatch(updateLab(expectedUpdatedLab, onSuccessSpy)) - - expect(onSuccessSpy).toHaveBeenCalledWith(expectedUpdatedLab) - }) - }) -}) diff --git a/src/__tests__/labs/labs.slice.test.ts b/src/__tests__/labs/labs.slice.test.ts deleted file mode 100644 index 5e05eb15fb..0000000000 --- a/src/__tests__/labs/labs.slice.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { AnyAction } from 'redux' -import { mocked } from 'ts-jest/utils' - -import labs, { fetchLabsStart, fetchLabsSuccess, searchLabs } from '../../labs/labs-slice' -import LabRepository from '../../shared/db/LabRepository' -import SortRequest from '../../shared/db/SortRequest' -import Lab from '../../shared/model/Lab' - -interface SearchContainer { - text: string - status: 'requested' | 'completed' | 'canceled' | 'all' - defaultSortRequest: SortRequest -} - -const defaultSortRequest: SortRequest = { - sorts: [ - { - field: 'requestedOn', - direction: 'desc', - }, - ], -} - -const expectedSearchObject: SearchContainer = { - text: 'search string', - status: 'all', - defaultSortRequest, -} - -describe('labs slice', () => { - beforeEach(() => { - jest.resetAllMocks() - }) - - describe('labs reducer', () => { - it('should create the proper intial state with empty labs array', () => { - const labsStore = labs(undefined, {} as AnyAction) - expect(labsStore.isLoading).toBeFalsy() - expect(labsStore.labs).toHaveLength(0) - expect(labsStore.statusFilter).toEqual('all') - }) - - it('it should handle the FETCH_LABS_SUCCESS action', () => { - const expectedLabs = [{ id: '1234' }] - const labsStore = labs(undefined, { - type: fetchLabsSuccess.type, - payload: expectedLabs, - }) - - expect(labsStore.isLoading).toBeFalsy() - expect(labsStore.labs).toEqual(expectedLabs) - }) - }) - - describe('searchLabs', () => { - it('should dispatch the FETCH_LABS_START action', async () => { - const dispatch = jest.fn() - const getState = jest.fn() - - await searchLabs('search string', 'all')(dispatch, getState, null) - - expect(dispatch).toHaveBeenCalledWith({ type: fetchLabsStart.type }) - }) - - it('should call the LabRepository search method with the correct search criteria', async () => { - const dispatch = jest.fn() - const getState = jest.fn() - jest.spyOn(LabRepository, 'search') - - await searchLabs(expectedSearchObject.text, expectedSearchObject.status)( - dispatch, - getState, - null, - ) - - expect(LabRepository.search).toHaveBeenCalledWith(expectedSearchObject) - }) - - it('should call the LabRepository findAll method if there is no string text and status is set to all', async () => { - const dispatch = jest.fn() - const getState = jest.fn() - jest.spyOn(LabRepository, 'findAll') - - await searchLabs('', expectedSearchObject.status)(dispatch, getState, null) - - expect(LabRepository.findAll).toHaveBeenCalledTimes(1) - }) - - it('should dispatch the FETCH_LABS_SUCCESS action', async () => { - const dispatch = jest.fn() - const getState = jest.fn() - - const expectedLabs = [ - { - type: 'text', - }, - ] as Lab[] - - const mockedLabRepository = mocked(LabRepository, true) - mockedLabRepository.search.mockResolvedValue(expectedLabs) - - await searchLabs(expectedSearchObject.text, expectedSearchObject.status)( - dispatch, - getState, - null, - ) - - expect(dispatch).toHaveBeenLastCalledWith({ - type: fetchLabsSuccess.type, - payload: expectedLabs, - }) - }) - }) - - describe('sort Request', () => { - it('should have called findAll with sort request in searchLabs method', async () => { - const dispatch = jest.fn() - const getState = jest.fn() - jest.spyOn(LabRepository, 'findAll') - - await searchLabs('', expectedSearchObject.status)(dispatch, getState, null) - - expect(LabRepository.findAll).toHaveBeenCalledWith(expectedSearchObject.defaultSortRequest) - }) - - it('should include sorts in the search criteria', async () => { - const dispatch = jest.fn() - const getState = jest.fn() - jest.spyOn(LabRepository, 'search') - - await searchLabs(expectedSearchObject.text, expectedSearchObject.status)( - dispatch, - getState, - null, - ) - - expect(LabRepository.search).toHaveBeenCalledWith(expectedSearchObject) - }) - }) -}) diff --git a/src/__tests__/labs/requests/NewLabRequest.test.tsx b/src/__tests__/labs/requests/NewLabRequest.test.tsx index a9175944b3..f97dd730fe 100644 --- a/src/__tests__/labs/requests/NewLabRequest.test.tsx +++ b/src/__tests__/labs/requests/NewLabRequest.test.tsx @@ -9,6 +9,8 @@ import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import NewLabRequest from '../../../labs/requests/NewLabRequest' +import * as validationUtil from '../../../labs/utils/validate-lab' +import { LabError } from '../../../labs/utils/validate-lab' import * as titleUtil from '../../../page-header/title/TitleContext' import TextFieldWithLabelFormGroup from '../../../shared/components/input/TextFieldWithLabelFormGroup' import TextInputWithLabelFormGroup from '../../../shared/components/input/TextInputWithLabelFormGroup' @@ -19,26 +21,31 @@ import Patient from '../../../shared/model/Patient' import { RootState } from '../../../shared/store' const mockStore = createMockStore([thunk]) - describe('New Lab Request', () => { - const setup = async (store = mockStore({ lab: { status: 'loading', error: {} } } as any)) => { - const history = createMemoryHistory() + let history: any + const setup = async ( + store = mockStore({ title: '', user: { user: { id: 'userId' } } } as any), + ) => { + history = createMemoryHistory() history.push(`/labs/new`) jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - const wrapper: ReactWrapper = await mount( - - - - - - - , - ) + let wrapper: any + await act(async () => { + wrapper = await mount( + + + + + + + , + ) + }) wrapper.find(NewLabRequest).props().updateTitle = jest.fn() wrapper.update() - return { wrapper } + return { wrapper: wrapper as ReactWrapper } } describe('form layout', () => { @@ -98,16 +105,26 @@ describe('New Lab Request', () => { }) }) - describe('errors', async () => { + describe('errors', () => { const error = { message: 'some message', patient: 'some patient message', type: 'some type error', - } - const store = mockStore({ lab: { status: 'error', error } } as any) - const { wrapper } = await setup(store) + } as LabError + + jest.spyOn(validationUtil, 'validateLabRequest').mockReturnValue(error) it('should display errors', async () => { + const { wrapper } = await setup() + + const saveButton = wrapper.find(Button).at(0) + await act(async () => { + const onClick = saveButton.prop('onClick') as any + await onClick() + }) + + wrapper.update() + const alert = wrapper.find(Alert) const typeInput = wrapper.find(TextInputWithLabelFormGroup) const patientTypeahead = wrapper.find(Typeahead) @@ -123,12 +140,9 @@ describe('New Lab Request', () => { }) }) - describe('on cancel', async () => { - const history = createMemoryHistory() - - const { wrapper } = await setup() - - it('should navigate back to /labs', () => { + describe('on cancel', () => { + it('should navigate back to /labs', async () => { + const { wrapper } = await setup() const cancelButton = wrapper.find(Button).at(1) act(() => { @@ -140,8 +154,7 @@ describe('New Lab Request', () => { }) }) - describe('on save', async () => { - const history = createMemoryHistory() + describe('on save', () => { let labRepositorySaveSpy: any const expectedDate = new Date() const expectedNotes = 'expected notes' @@ -157,7 +170,7 @@ describe('New Lab Request', () => { lab: { status: 'loading', error: {} }, user: { user: { id: 'fake id' } }, } as any) - const { wrapper } = await setup(store) + beforeEach(() => { jest.resetAllMocks() Date.now = jest.fn(() => expectedDate.valueOf()) @@ -169,6 +182,8 @@ describe('New Lab Request', () => { }) it('should save the lab request and navigate to "/labs/:id"', async () => { + const { wrapper } = await setup(store) + const patientTypeahead = wrapper.find(Typeahead) await act(async () => { const onChange = patientTypeahead.prop('onChange') diff --git a/src/__tests__/labs/utils/validate-lab.test.ts b/src/__tests__/labs/utils/validate-lab.test.ts new file mode 100644 index 0000000000..7d52f05c67 --- /dev/null +++ b/src/__tests__/labs/utils/validate-lab.test.ts @@ -0,0 +1,60 @@ +import { LabError, validateLabRequest, validateLabComplete } from '../../../labs/utils/validate-lab' +import Lab from '../../../shared/model/Lab' + +describe('lab validator', () => { + describe('validate request', () => { + it('should not return error when fields are filled', () => { + const lab = { + patient: 'some patient', + type: 'type test', + } as Lab + + const expectedError = {} as LabError + + const actualError = validateLabRequest(lab) + + expect(actualError).toEqual(expectedError) + }) + + it('should return error when fields are missing', () => { + const lab = {} as Lab + + const expectedError = { + patient: 'labs.requests.error.patientRequired', + type: 'labs.requests.error.typeRequired', + message: 'labs.requests.error.unableToRequest', + } as LabError + + const actualError = validateLabRequest(lab) + + expect(actualError).toEqual(expectedError) + }) + }) + + describe('validate completed', () => { + it('should not return error when result is filled', () => { + const lab = { + result: 'some result', + } as Lab + + const expectedError = {} as LabError + + const actualError = validateLabComplete(lab) + + expect(actualError).toEqual(expectedError) + }) + + it('should return error when result is missing', () => { + const lab = {} as Lab + + const expectedError = { + result: 'labs.requests.error.resultRequiredToComplete', + message: 'labs.requests.error.unableToComplete', + } as LabError + + const actualError = validateLabComplete(lab) + + expect(actualError).toEqual(expectedError) + }) + }) +}) diff --git a/src/__tests__/patients/care-goals/CareGoalTab.test.tsx b/src/__tests__/patients/care-goals/CareGoalTab.test.tsx index 57b0d42877..2ce1f2e2ae 100644 --- a/src/__tests__/patients/care-goals/CareGoalTab.test.tsx +++ b/src/__tests__/patients/care-goals/CareGoalTab.test.tsx @@ -3,7 +3,7 @@ import { createMemoryHistory } from 'history' import React from 'react' import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' -import { Router } from 'react-router-dom' +import { Router, Route } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' @@ -32,7 +32,9 @@ describe('Care Goals Tab', () => { wrapper = await mount( - + + + , ) diff --git a/src/__tests__/patients/care-plans/CarePlanTab.test.tsx b/src/__tests__/patients/care-plans/CarePlanTab.test.tsx index 693541168d..62b0a30260 100644 --- a/src/__tests__/patients/care-plans/CarePlanTab.test.tsx +++ b/src/__tests__/patients/care-plans/CarePlanTab.test.tsx @@ -4,7 +4,7 @@ import { createMemoryHistory } from 'history' import React from 'react' import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' -import { Router } from 'react-router-dom' +import { Router, Route } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' @@ -33,7 +33,9 @@ describe('Care Plan Tab', () => { wrapper = await mount( - + + + , ) diff --git a/src/__tests__/patients/view/ViewPatient.test.tsx b/src/__tests__/patients/view/ViewPatient.test.tsx index dcc2f9884b..b4ca4c142d 100644 --- a/src/__tests__/patients/view/ViewPatient.test.tsx +++ b/src/__tests__/patients/view/ViewPatient.test.tsx @@ -48,8 +48,11 @@ describe('ViewPatient', () => { let history: any let store: MockStore + let setButtonToolBarSpy: any const setup = async (permissions = [Permissions.ReadPatients]) => { + setButtonToolBarSpy = jest.fn() + jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) jest.spyOn(PatientRepository, 'find') jest.spyOn(PatientRepository, 'getLabs').mockResolvedValue([]) @@ -101,10 +104,6 @@ describe('ViewPatient', () => { }) it('should add a "Edit Patient" button to the button tool bar if has WritePatients permissions', async () => { - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter') - const setButtonToolBarSpy = jest.fn() - mocked(ButtonBarProvider).useButtonToolbarSetter.mockReturnValue(setButtonToolBarSpy) - await setup([Permissions.WritePatients]) const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] @@ -112,10 +111,6 @@ describe('ViewPatient', () => { }) it('button toolbar empty if only has ReadPatients permission', async () => { - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter') - const setButtonToolBarSpy = jest.fn() - mocked(ButtonBarProvider).useButtonToolbarSetter.mockReturnValue(setButtonToolBarSpy) - await setup() const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] diff --git a/src/labs/ViewLab.tsx b/src/labs/ViewLab.tsx index d253c4cb21..c9c5f05f31 100644 --- a/src/labs/ViewLab.tsx +++ b/src/labs/ViewLab.tsx @@ -1,11 +1,12 @@ import { Row, Column, Badge, Button, Alert, Toast, Callout, Label } from '@hospitalrun/components' import format from 'date-fns/format' import React, { useEffect, useState } from 'react' -import { useSelector, useDispatch } from 'react-redux' +import { useSelector } from 'react-redux' import { useParams, useHistory } from 'react-router-dom' import useAddBreadcrumbs from '../page-header/breadcrumbs/useAddBreadcrumbs' import { useUpdateTitle } from '../page-header/title/TitleContext' +import usePatient from '../patients/hooks/usePatient' import TextFieldWithLabelFormGroup from '../shared/components/input/TextFieldWithLabelFormGroup' import useTranslator from '../shared/hooks/useTranslator' import Lab from '../shared/model/Lab' @@ -13,7 +14,11 @@ import Patient from '../shared/model/Patient' import Permissions from '../shared/model/Permissions' import { RootState } from '../shared/store' import { uuid } from '../shared/util/uuid' -import { cancelLab, completeLab, updateLab, fetchLab } from './lab-slice' +import useCancelLab from './hooks/useCancelLab' +import useCompleteLab from './hooks/useCompleteLab' +import useLab from './hooks/useLab' +import useUpdateLab from './hooks/useUpdateLab' +import { LabError } from './utils/validate-lab' const getTitle = (patient: Patient | undefined, lab: Lab | undefined) => patient && lab ? `${lab.type} for ${patient.fullName}(${lab.code})` : '' @@ -22,14 +27,19 @@ const ViewLab = () => { const { id } = useParams() const { t } = useTranslator() const history = useHistory() - const dispatch = useDispatch() const { permissions } = useSelector((state: RootState) => state.user) - const { lab, patient, status, error } = useSelector((state: RootState) => state.lab) const [labToView, setLabToView] = useState() const [newNotes, setNewNotes] = useState() const [isEditable, setIsEditable] = useState(true) + const { data: lab } = useLab(id) + const { data: patient } = usePatient(lab?.patient) + const [updateLab] = useUpdateLab() + const [completeLab] = useCompleteLab() + const [cancelLab] = useCancelLab() + const [error, setError] = useState(undefined) + const updateTitle = useUpdateTitle() updateTitle(getTitle(patient, labToView)) @@ -41,12 +51,6 @@ const ViewLab = () => { ] useAddBreadcrumbs(breadcrumbs) - useEffect(() => { - if (id) { - dispatch(fetchLab(id)) - } - }, [id, dispatch]) - useEffect(() => { if (lab) { setLabToView({ ...lab }) @@ -66,45 +70,46 @@ const ViewLab = () => { } const onUpdate = async () => { - const onSuccess = (update: Lab) => { - history.push(`/labs/${update.id}`) - Toast( - 'success', - t('states.success'), - `${t('labs.successfullyUpdated')} ${update.type} for ${patient?.fullName}`, - ) - } if (labToView) { const newLab = labToView as Lab + if (newNotes) { newLab.notes = newLab.notes ? [...newLab.notes, newNotes] : [newNotes] setNewNotes('') } - dispatch(updateLab(newLab, onSuccess)) - } - } - const onComplete = async () => { - const onSuccess = (complete: Lab) => { - history.push(`/labs/${complete.id}`) + + const updatedLab = await updateLab(newLab) + history.push(`/labs/${updatedLab?.id}`) Toast( 'success', t('states.success'), - `${t('labs.successfullyCompleted')} ${complete.type} for ${patient?.fullName} `, + `${t('labs.successfullyUpdated')} ${updatedLab?.type} for ${patient?.fullName}`, ) } + setError(undefined) + } - if (labToView) { - dispatch(completeLab(labToView, onSuccess)) + const onComplete = async () => { + try { + if (labToView) { + const completedLab = await completeLab(labToView) + history.push(`/labs/${completedLab?.id}`) + Toast( + 'success', + t('states.success'), + `${t('labs.successfullyCompleted')} ${completedLab?.type} for ${patient?.fullName} `, + ) + } + setError(undefined) + } catch (e) { + setError(e) } } const onCancel = async () => { - const onSuccess = () => { - history.push('/labs') - } - if (labToView) { - dispatch(cancelLab(labToView, onSuccess)) + cancelLab(labToView) + history.push('/labs') } } @@ -188,7 +193,7 @@ const ViewLab = () => { return ( <> - {status === 'error' && ( + {error && ( )} @@ -227,8 +232,8 @@ const ViewLab = () => { label={t('labs.lab.result')} value={labToView.result} isEditable={isEditable} - isInvalid={!!error.result} - feedback={t(error.result as string)} + isInvalid={!!error?.result} + feedback={t(error?.result as string)} onChange={onResultChange} />