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 view all incidents
Browse files Browse the repository at this point in the history
  • Loading branch information
jackcmeyer committed May 3, 2020
1 parent da2ea77 commit f11d8e9
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 5 deletions.
49 changes: 49 additions & 0 deletions src/__tests__/incidents/incidents-slice.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import createMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { AnyAction } from 'redux'
import { RootState } from '../../store'
import incidents, {
fetchIncidents,
fetchIncidentsStart,
fetchIncidentsSuccess,
} from '../../incidents/incidents-slice'
import IncidentRepository from '../../clients/db/IncidentRepository'
import Incident from '../../model/Incident'

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

describe('Incidents Slice', () => {
describe('actions', () => {
it('should setup the default state correctly', () => {
const incidentsStore = incidents(undefined, {} as AnyAction)
expect(incidentsStore.status).toEqual('loading')
expect(incidentsStore.incidents).toEqual([])
})

it('should handle fetch incidents start', () => {
const incidentsStore = incidents(undefined, fetchIncidentsStart())
expect(incidentsStore.status).toEqual('loading')
})

it('should handle fetch incidents success', () => {
const expectedIncidents = [{ id: '123' }] as Incident[]
const incidentsStore = incidents(undefined, fetchIncidentsSuccess(expectedIncidents))
expect(incidentsStore.status).toEqual('completed')
expect(incidentsStore.incidents).toEqual(expectedIncidents)
})

describe('fetch incidents', () => {
it('should fetch all of the incidents', async () => {
const expectedIncidents = [{ id: '123' }] as Incident[]
jest.spyOn(IncidentRepository, 'findAll').mockResolvedValue(expectedIncidents)
const store = mockStore()

await store.dispatch(fetchIncidents())

expect(store.getActions()[0]).toEqual(fetchIncidentsStart())
expect(IncidentRepository.findAll).toHaveBeenCalledTimes(1)
expect(store.getActions()[1]).toEqual(fetchIncidentsSuccess(expectedIncidents))
})
})
})
})
62 changes: 59 additions & 3 deletions src/__tests__/incidents/list/ViewIncidents.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,24 @@ import * as titleUtil from '../../../page-header/useTitle'
import * as ButtonBarProvider from '../../../page-header/ButtonBarProvider'
import * as breadcrumbUtil from '../../../breadcrumbs/useAddBreadcrumbs'
import ViewIncidents from '../../../incidents/list/ViewIncidents'
import Incident from '../../../model/Incident'
import IncidentRepository from '../../../clients/db/IncidentRepository'

const mockStore = createMockStore([thunk])

