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

Commit

Permalink
feat(allergies): adds ability to add allergy to appointment
Browse files Browse the repository at this point in the history
  • Loading branch information
jackcmeyer committed Feb 24, 2020
1 parent 779bc10 commit 04a2160
Show file tree
Hide file tree
Showing 12 changed files with 453 additions and 1 deletion.
132 changes: 132 additions & 0 deletions src/__tests__/patients/allergies/Allergies.test.tsx
@@ -0,0 +1,132 @@
import '../../../__mocks__/matchMediaMock'
import React from 'react'
import { mount } from 'enzyme'
import Allergies from 'patients/allergies/Allergies'
import Permissions from 'model/Permissions'
import configureMockStore from 'redux-mock-store'
import { createMemoryHistory } from 'history'
import thunk from 'redux-thunk'
import { Router } from 'react-router'
import { Provider } from 'react-redux'
import Patient from 'model/Patient'
import User from 'model/User'
import { Button, Modal, List, ListItem, Alert } from '@hospitalrun/components'
import { act } from '@testing-library/react'
import { mocked } from 'ts-jest/utils'
import PatientRepository from 'clients/db/PatientRepository'
import Allergy from 'model/Allergy'
import NewAllergyModal from 'patients/allergies/NewAllergyModal'
import * as patientSlice from '../../../patients/patient-slice'

const mockStore = configureMockStore([thunk])
const history = createMemoryHistory()
const expectedPatient = {
id: '123',
rev: '123',
allergies: [
{ id: '1', name: 'allergy1' },
{ id: '2', name: 'allergy2' },
],
} as Patient

let user: any
let store: any

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

return wrapper
}

describe('Allergies', () => {
beforeEach(() => {
jest.resetAllMocks()
jest.spyOn(PatientRepository, 'saveOrUpdate')
})

describe('add new allergy button', () => {
it('should render a button to add new allergies', () => {
const wrapper = setup()

const addAllergyButton = wrapper.find(Button)
expect(addAllergyButton).toHaveLength(1)
expect(addAllergyButton.text().trim()).toEqual('patient.allergies.new')
})

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

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

it('should open the New Allergy Modal when clicked', () => {
const wrapper = setup()

act(() => {
const addAllergyButton = wrapper.find(Button)
const onClick = addAllergyButton.prop('onClick') as any
onClick({} as React.MouseEvent<HTMLButtonElement>)
})

wrapper.update()

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

it('should update the patient with the new allergy when the save button is clicked', async () => {
const expectedAllergy = { name: 'name' } as Allergy
const expectedUpdatedPatient = {
...expectedPatient,
allergies: [...(expectedPatient.allergies as any), expectedAllergy],
} as Patient

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

const wrapper = setup()

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

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

describe('allergy list', () => {
it('should list the patients allergies', () => {
const allergies = expectedPatient.allergies as Allergy[]
const wrapper = setup()

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

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

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

const alert = wrapper.find(Alert)

expect(alert).toHaveLength(1)
expect(alert.prop('title')).toEqual('patient.allergies.warning.noAllergies')
expect(alert.prop('message')).toEqual('patient.allergies.addAllergyAbove')
})
})
})
90 changes: 90 additions & 0 deletions src/__tests__/patients/allergies/NewAllergyModal.test.tsx
@@ -0,0 +1,90 @@
import '../../../__mocks__/matchMediaMock'
import React from 'react'
import NewAllergyModal from 'patients/allergies/NewAllergyModal'
import { shallow, mount } from 'enzyme'
import { Modal, Alert } from '@hospitalrun/components'
import { act } from '@testing-library/react'
import Allergy from 'model/Allergy'

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

const modal = wrapper.find(Modal)
expect(modal).toHaveLength(1)
expect(modal.prop('title')).toEqual('patient.allergies.new')
expect(modal.prop('closeButton')?.children).toEqual('actions.cancel')
expect(modal.prop('closeButton')?.color).toEqual('danger')
expect(modal.prop('successButton')?.children).toEqual('patient.allergies.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(
<NewAllergyModal 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 onSaveSpy = jest.fn()
const wrapper = mount(
<NewAllergyModal show onCloseButtonClick={jest.fn()} onSave={onSaveSpy} />,
)

act(() => {
const input = wrapper.findWhere((c) => c.prop('name') === 'name')
const onChange = input.prop('onChange')
onChange({ target: { value: expectedName } })
})

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 } as Allergy)
})

it('should display an error message if the name field is not filled out', () => {
const wrapper = mount(
<NewAllergyModal
show
toggle={jest.fn()}
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.allergies.error.nameRequired')
})
})
})
28 changes: 27 additions & 1 deletion src/__tests__/patients/view/ViewPatient.test.tsx
Expand Up @@ -12,6 +12,7 @@ import GeneralInformation from 'patients/GeneralInformation'
import { createMemoryHistory } from 'history'
import RelatedPersonTab from 'patients/related-persons/RelatedPersonTab'
import * as ButtonBarProvider from 'page-header/ButtonBarProvider'
import Allergies from 'patients/allergies/Allergies'
import Patient from '../../../model/Patient'
import PatientRepository from '../../../clients/db/PatientRepository'
import * as titleUtil from '../../../page-header/useTitle'
Expand Down Expand Up @@ -113,10 +114,11 @@ describe('ViewPatient', () => {
const tabs = tabsHeader.find(Tab)
expect(tabsHeader).toHaveLength(1)

expect(tabs).toHaveLength(3)
expect(tabs).toHaveLength(4)
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')
})

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 @@ -173,4 +175,28 @@ describe('ViewPatient', () => {
expect(relatedPersonTab).toHaveLength(1)
expect(relatedPersonTab.prop('patient')).toEqual(patient)
})

