This repository has been archived by the owner on Jan 9, 2023. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(diagnoses): adds ability to add a diagnosis
- Loading branch information
1 parent
bdf7aa1
commit e1ce6c9
Showing
10 changed files
with
424 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
src/__tests__/patients/diagnoses/AddDiagnosisModal.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import '../../../__mocks__/matchMediaMock' | ||
import React from 'react' | ||
import { shallow, mount } from 'enzyme' | ||
import { Modal, Alert } from '@hospitalrun/components' | ||
import { act } from '@testing-library/react' | ||
import AddDiagnosisModal from 'patients/diagnoses/AddDiagnosisModal' | ||
import Diagnosis from 'model/Diagnosis' | ||
|
||
describe('Add Diagnosis Modal', () => { | ||
it('should render a modal with the correct labels', () => { | ||
const wrapper = shallow( | ||
<AddDiagnosisModal show onCloseButtonClick={jest.fn()} onSave={jest.fn()} />, | ||
) | ||
|
||
const modal = wrapper.find(Modal) | ||
expect(modal).toHaveLength(1) | ||
expect(modal.prop('title')).toEqual('patient.diagnoses.new') | ||
expect(modal.prop('closeButton')?.children).toEqual('actions.cancel') | ||
expect(modal.prop('closeButton')?.color).toEqual('danger') | ||
expect(modal.prop('successButton')?.children).toEqual('patient.diagnoses.new') | ||
expect(modal.prop('successButton')?.color).toEqual('success') | ||
expect(modal.prop('successButton')?.icon).toEqual('add') | ||
}) | ||
|
||
describe('cancel', () => { | ||
it('should call the onCloseButtonClick function when the close button is clicked', () => { | ||
const onCloseButtonClickSpy = jest.fn() | ||
const wrapper = shallow( | ||
<AddDiagnosisModal show onCloseButtonClick={onCloseButtonClickSpy} onSave={jest.fn()} />, | ||
) | ||
|
||
act(() => { | ||
const modal = wrapper.find(Modal) | ||
const { onClick } = modal.prop('closeButton') as any | ||
onClick() | ||
}) | ||
|
||
expect(onCloseButtonClickSpy).toHaveBeenCalledTimes(1) | ||
}) | ||
}) | ||
|
||
describe('save', () => { | ||
it('should call the onSave function with the correct data when the save button is clicked', () => { | ||
const expectedName = 'expected name' | ||
const expectedDate = new Date() | ||
const onSaveSpy = jest.fn() | ||
const wrapper = mount( | ||
<AddDiagnosisModal show onCloseButtonClick={jest.fn()} onSave={onSaveSpy} />, | ||
) | ||
|
||
act(() => { | ||
const input = wrapper.findWhere((c: any) => c.prop('name') === 'name') | ||
const onChange = input.prop('onChange') | ||
onChange({ target: { value: expectedName } }) | ||
}) | ||
wrapper.update() | ||
|
||
act(() => { | ||
const input = wrapper.findWhere((c: any) => c.prop('name') === 'diagnosisDate') | ||
const onChange = input.prop('onChange') | ||
onChange(expectedDate) | ||
}) | ||
wrapper.update() | ||
|
||
act(() => { | ||
const modal = wrapper.find(Modal) | ||
const onSave = (modal.prop('successButton') as any).onClick | ||
onSave({} as React.MouseEvent<HTMLButtonElement>) | ||
}) | ||
|
||
expect(onSaveSpy).toHaveBeenCalledTimes(1) | ||
expect(onSaveSpy).toHaveBeenCalledWith({ | ||
name: expectedName, | ||
diagnosisDate: expectedDate.toISOString(), | ||
} as Diagnosis) | ||
}) | ||
|
||
it('should display an error message if the name field is not filled out', () => { | ||
const wrapper = mount( | ||
<AddDiagnosisModal show onCloseButtonClick={jest.fn()} onSave={jest.fn()} />, | ||
) | ||
|
||
act(() => { | ||
const modal = wrapper.find(Modal) | ||
const onSave = (modal.prop('successButton') as any).onClick | ||
onSave({} as React.MouseEvent<HTMLButtonElement>) | ||
}) | ||
wrapper.update() | ||
|
||
expect(wrapper.find(Alert)).toHaveLength(1) | ||
expect(wrapper.find(Alert).prop('title')).toEqual('states.error') | ||
expect(wrapper.find(Alert).prop('message')).toContain('patient.diagnoses.error.nameRequired') | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import '../../../__mocks__/matchMediaMock' | ||
import React from 'react' | ||
import { mount } from 'enzyme' | ||
import { createMemoryHistory } from 'history' | ||
import configureMockStore from 'redux-mock-store' | ||
import thunk from 'redux-thunk' | ||
import Patient from 'model/Patient' | ||
import Diagnosis from 'model/Diagnosis' | ||
import Permissions from 'model/Permissions' | ||
import { Router } from 'react-router' | ||
import { Provider } from 'react-redux' | ||
import Diagnoses from 'patients/diagnoses/Diagnoses' | ||
import { Button, Modal, List, ListItem, Alert } from '@hospitalrun/components' | ||
import { act } from 'react-dom/test-utils' | ||
import { mocked } from 'ts-jest/utils' | ||
import PatientRepository from 'clients/db/PatientRepository' | ||
import AddDiagnosisModal from 'patients/diagnoses/AddDiagnosisModal' | ||
import * as patientSlice from '../../../patients/patient-slice' | ||
|
||
const expectedPatient = { | ||
id: '123', | ||
diagnoses: [ | ||
{ id: '123', name: 'diagnosis1', diagnosisDate: new Date().toISOString() } as Diagnosis, | ||
], | ||
} as Patient | ||
|
||
const mockStore = configureMockStore([thunk]) | ||
const history = createMemoryHistory() | ||
|
||
let user: any | ||
let store: any | ||
|
||
const setup = (patient = expectedPatient, permissions = [Permissions.AddDiagnosis]) => { | ||
user = { permissions } | ||
store = mockStore({ patient, user }) | ||
const wrapper = mount( | ||
<Router history={history}> | ||
<Provider store={store}> | ||
<Diagnoses patient={patient} /> | ||
</Provider> | ||
</Router>, | ||
) | ||
|
||
return wrapper | ||
} | ||
describe('Diagnoses', () => { | ||
describe('add diagnoses button', () => { | ||
beforeEach(() => { | ||
jest.resetAllMocks() | ||
jest.spyOn(PatientRepository, 'saveOrUpdate') | ||
}) | ||
|
||
it('should render a add diagnoses button', () => { | ||
const wrapper = setup() | ||
|
||
const addDiagnosisButton = wrapper.find(Button) | ||
expect(addDiagnosisButton).toHaveLength(1) | ||
expect(addDiagnosisButton.text().trim()).toEqual('patient.diagnoses.new') | ||
}) | ||
|
||
it('should not render a diagnoses button if the user does not have permissions', () => { | ||
const wrapper = setup(expectedPatient, []) | ||
|
||
const addDiagnosisButton = wrapper.find(Button) | ||
expect(addDiagnosisButton).toHaveLength(0) | ||
}) | ||
|
||
it('should open the Add Diagnosis Modal', () => { | ||
const wrapper = setup() | ||
|
||
act(() => { | ||
wrapper.find(Button).prop('onClick')() | ||
}) | ||
wrapper.update() | ||
|
||
expect(wrapper.find(Modal).prop('show')).toBeTruthy() | ||
}) | ||
|
||
it('should update the patient with the new diagnosis when the save button is clicked', async () => { | ||
const expectedDiagnosis = { | ||
name: 'name', | ||
diagnosisDate: new Date().toISOString(), | ||
} as Diagnosis | ||
const expectedUpdatedPatient = { | ||
...expectedPatient, | ||
diagnoses: [...(expectedPatient.diagnoses as any), expectedDiagnosis], | ||
} as Patient | ||
|
||
const mockedPatientRepository = mocked(PatientRepository, true) | ||
mockedPatientRepository.saveOrUpdate.mockResolvedValue(expectedUpdatedPatient) | ||
|
||
const wrapper = setup() | ||
|
||
await act(async () => { | ||
const modal = wrapper.find(AddDiagnosisModal) | ||
await modal.prop('onSave')(expectedDiagnosis) | ||
}) | ||
|
||
expect(mockedPatientRepository.saveOrUpdate).toHaveBeenCalledWith(expectedUpdatedPatient) | ||
expect(store.getActions()).toContainEqual(patientSlice.updatePatientStart()) | ||
expect(store.getActions()).toContainEqual( | ||
patientSlice.updatePatientSuccess(expectedUpdatedPatient), | ||
) | ||
}) | ||
}) | ||
|
||
describe('diagnoses list', () => { | ||
it('should list the patients diagnoses', () => { | ||
const diagnoses = expectedPatient.diagnoses as Diagnosis[] | ||
const wrapper = setup() | ||
|
||
const list = wrapper.find(List) | ||
const listItems = wrapper.find(ListItem) | ||
|
||
expect(list).toHaveLength(1) | ||
expect(listItems).toHaveLength(diagnoses.length) | ||
}) | ||
|
||
it('should render a warning message if the patient does not have any diagnoses', () => { | ||
const wrapper = setup({ ...expectedPatient, diagnoses: [] }) | ||
|
||
const alert = wrapper.find(Alert) | ||
|
||
expect(alert).toHaveLength(1) | ||
expect(alert.prop('title')).toEqual('patient.diagnoses.warning.noDiagnoses') | ||
expect(alert.prop('message')).toEqual('patient.diagnoses.addDiagnosisAbove') | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export default interface Diagnosis { | ||
id: string | ||
name: string | ||
diagnosisDate: string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import React, { useState, useEffect } from 'react' | ||
import { Modal, Alert } from '@hospitalrun/components' | ||
import { useTranslation } from 'react-i18next' | ||
import Diagnosis from 'model/Diagnosis' | ||
import TextInputWithLabelFormGroup from 'components/input/TextInputWithLabelFormGroup' | ||
import DatePickerWithLabelFormGroup from 'components/input/DatePickerWithLabelFormGroup' | ||
|
||
interface Props { | ||
show: boolean | ||
onCloseButtonClick: () => void | ||
onSave: (diagnosis: Diagnosis) => void | ||
} | ||
|
||
const AddDiagnosisModal = (props: Props) => { | ||
const { show, onCloseButtonClick, onSave } = props | ||
const [diagnosis, setDiagnosis] = useState({ name: '', diagnosisDate: new Date().toISOString() }) | ||
const [errorMessage, setErrorMessage] = useState('') | ||
|
||
const { t } = useTranslation() | ||
|
||
useEffect(() => { | ||
setErrorMessage('') | ||
setDiagnosis({ name: '', diagnosisDate: new Date().toISOString() }) | ||
}, [show]) | ||
|
||
const onSaveButtonClick = () => { | ||
let newErrorMessage = '' | ||
if (!diagnosis.name) { | ||
newErrorMessage += t('patient.diagnoses.error.nameRequired') | ||
} | ||
setErrorMessage(newErrorMessage) | ||
|
||
if (!newErrorMessage) { | ||
onSave(diagnosis as Diagnosis) | ||
} | ||
} | ||
|
||
const onNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const name = event.target.value | ||
setDiagnosis((prevDiagnosis) => ({ ...prevDiagnosis, name })) | ||
} | ||
|
||
const onDiagnosisDateChange = (diagnosisDate: Date) => { | ||
if (diagnosisDate) { | ||
setDiagnosis((prevDiagnosis) => ({ | ||
...prevDiagnosis, | ||
diagnosisDate: diagnosisDate.toISOString(), | ||
})) | ||
} | ||
} | ||
|
||
const body = ( | ||
<> | ||
<form> | ||
{errorMessage && <Alert color="danger" title={t('states.error')} message={errorMessage} />} | ||
<div className="row"> | ||
<div className="col-md-12"> | ||
<div className="form-group"> | ||
<TextInputWithLabelFormGroup | ||
name="name" | ||
label={t('patient.diagnoses.diagnosisName')} | ||
isEditable | ||
placeholder={t('patient.diagnoses.diagnosisName')} | ||
value={diagnosis.name} | ||
onChange={onNameChange} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="row"> | ||
<div className="col-md-12"> | ||
<DatePickerWithLabelFormGroup | ||
name="diagnosisDate" | ||
label={t('patient.diagnoses.diagnosisDate')} | ||
value={new Date(diagnosis.diagnosisDate)} | ||
isEditable | ||
onChange={onDiagnosisDateChange} | ||
/> | ||
</div> | ||
</div> | ||
</form> | ||
</> | ||
) | ||
return ( | ||
<Modal | ||
show={show} | ||
toggle={onCloseButtonClick} | ||
title={t('patient.diagnoses.new')} | ||
body={body} | ||
closeButton={{ | ||
children: t('actions.cancel'), | ||
color: 'danger', | ||
onClick: onCloseButtonClick, | ||
}} | ||
successButton={{ | ||
children: t('patient.diagnoses.new'), | ||
color: 'success', | ||
icon: 'add', | ||
iconLocation: 'left', | ||
onClick: onSaveButtonClick, | ||
}} | ||
/> | ||
) | ||
} | ||
|
||
export default AddDiagnosisModal |
Oops, something went wrong.