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

Commit

Permalink
Merge pull request #2273 from HospitalRun/remove-redux
Browse files Browse the repository at this point in the history
refactor(redux): add react-query for incidents and patients search
  • Loading branch information
matteovivona authored Aug 12, 2020
2 parents c0225b1 + 7971cf1 commit b1884c8
Show file tree
Hide file tree
Showing 72 changed files with 2,076 additions and 1,591 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"react-bootstrap-typeahead": "~5.1.0",
"react-dom": "~16.13.0",
"react-i18next": "~11.7.0",
"react-query": "~2.5.13",
"react-query-devtools": "~2.3.2",
"react-redux": "~7.2.0",
"react-router": "~5.2.0",
"react-router-dom": "~5.2.0",
Expand Down Expand Up @@ -114,8 +116,8 @@
"build": "react-scripts build",
"update": "npx npm-check -u",
"prepublishOnly": "npm run build",
"test": "npm run translation:check && react-scripts test --detectOpenHandles",
"test:ci": "cross-env CI=true react-scripts test --passWithNoTests",
"test": "npm run translation:check && react-scripts test --detectOpenHandles --testPathIgnorePatterns=src/__tests__/test-utils",
"test:ci": "cross-env CI=true react-scripts test --passWithNoTests --testPathIgnorePatterns=src/__tests__/test-utils",
"lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" \"scripts/check-translations/**/*.{js,ts}\"",
"lint:fix": "eslint \"src/**/*.{js,jsx,ts,tsx}\" \"scripts/check-translations/**/*.{js,ts}\" --fix",
"lint-staged": "lint-staged",
Expand Down
20 changes: 12 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Spinner } from '@hospitalrun/components'
import React, { Suspense, useEffect, useState } from 'react'
import { ReactQueryDevtools } from 'react-query-devtools'
import { useDispatch } from 'react-redux'
import { BrowserRouter, Route, Switch } from 'react-router-dom'

Expand Down Expand Up @@ -33,14 +34,17 @@ const App: React.FC = () => {
}