it('should mark the rallergies tab as active when it is clicked and render the allergies component when route is /patients/:id/allergies', 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(3).prop('onClick')()
})

wrapper.update()

const tabsHeader = wrapper.find(TabsHeader)
const tabs = tabsHeader.find(Tab)
const allergiesTab = wrapper.find(Allergies)

expect(history.location.pathname).toEqual(`/patients/${patient.id}/allergies`)
expect(tabs.at(3).prop('active')).toBeTruthy()
expect(allergiesTab).toHaveLength(1)
expect(allergiesTab.prop('patient')).toEqual(patient)
})
})
11 changes: 11 additions & 0 deletions src/locales/en-US/translation.json
Expand Up @@ -52,6 +52,17 @@
},
"errors": {
"patientGivenNameRequired": "Patient Given Name is required."
},
"allergies": {
"label": "Allergies",
"new": "Add Allergy",
"error": {
"nameRequired": "Name is required."
},
"warning": {
"noAllergies": "No Allergies"
},
"addAllergyAbove": "Add an allergy using the button above."
}
},
"sex": {
Expand Down
4 changes: 4 additions & 0 deletions src/model/Allergy.ts
@@ -0,0 +1,4 @@
export default interface Allergy {
id: string
name: string
}
2 changes: 2 additions & 0 deletions src/model/Patient.ts
Expand Up @@ -2,6 +2,7 @@ import AbstractDBModel from './AbstractDBModel'
import Name from './Name'
import ContactInformation from './ContactInformation'
import RelatedPerson from './RelatedPerson'
import Allergy from './Allergy'

export default interface Patient extends AbstractDBModel, Name, ContactInformation {
sex: string
Expand All @@ -12,4 +13,5 @@ export default interface Patient extends AbstractDBModel, Name, ContactInformati
type?: string
friendlyId: string
relatedPersons?: RelatedPerson[]
allergies?: Allergy[]
}
1 change: 1 addition & 0 deletions src/model/Permissions.ts
Expand Up @@ -4,6 +4,7 @@ enum Permissions {
ReadAppointments = 'read:appointments',
WriteAppointments = 'write:appointments',
DeleteAppointment = 'delete:appointment',
AddAllergy = 'write:allergy',
}

export default Permissions

0 comments on commit 04a2160

Please sign in to comment.