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

feat(patients): add notes to patients #1961

Merged
merged 41 commits into from
Apr 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
6ecd7dd
Merge pull request #1 from HospitalRun/master
wwbarros Mar 1, 2020
0fec46f
Merge pull request #2 from HospitalRun/master
wwbarros Mar 2, 2020
53f07fb
Merge pull request #3 from HospitalRun/master
wwbarros Mar 8, 2020
0639e17
Merge pull request #4 from HospitalRun/master
wwbarros Mar 11, 2020
b43ae11
feat(notes): add notes to pacient
wwbarros Mar 12, 2020
85f83de
Merge branch 'master' into feature/add-notes-to-patients
Mar 12, 2020
b4f52f9
Merge branch 'master' into feature/add-notes-to-patients
Mar 13, 2020
fdbf89e
Merge branch 'master' into feature/add-notes-to-patients
Mar 13, 2020
007f779
Merge branch 'master' into feature/add-notes-to-patients
Mar 13, 2020
6f09d6c
Merge branch 'master' into feature/add-notes-to-patients
Mar 14, 2020
8363f65
Merge branch 'master' into feature/add-notes-to-patients
Mar 14, 2020
494bebe
Merge branch 'master' into feature/add-notes-to-patients
Mar 15, 2020
05a1339
Merge branch 'master' into feature/add-notes-to-patients
Mar 15, 2020
9d22dd1
Merge branch 'master' into feature/add-notes-to-patients
Mar 15, 2020
409feca
Merge branch 'master' into feature/add-notes-to-patients
Mar 16, 2020
e5583f9
Merge branch 'master' into feature/add-notes-to-patients
Mar 16, 2020
45aff0f
Merge branch 'master' into feature/add-notes-to-patients
Mar 16, 2020
9106487
Merge branch 'master' into feature/add-notes-to-patients
Mar 17, 2020
7b92279
Merge branch 'master' into feature/add-notes-to-patients
Mar 18, 2020
9e90182
Merge branch 'master' into feature/add-notes-to-patients
Mar 18, 2020
09b97fc
Merge branch 'master' into feature/add-notes-to-patients
Mar 18, 2020
42ee418
Merge branch 'master' into feature/add-notes-to-patients
Mar 19, 2020
607d2b3
Merge branch 'master' into feature/add-notes-to-patients
Mar 19, 2020
503c0d2
Merge branch 'master' into feature/add-notes-to-patients
Mar 19, 2020
2c3d37c
Merge branch 'master' into feature/add-notes-to-patients
Mar 19, 2020
bfb648f
Merge branch 'master' into feature/add-notes-to-patients
Mar 19, 2020
2ee94d2
Merge branch 'master' into feature/add-notes-to-patients
Mar 21, 2020
f525861
Merge branch 'master' into feature/add-notes-to-patients
Mar 21, 2020
5491e66
Merge branch 'master' into feature/add-notes-to-patients
Mar 21, 2020
5385b0f
Merge branch 'master' into feature/add-notes-to-patients
Mar 21, 2020
92ef93d
Merge branch 'master' into feature/add-notes-to-patients
matteovivona Mar 31, 2020
90a5d1d
Merge branch 'master' into feature/add-notes-to-patients
Apr 1, 2020
c715b06
Merge branch 'master' into feature/add-notes-to-patients
Apr 1, 2020
c6a206b
Merge branch 'master' into feature/add-notes-to-patients
Apr 2, 2020
b25ea3a
Merge branch 'master' of github.com:HospitalRun/hospitalrun-frontend …
jackcmeyer Apr 3, 2020
b3ddd24
feat(patients): adds tests for new notes modal
jackcmeyer Apr 3, 2020
6edc0ad
feat(patient): add notes tab tests
jackcmeyer Apr 3, 2020
a12809a
Merge branch 'master' into feature/add-notes-to-patients
Apr 3, 2020
3e03a04
Merge branch 'master' into feature/add-notes-to-patients
Apr 3, 2020
089a4e7
Merge branch 'master' into feature/add-notes-to-patients
Apr 3, 2020
126e195
ci(env): add back example env file
jackcmeyer Apr 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1 +1 @@
REACT_APP_HOSPITALRUN_API=http://0.0.0.0:3001
REACT_APP_HOSPITALRUN_API=http://0.0.0.0:3001
112 changes: 112 additions & 0 deletions src/__tests__/patients/notes/NewNoteModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import '../../../__mocks__/matchMediaMock'
import React from 'react'
import NewNoteModal from 'patients/notes/NewNoteModal'
import { shallow, mount } from 'enzyme'
import { Modal, Alert } from '@hospitalrun/components'
import { act } from '@testing-library/react'
import TextFieldWithLabelFormGroup from 'components/input/TextFieldWithLabelFormGroup'

