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

Commit

Permalink
feat(incidents): adds ability to report incident
Browse files Browse the repository at this point in the history
  • Loading branch information
jackcmeyer committed May 3, 2020
1 parent 2e9e985 commit 4a4a682
Show file tree
Hide file tree
Showing 13 changed files with 631 additions and 13 deletions.
1 change: 1 addition & 0 deletions src/__tests__/incidents/Incidents.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('Incidents', () => {
user: { permissions: [Permissions.ReportIncident] },
breadcrumbs: { breadcrumbs: [] },
components: { sidebarCollapsed: false },
incident: {},
})

const wrapper = mount(
Expand Down
147 changes: 147 additions & 0 deletions src/__tests__/incidents/incident-slice.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { AnyAction } from 'redux'
import createMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import shortid from 'shortid'
import { addDays } from 'date-fns'
import incident, {
reportIncidentStart,
reportIncidentSuccess,
reportIncidentError,
reportIncident,
} from '../../incidents/incident-slice'
import Incident from '../../model/Incident'
import { RootState } from '../../store'
import IncidentRepository from '../../clients/db/IncidentRepository'

const mockStore = createMockStore<RootState, any>([thunk])

describe('incident slice', () => {
describe('actions', () => {
it('should default the store correctly', () => {
const incidentStore = incident(undefined, {} as AnyAction)
expect(incidentStore.status).toEqual('loading')
expect(incidentStore.incident).toBeUndefined()
expect(incidentStore.error).toBeUndefined()
})

it('should handle the report incident start', () => {
const incidentStore = incident(undefined, reportIncidentStart)
expect(incidentStore.status).toEqual('loading')
})

it('should handle the report incident success', () => {
const expectedIncident = {
id: 'some id',
reportedOn: new Date().toISOString(),
reportedBy: 'some user id',
description: 'description',
date: new Date().toISOString(),
department: 'some department',
category: 'category',
categoryItem: 'categoryItem',
code: 'some code',
} as Incident

const incidentStore = incident(undefined, reportIncidentSuccess(expectedIncident))
expect(incidentStore.status).toEqual('completed')
expect(incidentStore.incident).toEqual(expectedIncident)
expect(incidentStore.error).toBeUndefined()
})

it('should handle the report incident error', () => {
const expectedError = {
date: 'some description error',
description: 'some description error',
}

const incidentStore = incident(undefined, reportIncidentError(expectedError))
expect(incidentStore.status).toEqual('error')
expect(incidentStore.error).toEqual(expectedError)
})
})

describe('report incident', () => {
beforeEach(() => {
jest.restoreAllMocks()
})

it('should successfully create an incident', async () => {
const expectedDate = new Date()
const expectedShortId = '123456'
Date.now = jest.fn().mockReturnValue(expectedDate)
jest.spyOn(shortid, 'generate').mockReturnValue(expectedShortId)
const onSuccessSpy = jest.fn()
const newIncident = {
description: 'description',
date: expectedDate.toISOString(),
department: 'some department',
category: 'category',
categoryItem: 'categoryItem',
} as Incident

const expectedIncident = {
...newIncident,
code: `I-${expectedShortId}`,
reportedOn: expectedDate.toISOString(),
reportedBy: 'some user id',
}

jest.spyOn(IncidentRepository, 'save').mockResolvedValue(expectedIncident)

const store = mockStore({ user: { user: { id: expectedIncident.reportedBy } } })

await store.dispatch(reportIncident(newIncident, onSuccessSpy))

expect(store.getActions()[0]).toEqual(reportIncidentStart())
expect(store.getActions()[1]).toEqual(reportIncidentSuccess(expectedIncident))
expect(IncidentRepository.save).toHaveBeenCalledWith(expectedIncident)
expect(onSuccessSpy).toHaveBeenLastCalledWith(expectedIncident)
})

it('should validate the required fields apart of an incident', async () => {
const onSuccessSpy = jest.fn()
const newIncident = {} as Incident
jest.spyOn(IncidentRepository, 'save')
const expectedError = {
date: 'incidents.reports.error.dateRequired',
department: 'incidents.reports.error.departmentRequired',
category: 'incidents.reports.error.categoryRequired',
categoryItem: 'incidents.reports.error.categoryItemRequired',
description: 'incidents.reports.error.descriptionRequired',
}

const store = mockStore({ user: { user: { id: 'some id' } } })

await store.dispatch(reportIncident(newIncident, onSuccessSpy))

expect(store.getActions()[0]).toEqual(reportIncidentStart())
expect(store.getActions()[1]).toEqual(reportIncidentError(expectedError))
expect(IncidentRepository.save).not.toHaveBeenCalled()
expect(onSuccessSpy).not.toHaveBeenCalled()
})

it('should validate that the incident date is not a date in the future', async () => {
const onSuccessSpy = jest.fn()
const newIncident = {
description: 'description',
date: addDays(new Date(), 4),
department: 'some department',
category: 'category',
categoryItem: 'categoryItem',
} as Incident
jest.spyOn(IncidentRepository, 'save')
const expectedError = {
date: 'incidents.reports.error.dateMustBeInThePast',
}

const store = mockStore({ user: { user: { id: 'some id' } } })

await store.dispatch(reportIncident(newIncident, onSuccessSpy))

expect(store.getActions()[0]).toEqual(reportIncidentStart())
expect(store.getActions()[1]).toEqual(reportIncidentError(expectedError))
expect(IncidentRepository.save).not.toHaveBeenCalled()
expect(onSuccessSpy).not.toHaveBeenCalled()
})
})
})
192 changes: 183 additions & 9 deletions src/__tests__/incidents/report/ReportIncident.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@ import { Provider } from 'react-redux'
import { Route, Router } from 'react-router'
import createMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { Button } from '@hospitalrun/components'
import Permissions from '../../../model/Permissions'
import * as titleUtil from '../../../page-header/useTitle'
import * as ButtonBarProvider from '../../../page-header/ButtonBarProvider'
import * as breadcrumbUtil from '../../../breadcrumbs/useAddBreadcrumbs'
import ReportIncident from '../../../incidents/report/ReportIncident'
import IncidentRepository from '../../../clients/db/IncidentRepository'

const mockStore = createMockStore([thunk])

describe('Report Incident', () => {
let history: any

let setButtonToolBarSpy: any
const setup = async (permissions: Permissions[]) => {
const setup = async (permissions: Permissions[], error: any = {}) => {
jest.resetAllMocks()
jest.spyOn(breadcrumbUtil, 'default')
setButtonToolBarSpy = jest.fn()
Expand All @@ -32,6 +34,12 @@ describe('Report Incident', () => {
title: '',
user: {
permissions,
user: {
id: 'some id',
},
},
incident: {
error,
},
})

Expand All @@ -53,17 +61,183 @@ describe('Report Incident', () => {
return wrapper
}

it('should set the title', async () => {
await setup([Permissions.ReportIncident])
describe('layout', () => {
it('should set the title', async () => {
await setup([Permissions.ReportIncident])

expect(titleUtil.default).toHaveBeenCalledWith('incidents.reports.new')
})

it('should set the breadcrumbs properly', async () => {
await setup([Permissions.ReportIncident])

expect(breadcrumbUtil.default).toHaveBeenCalledWith([
{ i18nKey: 'incidents.reports.new', location: '/incidents/new' },
])
})

it('should have a date input', async () => {
const wrapper = await setup([Permissions.ReportIncident])

expect(titleUtil.default).toHaveBeenCalledWith('incidents.reports.new')
const dateInput = wrapper.findWhere((w) => w.prop('name') === 'dateOfIncident')

expect(dateInput).toHaveLength(1)
expect(dateInput.prop('label')).toEqual('incidents.reports.dateOfIncident')
expect(dateInput.prop('isEditable')).toBeTruthy()
expect(dateInput.prop('isRequired')).toBeTruthy()
})

it('should have a department input', async () => {
const wrapper = await setup([Permissions.ReportIncident])

const departmentInput = wrapper.findWhere((w) => w.prop('name') === 'department')

expect(departmentInput).toHaveLength(1)
expect(departmentInput.prop('label')).toEqual('incidents.reports.department')
expect(departmentInput.prop('isEditable')).toBeTruthy()
expect(departmentInput.prop('isRequired')).toBeTruthy()
})

it('should have a category input', async () => {
const wrapper = await setup([Permissions.ReportIncident])

const categoryInput = wrapper.findWhere((w) => w.prop('name') === 'category')

expect(categoryInput).toHaveLength(1)
expect(categoryInput.prop('label')).toEqual('incidents.reports.category')
expect(categoryInput.prop('isEditable')).toBeTruthy()
expect(categoryInput.prop('isRequired')).toBeTruthy()
})

it('should have a category item input', async () => {
const wrapper = await setup([Permissions.ReportIncident])

const categoryInput = wrapper.findWhere((w) => w.prop('name') === 'categoryItem')

expect(categoryInput).toHaveLength(1)
expect(categoryInput.prop('label')).toEqual('incidents.reports.categoryItem')
expect(categoryInput.prop('isEditable')).toBeTruthy()
expect(categoryInput.prop('isRequired')).toBeTruthy()
})

it('should have a description input', async () => {
const wrapper = await setup([Permissions.ReportIncident])

const descriptionInput = wrapper.findWhere((w) => w.prop('name') === 'description')

expect(descriptionInput).toHaveLength(1)
expect(descriptionInput.prop('label')).toEqual('incidents.reports.description')
expect(descriptionInput.prop('isEditable')).toBeTruthy()
expect(descriptionInput.prop('isRequired')).toBeTruthy()
})
})

it('should set the breadcrumbs properly', async () => {
await setup([Permissions.ReportIncident])
describe('error handling', () => {
it('should display the error messages', async () => {
const error = {
date: 'some date error',
department: 'some department error',
category: 'some category error',
categoryItem: 'some category item error',
description: 'some description error',
}

expect(breadcrumbUtil.default).toHaveBeenCalledWith([
{ i18nKey: 'incidents.reports.new', location: '/incidents/new' },
])
const wrapper = await setup([Permissions.ReportIncident], error)

const dateInput = wrapper.findWhere((w) => w.prop('name') === 'dateOfIncident')
const departmentInput = wrapper.findWhere((w) => w.prop('name') === 'department')
const categoryInput = wrapper.findWhere((w) => w.prop('name') === 'category')
const categoryItemInput = wrapper.findWhere((w) => w.prop('name') === 'categoryItem')
const descriptionInput = wrapper.findWhere((w) => w.prop('name') === 'description')

expect(dateInput.prop('isInvalid')).toBeTruthy()
expect(dateInput.prop('feedback')).toEqual(error.date)

expect(departmentInput.prop('isInvalid')).toBeTruthy()
expect(departmentInput.prop('feedback')).toEqual(error.department)

expect(categoryInput.prop('isInvalid')).toBeTruthy()
expect(categoryInput.prop('feedback')).toEqual(error.category)

expect(categoryItemInput.prop('isInvalid')).toBeTruthy()
expect(categoryItemInput.prop('feedback')).toEqual(error.categoryItem)

expect(descriptionInput.prop('isInvalid')).toBeTruthy()
expect(descriptionInput.prop('feedback')).toEqual(error.description)
})
})

describe('on save', () => {
it('should dispatch the report incident action', async () => {
const wrapper = await setup([Permissions.ReportIncident])
const expectedIncident = {
date: new Date().toISOString(),
department: 'some department',
category: 'some category',
categoryItem: 'some category item',
description: 'some description',
}
jest
.spyOn(IncidentRepository, 'save')
.mockResolvedValue({ id: 'someId', ...expectedIncident })

const dateInput = wrapper.findWhere((w) => w.prop('name') === 'dateOfIncident')
act(() => {
const onChange = dateInput.prop('onChange')
onChange(new Date(expectedIncident.date))
})

const departmentInput = wrapper.findWhere((w) => w.prop('name') === 'department')
act(() => {
const onChange = departmentInput.prop('onChange')
onChange({ currentTarget: { value: expectedIncident.department } })
})

const categoryInput = wrapper.findWhere((w) => w.prop('name') === 'category')
act(() => {
const onChange = categoryInput.prop('onChange')
onChange({ currentTarget: { value: expectedIncident.category } })
})

const categoryItemInput = wrapper.findWhere((w) => w.prop('name') === 'categoryItem')
act(() => {
const onChange = categoryItemInput.prop('onChange')
onChange({ currentTarget: { value: expectedIncident.categoryItem } })
})

const descriptionInput = wrapper.findWhere((w) => w.prop('name') === 'description')
act(() => {
const onChange = descriptionInput.prop('onChange')
onChange({ currentTarget: { value: expectedIncident.description } })
})
wrapper.update()

const saveButton = wrapper.find(Button).at(0)
await act(async () => {
const onClick = saveButton.prop('onClick')
onClick()
})

expect(IncidentRepository.save).toHaveBeenCalledTimes(1)
expect(IncidentRepository.save).toHaveBeenCalledWith(
expect.objectContaining(expectedIncident),
)
expect(history.location.pathname).toEqual(`/incidents/someId`)
})
})

describe('on cancel', () => {
it('should navigate to /incidents', async () => {
const wrapper = await setup([Permissions.ReportIncident])

const cancelButton = wrapper.find(Button).at(1)

act(() => {
const onClick = cancelButton.prop('onClick') as any
onClick()
})

expect(history.location.pathname).toEqual('/incidents')
})
})
})
11 changes: 11 additions & 0 deletions src/clients/db/IncidentRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Repository from './Repository'
import { incidents } from '../../config/pouchdb'
import Incident from '../../model/Incident'

export class IncidentRepository extends Repository<Incident> {
constructor() {
super(incidents)
}
}

export default new IncidentRepository()
Loading

0 comments on commit 4a4a682

Please sign in to comment.