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 #2516 from kcdraidgroup/master
Browse files Browse the repository at this point in the history
test: migrate tests to React Testing Library
  • Loading branch information
matteovivona committed Jan 15, 2021
2 parents 45647d3 + a5f3b13 commit 660a895
Show file tree
Hide file tree
Showing 169 changed files with 5,719 additions and 8,506 deletions.
6 changes: 3 additions & 3 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ _Note: We no longer support the use of yarn._

## 4. Before pushing your changes, check locally that your branch passes CI checks

### We use Jest + Enzyme for Behavior-Driven Development Tests
### We use Jest + React Testing Library for Behavior-Driven Development Tests

`npm run test:ci` will run the entire test suite

Expand Down Expand Up @@ -94,6 +94,6 @@ _Note: We no longer support the use of yarn._

- [Getting started with PouchDB and CouchDB (tutorial) by Nolan Lawson](https://youtu.be/-Z7UF2TuSp0)

### Enzyme
### React Testing Library

- [Enzyme Cheatsheet by @rstacruz](https://devhints.io/enzyme)
- [React Testing Library Cheatsheet](https://testing-library.com/docs/react-testing-library/cheatsheet/)
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
roots: ['<rootDir>/src'],
setupFilesAfterEnv: ['<rootDir>/setupTests.js'],
testMatch: ['**/__tests__/**/*.+(ts|tsx)', '**/?(*.)+(spec|test).+(ts|tsx)'],
coverageDirectory: './coverage',
testPathIgnorePatterns: ['<rootDir>/jest.config.js'],
Expand Down
21 changes: 13 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@
"@commitlint/config-conventional": "~11.0.0",
"@commitlint/core": "~11.0.0",
"@commitlint/prompt": "~11.0.0",
"@testing-library/react": "~11.2.0",
"@testing-library/dom": "~7.29.0",
"@testing-library/jest-dom": "~5.11.6",
"@testing-library/react": "~11.2.2",
"@testing-library/react-hooks": "~4.0.0",
"@types/enzyme": "^3.10.5",
"@testing-library/user-event": "~12.6.0",
"@types/jest": "~26.0.0",
"@types/lodash": "^4.14.150",
"@types/node": "~14.11.1",
Expand All @@ -87,8 +89,6 @@
"cross-env": "~7.0.0",
"cz-conventional-changelog": "~3.3.0",
"dateformat": "~4.4.0",
"enzyme": "~3.11.0",
"enzyme-adapter-react-16": "~1.15.2",
"eslint": "~6.8.0",
"eslint-config-airbnb": "~18.2.0",
"eslint-config-prettier": "~6.15.0",
Expand All @@ -101,11 +101,13 @@
"eslint-plugin-react-hooks": "~4.1.0",
"history": "4.10.1",
"husky": "~4.3.0",
"jest": "24.9.0",
"lint-staged": "~10.5.0",
"jest-canvas-mock": "~2.3.0",
"jest-environment-jsdom-sixteen": "~1.0.3",
"lint-staged": "~10.5.0",
"memdown": "~5.1.0",
"prettier": "~2.2.0",
"react-select-event": "~5.1.0",
"react-test-renderer": "~16.13.1",
"redux-mock-store": "~1.5.4",
"rimraf": "~3.0.2",
"source-map-explorer": "^2.2.2",
Expand All @@ -118,8 +120,8 @@
"build": "react-scripts build",
"update": "npx npm-check -u",
"prepublishOnly": "npm run build",
"test": "npm run translation:check && react-scripts test --testPathIgnorePatterns=src/__tests__/test-utils --detectOpenHandles",
"test:ci": "cross-env CI=true react-scripts test --testPathIgnorePatterns=src/__tests__/test-utils --passWithNoTests",
"test": "npm run translation:check && react-scripts test --testPathIgnorePatterns=src/__tests__/test-utils --env=jest-environment-jsdom-sixteen",
"test:ci": "cross-env CI=true react-scripts test --testPathIgnorePatterns=src/__tests__/test-utils --passWithNoTests --env=jest-environment-jsdom-sixteen --maxWorkers=2",
"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 Expand Up @@ -159,5 +161,8 @@
"npm run test:ci",
"git add ."
]
},
"jest": {
"restoreMocks": true
}
}
29 changes: 20 additions & 9 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,30 @@ const App: React.FC = () => {
const [loading, setLoading] = useState(true)

useEffect(() => {
const init = async () => {
try {
const session = await remoteDb.getSession()
let cancelled = false

remoteDb
.getSession()
.then((session) => {
if (cancelled) {
return
}
if (session.userCtx.name) {
await dispatch(getCurrentSession(session.userCtx.name))
dispatch(getCurrentSession(session.userCtx.name))
}
} catch (e) {
})
.catch((e) => {
console.log(e)
}
setLoading(false)
}
})
.finally(() => {
if (!cancelled) {
setLoading(false)
}
})

init()
return () => {
cancelled = true
}
}, [dispatch])

if (loading) {
Expand Down
40 changes: 31 additions & 9 deletions src/__tests__/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,41 @@
import { shallow } from 'enzyme'
import { render, screen } from '@testing-library/react'
import React from 'react'
import { Provider } from 'react-redux'
import configureStore from 'redux-mock-store'
import createMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'

import App from '../App'
import { RootState } from '../shared/store'

it('renders without crashing', () => {
const mockStore = configureStore()({})
const mockStore = createMockStore<RootState, any>([thunk])

const AppWithStore = () => (
<Provider store={mockStore}>
it('renders without crashing', async () => {
// Supress the console.log in the test ouput
// eslint-disable-next-line @typescript-eslint/no-empty-function
jest.spyOn(console, 'log').mockImplementation(() => {})

const store = mockStore({
components: {
sidebarCollapsed: false,
},
breadcrumbs: {
breadcrumbs: [],
},
user: {
permissions: [],
},
} as any)

render(
<Provider store={store}>
<App />
</Provider>
</Provider>,
)

const wrapper = shallow(<AppWithStore />)
expect(wrapper).toBeDefined()
expect(
await screen.findByRole('heading', { name: /dashboard\.label/i }, { timeout: 8000 }),
).toBeInTheDocument()

// eslint-disable-next-line no-console
;(console.log as jest.Mock).mockRestore()
})
123 changes: 61 additions & 62 deletions src/__tests__/HospitalRun.test.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import { Toaster } from '@hospitalrun/components'
import { mount, ReactWrapper } from 'enzyme'
import { render, screen, within } from '@testing-library/react'
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'
import thunk from 'redux-thunk'

import Dashboard from '../dashboard/Dashboard'
import HospitalRun from '../HospitalRun'
import ViewImagings from '../imagings/search/ViewImagings'
import Incidents from '../incidents/Incidents'
import ViewLabs from '../labs/ViewLabs'
import ViewMedications from '../medications/search/ViewMedications'
import { addBreadcrumbs } from '../page-header/breadcrumbs/breadcrumbs-slice'
import * as titleUtil from '../page-header/title/TitleContext'
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'
Expand All @@ -28,8 +19,7 @@ const { TitleProvider } = titleUtil
const mockStore = createMockStore<RootState, any>([thunk])

describe('HospitalRun', () => {
const setup = async (route: string, permissions: Permissions[] = []) => {
jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn())
const setup = (route: string, permissions: Permissions[] = []) => {
const store = mockStore({
user: { user: { id: '123' }, permissions },
appointments: { appointments: [] },
Expand All @@ -39,7 +29,8 @@ describe('HospitalRun', () => {
breadcrumbs: { breadcrumbs: [] },
components: { sidebarCollapsed: false },
} as any)
const wrapper = mount(

const results = render(
<Provider store={store}>
<MemoryRouter initialEntries={[route]}>
<TitleProvider>
Expand All @@ -49,20 +40,17 @@ describe('HospitalRun', () => {
</Provider>,
)

await act(async () => {
wrapper.update()
})

return { wrapper: wrapper as ReactWrapper, store: store as any }
return { ...results, store }
}

describe('routing', () => {
describe('/appointments', () => {
it('should render the appointments screen when /appointments is accessed', async () => {
const permissions: Permissions[] = [Permissions.ReadAppointments]
const { wrapper, store } = await setup('/appointments', permissions)
it('should render the appointments screen when /appointments is accessed', () => {
const { store } = setup('/appointments', [Permissions.ReadAppointments])

expect(wrapper.find(Appointments)).toHaveLength(1)
expect(
screen.getByRole('heading', { name: /scheduling\.appointments\.label/i }),
).toBeInTheDocument()

expect(store.getActions()).toContainEqual(
addBreadcrumbs([
Expand All @@ -72,98 +60,109 @@ describe('HospitalRun', () => {
)
})

it('should render the Dashboard when the user does not have read appointment privileges', async () => {
const { wrapper } = await setup('/appointments')
expect(wrapper.find(Dashboard)).toHaveLength(1)
it('should render the Dashboard when the user does not have read appointment privileges', () => {
setup('/appointments')

expect(screen.getByRole('heading', { name: /dashboard/i })).toBeInTheDocument()
expect(window.location.pathname).toBe('/')
})
})

describe('/labs', () => {
it('should render the Labs component when /labs is accessed', async () => {
it('should render the Labs component when /labs is accessed', () => {
jest.spyOn(LabRepository, 'findAll').mockResolvedValue([])
const permissions: Permissions[] = [Permissions.ViewLabs]
const { wrapper } = await setup('/labs', permissions)

expect(wrapper.find(ViewLabs)).toHaveLength(1)
setup('/labs', [Permissions.ViewLabs])

const table = screen.getByRole('table')
expect(within(table).getByText(/labs.lab.code/i)).toBeInTheDocument()
expect(within(table).getByText(/labs.lab.type/i)).toBeInTheDocument()
expect(within(table).getByText(/labs.lab.requestedOn/i)).toBeInTheDocument()
expect(within(table).getByText(/labs.lab.status/i)).toBeInTheDocument()
expect(within(table).getByText(/actions.label/i)).toBeInTheDocument()
})

it('should render the dashboard if the user does not have permissions to view labs', async () => {
it('should render the dashboard if the user does not have permissions to view labs', () => {
jest.spyOn(LabRepository, 'findAll').mockResolvedValue([])
const { wrapper } = await setup('/labs')
setup('/labs')

expect(wrapper.find(ViewLabs)).toHaveLength(0)
expect(wrapper.find(Dashboard)).toHaveLength(1)
expect(screen.getByRole('heading', { name: /dashboard/i })).toBeInTheDocument()
expect(window.location.pathname).toBe('/')
})
})

describe('/medications', () => {
it('should render the Medications component when /medications is accessed', async () => {
it('should render the Medications component when /medications is accessed', () => {
jest.spyOn(MedicationRepository, 'search').mockResolvedValue([])
const permissions: Permissions[] = [Permissions.ViewMedications]
const { wrapper } = await setup('/medications', permissions)
setup('/medications', [Permissions.ViewMedications])

expect(wrapper.find(ViewMedications)).toHaveLength(1)
const medicationInput = screen.getByRole(/combobox/i) as HTMLInputElement
expect(medicationInput.value).toBe('medications.filter.all')
expect(screen.getByLabelText(/medications.search/i)).toBeInTheDocument()
})

it('should render the dashboard if the user does not have permissions to view medications', async () => {
it('should render the dashboard if the user does not have permissions to view medications', () => {
jest.spyOn(MedicationRepository, 'findAll').mockResolvedValue([])
const { wrapper } = await setup('/medications')
setup('/medications')

expect(wrapper.find(ViewMedications)).toHaveLength(0)
expect(wrapper.find(Dashboard)).toHaveLength(1)
expect(screen.getByRole('heading', { name: /dashboard/i })).toBeInTheDocument()
expect(window.location.pathname).toBe('/')
})
})

describe('/incidents', () => {
it('should render the Incidents component when /incidents is accessed', async () => {
it('should render the Incidents component when /incidents is accessed', () => {
jest.spyOn(IncidentRepository, 'search').mockResolvedValue([])
const permissions: Permissions[] = [Permissions.ViewIncidents]
const { wrapper } = await setup('/incidents', permissions)
setup('/incidents', permissions)

expect(wrapper.find(Incidents)).toHaveLength(1)
const incidentInput = screen.getByRole(/combobox/i) as HTMLInputElement
expect(incidentInput.value).toBe('incidents.status.reported')
expect(screen.getByRole('button', { name: /incidents.reports.new/i })).toBeInTheDocument()
})

it('should render the dashboard if the user does not have permissions to view incidents', async () => {
it('should render the dashboard if the user does not have permissions to view incidents', () => {
jest.spyOn(LabRepository, 'findAll').mockResolvedValue([])
const { wrapper } = await setup('/incidents')
setup('/incidents')

expect(wrapper.find(Incidents)).toHaveLength(0)
expect(wrapper.find(Dashboard)).toHaveLength(1)
expect(screen.getByRole('heading', { name: /dashboard/i })).toBeInTheDocument()
expect(window.location.pathname).toBe('/')
})
})

describe('/imaging', () => {
it('should render the Imagings component when /imaging is accessed', async () => {
it('should render the Imagings component when /imaging is accessed', () => {
jest.spyOn(ImagingRepository, 'search').mockResolvedValue([])
const permissions: Permissions[] = [Permissions.ViewImagings]
const { wrapper } = await setup('/imaging', permissions)
setup('/imaging', permissions)

expect(wrapper.find(ViewImagings)).toHaveLength(1)
expect(screen.getByRole('heading', { name: /imagings.label/i })).toBeInTheDocument()
})

it('should render the dashboard if the user does not have permissions to view imagings', async () => {
it('should render the dashboard if the user does not have permissions to view imagings', () => {
jest.spyOn(LabRepository, 'findAll').mockResolvedValue([])
const { wrapper } = await setup('/imaging')
setup('/imaging')

expect(wrapper.find(ViewImagings)).toHaveLength(0)
expect(wrapper.find(Dashboard)).toHaveLength(1)
expect(screen.getByRole('heading', { name: /dashboard/i })).toBeInTheDocument()
expect(window.location.pathname).toBe('/')
})
})

describe('/settings', () => {
it('should render the Settings component when /settings is accessed', async () => {
const { wrapper } = await setup('/settings')
expect(wrapper.find(Settings)).toHaveLength(1)
it('should render the Settings component when /settings is accessed', () => {
setup('/settings')

expect(screen.getByText(/settings.language.label/i)).toBeInTheDocument()
})
})
})

describe('layout', () => {
it('should render a Toaster', async () => {
it('should render a Toaster', () => {
const permissions: Permissions[] = [Permissions.WritePatients]
const { wrapper } = await setup('/', permissions)
setup('/', permissions)

expect(wrapper.find(Toaster)).toHaveLength(1)
const main = screen.getByRole('main')
expect(main.lastChild).toHaveClass('Toastify')
})
})
})
Loading

1 comment on commit 660a895

@vercel
Copy link

@vercel vercel bot commented on 660a895 Jan 15, 2021

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.