describe('New Note Modal', () => {
it('should render a modal with the correct labels', () => {
const wrapper = shallow(
<NewNoteModal show onCloseButtonClick={jest.fn()} onSave={jest.fn()} toggle={jest.fn()} />,
)

const modal = wrapper.find(Modal)
expect(modal).toHaveLength(1)
expect(modal.prop('title')).toEqual('patient.notes.new')
expect(modal.prop('closeButton')?.children).toEqual('actions.cancel')
expect(modal.prop('closeButton')?.color).toEqual('danger')
expect(modal.prop('successButton')?.children).toEqual('patient.notes.new')
expect(modal.prop('successButton')?.color).toEqual('success')
expect(modal.prop('successButton')?.icon).toEqual('add')
})

it('should render a notes rich text editor', () => {
const wrapper = mount(
<NewNoteModal show onCloseButtonClick={jest.fn()} onSave={jest.fn()} toggle={jest.fn()} />,
)

const noteTextField = wrapper.find(TextFieldWithLabelFormGroup)
expect(noteTextField.prop('label')).toEqual('patient.note')
expect(noteTextField.prop('isRequired')).toBeTruthy()
expect(noteTextField).toHaveLength(1)
})

describe('on cancel', () => {
it('should call the onCloseButtonCLick function when the cancel button is clicked', () => {
const onCloseButtonClickSpy = jest.fn()
const wrapper = shallow(
<NewNoteModal
show
onCloseButtonClick={onCloseButtonClickSpy}
onSave={jest.fn()}
toggle={jest.fn()}
/>,
)

act(() => {
const modal = wrapper.find(Modal)
const { onClick } = modal.prop('closeButton') as any
onClick()
})

expect(onCloseButtonClickSpy).toHaveBeenCalledTimes(1)
})
})

describe('on save', () => {
const expectedDate = new Date()
const expectedNote = 'test'

Date.now = jest.fn(() => expectedDate.valueOf())
it('should call the onSave callback', () => {
const onSaveSpy = jest.fn()
const wrapper = mount(
<NewNoteModal show onCloseButtonClick={jest.fn()} onSave={onSaveSpy} toggle={jest.fn()} />,
)

act(() => {
const noteTextField = wrapper.find(TextFieldWithLabelFormGroup)
const onChange = noteTextField.prop('onChange') as any
onChange({ currentTarget: { value: expectedNote } })
})

wrapper.update()
act(() => {
const modal = wrapper.find(Modal)
const { onClick } = modal.prop('successButton') as any
onClick()
})

expect(onSaveSpy).toHaveBeenCalledTimes(1)
expect(onSaveSpy).toHaveBeenCalledWith({
text: expectedNote,
date: expectedDate.toISOString(),
})
})

it('should require a note be added', async () => {
const onSaveSpy = jest.fn()
const wrapper = mount(
<NewNoteModal show onCloseButtonClick={jest.fn()} onSave={onSaveSpy} toggle={jest.fn()} />,
)

await act(async () => {
const modal = wrapper.find(Modal)
const { onClick } = modal.prop('successButton') as any
await onClick()
})
wrapper.update()

const notesTextField = wrapper.find(TextFieldWithLabelFormGroup)
const errorAlert = wrapper.find(Alert)

expect(errorAlert).toHaveLength(1)
expect(errorAlert.prop('title')).toEqual('states.error')
expect(errorAlert.prop('message')).toEqual('patient.notes.error.unableToAdd')
expect(notesTextField.prop('feedback')).toEqual('patient.notes.error.noteRequired')
expect(onSaveSpy).not.toHaveBeenCalled()
})
})
})
129 changes: 129 additions & 0 deletions src/__tests__/patients/notes/NotesTab.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import '../../../__mocks__/matchMediaMock'
import React from 'react'
import PatientRepository from 'clients/db/PatientRepository'
import Note from 'model/Note'
import { createMemoryHistory } from 'history'
import configureMockStore from 'redux-mock-store'
import Patient from 'model/Patient'
import thunk from 'redux-thunk'
import { mount } from 'enzyme'
import { Router } from 'react-router'
import { Provider } from 'react-redux'
import NoteTab from 'patients/notes/NoteTab'
import * as components from '@hospitalrun/components'
import { act } from 'react-dom/test-utils'
import { mocked } from 'ts-jest/utils'
import NewNoteModal from 'patients/notes/NewNoteModal'
import Permissions from '../../../model/Permissions'
import * as patientSlice from '../../../patients/patient-slice'