describe('View Incidents', () => {
let history: any
const expectedDate = new Date(2020, 5, 3, 19, 48)
const expectedIncidents = [
{
id: '123',
code: 'some code',
status: 'reported',
reportedBy: 'some user id',
date: expectedDate.toISOString(),
reportedOn: expectedDate.toISOString(),
},
] as Incident[]

let setButtonToolBarSpy: any
const setup = async (permissions: Permissions[]) => {
Expand All @@ -25,6 +38,7 @@ describe('View Incidents', () => {
setButtonToolBarSpy = jest.fn()
jest.spyOn(titleUtil, 'default')
jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy)
jest.spyOn(IncidentRepository, 'findAll').mockResolvedValue(expectedIncidents)

history = createMemoryHistory()
history.push(`/incidents`)
Expand All @@ -33,6 +47,9 @@ describe('View Incidents', () => {
user: {
permissions,
},
incidents: {
incidents: expectedIncidents,
},
})

let wrapper: any
Expand All @@ -53,9 +70,48 @@ describe('View Incidents', () => {
return wrapper
}

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

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

it('should render a table with the incidents', async () => {
const wrapper = await setup([Permissions.ViewIncidents])

expect(titleUtil.default).toHaveBeenCalledWith('incidents.reports.label')
const table = wrapper.find('table')
const tableHeader = table.find('thead')
const tableBody = table.find('tbody')
const tableHeaders = tableHeader.find('th')
const tableColumns = tableBody.find('td')

expect(tableHeaders.at(0).text()).toEqual('incidents.reports.code')
expect(tableHeaders.at(1).text()).toEqual('incidents.reports.dateOfIncident')
expect(tableHeaders.at(2).text()).toEqual('incidents.reports.reportedBy')
expect(tableHeaders.at(3).text()).toEqual('incidents.reports.reportedOn')
expect(tableHeaders.at(4).text()).toEqual('incidents.reports.status')

expect(tableColumns.at(0).text()).toEqual(expectedIncidents[0].code)
expect(tableColumns.at(1).text()).toEqual('2020-06-03 07:48 PM')
expect(tableColumns.at(2).text()).toEqual(expectedIncidents[0].reportedBy)
expect(tableColumns.at(3).text()).toEqual('2020-06-03 07:48 PM')
expect(tableColumns.at(4).text()).toEqual(expectedIncidents[0].status)
})
})

describe('on table row click', () => {
it('should navigate to the incident when the table row is clicked', async () => {
const wrapper = await setup([Permissions.ViewIncidents])

const tr = wrapper.find('tr').at(1)

act(() => {
const onClick = tr.prop('onClick')
onClick()
})

expect(history.location.pathname).toEqual(`/incidents/${expectedIncidents[0].id}`)
})
})
})
44 changes: 44 additions & 0 deletions src/incidents/incidents-slice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { AppThunk } from 'store'
import Incident from '../model/Incident'
import IncidentRepository from '../clients/db/IncidentRepository'

interface IncidentsState {
incidents: Incident[]
status: 'loading' | 'completed'
}

const initialState: IncidentsState = {
incidents: [],
status: 'loading',
}

function start(state: IncidentsState) {
state.status = 'loading'
}

function finish(state: IncidentsState, { payload }: PayloadAction<Incident[]>) {
state.status = 'completed'
state.incidents = payload
}

const incidentSlice = createSlice({
name: 'lab',
initialState,
reducers: {
fetchIncidentsStart: start,
fetchIncidentsSuccess: finish,
},
})

export const { fetchIncidentsStart, fetchIncidentsSuccess } = incidentSlice.actions

export const fetchIncidents = (): AppThunk => async (dispatch) => {
dispatch(fetchIncidentsStart())

const incidents = await IncidentRepository.findAll()

dispatch(fetchIncidentsSuccess(incidents))
}

export default incidentSlice.reducer
45 changes: 43 additions & 2 deletions src/incidents/list/ViewIncidents.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,53 @@
import React from 'react'
import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import format from 'date-fns/format'
import { useHistory } from 'react-router'
import useTitle from '../../page-header/useTitle'
import { RootState } from '../../store'
import { fetchIncidents } from '../incidents-slice'
import Incident from '../../model/Incident'

const ViewIncidents = () => {
const { t } = useTranslation()
const history = useHistory()
const dispatch = useDispatch()
useTitle(t('incidents.reports.label'))

return <h1>Reported Incidents</h1>
const { incidents } = useSelector((state: RootState) => state.incidents)

useEffect(() => {
dispatch(fetchIncidents())
}, [dispatch])

const onTableRowClick = (incident: Incident) => {
history.push(`incidents/${incident.id}`)
}

return (
<table className="table table-hover">
<thead className="thead-light">
<tr>
<th>{t('incidents.reports.code')}</th>
<th>{t('incidents.reports.dateOfIncident')}</th>
<th>{t('incidents.reports.reportedBy')}</th>
<th>{t('incidents.reports.reportedOn')}</th>
<th>{t('incidents.reports.status')}</th>
</tr>
</thead>
<tbody>
{incidents.map((incident: Incident) => (
<tr onClick={() => onTableRowClick(incident)} key={incident.id}>
<td>{incident.code}</td>
<td>{format(new Date(incident.date), 'yyyy-MM-dd hh:mm a')}</td>
<td>{incident.reportedBy}</td>
<td>{format(new Date(incident.reportedOn), 'yyyy-MM-dd hh:mm a')}</td>
<td>{incident.status}</td>
</tr>
))}
</tbody>
</table>
)
}

export default ViewIncidents
4 changes: 4 additions & 0 deletions src/locales/enUs/translations/incidents/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export default {
category: 'Category',
categoryItem: 'Category Item',
description: 'Description of Incident',
code: 'Code',
reportedBy: 'Reported By',
reportedOn: 'Reported On',
status: 'Status',
error: {
dateRequired: 'Date is required.',
dateMustBeInThePast: 'Date must be in the past.',
Expand Down
1 change: 1 addition & 0 deletions src/model/Incident.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export default interface Incident extends AbstractDBModel {
category: string
categoryItem: string
description: string
status: 'reported'
}
2 changes: 2 additions & 0 deletions src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import title from '../page-header/title-slice'
import user from '../user/user-slice'
import lab from '../labs/lab-slice'
import incident from '../incidents/incident-slice'
import incidents from '../incidents/incidents-slice'
import breadcrumbs from '../breadcrumbs/breadcrumbs-slice'
import components from '../components/component-slice'

Expand All @@ -22,6 +23,7 @@ const reducer = combineReducers({
components,
lab,
incident,
incidents,
})

const store = configureStore({
Expand Down

0 comments on commit f11d8e9

Please sign in to comment.