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

Commit

Permalink
Merge branch 'master' into edit-appointment
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewDorner committed Feb 21, 2020
2 parents f32c573 + df43d75 commit d639700
Show file tree
Hide file tree
Showing 13 changed files with 277 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ jobs:
fetch-depth: 1
- uses: preactjs/compressed-size-action@v1
with:
repo-token: "${{ secrets.GH_TOKEN }}"
repo-token: "${{ secrets.GITHUB_TOKEN }}"
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@types/pouchdb-find": "~6.3.4",
"bootstrap": "~4.4.1",
"date-fns": "~2.9.0",
"i18next": "~19.2.0",
"i18next": "~19.3.1",
"i18next-browser-languagedetector": "~4.0.1",
"i18next-xhr-backend": "~3.2.2",
"node-sass": "~4.13.0",
Expand All @@ -29,7 +29,7 @@
"react-scripts": "~3.3.0",
"redux": "~4.0.5",
"redux-thunk": "~2.3.0",
"typescript": "~3.7.4"
"typescript": "~3.8.2"
},
"repository": {
"type": "git",
Expand Down
138 changes: 72 additions & 66 deletions src/HospitalRun.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import Appointments from 'scheduling/appointments/Appointments'
import NewAppointment from 'scheduling/appointments/new/NewAppointment'
import EditAppointment from 'scheduling/appointments/edit/EditAppointment'
import ViewAppointment from 'scheduling/appointments/view/ViewAppointment'
import { ButtonBarProvider } from 'page-header/ButtonBarProvider'
import ButtonToolBar from 'page-header/ButtonToolBar'
import Sidebar from './components/Sidebar'
import Permissions from './model/Permissions'
import Dashboard from './dashboard/Dashboard'
Expand All @@ -20,77 +22,81 @@ import PrivateRoute from './components/PrivateRoute'
const HospitalRun = () => {
const { title } = useSelector((state: RootState) => state.title)
const { permissions } = useSelector((state: RootState) => state.user)

return (
<div>
<Navbar />
<div className="container-fluid">
<Sidebar />
<div className="row">
<main role="main" className="col-md-9 ml-sm-auto col-lg-10 px-4">
<div className="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 className="h2">{title}</h1>
</div>
<div>
<Switch>
<Route exact path="/" component={Dashboard} />
<PrivateRoute
isAuthenticated={permissions.includes(Permissions.ReadPatients)}
exact
path="/patients"
component={Patients}
/>
<PrivateRoute
isAuthenticated={permissions.includes(Permissions.WritePatients)}
exact
path="/patients/new"
component={NewPatient}
/>
<PrivateRoute
isAuthenticated={
permissions.includes(Permissions.WritePatients) &&
permissions.includes(Permissions.ReadPatients)
}
exact
path="/patients/edit/:id"
component={EditPatient}
/>
<PrivateRoute
isAuthenticated={permissions.includes(Permissions.ReadPatients)}
path="/patients/:id"
component={ViewPatient}
/>
<PrivateRoute
isAuthenticated={permissions.includes(Permissions.ReadAppointments)}
exact
path="/appointments"
component={Appointments}
/>
<PrivateRoute
isAuthenticated={permissions.includes(Permissions.WriteAppointments)}
exact
path="/appointments/new"
component={NewAppointment}
/>
<PrivateRoute
isAuthenticated={
permissions.includes(Permissions.WriteAppointments) &&
permissions.includes(Permissions.ReadAppointments)
}
exact
path="/appointments/edit/:id"
component={EditAppointment}
/>
<PrivateRoute
isAuthenticated={permissions.includes(Permissions.ReadAppointments)}
exact
path="/appointments/:id"
component={ViewAppointment}
/>
</Switch>
</div>
<Toaster autoClose={5000} hideProgressBar draggable />
</main>
</div>
<ButtonBarProvider>
<div className="row">
<main role="main" className="col-md-9 ml-sm-auto col-lg-10 px-4">
<div className="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 className="h2">{title}</h1>
<ButtonToolBar />
</div>
<div>
<Switch>
<Route exact path="/" component={Dashboard} />
<PrivateRoute
isAuthenticated={permissions.includes(Permissions.ReadPatients)}
exact
path="/patients"
component={Patients}
/>
<PrivateRoute
isAuthenticated={permissions.includes(Permissions.WritePatients)}
exact
path="/patients/new"
component={NewPatient}
/>
<PrivateRoute
isAuthenticated={
permissions.includes(Permissions.WritePatients) &&
permissions.includes(Permissions.ReadPatients)
}
exact
path="/patients/edit/:id"
component={EditPatient}
/>
<PrivateRoute
isAuthenticated={permissions.includes(Permissions.ReadPatients)}
path="/patients/:id"
component={ViewPatient}
/>
<PrivateRoute
isAuthenticated={permissions.includes(Permissions.ReadAppointments)}
exact
path="/appointments"
component={Appointments}
/>
<PrivateRoute
isAuthenticated={permissions.includes(Permissions.WriteAppointments)}
exact
path="/appointments/new"
component={NewAppointment}
/>
<PrivateRoute
isAuthenticated={
permissions.includes(Permissions.WriteAppointments) &&
permissions.includes(Permissions.ReadAppointments)
}
exact
path="/appointments/edit/:id"
component={EditAppointment}
/>
<PrivateRoute
isAuthenticated={permissions.includes(Permissions.ReadAppointments)}
exact
path="/appointments/:id"
component={ViewAppointment}
/>
</Switch>
</div>
<Toaster autoClose={5000} hideProgressBar draggable />
</main>
</div>
</ButtonBarProvider>
</div>
</div>
)
Expand Down
27 changes: 27 additions & 0 deletions src/__tests__/page-header/ButtonBarProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import '../../__mocks__/matchMediaMock'
import React from 'react'
import { renderHook } from '@testing-library/react-hooks'
import {
ButtonBarProvider,
useButtons,
useButtonToolbarSetter,
} from 'page-header/ButtonBarProvider'
import { Button } from '@hospitalrun/components'

describe('Button Bar Provider', () => {
it('should update and fetch data from the button bar provider', () => {
const expectedButtons = [<Button>test 1</Button>]
const wrapper = ({ children }: any) => <ButtonBarProvider>{children}</ButtonBarProvider>

const { result } = renderHook(
() => {
const update = useButtonToolbarSetter()
update(expectedButtons)
return useButtons()
},
{ wrapper },
)

expect(result.current).toEqual(expectedButtons)
})
})
27 changes: 27 additions & 0 deletions src/__tests__/page-header/ButtonToolBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import '../../__mocks__/matchMediaMock'
import React from 'react'
import { Button } from '@hospitalrun/components'
import { mocked } from 'ts-jest/utils'
import { mount } from 'enzyme'
import * as ButtonBarProvider from '../../page-header/ButtonBarProvider'
import ButtonToolBar from '../../page-header/ButtonToolBar'

describe('Button Tool Bar', () => {
beforeEach(() => {
jest.resetAllMocks()
})

it('should render the buttons in the provider', () => {
const buttons: React.ReactNode[] = [
<Button key="test1">Test 1</Button>,
<Button key="test2">Test 2</Button>,
]
jest.spyOn(ButtonBarProvider, 'useButtons')
mocked(ButtonBarProvider).useButtons.mockReturnValue(buttons)

const wrapper = mount(<ButtonToolBar />)

expect(wrapper.childAt(0).getElement()).toEqual(buttons[0])
expect(wrapper.childAt(1).getElement()).toEqual(buttons[1])
})
})
16 changes: 16 additions & 0 deletions src/__tests__/patients/list/Patients.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import thunk from 'redux-thunk'
import configureStore from 'redux-mock-store'
import { mocked } from 'ts-jest/utils'
import { act } from 'react-dom/test-utils'
import * as ButtonBarProvider from 'page-header/ButtonBarProvider'
import Patients from '../../../patients/list/Patients'
import PatientRepository from '../../../clients/db/PatientRepository'
import * as patientSlice from '../../../patients/patients-slice'
Expand Down Expand Up @@ -42,6 +43,10 @@ describe('Patients', () => {
})

describe('layout', () => {
afterEach(() => {
jest.restoreAllMocks()
})

it('should render a search input with button', () => {
const wrapper = setup()
const searchInput = wrapper.find(TextInput)
Expand All @@ -66,6 +71,17 @@ describe('Patients', () => {
`${patients[0].fullName} (${patients[0].friendlyId})`,
)
})

it('should add a "New Patient" button to the button tool bar', () => {
jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter')
const setButtonToolBarSpy = jest.fn()
mocked(ButtonBarProvider).useButtonToolbarSetter.mockReturnValue(setButtonToolBarSpy)

setup()

const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0]
expect((actualButtons[0] as any).props.children).toEqual('patients.newPatient')
})
})

describe('search functionality', () => {
Expand Down
31 changes: 12 additions & 19 deletions src/__tests__/patients/view/ViewPatient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import thunk from 'redux-thunk'
import GeneralInformation from 'patients/GeneralInformation'
import { createMemoryHistory } from 'history'
import RelatedPersonTab from 'patients/related-persons/RelatedPersonTab'
import * as ButtonBarProvider from 'page-header/ButtonBarProvider'
import Patient from '../../../model/Patient'
import PatientRepository from '../../../clients/db/PatientRepository'
import * as titleUtil from '../../../page-header/useTitle'
Expand Down Expand Up @@ -71,25 +72,6 @@ describe('ViewPatient', () => {
jest.restoreAllMocks()
})

it('should navigate to /patients/edit/:id when edit is clicked', async () => {
let wrapper: any
await act(async () => {
wrapper = await setup()
})

wrapper.update()

const editButton = wrapper.find(Button).at(3)
const onClick = editButton.prop('onClick') as any
expect(editButton.text().trim()).toEqual('actions.edit')

act(() => {
onClick()
})

expect(history.location.pathname).toEqual('/patients/edit/123')
})

it('should dispatch fetchPatient when component loads', async () => {
await act(async () => {
await setup()
Expand All @@ -110,6 +92,17 @@ describe('ViewPatient', () => {
)
})

it('should add a "Edit Patient" button to the button tool bar', () => {
jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter')
const setButtonToolBarSpy = jest.fn()
mocked(ButtonBarProvider).useButtonToolbarSetter.mockReturnValue(setButtonToolBarSpy)

setup()

const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0]
expect((actualButtons[0] as any).props.children).toEqual('actions.edit')
})

it('should render a tabs header with the correct tabs', async () => {
let wrapper: any
await act(async () => {
Expand Down
14 changes: 14 additions & 0 deletions src/__tests__/scheduling/appointments/Appointments.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { act } from '@testing-library/react'
import PatientRepository from 'clients/db/PatientRepository'
import { mocked } from 'ts-jest/utils'
import Patient from 'model/Patient'
import * as ButtonBarProvider from 'page-header/ButtonBarProvider'
import * as titleUtil from '../../../page-header/useTitle'

describe('Appointments', () => {
Expand Down Expand Up @@ -51,6 +52,19 @@ describe('Appointments', () => {
expect(titleUtil.default).toHaveBeenCalledWith('scheduling.appointments.label')
})

it('should add a "New Appointment" button to the button tool bar', async () => {
jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter')
const setButtonToolBarSpy = jest.fn()
mocked(ButtonBarProvider).useButtonToolbarSetter.mockReturnValue(setButtonToolBarSpy)

await act(async () => {
await setup()
})

const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0]
expect((actualButtons[0] as any).props.children).toEqual('scheduling.appointments.new')
})

it('should render a calendar with the proper events', async () => {
let wrapper: any
await act(async () => {
Expand Down
38 changes: 38 additions & 0 deletions src/page-header/ButtonBarProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useState } from 'react'

type Props = {
children?: React.ReactNode
}

type ButtonUpdater = (buttons: React.ReactNode[]) => void

const ButtonBarStateContext = React.createContext<React.ReactNode[]>([])
const ButtonBarUpdateContext = React.createContext<ButtonUpdater>(() => {
// empty initial state
})

function ButtonBarProvider(props: Props) {
const { children } = props
const [state, setState] = useState<React.ReactNode[]>([])
return (
<ButtonBarStateContext.Provider value={state}>
<ButtonBarUpdateContext.Provider value={setState}>{children}</ButtonBarUpdateContext.Provider>
</ButtonBarStateContext.Provider>
)
}
function useButtons() {
const context = React.useContext(ButtonBarStateContext)
if (context === undefined) {
throw new Error('useButtons must be used within a Button Bar Context')
}
return context
}
function useButtonToolbarSetter() {
const context = React.useContext(ButtonBarUpdateContext)
if (context === undefined) {
throw new Error('useButtonToolBarSetter must be used within a Button Bar Context')
}
return context
}

export { ButtonBarProvider, useButtons, useButtonToolbarSetter }

0 comments on commit d639700

Please sign in to comment.