const expectedPatient = {
id: '123',
notes: [{ date: new Date().toISOString(), text: 'notes1' } as Note],
} as Patient

const mockStore = configureMockStore([thunk])
const history = createMemoryHistory()

let user: any
let store: any

const setup = (patient = expectedPatient, permissions = [Permissions.WritePatients]) => {
user = { permissions }
store = mockStore({ patient, user })
const wrapper = mount(
<Router history={history}>
<Provider store={store}>
<NoteTab patient={patient} />
</Provider>
</Router>,
)

return wrapper
}

describe('Notes Tab', () => {
describe('Add New Note', () => {
beforeEach(() => {
jest.resetAllMocks()
jest.spyOn(PatientRepository, 'saveOrUpdate')
})

it('should render a add notes button', () => {
const wrapper = setup()

const addNoteButton = wrapper.find(components.Button)
expect(addNoteButton).toHaveLength(1)
expect(addNoteButton.text().trim()).toEqual('patient.notes.new')
})

it('should not render a add notes button if the user does not have permissions', () => {
const wrapper = setup(expectedPatient, [])

const addNotesButton = wrapper.find(components.Button)
expect(addNotesButton).toHaveLength(0)
})

it('should open the Add Notes Modal', () => {
const wrapper = setup()

act(() => {
const onClick = wrapper.find(components.Button).prop('onClick') as any
onClick()
})
wrapper.update()

expect(wrapper.find(components.Modal).prop('show')).toBeTruthy()
})

it('should update the patient with the new diagnosis when the save button is clicked', async () => {
const expectedNote = {
text: 'note text',
date: new Date().toISOString(),
} as Note
const expectedUpdatedPatient = {
...expectedPatient,
notes: [...(expectedPatient.notes as any), expectedNote],
} as Patient

const mockedPatientRepository = mocked(PatientRepository, true)
mockedPatientRepository.saveOrUpdate.mockResolvedValue(expectedUpdatedPatient)

const wrapper = setup()

await act(async () => {
const modal = wrapper.find(NewNoteModal)
await modal.prop('onSave')(expectedNote)
})

expect(mockedPatientRepository.saveOrUpdate).toHaveBeenCalledWith(expectedUpdatedPatient)
expect(store.getActions()).toContainEqual(patientSlice.updatePatientStart())
expect(store.getActions()).toContainEqual(
patientSlice.updatePatientSuccess(expectedUpdatedPatient),
)
})
})

describe('notes list', () => {
it('should list the patients diagnoses', () => {
const notes = expectedPatient.notes as Note[]
const wrapper = setup()

const list = wrapper.find(components.List)
const listItems = wrapper.find(components.ListItem)

expect(list).toHaveLength(1)
expect(listItems).toHaveLength(notes.length)
})

it('should render a warning message if the patient does not have any diagnoses', () => {
const wrapper = setup({ ...expectedPatient, notes: [] })

const alert = wrapper.find(components.Alert)

expect(alert).toHaveLength(1)
expect(alert.prop('title')).toEqual('patient.notes.warning.noNotes')
expect(alert.prop('message')).toEqual('patient.notes.addNoteAbove')
})
})
})
28 changes: 27 additions & 1 deletion src/__tests__/patients/view/ViewPatient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import RelatedPersonTab from 'patients/related-persons/RelatedPersonTab'
import * as ButtonBarProvider from 'page-header/ButtonBarProvider'
import Allergies from 'patients/allergies/Allergies'
import Diagnoses from 'patients/diagnoses/Diagnoses'
import NotesTab from 'patients/notes/NoteTab'
import Patient from '../../../model/Patient'
import PatientRepository from '../../../clients/db/PatientRepository'
import * as titleUtil from '../../../page-header/useTitle'
Expand Down Expand Up @@ -126,12 +127,13 @@ describe('ViewPatient', () => {
const tabs = tabsHeader.find(Tab)
expect(tabsHeader).toHaveLength(1)

expect(tabs).toHaveLength(5)
expect(tabs).toHaveLength(6)
expect(tabs.at(0).prop('label')).toEqual('patient.generalInformation')
expect(tabs.at(1).prop('label')).toEqual('patient.relatedPersons.label')
expect(tabs.at(2).prop('label')).toEqual('scheduling.appointments.label')
expect(tabs.at(3).prop('label')).toEqual('patient.allergies.label')
expect(tabs.at(4).prop('label')).toEqual('patient.diagnoses.label')
expect(tabs.at(5).prop('label')).toEqual('patient.notes.label')
})