return (
<BrowserRouter>
<Suspense fallback={<Spinner color="blue" loading size={[10, 25]} type="ScaleLoader" />}>
<Switch>
<Route exact path="/login" component={Login} />
<Route path="/" component={HospitalRun} />
</Switch>
</Suspense>
</BrowserRouter>
<>
<BrowserRouter>
<Suspense fallback={<Spinner color="blue" loading size={[10, 25]} type="ScaleLoader" />}>
<Switch>
<Route exact path="/login" component={Login} />
<Route path="/" component={HospitalRun} />
</Switch>
</Suspense>
</BrowserRouter>
<ReactQueryDevtools initialIsOpen={false} />
</>
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/HospitalRun.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { addBreadcrumbs } from '../page-header/breadcrumbs/breadcrumbs-slice'
import Appointments from '../scheduling/appointments/Appointments'
import Settings from '../settings/Settings'
import ImagingRepository from '../shared/db/ImagingRepository'
import IncidentRepository from '../shared/db/IncidentRepository'
import LabRepository from '../shared/db/LabRepository'
import Permissions from '../shared/model/Permissions'
import { RootState } from '../shared/store'
Expand All @@ -32,7 +33,6 @@ describe('HospitalRun', () => {
appointments: { appointments: [] },
breadcrumbs: { breadcrumbs: [] },
components: { sidebarCollapsed: false },
incidents: { incidents: [] },
} as any)

const wrapper = mount(
Expand Down Expand Up @@ -127,12 +127,12 @@ describe('HospitalRun', () => {

describe('/incidents', () => {
it('should render the Incidents component when /incidents is accessed', async () => {
jest.spyOn(IncidentRepository, 'search').mockResolvedValue([])
const store = mockStore({
title: 'test',
user: { user: { id: '123' }, permissions: [Permissions.ViewIncidents] },
breadcrumbs: { breadcrumbs: [] },
components: { sidebarCollapsed: false },
incidents: { incidents: [] },
} as any)

let wrapper: any
Expand Down
121 changes: 40 additions & 81 deletions src/__tests__/incidents/Incidents.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { act } from '@testing-library/react'
import { mount } from 'enzyme'
import { mount, ReactWrapper } from 'enzyme'
import React from 'react'
import { act } from 'react-dom/test-utils'
import { Provider } from 'react-redux'
import { MemoryRouter } from 'react-router-dom'
import createMockStore from 'redux-mock-store'
Expand All @@ -17,102 +17,61 @@ import { RootState } from '../../shared/store'
const mockStore = createMockStore<RootState, any>([thunk])

describe('Incidents', () => {
const setup = async (permissions: Permissions[], path: string) => {
const expectedIncident = {
id: '1234',
code: '1234',
date: new Date().toISOString(),
reportedOn: new Date().toISOString(),
} as Incident
jest.spyOn(IncidentRepository, 'search').mockResolvedValue([])
jest.spyOn(IncidentRepository, 'find').mockResolvedValue(expectedIncident)
const store = mockStore({
title: 'test',
user: { permissions },
breadcrumbs: { breadcrumbs: [] },
components: { sidebarCollapsed: false },
} as any)

let wrapper: any
await act(async () => {
wrapper = await mount(
<Provider store={store}>
<MemoryRouter initialEntries={[path]}>
<Incidents />
</MemoryRouter>
</Provider>,
)
})
wrapper.update()

return { wrapper: wrapper as ReactWrapper }
}

describe('routing', () => {
describe('/incidents/new', () => {
it('should render the new incident screen when /incidents/new is accessed', () => {
const expectedIncident = {
id: '1234',
code: '1234',
} as Incident
jest.spyOn(IncidentRepository, 'find').mockResolvedValue(expectedIncident)
const store = mockStore({
title: 'test',
user: { permissions: [Permissions.ReportIncident] },
breadcrumbs: { breadcrumbs: [] },
components: { sidebarCollapsed: false },
incident: {
incident: expectedIncident,
},
} as any)

const wrapper = mount(
<Provider store={store}>
<MemoryRouter initialEntries={['/incidents/new']}>
<Incidents />
</MemoryRouter>
</Provider>,
)
it('should render the new incident screen when /incidents/new is accessed', async () => {
const { wrapper } = await setup([Permissions.ReportIncident], '/incidents/new')

expect(wrapper.find(ReportIncident)).toHaveLength(1)
})

it('should not navigate to /incidents/new if the user does not have ReportIncident permissions', () => {
const store = mockStore({
title: 'test',
user: { permissions: [] },
breadcrumbs: { breadcrumbs: [] },
components: { sidebarCollapsed: false },
} as any)

const wrapper = mount(
<Provider store={store}>
<MemoryRouter initialEntries={['/incidents/new']}>
<Incidents />
</MemoryRouter>
</Provider>,
)
it('should not navigate to /incidents/new if the user does not have ReportIncident permissions', async () => {
const { wrapper } = await setup([], '/incidents/new')

expect(wrapper.find(ReportIncident)).toHaveLength(0)
})
})

describe('/incidents/:id', () => {
it('should render the view incident screen when /incidents/:id is accessed', async () => {
const store = mockStore({
title: 'test',
user: { permissions: [Permissions.ViewIncident] },
breadcrumbs: { breadcrumbs: [] },
components: { sidebarCollapsed: false },
incident: {
incident: {
id: '1234',
code: '1234 ',
date: new Date().toISOString(),
reportedOn: new Date().toISOString(),
},
},
} as any)

let wrapper: any
const { wrapper } = await setup([Permissions.ViewIncident], '/incidents/1234')

await act(async () => {
wrapper = await mount(
<Provider store={store}>
<MemoryRouter initialEntries={['/incidents/1234']}>
<Incidents />
</MemoryRouter>
</Provider>,
)

expect(wrapper.find(ViewIncident)).toHaveLength(1)
})
expect(wrapper.find(ViewIncident)).toHaveLength(1)
})

it('should not navigate to /incidents/:id if the user does not have ViewIncident permissions', async () => {
const store = mockStore({
title: 'test',
user: { permissions: [] },
breadcrumbs: { breadcrumbs: [] },
components: { sidebarCollapsed: false },
} as any)

const wrapper = await mount(
<Provider store={store}>
<MemoryRouter initialEntries={['/incidents/1234']}>
<Incidents />
</MemoryRouter>
</Provider>,
)
const { wrapper } = await setup([], '/incidents/1234')

expect(wrapper.find(ViewIncident)).toHaveLength(0)
})
Expand Down
28 changes: 28 additions & 0 deletions src/__tests__/incidents/hooks/useIncident.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { renderHook, act } from '@testing-library/react-hooks'

import useIncident from '../../../incidents/hooks/useIncident'
import IncidentRepository from '../../../shared/db/IncidentRepository'
import Incident from '../../../shared/model/Incident'
import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util'

describe('useIncident', () => {
it('should get an incident by id', async () => {
const expectedIncidentId = 'some id'
const expectedIncident = {
id: expectedIncidentId,
} as Incident
jest.spyOn(IncidentRepository, 'find').mockResolvedValue(expectedIncident)

let actualData: any
await act(async () => {
const renderHookResult = renderHook(() => useIncident(expectedIncidentId))
const { result } = renderHookResult
await waitUntilQueryIsSuccessful(renderHookResult)
actualData = result.current.data
})

expect(IncidentRepository.find).toHaveBeenCalledTimes(1)
expect(IncidentRepository.find).toBeCalledWith(expectedIncidentId)
expect(actualData).toEqual(expectedIncident)
})
})
34 changes: 34 additions & 0 deletions src/__tests__/incidents/hooks/useIncidents.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { act, renderHook } from '@testing-library/react-hooks'

import useIncidents from '../../../incidents/hooks/useIncidents'
import IncidentFilter from '../../../incidents/IncidentFilter'
import IncidentSearchRequest from '../../../incidents/model/IncidentSearchRequest'
import IncidentRepository from '../../../shared/db/IncidentRepository'
import Incident from '../../../shared/model/Incident'
import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util'

describe('useIncidents', () => {
it('it should search incidents', async () => {
const expectedSearchRequest: IncidentSearchRequest = {
status: IncidentFilter.all,
}
const expectedIncidents = [
{
id: 'some id',
},
] as Incident[]
jest.spyOn(IncidentRepository, 'search').mockResolvedValue(expectedIncidents)

let actualData: any
await act(async () => {
const renderHookResult = renderHook(() => useIncidents(expectedSearchRequest))
const { result } = renderHookResult
await waitUntilQueryIsSuccessful(renderHookResult)
actualData = result.current.data
})

expect(IncidentRepository.search).toHaveBeenCalledTimes(1)
expect(IncidentRepository.search).toBeCalledWith(expectedSearchRequest)
expect(actualData).toEqual(expectedIncidents)
})
})
62 changes: 62 additions & 0 deletions src/__tests__/incidents/hooks/useReportIncident.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { subDays } from 'date-fns'
import shortid from 'shortid'

import useReportIncident from '../../../incidents/hooks/useReportIncident'
import * as incidentValidator from '../../../incidents/util/validate-incident'
import { IncidentError } from '../../../incidents/util/validate-incident'
import IncidentRepository from '../../../shared/db/IncidentRepository'
import Incident from '../../../shared/model/Incident'
import executeMutation from '../../test-utils/use-mutation.util'

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

it('should save the incident with correct data', async () => {
const expectedCode = '123456'
const expectedDate = new Date(Date.now())
const expectedStatus = 'reported'
const expectedReportedBy = 'some user'
Date.now = jest.fn().mockReturnValue(expectedDate)

const givenIncidentRequest = {
category: 'some category',
categoryItem: 'some category item',
date: subDays(new Date(), 3).toISOString(),
department: 'some department',
description: 'some description',
} as Incident

const expectedIncident = {
...givenIncidentRequest,
code: `I-${expectedCode}`,
reportedOn: expectedDate.toISOString(),
status: expectedStatus,
reportedBy: expectedReportedBy,
} as Incident
jest.spyOn(shortid, 'generate').mockReturnValue(expectedCode)
jest.spyOn(IncidentRepository, 'save').mockResolvedValue(expectedIncident)

const actualData = await executeMutation(() => useReportIncident(), givenIncidentRequest)
expect(IncidentRepository.save).toHaveBeenCalledTimes(1)
expect(IncidentRepository.save).toBeCalledWith(expectedIncident)
expect(actualData).toEqual(expectedIncident)
})

it('should throw an error if validation fails', async () => {
const expectedIncidentError = {
description: 'some description error',
} as IncidentError

jest.spyOn(incidentValidator, 'default').mockReturnValue(expectedIncidentError)
jest.spyOn(IncidentRepository, 'save').mockResolvedValue({} as Incident)

try {
await executeMutation(() => useReportIncident(), {})
} catch (e) {
expect(e).toEqual(expectedIncidentError)
expect(IncidentRepository.save).not.toHaveBeenCalled()
}
})
})
Loading

1 comment on commit b1884c8

@vercel
Copy link

@vercel vercel bot commented on b1884c8 Aug 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.