it('should mark the general information tab as active and render the general information component when route is /patients/:id', async () => {
Expand Down Expand Up @@ -236,4 +238,28 @@ describe('ViewPatient', () => {
expect(diagnosesTab).toHaveLength(1)
expect(diagnosesTab.prop('patient')).toEqual(patient)
})

it('should mark the notes tab as active when it is clicked and render the note component when route is /patients/:id/notes', async () => {
let wrapper: any
await act(async () => {
wrapper = await setup()
})

await act(async () => {
const tabsHeader = wrapper.find(TabsHeader)
const tabs = tabsHeader.find(Tab)
tabs.at(5).prop('onClick')()
})

wrapper.update()

const tabsHeader = wrapper.find(TabsHeader)
const tabs = tabsHeader.find(Tab)
const notesTab = wrapper.find(NotesTab)

expect(history.location.pathname).toEqual(`/patients/${patient.id}/notes`)
expect(tabs.at(5).prop('active')).toBeTruthy()
expect(notesTab).toHaveLength(1)
expect(notesTab.prop('patient')).toEqual(patient)
})
})
16 changes: 13 additions & 3 deletions src/components/input/TextFieldWithLabelFormGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,25 @@ interface Props {
isEditable?: boolean
placeholder?: string
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
isRequired?: boolean
feedback?: string
isInvalid?: boolean
}

const TextFieldWithLabelFormGroup = (props: Props) => {
const { value, label, name, isEditable, onChange } = props
const { value, label, name, isEditable, isInvalid, feedback, onChange } = props
const id = `${name}TextField`
return (
<div className="form-group">
<Label text={label} htmlFor={id} />
<TextField rows={4} value={value} disabled={!isEditable} onChange={onChange} />
<Label text={label} htmlFor={id} isRequired />
<TextField
rows={4}
value={value}
disabled={!isEditable}
onChange={onChange}
isInvalid={isInvalid}
feedback={feedback}
/>
</div>
)
}
Expand Down
13 changes: 13 additions & 0 deletions src/locales/enUs/translations/patient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ export default {
addDiagnosisAbove: 'Add a diagnosis using the button above.',
successfullyAdded: 'Successfully added a new diagnosis!',
},
note: 'Note',
notes: {
label: 'Notes',
new: 'Add New Note',
warning: {
noNotes: 'No Notes',
},
error: {
noteRequired: 'Note is required.',
unableToAdd: 'Unable to add new note.',
},
addNoteAbove: 'Add a note using the button above.',
},
types: {
charity: 'Charity',
private: 'Private',
Expand Down
3 changes: 2 additions & 1 deletion src/locales/enUs/translations/patients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default {
viewPatient: 'View Patient',
newPatient: 'New Patient',
successfullyCreated: 'Successfully created patient',
successfullyAddedRelatedPerson: 'Successfully added the new related person',
successfullyAddedNote: 'Successfully added the new note',
successfullyAddedRelatedPerson: 'Successfully added a new related person',
},
}
4 changes: 4 additions & 0 deletions src/model/Note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default interface Note {
date: string
text: string
}
2 changes: 2 additions & 0 deletions src/model/Patient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ContactInformation from './ContactInformation'
import RelatedPerson from './RelatedPerson'
import Allergy from './Allergy'
import Diagnosis from './Diagnosis'
import Note from './Note'

export default interface Patient extends AbstractDBModel, Name, ContactInformation {
sex: string
Expand All @@ -16,4 +17,5 @@ export default interface Patient extends AbstractDBModel, Name, ContactInformati
relatedPersons?: RelatedPerson[]
allergies?: Allergy[]
diagnoses?: Diagnosis[]
notes?: Note[]
}
Loading