diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 4d4977d6fb..e6feafbf74 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -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 @@ -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/) diff --git a/jest.config.js b/jest.config.js index 4d0025510f..9cacc136f4 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,6 @@ module.exports = { roots: ['/src'], + setupFilesAfterEnv: ['/setupTests.js'], testMatch: ['**/__tests__/**/*.+(ts|tsx)', '**/?(*.)+(spec|test).+(ts|tsx)'], coverageDirectory: './coverage', testPathIgnorePatterns: ['/jest.config.js'], diff --git a/package.json b/package.json index 62279a2347..11c2c27fa5 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", @@ -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", @@ -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", @@ -159,5 +161,8 @@ "npm run test:ci", "git add ." ] + }, + "jest": { + "restoreMocks": true } } diff --git a/src/App.tsx b/src/App.tsx index 1fa00b0480..c603d7ce96 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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) { diff --git a/src/__tests__/App.test.tsx b/src/__tests__/App.test.tsx index d137cbd9cb..c98e594d80 100644 --- a/src/__tests__/App.test.tsx +++ b/src/__tests__/App.test.tsx @@ -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([thunk]) - const AppWithStore = () => ( - +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( + - + , ) - const wrapper = shallow() - 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() }) diff --git a/src/__tests__/HospitalRun.test.tsx b/src/__tests__/HospitalRun.test.tsx index 326f14ccb7..85f6df89c8 100644 --- a/src/__tests__/HospitalRun.test.tsx +++ b/src/__tests__/HospitalRun.test.tsx @@ -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' @@ -28,8 +19,7 @@ const { TitleProvider } = titleUtil const mockStore = createMockStore([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: [] }, @@ -39,7 +29,8 @@ describe('HospitalRun', () => { breadcrumbs: { breadcrumbs: [] }, components: { sidebarCollapsed: false }, } as any) - const wrapper = mount( + + const results = render( @@ -49,20 +40,17 @@ describe('HospitalRun', () => { , ) - 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([ @@ -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') }) }) }) diff --git a/src/__tests__/imagings/Imagings.test.tsx b/src/__tests__/imagings/Imagings.test.tsx index 6b1865dc0b..e1bdc04256 100644 --- a/src/__tests__/imagings/Imagings.test.tsx +++ b/src/__tests__/imagings/Imagings.test.tsx @@ -1,4 +1,4 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render } from '@testing-library/react' import React from 'react' import { Provider } from 'react-redux' import { MemoryRouter } from 'react-router-dom' @@ -19,7 +19,6 @@ const { TitleProvider } = titleUtil const mockStore = createMockStore([thunk]) describe('Imagings', () => { - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) jest.spyOn(ImagingRepository, 'findAll').mockResolvedValue([]) jest .spyOn(ImagingRepository, 'find') @@ -39,31 +38,29 @@ describe('Imagings', () => { }, } as any) - const wrapper = mount( + return render( {isNew ? : } , ) - wrapper.update() - return { wrapper: wrapper as ReactWrapper } } describe('routing', () => { describe('/imaging/new', () => { it('should render the new imaging request screen when /imaging/new is accessed', async () => { const permissions: Permissions[] = [Permissions.RequestImaging] - const { wrapper } = setup(permissions, true) + const { container } = setup(permissions, true) - expect(wrapper.find(NewImagingRequest)).toHaveLength(1) + expect(container).toBeInTheDocument() }) it('should not navigate to /imagings/new if the user does not have RequestImaging permissions', async () => { const permissions: Permissions[] = [] - const { wrapper } = setup(permissions) + const { container } = setup(permissions) - expect(wrapper.find(NewImagingRequest)).toHaveLength(0) + expect(container).toMatchInlineSnapshot(`
`) }) }) }) diff --git a/src/__tests__/imagings/hooks/useImagingRequest.test.tsx b/src/__tests__/imagings/hooks/useImagingRequest.test.tsx index 98b8b46d7d..9dff24448e 100644 --- a/src/__tests__/imagings/hooks/useImagingRequest.test.tsx +++ b/src/__tests__/imagings/hooks/useImagingRequest.test.tsx @@ -1,9 +1,7 @@ -import { renderHook, act } from '@testing-library/react-hooks' - import useImagingRequest from '../../../imagings/hooks/useImagingRequest' import ImagingRepository from '../../../shared/db/ImagingRepository' import Imaging from '../../../shared/model/Imaging' -import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util' +import executeQuery from '../../test-utils/use-query.util' describe('useImagingRequest', () => { it('should get an imaging request by id', async () => { @@ -17,13 +15,7 @@ describe('useImagingRequest', () => { } as Imaging jest.spyOn(ImagingRepository, 'find').mockResolvedValue(expectedImagingRequest) - let actualData: any - await act(async () => { - const renderHookResult = renderHook(() => useImagingRequest(expectedImagingId)) - const { result } = renderHookResult - await waitUntilQueryIsSuccessful(renderHookResult) - actualData = result.current.data - }) + const actualData = await executeQuery(() => useImagingRequest(expectedImagingId)) expect(ImagingRepository.find).toHaveBeenCalledTimes(1) expect(ImagingRepository.find).toBeCalledWith(expectedImagingId) diff --git a/src/__tests__/imagings/hooks/useImagingSearch.test.tsx b/src/__tests__/imagings/hooks/useImagingSearch.test.tsx index 8a50eeccee..89bd336b11 100644 --- a/src/__tests__/imagings/hooks/useImagingSearch.test.tsx +++ b/src/__tests__/imagings/hooks/useImagingSearch.test.tsx @@ -1,11 +1,9 @@ -import { act, renderHook } from '@testing-library/react-hooks' - import useImagingSearch from '../../../imagings/hooks/useImagingSearch' import ImagingSearchRequest from '../../../imagings/model/ImagingSearchRequest' import ImagingRepository from '../../../shared/db/ImagingRepository' import SortRequest from '../../../shared/db/SortRequest' import Imaging from '../../../shared/model/Imaging' -import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util' +import executeQuery from '../../test-utils/use-query.util' const defaultSortRequest: SortRequest = { sorts: [ @@ -25,13 +23,7 @@ describe('useImagingSearch', () => { const expectedImagingRequests = [{ id: 'some id' }] as Imaging[] jest.spyOn(ImagingRepository, 'search').mockResolvedValue(expectedImagingRequests) - let actualData: any - await act(async () => { - const renderHookResult = renderHook(() => useImagingSearch(expectedSearchRequest)) - const { result } = renderHookResult - await waitUntilQueryIsSuccessful(renderHookResult) - actualData = result.current.data - }) + const actualData = await executeQuery(() => useImagingSearch(expectedSearchRequest)) expect(ImagingRepository.search).toHaveBeenCalledTimes(1) expect(ImagingRepository.search).toBeCalledWith({ diff --git a/src/__tests__/imagings/hooks/useRequestImaging.test.tsx b/src/__tests__/imagings/hooks/useRequestImaging.test.tsx index a62f8f9aab..714729d2fd 100644 --- a/src/__tests__/imagings/hooks/useRequestImaging.test.tsx +++ b/src/__tests__/imagings/hooks/useRequestImaging.test.tsx @@ -1,17 +1,15 @@ -/* eslint-disable no-console */ - import useRequestImaging from '../../../imagings/hooks/useRequestImaging' import { ImagingRequestError } from '../../../imagings/util/validate-imaging-request' import * as imagingRequestValidator from '../../../imagings/util/validate-imaging-request' import ImagingRepository from '../../../shared/db/ImagingRepository' import Imaging from '../../../shared/model/Imaging' import { UserState, LoginError } from '../../../user/user-slice' +import { expectOneConsoleError } from '../../test-utils/console.utils' import executeMutation from '../../test-utils/use-mutation.util' describe('useReportIncident', () => { beforeEach(() => { jest.restoreAllMocks() - console.error = jest.fn() }) const user = { @@ -50,12 +48,15 @@ describe('useReportIncident', () => { const expectedImagingRequestError = { patient: 'some patient error', } as ImagingRequestError + expectOneConsoleError(expectedImagingRequestError) jest.spyOn(imagingRequestValidator, 'default').mockReturnValue(expectedImagingRequestError) jest.spyOn(ImagingRepository, 'save').mockResolvedValue({} as Imaging) try { - await executeMutation(() => useRequestImaging(user), {}) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore testing function failure + await executeMutation(() => useRequestImaging(), {} as Imaging) } catch (e) { expect(e).toEqual(expectedImagingRequestError) expect(ImagingRepository.save).not.toHaveBeenCalled() diff --git a/src/__tests__/imagings/requests/NewImagingRequest.test.tsx b/src/__tests__/imagings/requests/NewImagingRequest.test.tsx index b4c6d3319b..db10846c83 100644 --- a/src/__tests__/imagings/requests/NewImagingRequest.test.tsx +++ b/src/__tests__/imagings/requests/NewImagingRequest.test.tsx @@ -1,10 +1,10 @@ -import { Button, Typeahead, Label } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router, Route } from 'react-router-dom' +import selectEvent from 'react-select-event' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' @@ -12,14 +12,9 @@ import NewImagingRequest from '../../../imagings/requests/NewImagingRequest' import * as breadcrumbUtil from '../../../page-header/breadcrumbs/useAddBreadcrumbs' import * as ButtonBarProvider from '../../../page-header/button-toolbar/ButtonBarProvider' import * as titleUtil from '../../../page-header/title/TitleContext' -import SelectWithLabelFormGroup from '../../../shared/components/input/SelectWithLabelFormGroup' -import TextFieldWithLabelFormGroup from '../../../shared/components/input/TextFieldWithLabelFormGroup' -import TextInputWithLabelFormGroup from '../../../shared/components/input/TextInputWithLabelFormGroup' -import ImagingRepository from '../../../shared/db/ImagingRepository' -import Imaging from '../../../shared/model/Imaging' -import Patient from '../../../shared/model/Patient' import { RootState } from '../../../shared/store' import { UserState, LoginError } from '../../../user/user-slice' +import { expectOneConsoleError } from '../../test-utils/console.utils' const mockStore = createMockStore([thunk]) @@ -27,11 +22,10 @@ describe('New Imaging Request', () => { let history: any let setButtonToolBarSpy: any - const setup = async () => { + const setup = () => { jest.resetAllMocks() jest.spyOn(breadcrumbUtil, 'default') setButtonToolBarSpy = jest.fn() - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) history = createMemoryHistory() @@ -44,178 +38,118 @@ describe('New Imaging Request', () => { } as UserState, } as any) - let wrapper: any - await act(async () => { - wrapper = await mount( - - - - - - - - - - - , - ) - }) - wrapper.find(NewImagingRequest).props().updateTitle = jest.fn() - wrapper.update() - return wrapper as ReactWrapper + return render( + + + + + + + + + + + , + ) } - describe('title and breadcrumbs', () => { - it('should have called the useUpdateTitle hook', async () => { - await setup() - expect(titleUtil.useUpdateTitle).toHaveBeenCalledTimes(1) - }) - }) - describe('form layout', () => { - it('should render a patient typeahead', async () => { - const wrapper = await setup() - const typeaheadDiv = wrapper.find('.patient-typeahead') - - expect(typeaheadDiv).toBeDefined() + it('Renders a patient input field with correct label', () => { + setup() + const imgPatientInput = screen.getByPlaceholderText(/imagings\.imaging\.patient/i) - const label = typeaheadDiv.find(Label) - const typeahead = typeaheadDiv.find(Typeahead) + expect(screen.getAllByText(/imagings\.imaging\.patient/i)[0]).toBeInTheDocument() - expect(label).toBeDefined() - expect(label.prop('text')).toEqual('imagings.imaging.patient') - expect(typeahead).toBeDefined() - expect(typeahead.prop('placeholder')).toEqual('imagings.imaging.patient') - expect(typeahead.prop('searchAccessor')).toEqual('fullName') + userEvent.type(imgPatientInput, 'Cmdr. Data') + expect(imgPatientInput).toHaveDisplayValue('Cmdr. Data') }) - it('should render a dropdown list of visits', async () => { - const wrapper = await setup() - const visitsTypeSelect = wrapper.find('.visits').find(SelectWithLabelFormGroup) - expect(visitsTypeSelect).toBeDefined() - expect(visitsTypeSelect.prop('label')).toEqual('patient.visits.label') - expect(visitsTypeSelect.prop('isRequired')).toBeTruthy() - }) + it('Renders a dropdown list of visits', async () => { + setup() + const dropdownVisits = within(screen.getByTestId('visitSelect')).getByRole('combobox') + expect(screen.getByText(/patient\.visits\.label/i)).toBeInTheDocument() + expect(dropdownVisits.getAttribute('aria-expanded')).toBe('false') - it('should render a type input box', async () => { - const wrapper = await setup() - const typeInputBox = wrapper.find(TextInputWithLabelFormGroup) - - expect(typeInputBox).toBeDefined() - expect(typeInputBox.prop('label')).toEqual('imagings.imaging.type') - expect(typeInputBox.prop('isRequired')).toBeTruthy() - expect(typeInputBox.prop('isEditable')).toBeTruthy() - }) - - it('should render a status types select', async () => { - const wrapper = await setup() - const statusTypesSelect = wrapper.find('.imaging-status').find(SelectWithLabelFormGroup) - - expect(statusTypesSelect).toBeDefined() - expect(statusTypesSelect.prop('label')).toEqual('imagings.imaging.status') - expect(statusTypesSelect.prop('isRequired')).toBeTruthy() - expect(statusTypesSelect.prop('isEditable')).toBeTruthy() - expect(statusTypesSelect.prop('options')).toHaveLength(3) - expect(statusTypesSelect.prop('options')[0].label).toEqual('imagings.status.requested') - expect(statusTypesSelect.prop('options')[0].value).toEqual('requested') - expect(statusTypesSelect.prop('options')[1].label).toEqual('imagings.status.completed') - expect(statusTypesSelect.prop('options')[1].value).toEqual('completed') - expect(statusTypesSelect.prop('options')[2].label).toEqual('imagings.status.canceled') - expect(statusTypesSelect.prop('options')[2].value).toEqual('canceled') + selectEvent.openMenu(dropdownVisits) + expect(dropdownVisits).toHaveDisplayValue(['']) + expect(dropdownVisits.getAttribute('aria-expanded')).toBe('true') }) - it('should render a notes text field', async () => { - const wrapper = await setup() - const notesTextField = wrapper.find(TextFieldWithLabelFormGroup) + it('Renders an image type input box', async () => { + setup() + const imgTypeInput = screen.getByLabelText(/imagings\.imaging\.type/i) + expect(screen.getByText(/imagings\.imaging\.type/i)).toBeInTheDocument() - expect(notesTextField).toBeDefined() - expect(notesTextField.prop('label')).toEqual('imagings.imaging.notes') - expect(notesTextField.prop('isRequired')).toBeFalsy() - expect(notesTextField.prop('isEditable')).toBeTruthy() + userEvent.type(imgTypeInput, 'tricorder imaging') + expect(imgTypeInput).toHaveDisplayValue('tricorder imaging') }) - it('should render a save button', async () => { - const wrapper = await setup() - const saveButton = wrapper.find(Button).at(0) - expect(saveButton).toBeDefined() - expect(saveButton.text().trim()).toEqual('imagings.requests.create') + it('Renders a status types select input field', async () => { + setup() + const dropdownStatusTypes = within(screen.getByTestId('statusSelect')).getByRole('combobox') + expect(screen.getByText(/patient\.visits\.label/i)).toBeInTheDocument() + + expect(dropdownStatusTypes.getAttribute('aria-expanded')).toBe('false') + selectEvent.openMenu(dropdownStatusTypes) + expect(dropdownStatusTypes.getAttribute('aria-expanded')).toBe('true') + expect(dropdownStatusTypes).toHaveDisplayValue(['imagings.status.requested']) + + const optionsContent = screen + .getAllByRole('option') + .map((option) => option.lastElementChild?.innerHTML) + expect( + optionsContent.includes( + 'imagings.status.requested' && 'imagings.status.completed' && 'imagings.status.canceled', + ), + ).toBe(true) }) - it('should render a cancel button', async () => { - const wrapper = await setup() - const cancelButton = wrapper.find(Button).at(1) - expect(cancelButton).toBeDefined() - expect(cancelButton.text().trim()).toEqual('actions.cancel') + it('Renders a notes text field', async () => { + setup() + const notesInputField = screen.getByRole('textbox', { + name: /imagings\.imaging\.notes/i, + }) + expect(screen.getByLabelText(/imagings\.imaging\.notes/i)).toBeInTheDocument() + expect(notesInputField).toBeInTheDocument() + userEvent.type(notesInputField, 'Spot likes nutritional formula 221') }) }) describe('on cancel', () => { - it('should navigate back to /imaging', async () => { - const wrapper = await setup() - const cancelButton = wrapper.find(Button).at(1) - - act(() => { - const onClick = cancelButton.prop('onClick') as any - onClick({} as React.MouseEvent) - }) + it('Navigate back to /imaging', async () => { + setup() + expect(history.location.pathname).toEqual('/imaging/new') + userEvent.click( + screen.getByRole('button', { + name: /actions\.cancel/i, + }), + ) expect(history.location.pathname).toEqual('/imaging') }) }) describe('on save', () => { - it('should save the imaging request and navigate to "/imaging"', async () => { - const expectedDate = new Date() - const expectedImaging = { - patient: 'patient', - type: 'expected type', - status: 'requested', - visitId: 'expected visitId', - notes: 'expected notes', - id: '1234', - requestedOn: expectedDate.toISOString(), - } as Imaging - - const wrapper = await setup() - jest.spyOn(ImagingRepository, 'save').mockResolvedValue({ ...expectedImaging }) - - const patientTypeahead = wrapper.find(Typeahead) - await act(async () => { - const onChange = patientTypeahead.prop('onChange') - await onChange([{ fullName: expectedImaging.patient }] as Patient[]) - }) - - const typeInput = wrapper.find(TextInputWithLabelFormGroup) - act(() => { - const onChange = typeInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedImaging.type } }) - }) - - const statusSelect = wrapper.find('.imaging-status').find(SelectWithLabelFormGroup) - act(() => { - const onChange = statusSelect.prop('onChange') as any - onChange({ currentTarget: { value: expectedImaging.status } }) - }) - - const visitsSelect = wrapper.find('.visits').find(SelectWithLabelFormGroup) - act(() => { - const onChange = visitsSelect.prop('onChange') as any - onChange({ currentTarget: { value: expectedImaging.visitId } }) - }) - - const notesTextField = wrapper.find(TextFieldWithLabelFormGroup) - act(() => { - const onChange = notesTextField.prop('onChange') as any - onChange({ currentTarget: { value: expectedImaging.notes } }) - }) - wrapper.update() - - const saveButton = wrapper.find(Button).at(0) - const onClick = saveButton.prop('onClick') as any - expect(saveButton.text().trim()).toEqual('imagings.requests.create') - await act(async () => { - await onClick() + it('Save the imaging request and navigate to "/imaging"', async () => { + expectOneConsoleError({ patient: 'imagings.requests.error.patientRequired' }) + setup() + const patient = screen.getByPlaceholderText(/imagings\.imaging\.patient/i) + const imgTypeInput = screen.getByLabelText(/imagings\.imaging\.type/i) + const notesInputField = screen.getByRole('textbox', { + name: /imagings\.imaging\.notes/i, }) + const dropdownStatusTypes = within(screen.getByTestId('statusSelect')).getByRole('combobox') + const dropdownVisits = within(screen.getByTestId('visitSelect')).getByRole('combobox') + userEvent.type(patient, 'Worf') + userEvent.type(imgTypeInput, 'Medical Tricorder') + userEvent.type(notesInputField, 'Batliff') + selectEvent.create(dropdownVisits, 'Med Bay') + selectEvent.select(dropdownStatusTypes, 'imagings.status.requested') + userEvent.click( + screen.getByRole('button', { + name: /imagings\.requests\.create/i, + }), + ) expect(history.location.pathname).toEqual(`/imaging/new`) }) diff --git a/src/__tests__/imagings/search/ImagingRequestTable.test.tsx b/src/__tests__/imagings/search/ImagingRequestTable.test.tsx index 0f821a7708..c300a6fe6a 100644 --- a/src/__tests__/imagings/search/ImagingRequestTable.test.tsx +++ b/src/__tests__/imagings/search/ImagingRequestTable.test.tsx @@ -1,82 +1,53 @@ -import { Table } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' +import format from 'date-fns/format' import React from 'react' -import { act } from 'react-dom/test-utils' import ImagingSearchRequest from '../../../imagings/model/ImagingSearchRequest' import ImagingRequestTable from '../../../imagings/search/ImagingRequestTable' import ImagingRepository from '../../../shared/db/ImagingRepository' -import SortRequest from '../../../shared/db/SortRequest' import Imaging from '../../../shared/model/Imaging' -const defaultSortRequest: SortRequest = { - sorts: [ - { - field: 'requestedOn', - direction: 'desc', - }, - ], -} - describe('Imaging Request Table', () => { const expectedImaging = { code: 'I-1234', id: '1234', type: 'imaging type', patient: 'patient', - fullName: 'full name', - status: 'requested', + fullName: 'Jean Luc Picard', requestedOn: new Date().toISOString(), + status: 'requested', requestedBy: 'some user', + // requestedByFullName gets passed into the custom hook that spreads it into the save function + requestedByFullName: 'Full Name Mock', } as Imaging - const expectedImagings = [expectedImaging] - const setup = async (searchRequest: ImagingSearchRequest) => { + const setup = (searchRequest: ImagingSearchRequest) => { jest.resetAllMocks() - jest.spyOn(ImagingRepository, 'search').mockResolvedValue(expectedImagings) - let wrapper: any - - await act(async () => { - wrapper = await mount() - }) - wrapper.update() + jest.spyOn(ImagingRepository, 'search').mockResolvedValue([expectedImaging]) - return { wrapper: wrapper as ReactWrapper } + return render() } - it('should search for imaging requests ', async () => { - const expectedSearch: ImagingSearchRequest = { status: 'all', text: 'text' } - await setup(expectedSearch) - - expect(ImagingRepository.search).toHaveBeenCalledTimes(1) - expect(ImagingRepository.search).toHaveBeenCalledWith({ ...expectedSearch, defaultSortRequest }) - }) - it('should render a table of imaging requests', async () => { const expectedSearch: ImagingSearchRequest = { status: 'all', text: 'text' } - const { wrapper } = await setup(expectedSearch) - - const table = wrapper.find(Table) - const columns = table.prop('columns') - expect(columns[0]).toEqual( - expect.objectContaining({ label: 'imagings.imaging.code', key: 'code' }), - ) - expect(columns[1]).toEqual( - expect.objectContaining({ label: 'imagings.imaging.type', key: 'type' }), - ) - expect(columns[2]).toEqual( - expect.objectContaining({ label: 'imagings.imaging.requestedOn', key: 'requestedOn' }), - ) - expect(columns[3]).toEqual( - expect.objectContaining({ label: 'imagings.imaging.patient', key: 'fullName' }), + setup(expectedSearch) + const headers = await screen.findAllByRole('columnheader') + const cells = screen.getAllByRole('cell') + + expect(headers[0]).toHaveTextContent(/imagings.imaging.code/i) + expect(headers[1]).toHaveTextContent(/imagings.imaging.type/i) + expect(headers[2]).toHaveTextContent(/imagings.imaging.requestedOn/i) + expect(headers[3]).toHaveTextContent(/imagings.imaging.patient/i) + expect(headers[4]).toHaveTextContent(/imagings.imaging.requestedBy/i) + expect(headers[5]).toHaveTextContent(/imagings.imaging.status/i) + + expect(cells[0]).toHaveTextContent(expectedImaging.code) + expect(cells[1]).toHaveTextContent(expectedImaging.type) + expect(cells[2]).toHaveTextContent( + format(new Date(expectedImaging.requestedOn), 'yyyy-MM-dd hh:mm a'), ) - expect(columns[4]).toEqual( - expect.objectContaining({ label: 'imagings.imaging.requestedBy', key: 'requestedBy' }), - ) - expect(columns[5]).toEqual( - expect.objectContaining({ label: 'imagings.imaging.status', key: 'status' }), - ) - - expect(table.prop('data')).toEqual([expectedImaging]) + expect(cells[3]).toHaveTextContent(expectedImaging.fullName) + expect(cells[4]).toHaveTextContent(expectedImaging.requestedByFullName as string) + expect(cells[5]).toHaveTextContent(expectedImaging.status) }) }) diff --git a/src/__tests__/incidents/Incidents.test.tsx b/src/__tests__/incidents/Incidents.test.tsx index 07f73e607b..61df9ed792 100644 --- a/src/__tests__/incidents/Incidents.test.tsx +++ b/src/__tests__/incidents/Incidents.test.tsx @@ -1,15 +1,12 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, waitFor } from '@testing-library/react' +import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' -import { MemoryRouter } from 'react-router-dom' +import { Router, Route } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import Incidents from '../../incidents/Incidents' -import ReportIncident from '../../incidents/report/ReportIncident' -import ViewIncident from '../../incidents/view/ViewIncident' -import VisualizeIncidents from '../../incidents/visualize/VisualizeIncidents' import * as titleUtil from '../../page-header/title/TitleContext' import IncidentRepository from '../../shared/db/IncidentRepository' import Incident from '../../shared/model/Incident' @@ -18,88 +15,90 @@ import { RootState } from '../../shared/store' const mockStore = createMockStore([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(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(IncidentRepository, 'search').mockResolvedValue([]) - jest.spyOn(IncidentRepository, 'find').mockResolvedValue(expectedIncident) - const store = mockStore({ - user: { permissions }, - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - } as any) - - let wrapper: any - await act(async () => { - wrapper = await mount( - - +const expectedIncident = { + id: '1234', + code: '1234', + date: new Date().toISOString(), + reportedOn: new Date().toISOString(), + reportedBy: 'some user', +} as Incident + +function setup(permissions: Permissions[], path: string) { + jest.spyOn(IncidentRepository, 'search').mockResolvedValue([]) + jest.spyOn(IncidentRepository, 'find').mockResolvedValue(expectedIncident) + + const store = mockStore({ + user: { permissions }, + breadcrumbs: { breadcrumbs: [] }, + components: { sidebarCollapsed: false }, + } as any) + const history = createMemoryHistory({ initialEntries: [path] }) + + return { + history, + ...render( + + + - - , - ) - }) - wrapper.find(Incidents).props().updateTitle = jest.fn() - wrapper.update() - - return { wrapper: wrapper as ReactWrapper } + + + , + ), } +} - describe('title', () => { - it('should have called the useUpdateTitle hook', async () => { - await setup([Permissions.ViewIncidents], '/incidents') - expect(titleUtil.useUpdateTitle).toHaveBeenCalledTimes(1) - }) - }) - +describe('Incidents', () => { describe('routing', () => { describe('/incidents/new', () => { - it('should render the new incident screen when /incidents/new is accessed', async () => { - const { wrapper } = await setup([Permissions.ReportIncident], '/incidents/new') + it('The new incident screen when /incidents/new is accessed', () => { + setup([Permissions.ReportIncident], '/incidents/new') - expect(wrapper.find(ReportIncident)).toHaveLength(1) + expect(screen.getByRole('form', { name: /report incident form/i })).toBeInTheDocument() }) it('should not navigate to /incidents/new if the user does not have ReportIncident permissions', async () => { - const { wrapper } = await setup([], '/incidents/new') + const { history } = setup([], '/incidents/new') - expect(wrapper.find(ReportIncident)).toHaveLength(0) + await waitFor(() => { + expect(history.location.pathname).toBe('/') + }) }) }) describe('/incidents/visualize', () => { - it('should render the incident visualize screen when /incidents/visualize is accessed', async () => { - const { wrapper } = await setup([Permissions.ViewIncidentWidgets], '/incidents/visualize') + it('The incident visualize screen when /incidents/visualize is accessed', async () => { + const { container } = setup([Permissions.ViewIncidentWidgets], '/incidents/visualize') - expect(wrapper.find(VisualizeIncidents)).toHaveLength(1) + await waitFor(() => { + expect(container.querySelector('canvas')).toBeInTheDocument() + }) }) it('should not navigate to /incidents/visualize if the user does not have ViewIncidentWidgets permissions', async () => { - const { wrapper } = await setup([], '/incidents/visualize') + const { history } = setup([], '/incidents/visualize') - expect(wrapper.find(VisualizeIncidents)).toHaveLength(0) + await waitFor(() => { + expect(history.location.pathname).toBe('/') + }) }) }) describe('/incidents/:id', () => { - it('should render the view incident screen when /incidents/:id is accessed', async () => { - const { wrapper } = await setup([Permissions.ViewIncident], '/incidents/1234') + it('The view incident screen when /incidents/:id is accessed', async () => { + setup([Permissions.ViewIncident], `/incidents/${expectedIncident.id}`) - expect(wrapper.find(ViewIncident)).toHaveLength(1) + expect(await screen.findByText(expectedIncident.reportedBy)).toBeInTheDocument() }) it('should not navigate to /incidents/:id if the user does not have ViewIncident permissions', async () => { - const { wrapper } = await setup([], '/incidents/1234') + const { history } = setup([], `/incidents/${expectedIncident.id}`) - expect(wrapper.find(ViewIncident)).toHaveLength(0) + await waitFor(() => { + expect(history.location.pathname).toBe('/') + }) }) }) }) diff --git a/src/__tests__/incidents/hooks/useIncident.test.tsx b/src/__tests__/incidents/hooks/useIncident.test.tsx index d39c80e060..11570a0571 100644 --- a/src/__tests__/incidents/hooks/useIncident.test.tsx +++ b/src/__tests__/incidents/hooks/useIncident.test.tsx @@ -1,9 +1,7 @@ -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' +import executeQuery from '../../test-utils/use-query.util' describe('useIncident', () => { it('should get an incident by id', async () => { @@ -13,13 +11,7 @@ describe('useIncident', () => { } 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 - }) + const actualData = await executeQuery(() => useIncident(expectedIncidentId)) expect(IncidentRepository.find).toHaveBeenCalledTimes(1) expect(IncidentRepository.find).toBeCalledWith(expectedIncidentId) diff --git a/src/__tests__/incidents/hooks/useIncidents.test.tsx b/src/__tests__/incidents/hooks/useIncidents.test.tsx index 624703c285..2507ddd137 100644 --- a/src/__tests__/incidents/hooks/useIncidents.test.tsx +++ b/src/__tests__/incidents/hooks/useIncidents.test.tsx @@ -1,11 +1,9 @@ -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' +import executeQuery from '../../test-utils/use-query.util' describe('useIncidents', () => { it('it should search incidents', async () => { @@ -19,13 +17,7 @@ describe('useIncidents', () => { ] 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 - }) + const actualData = await executeQuery(() => useIncidents(expectedSearchRequest)) expect(IncidentRepository.search).toHaveBeenCalledTimes(1) expect(IncidentRepository.search).toBeCalledWith(expectedSearchRequest) diff --git a/src/__tests__/incidents/hooks/useReportIncident.test.tsx b/src/__tests__/incidents/hooks/useReportIncident.test.tsx index 9d0be1b893..7753dceedf 100644 --- a/src/__tests__/incidents/hooks/useReportIncident.test.tsx +++ b/src/__tests__/incidents/hooks/useReportIncident.test.tsx @@ -1,5 +1,3 @@ -/* eslint-disable no-console */ - import subDays from 'date-fns/subDays' import shortid from 'shortid' @@ -8,12 +6,12 @@ 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 { expectOneConsoleError } from '../../test-utils/console.utils' import executeMutation from '../../test-utils/use-mutation.util' describe('useReportIncident', () => { beforeEach(() => { jest.restoreAllMocks() - console.error = jest.fn() }) it('should save the incident with correct data', async () => { @@ -51,12 +49,13 @@ describe('useReportIncident', () => { const expectedIncidentError = { description: 'some description error', } as IncidentError + expectOneConsoleError(expectedIncidentError) jest.spyOn(incidentValidator, 'default').mockReturnValue(expectedIncidentError) jest.spyOn(IncidentRepository, 'save').mockResolvedValue({} as Incident) try { - await executeMutation(() => useReportIncident(), {}) + await executeMutation(() => useReportIncident(), {} as Incident) } catch (e) { expect(e).toEqual(expectedIncidentError) expect(IncidentRepository.save).not.toHaveBeenCalled() diff --git a/src/__tests__/incidents/hooks/useResolvedIncident.test.tsx b/src/__tests__/incidents/hooks/useResolvedIncident.test.tsx index f7ae6117c2..a28eaef684 100644 --- a/src/__tests__/incidents/hooks/useResolvedIncident.test.tsx +++ b/src/__tests__/incidents/hooks/useResolvedIncident.test.tsx @@ -1,9 +1,9 @@ -import { act, renderHook } from '@testing-library/react-hooks' import subDays from 'date-fns/subDays' import useResolveIncident from '../../../incidents/hooks/useResolveIncident' import IncidentRepository from '../../../shared/db/IncidentRepository' import Incident from '../../../shared/model/Incident' +import executeMutation from '../../test-utils/use-mutation.util' describe('useResolvedIncident', () => { it('should mark incident as resolved and save', async () => { @@ -30,20 +30,7 @@ describe('useResolvedIncident', () => { } as Incident jest.spyOn(IncidentRepository, 'save').mockResolvedValue(expectedIncident) - let mutateToTest: any - await act(async () => { - const renderHookResult = renderHook(() => useResolveIncident()) - const { result, waitForNextUpdate } = renderHookResult - await waitForNextUpdate() - const [mutate] = result.current - mutateToTest = mutate - }) - - let actualData: any - await act(async () => { - const result = await mutateToTest(givenIncident) - actualData = result - }) + const actualData = await executeMutation(() => useResolveIncident(), givenIncident) expect(IncidentRepository.save).toHaveBeenCalledTimes(1) expect(IncidentRepository.save).toBeCalledWith(expectedIncident) diff --git a/src/__tests__/incidents/list/ViewIncidents.test.tsx b/src/__tests__/incidents/list/ViewIncidents.test.tsx index db356f4074..259d913bc1 100644 --- a/src/__tests__/incidents/list/ViewIncidents.test.tsx +++ b/src/__tests__/incidents/list/ViewIncidents.test.tsx @@ -1,5 +1,4 @@ -import { act } from '@testing-library/react' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, within } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' @@ -7,9 +6,7 @@ import { Route, Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' -import IncidentFilter from '../../../incidents/IncidentFilter' import ViewIncidents from '../../../incidents/list/ViewIncidents' -import ViewIncidentsTable from '../../../incidents/list/ViewIncidentsTable' import * as breadcrumbUtil from '../../../page-header/breadcrumbs/useAddBreadcrumbs' import * as ButtonBarProvider from '../../../page-header/button-toolbar/ButtonBarProvider' import * as titleUtil from '../../../page-header/title/TitleContext' @@ -20,78 +17,53 @@ import { RootState } from '../../../shared/store' 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[] +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[]) => { - jest.resetAllMocks() - jest.spyOn(breadcrumbUtil, 'default') - setButtonToolBarSpy = jest.fn() - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) - jest.spyOn(IncidentRepository, 'findAll').mockResolvedValue(expectedIncidents) - jest.spyOn(IncidentRepository, 'search').mockResolvedValue(expectedIncidents) +let setButtonToolBarSpy: any +const setup = (permissions: Permissions[]) => { + jest.resetAllMocks() + jest.spyOn(breadcrumbUtil, 'default') + setButtonToolBarSpy = jest.fn() + jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) + jest.spyOn(IncidentRepository, 'findAll').mockResolvedValue(expectedIncidents) + jest.spyOn(IncidentRepository, 'search').mockResolvedValue(expectedIncidents) - history = createMemoryHistory() - history.push(`/incidents`) - const store = mockStore({ - user: { - permissions, - }, - } as any) + const history = createMemoryHistory({ initialEntries: [`/incidents`] }) + const store = mockStore({ user: { permissions } } as any) - let wrapper: any - await act(async () => { - wrapper = await mount( - - - - - - - - - - - , - ) - }) - wrapper.find(ViewIncidents).props().updateTitle = jest.fn() - wrapper.update() - return { wrapper: wrapper as ReactWrapper } - } + return render( + + + + + + + + + + + , + ) +} - it('should have called the useUpdateTitle hook', async () => { - await setup([Permissions.ViewIncidents]) - expect(titleUtil.useUpdateTitle).toHaveBeenCalledTimes(1) - }) - - it('should filter incidents by status=reported on first load ', async () => { - await setup([Permissions.ViewIncidents]) - - expect(IncidentRepository.search).toHaveBeenCalled() - expect(IncidentRepository.search).toHaveBeenCalledWith({ status: IncidentFilter.reported }) - }) - - describe('layout', () => { - it('should render a table with the incidents', async () => { - const { wrapper } = await setup([Permissions.ViewIncidents]) - const table = wrapper.find(ViewIncidentsTable) +it('should filter incidents by status=reported on first load ', () => { + setup([Permissions.ViewIncidents]) + expect(screen.getByRole('combobox')).toHaveValue('incidents.status.reported') +}) - expect(table.exists()).toBeTruthy() - expect(table.prop('searchRequest')).toEqual({ status: IncidentFilter.reported }) - }) +describe('layout', () => { + it('should render a table with the incidents', async () => { + setup([Permissions.ViewIncidents]) + expect(within(await screen.findByRole('table')).getByText('some code')).toBeInTheDocument() }) }) diff --git a/src/__tests__/incidents/list/ViewIncidentsTable.test.tsx b/src/__tests__/incidents/list/ViewIncidentsTable.test.tsx index c0168d6794..955eb58b4b 100644 --- a/src/__tests__/incidents/list/ViewIncidentsTable.test.tsx +++ b/src/__tests__/incidents/list/ViewIncidentsTable.test.tsx @@ -1,9 +1,8 @@ -import { Table, Dropdown } from '@hospitalrun/components' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import format from 'date-fns/format' -import { mount, ReactWrapper } from 'enzyme' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Router } from 'react-router' import IncidentFilter from '../../../incidents/IncidentFilter' @@ -11,84 +10,68 @@ import ViewIncidentsTable, { populateExportData } from '../../../incidents/list/ import IncidentSearchRequest from '../../../incidents/model/IncidentSearchRequest' import IncidentRepository from '../../../shared/db/IncidentRepository' import Incident from '../../../shared/model/Incident' +import { extractUsername } from '../../../shared/util/extractUsername' describe('View Incidents Table', () => { - const setup = async ( + const expectedIncident = { + id: 'incidentId1', + code: 'someCode', + date: new Date(2020, 7, 4, 0, 0, 0, 0).toISOString(), + reportedOn: new Date(2020, 8, 4, 0, 0, 0, 0).toISOString(), + reportedBy: 'com.test:user', + status: 'reported', + } as Incident + + const setup = ( expectedSearchRequest: IncidentSearchRequest, - expectedIncidents: Incident[], + expectedIncidents = [expectedIncident], ) => { jest.spyOn(IncidentRepository, 'search').mockResolvedValue(expectedIncidents) - - let wrapper: any const history = createMemoryHistory() - await act(async () => { - wrapper = await mount( + + return { + history, + ...render( , - ) - }) - wrapper.update() - - return { wrapper: wrapper as ReactWrapper, history } + ), + } } beforeEach(() => { jest.resetAllMocks() }) - it('should call the incidents search with the search request', async () => { - const expectedSearchRequest: IncidentSearchRequest = { - status: IncidentFilter.all, - } - await setup(expectedSearchRequest, []) - - expect(IncidentRepository.search).toHaveBeenCalledTimes(1) - expect(IncidentRepository.search).toHaveBeenCalledWith(expectedSearchRequest) - }) - it('should display a table of incidents', async () => { - const expectedIncidents: Incident[] = [ - { - id: 'incidentId1', - code: 'someCode', - date: new Date(2020, 7, 4, 0, 0, 0, 0).toISOString(), - reportedOn: new Date(2020, 8, 4, 0, 0, 0, 0).toISOString(), - reportedBy: 'com.test:user', - status: 'reported', - } as Incident, - ] - const { wrapper } = await setup({ status: IncidentFilter.all }, expectedIncidents) - - const incidentsTable = wrapper.find(Table) - - expect(incidentsTable.exists()).toBeTruthy() - expect(incidentsTable.prop('data')).toEqual(expectedIncidents) - expect(incidentsTable.prop('columns')).toEqual([ - expect.objectContaining({ label: 'incidents.reports.code', key: 'code' }), - expect.objectContaining({ label: 'incidents.reports.dateOfIncident', key: 'date' }), - expect.objectContaining({ label: 'incidents.reports.reportedBy', key: 'reportedBy' }), - expect.objectContaining({ label: 'incidents.reports.reportedOn', key: 'reportedOn' }), - expect.objectContaining({ label: 'incidents.reports.status', key: 'status' }), - ]) - expect(incidentsTable.prop('actionsHeaderText')).toEqual('actions.label') + setup({ status: IncidentFilter.all }) + expect(await screen.findByRole('table')).toBeInTheDocument() + + const headers = screen.getAllByRole('columnheader') + const cells = screen.getAllByRole('cell') + expect(headers[0]).toHaveTextContent(/incidents.reports.code/i) + expect(headers[1]).toHaveTextContent(/incidents.reports.dateOfIncident/i) + expect(headers[2]).toHaveTextContent(/incidents.reports.reportedBy/i) + expect(headers[3]).toHaveTextContent(/incidents.reports.reportedOn/i) + expect(headers[4]).toHaveTextContent(/incidents.reports.status/i) + expect(headers[5]).toHaveTextContent(/actions.label/i) + expect(cells[0]).toHaveTextContent(expectedIncident.code) + expect(cells[1]).toHaveTextContent( + format(new Date(expectedIncident.date), 'yyyy-MM-dd hh:mm a'), + ) + expect(cells[2]).toHaveTextContent(extractUsername(expectedIncident.reportedBy)) + expect(cells[3]).toHaveTextContent( + format(new Date(expectedIncident.reportedOn), 'yyyy-MM-dd hh:mm a'), + ) + expect(cells[4]).toHaveTextContent(expectedIncident.status) + expect(screen.getByRole('button', { name: /actions.view/i })).toBeInTheDocument() }) it('should display a download button', async () => { - const expectedIncidents: Incident[] = [ - { - id: 'incidentId1', - code: 'someCode', - date: new Date(2020, 7, 4, 0, 0, 0, 0).toISOString(), - reportedOn: new Date(2020, 8, 4, 0, 0, 0, 0).toISOString(), - reportedBy: 'com.test:user', - status: 'reported', - } as Incident, - ] - const { wrapper } = await setup({ status: IncidentFilter.all }, expectedIncidents) - - const dropDownButton = wrapper.find(Dropdown) - expect(dropDownButton.exists()).toBeTruthy() + setup({ status: IncidentFilter.all }) + expect( + await screen.findByRole('button', { name: /incidents.reports.download/i }), + ).toBeInTheDocument() }) it('should populate export data correctly', async () => { @@ -126,48 +109,10 @@ describe('View Incidents Table', () => { expect(exportData).toEqual(expectedExportData) }) - it('should format the data correctly', async () => { - const expectedIncidents: Incident[] = [ - { - id: 'incidentId1', - code: 'someCode', - date: new Date(2020, 7, 4, 12, 0, 0, 0).toISOString(), - reportedOn: new Date(2020, 8, 4, 12, 0, 0, 0).toISOString(), - reportedBy: 'com.test:user', - status: 'reported', - } as Incident, - ] - const { wrapper } = await setup({ status: IncidentFilter.all }, expectedIncidents) - - const incidentsTable = wrapper.find(Table) - const dateFormatter = incidentsTable.prop('columns')[1].formatter as any - const reportedByFormatter = incidentsTable.prop('columns')[2].formatter as any - const reportedOnFormatter = incidentsTable.prop('columns')[3].formatter as any - - expect(dateFormatter(expectedIncidents[0])).toEqual('2020-08-04 12:00 PM') - expect(reportedOnFormatter(expectedIncidents[0])).toEqual('2020-09-04 12:00 PM') - expect(reportedByFormatter(expectedIncidents[0])).toEqual('user') - }) - it('should navigate to the view incident screen when view button is clicked', async () => { - const expectedIncidents: Incident[] = [ - { - id: 'incidentId1', - code: 'someCode', - date: new Date(2020, 7, 4, 12, 0, 0, 0).toISOString(), - reportedOn: new Date(2020, 8, 4, 12, 0, 0, 0).toISOString(), - reportedBy: 'com.test:user', - status: 'reported', - } as Incident, - ] - const { wrapper, history } = await setup({ status: IncidentFilter.all }, expectedIncidents) - - act(() => { - const table = wrapper.find(Table) as any - const onViewClick = table.prop('actions')[0].action as any - onViewClick(expectedIncidents[0]) - }) - - expect(history.location.pathname).toEqual(`/incidents/${expectedIncidents[0].id}`) + const { history } = setup({ status: IncidentFilter.all }) + expect(await screen.findByRole('table')).toBeInTheDocument() + userEvent.click(screen.getByRole('button', { name: /actions.view/i })) + expect(history.location.pathname).toEqual(`/incidents/${expectedIncident.id}`) }) }) diff --git a/src/__tests__/incidents/report/ReportIncident.test.tsx b/src/__tests__/incidents/report/ReportIncident.test.tsx index 759263d04e..acb34e78a5 100644 --- a/src/__tests__/incidents/report/ReportIncident.test.tsx +++ b/src/__tests__/incidents/report/ReportIncident.test.tsx @@ -1,10 +1,7 @@ -/* eslint-disable no-console */ - -import { Button, Typeahead, Label } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Route, Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' @@ -15,10 +12,9 @@ import * as validationUtil from '../../../incidents/util/validate-incident' import * as breadcrumbUtil from '../../../page-header/breadcrumbs/useAddBreadcrumbs' import * as ButtonBarProvider from '../../../page-header/button-toolbar/ButtonBarProvider' import * as titleUtil from '../../../page-header/title/TitleContext' -import IncidentRepository from '../../../shared/db/IncidentRepository' -import Incident from '../../../shared/model/Incident' import Permissions from '../../../shared/model/Permissions' import { RootState } from '../../../shared/store' +import { expectOneConsoleError } from '../../test-utils/console.utils' const mockStore = createMockStore([thunk]) @@ -27,14 +23,12 @@ describe('Report Incident', () => { beforeEach(() => { jest.resetAllMocks() - console.error = jest.fn() }) let setButtonToolBarSpy: any - const setup = async (permissions: Permissions[]) => { + const setup = (permissions: Permissions[]) => { jest.spyOn(breadcrumbUtil, 'default') setButtonToolBarSpy = jest.fn() - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) history = createMemoryHistory() @@ -48,229 +42,141 @@ describe('Report Incident', () => { }, } as any) - let wrapper: any - await act(async () => { - wrapper = await mount( - - - - - - - - - - - , - ) - }) - wrapper.find(ReportIncident).props().updateTitle = jest.fn() - wrapper.update() - return wrapper as ReactWrapper + return render( + + + + + + + + + + + , + ) } + it('renders a department form element that allows user input', async () => { + setup([Permissions.ViewIncident, Permissions.ResolveIncident]) + const departmentInput = screen.getByLabelText(/incidents\.reports\.department/i) - describe('title', () => { - it('should have called the useUpdateTitle hook', async () => { - await setup([Permissions.ReportIncident]) - expect(titleUtil.useUpdateTitle).toHaveBeenCalledTimes(1) - }) - }) - - describe('layout', () => { - 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]) - - 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() - }) + expect(departmentInput).toBeEnabled() + expect(departmentInput).toBeInTheDocument() - 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]) + userEvent.type(departmentInput, 'Engineering Bay') + expect(departmentInput).toHaveDisplayValue('Engineering Bay') + }) - const categoryInput = wrapper.findWhere((w) => w.prop('name') === 'categoryItem') + it('renders a category form element that allows user input', async () => { + setup([Permissions.ViewIncident, Permissions.ResolveIncident]) + const categoryInput = screen.getByLabelText(/incidents\.reports\.category\b/i) - expect(categoryInput).toHaveLength(1) - expect(categoryInput.prop('label')).toEqual('incidents.reports.categoryItem') - expect(categoryInput.prop('isEditable')).toBeTruthy() - expect(categoryInput.prop('isRequired')).toBeTruthy() - }) + expect(categoryInput).toBeEnabled() + expect(categoryInput).toBeInTheDocument() - it('should have a description input', async () => { - const wrapper = await setup([Permissions.ReportIncident]) + userEvent.type(categoryInput, 'Warp Engine') + expect(categoryInput).toHaveDisplayValue('Warp Engine') + }) - const descriptionInput = wrapper.findWhere((w) => w.prop('name') === 'description') + it('renders a category item form element that allows user input', async () => { + setup([Permissions.ViewIncident, Permissions.ResolveIncident]) + const categoryItemInput = screen.getByLabelText(/incidents\.reports\.categoryitem/i) - expect(descriptionInput).toHaveLength(1) - expect(descriptionInput.prop('label')).toEqual('incidents.reports.description') - expect(descriptionInput.prop('isEditable')).toBeTruthy() - expect(descriptionInput.prop('isRequired')).toBeTruthy() - }) + expect(categoryItemInput).toBeInTheDocument() + expect(categoryItemInput).toBeEnabled() - it('should render a patient typeahead', async () => { - const wrapper = await setup([Permissions.ReportIncident]) - const typeaheadDiv = wrapper.find('.patient-typeahead') + userEvent.type(categoryItemInput, 'Warp Coil') + expect(categoryItemInput).toHaveDisplayValue('Warp Coil') + }) - expect(typeaheadDiv).toBeDefined() + it('renders a description formField element that allows user input', async () => { + setup([Permissions.ViewIncident, Permissions.ResolveIncident]) + const descriptionInput = screen.getByLabelText(/incidents\.reports\.description/i) - const label = typeaheadDiv.find(Label) - const typeahead = typeaheadDiv.find(Typeahead) + expect(descriptionInput).toBeInTheDocument() + expect(descriptionInput).toBeEnabled() - expect(label).toBeDefined() - expect(label.prop('text')).toEqual('incidents.reports.patient') - expect(typeahead).toBeDefined() - expect(typeahead.prop('placeholder')).toEqual('incidents.reports.patient') - expect(typeahead.prop('searchAccessor')).toEqual('fullName') - }) + userEvent.type(descriptionInput, 'Geordi requested analysis') + expect(descriptionInput).toHaveDisplayValue('Geordi requested analysis') }) - describe('on save', () => { - it('should report the incident', 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', - } as Incident - 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') as any - onClick() - }) - - expect(IncidentRepository.save).toHaveBeenCalledTimes(1) - expect(IncidentRepository.save).toHaveBeenCalledWith( - expect.objectContaining(expectedIncident), - ) - expect(history.location.pathname).toEqual(`/incidents/someId`) - }) - - it('should display errors if validation fails', async () => { - const error = { - name: 'incident error', - message: 'something went wrong', - date: 'some date error', - department: 'some department error', - category: 'some category error', - categoryItem: 'some category item error', - description: 'some description error', - } - jest.spyOn(validationUtil, 'default').mockReturnValue(error) - - const wrapper = await setup([Permissions.ReportIncident]) - - const saveButton = wrapper.find(Button).at(0) - await act(async () => { - const onClick = saveButton.prop('onClick') as any - await onClick() - }) - wrapper.update() - - 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) - }) + // ! Remove test? Save button is always rendered regardless of input values + // it(' renders action save button after all the input fields are filled out', async () => { + // setup([Permissions.ViewIncident, Permissions.ResolveIncident]) + + // expect(screen.queryByRole('button', { name: /incidents\.reports\.new/i })).not.toBeInTheDocument() + // const departmentInput = screen.getByLabelText(/incidents\.reports\.department/i) + // const categoryInput = screen.getByLabelText(/incidents\.reports\.category\b/i) + // const categoryItemInput = screen.getByLabelText(/incidents\.reports\.categoryitem/i) + // const descriptionInput = screen.getByLabelText(/incidents\.reports\.description/i) + + // userEvent.type(departmentInput, 'Engineering Bay') + // userEvent.type(categoryInput, 'Warp Engine') + // userEvent.type(categoryItemInput, 'Warp Coil') + // userEvent.type(descriptionInput, 'Geordi requested analysis') + + // userEvent.click( + // screen.getByRole('button', { + // name: /incidents\.reports\.new/i, + // }), + // ) + // }) + + it('should display errors if validation fails', async () => { + const error = { + name: 'incident error', + message: 'something went wrong', + date: 'some date error', + department: 'some department error', + category: 'some category error', + categoryItem: 'some category item error', + description: 'some description error', + } + expectOneConsoleError(error) + jest.spyOn(validationUtil, 'default').mockReturnValue(error) + const { container } = setup([Permissions.ReportIncident]) + + userEvent.click( + screen.getByRole('button', { + name: /incidents\.reports\.new/i, + }), + ) + + const departmentInput = screen.getByLabelText(/incidents\.reports\.department/i) + const categoryInput = screen.getByLabelText(/incidents\.reports\.category\b/i) + const categoryItemInput = screen.getByLabelText(/incidents\.reports\.categoryitem/i) + const descriptionInput = screen.getByLabelText(/incidents\.reports\.description/i) + const dateInput = within(await screen.findByTestId('dateOfIncidentDateTimePicker')).getByRole( + 'textbox', + ) + + const invalidInputs = container.querySelectorAll('.is-invalid') + expect(invalidInputs).toHaveLength(5) + + expect(dateInput).toHaveClass('is-invalid') + + expect(departmentInput).toHaveClass('is-invalid') + + expect(categoryInput).toHaveClass('is-invalid') + + expect(categoryItemInput).toHaveClass('is-invalid') + + expect(descriptionInput).toHaveClass('is-invalid') }) describe('on cancel', () => { it('should navigate to /incidents', async () => { - const wrapper = await setup([Permissions.ReportIncident]) + setup([Permissions.ReportIncident]) - const cancelButton = wrapper.find(Button).at(1) + expect(history.location.pathname).toBe('/incidents/new') - act(() => { - const onClick = cancelButton.prop('onClick') as any - onClick() - }) + userEvent.click( + screen.getByRole('button', { + name: /actions\.cancel/i, + }), + ) - expect(history.location.pathname).toEqual('/incidents') + expect(history.location.pathname).toBe('/incidents') }) }) }) diff --git a/src/__tests__/incidents/view/ViewIncident.test.tsx b/src/__tests__/incidents/view/ViewIncident.test.tsx index b0706f7eb9..e43804cedc 100644 --- a/src/__tests__/incidents/view/ViewIncident.test.tsx +++ b/src/__tests__/incidents/view/ViewIncident.test.tsx @@ -1,80 +1,73 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Route, Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import ViewIncident from '../../../incidents/view/ViewIncident' -import ViewIncidentDetails from '../../../incidents/view/ViewIncidentDetails' import * as breadcrumbUtil from '../../../page-header/breadcrumbs/useAddBreadcrumbs' -import * as ButtonBarProvider from '../../../page-header/button-toolbar/ButtonBarProvider' -import * as titleUtil from '../../../page-header/title/TitleContext' +import { ButtonBarProvider } from '../../../page-header/button-toolbar/ButtonBarProvider' +import { TitleProvider } from '../../../page-header/title/TitleContext' import IncidentRepository from '../../../shared/db/IncidentRepository' import Incident from '../../../shared/model/Incident' import Permissions from '../../../shared/model/Permissions' import { RootState } from '../../../shared/store' -const { TitleProvider } = titleUtil const mockStore = createMockStore([thunk]) -describe('View Incident', () => { - const setup = async (permissions: Permissions[]) => { - jest.resetAllMocks() - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(breadcrumbUtil, 'default') - jest.spyOn(IncidentRepository, 'find').mockResolvedValue({ - id: '1234', - date: new Date().toISOString(), - code: 'some code', - reportedOn: new Date().toISOString(), - } as Incident) - const history = createMemoryHistory() - history.push(`/incidents/1234`) +const setup = (permissions: Permissions[], id: string | undefined) => { + jest.resetAllMocks() + jest.spyOn(breadcrumbUtil, 'default') + jest.spyOn(IncidentRepository, 'find').mockResolvedValue({ + id, + date: new Date().toISOString(), + code: 'some code', + reportedOn: new Date().toISOString(), + } as Incident) - const store = mockStore({ - user: { - permissions, - }, - } as any) + const history = createMemoryHistory({ initialEntries: [`/incidents/${id}`] }) + const store = mockStore({ + user: { + permissions, + }, + } as any) - let wrapper: any - await act(async () => { - wrapper = await mount( - - - - - - - - - - - , - ) - }) - wrapper.update() - return { wrapper: wrapper as ReactWrapper, history } + return { + history, + ...render( + + + + + + + + + + + , + ), } +} - it('should set the breadcrumbs properly', async () => { - await setup([Permissions.ViewIncident]) +it('should not render ViewIncidentDetails if there are no Permissions', async () => { + setup(undefined, '1234') - expect(breadcrumbUtil.default).toHaveBeenCalledWith([ - { i18nKey: 'incidents.reports.view', location: '/incidents/1234' }, - ]) - }) + expect( + screen.queryByRole('heading', { + name: /incidents\.reports\.dateofincident/i, + }), + ).not.toBeInTheDocument() +}) - it('should render ViewIncidentDetails', async () => { - const permissions = [Permissions.ReportIncident, Permissions.ResolveIncident] - const { wrapper } = await setup(permissions) +it('should not ViewIncidentDetails no there is no ID', async () => { + setup([Permissions.ReportIncident, Permissions.ResolveIncident], undefined) - const viewIncidentDetails = wrapper.find(ViewIncidentDetails) - expect(viewIncidentDetails.exists()).toBeTruthy() - expect(viewIncidentDetails.prop('permissions')).toEqual(permissions) - expect(viewIncidentDetails.prop('incidentId')).toEqual('1234') - }) + expect( + screen.queryByRole('heading', { + name: /incidents\.reports\.dateofincident/i, + }), + ).not.toBeInTheDocument() }) diff --git a/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx b/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx index 69619d1626..3340e9e6ca 100644 --- a/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx +++ b/src/__tests__/incidents/view/ViewIncidentDetails.test.tsx @@ -1,8 +1,7 @@ -import { Button } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Router } from 'react-router' import ViewIncidentDetails from '../../../incidents/view/ViewIncidentDetails' @@ -14,6 +13,7 @@ import Permissions from '../../../shared/model/Permissions' describe('View Incident Details', () => { const expectedDate = new Date(2020, 5, 1, 19, 48) + const reportedDate = new Date(2020, 5, 1, 19, 50) const expectedResolveDate = new Date() let incidentRepositorySaveSpy: any const expectedIncidentId = '1234' @@ -23,14 +23,14 @@ describe('View Incident Details', () => { department: 'some department', description: 'some description', category: 'some category', - categoryItem: 'some category item', + categoryItem: 'some categoryItem', status: 'reported', reportedBy: 'some user id', - reportedOn: expectedDate.toISOString(), + reportedOn: reportedDate.toISOString(), date: expectedDate.toISOString(), } as Incident - const setup = async (mockIncident: Incident, permissions: Permissions[]) => { + const setup = (mockIncident: Incident, permissions: Permissions[]) => { jest.resetAllMocks() Date.now = jest.fn(() => expectedResolveDate.valueOf()) jest.spyOn(breadcrumbUtil, 'default') @@ -41,161 +41,143 @@ describe('View Incident Details', () => { const history = createMemoryHistory() history.push(`/incidents/1234`) - let wrapper: any - await act(async () => { - wrapper = await mount( - - - - - , - ) - }) - wrapper.update() - return { wrapper: wrapper as ReactWrapper, history } - } - - describe('view details', () => { - it('should call find incident by id', async () => { - await setup(expectedIncident, [Permissions.ViewIncident]) - - expect(IncidentRepository.find).toHaveBeenCalledTimes(1) - expect(IncidentRepository.find).toHaveBeenCalledWith(expectedIncidentId) - }) - - it('should render the date of incident', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) - - const dateOfIncidentFormGroup = wrapper.find('.incident-date') - expect(dateOfIncidentFormGroup.find('h4').text()).toEqual('incidents.reports.dateOfIncident') - expect(dateOfIncidentFormGroup.find('h5').text()).toEqual('2020-06-01 07:48 PM') - }) - it('should render the status', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + const renderResults = render( + + + + + , + ) - const dateOfIncidentFormGroup = wrapper.find('.incident-status') - expect(dateOfIncidentFormGroup.find('h4').text()).toEqual('incidents.reports.status') - expect(dateOfIncidentFormGroup.find('h5').text()).toEqual(expectedIncident.status) - }) - - it('should render the reported by', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) - - const dateOfIncidentFormGroup = wrapper.find('.incident-reported-by') - expect(dateOfIncidentFormGroup.find('h4').text()).toEqual('incidents.reports.reportedBy') - expect(dateOfIncidentFormGroup.find('h5').text()).toEqual(expectedIncident.reportedBy) - }) - - it('should render the reported on', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) - - const dateOfIncidentFormGroup = wrapper.find('.incident-reported-on') - expect(dateOfIncidentFormGroup.find('h4').text()).toEqual('incidents.reports.reportedOn') - expect(dateOfIncidentFormGroup.find('h5').text()).toEqual('2020-06-01 07:48 PM') - }) + return { ...renderResults, history } + } - it('should render the resolved on if incident status is resolved', async () => { - const mockIncident = { - ...expectedIncident, - status: 'resolved', - resolvedOn: '2020-07-10 06:33 PM', - } as Incident - const { wrapper } = await setup(mockIncident, [Permissions.ViewIncident]) - - const dateOfResolutionFormGroup = wrapper.find('.incident-resolved-on') - expect(dateOfResolutionFormGroup.find('h4').text()).toEqual('incidents.reports.resolvedOn') - expect(dateOfResolutionFormGroup.find('h5').text()).toEqual('2020-07-10 06:33 PM') - }) + describe('view incident details', () => { + describe('view incident details header', () => { + it('should render the date of the incident', async () => { + setup(expectedIncident, [Permissions.ViewIncident]) + expect( + await screen.findByRole('heading', { + name: /incidents\.reports\.dateofincident/i, + }), + ).toBeInTheDocument() + + expect( + await screen.findByRole('heading', { name: /2020-06-01 07:48 PM/i }), + ).toBeInTheDocument() + }) - it('should not render the resolved on if incident status is not resolved', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + it('should render the status of the incident', async () => { + setup(expectedIncident, [Permissions.ViewIncident]) - const completedOn = wrapper.find('.incident-resolved-on') - expect(completedOn).toHaveLength(0) - }) + expect( + await screen.findByRole('heading', { + name: /incidents\.reports\.status/i, + }), + ).toBeInTheDocument() - it('should render the department', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + expect(await screen.findByRole('heading', { name: 'reported' })).toBeInTheDocument() + }) - const departmentInput = wrapper.findWhere((w: any) => w.prop('name') === 'department') - expect(departmentInput.prop('label')).toEqual('incidents.reports.department') - expect(departmentInput.prop('value')).toEqual(expectedIncident.department) - }) + it('should render who reported the incident', async () => { + setup(expectedIncident, [Permissions.ViewIncident]) - it('should render the category', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + expect( + await screen.findByRole('heading', { + name: /incidents\.reports\.reportedby/i, + }), + ).toBeInTheDocument() - const categoryInput = wrapper.findWhere((w: any) => w.prop('name') === 'category') - expect(categoryInput.prop('label')).toEqual('incidents.reports.category') - expect(categoryInput.prop('value')).toEqual(expectedIncident.category) + expect(await screen.findByRole('heading', { name: 'some user id' })) + }) + it('should render the date the incident was reported', async () => { + setup(expectedIncident, [Permissions.ViewIncident]) + + expect( + await screen.findByRole('heading', { + name: /incidents\.reports\.reportedon/i, + }), + ).toBeInTheDocument() + + expect( + await screen.findByRole('heading', { name: /2020-06-01 07:50 PM/i }), + ).toBeInTheDocument() + }) }) + describe('form elements should not be editable', () => { + it('should render the department input with label and display value', async () => { + setup(expectedIncident, [Permissions.ViewIncident]) + expect(await screen.findByText(/incidents\.reports\.department/i)).toBeInTheDocument() - it('should render the category item', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + expect( + await screen.findByRole('textbox', { name: /incidents\.reports\.department/i }), + ).not.toBeEnabled() - const categoryItemInput = wrapper.findWhere((w: any) => w.prop('name') === 'categoryItem') - expect(categoryItemInput.prop('label')).toEqual('incidents.reports.categoryItem') - expect(categoryItemInput.prop('value')).toEqual(expectedIncident.categoryItem) - }) + expect( + await screen.findByRole('textbox', { name: /incidents\.reports\.department/i }), + ).toHaveDisplayValue('some department') + }) - it('should render the description', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + it('should render the category input with label and display value', async () => { + setup(expectedIncident, [Permissions.ViewIncident, Permissions.ResolveIncident]) + expect(await screen.findByText(/incidents\.reports\.category$/i)).toBeInTheDocument() - const descriptionTextInput = wrapper.findWhere((w: any) => w.prop('name') === 'description') - expect(descriptionTextInput.prop('label')).toEqual('incidents.reports.description') - expect(descriptionTextInput.prop('value')).toEqual(expectedIncident.description) - }) + expect( + await screen.findByRole('textbox', { name: /incidents\.reports\.category$/i }), + ).not.toBeEnabled() - it('should display a resolve incident button if the incident is in a reported state', async () => { - const { wrapper } = await setup(expectedIncident, [ - Permissions.ViewIncident, - Permissions.ResolveIncident, - ]) + expect( + await screen.findByRole('textbox', { name: /incidents\.reports\.category$/i }), + ).toHaveDisplayValue('some category') + }) + it('should render the categoryItem input with label and display value', async () => { + setup(expectedIncident, [Permissions.ViewIncident, Permissions.ResolveIncident]) + expect(await screen.findByText(/incidents\.reports\.categoryItem$/i)).toBeInTheDocument() - const buttons = wrapper.find(Button) - expect(buttons.at(0).text().trim()).toEqual('incidents.reports.resolve') - }) + expect( + await screen.findByRole('textbox', { name: /incidents\.reports\.categoryItem$/i }), + ).not.toBeEnabled() - it('should not display a resolve incident button if the user has no access ResolveIncident access', async () => { - const { wrapper } = await setup(expectedIncident, [Permissions.ViewIncident]) + expect( + await screen.findByRole('textbox', { name: /incidents\.reports\.categoryItem$/i }), + ).toHaveDisplayValue('some categoryItem') + }) - const resolveButton = wrapper.find(Button) - expect(resolveButton).toHaveLength(0) + it('should render the description input with label and display value', async () => { + setup(expectedIncident, [Permissions.ViewIncident, Permissions.ResolveIncident]) + expect( + await screen.findByRole('textbox', { + name: /incidents\.reports\.description/i, + }), + ).toBeInTheDocument() + expect( + screen.getByRole('textbox', { + name: /incidents\.reports\.description/i, + }), + ).not.toBeEnabled() + + expect( + await screen.findByRole('textbox', { name: /incidents\.reports\.description/i }), + ).toHaveDisplayValue('some description') + }) }) + describe('on resolve', () => { + it('should mark the status as resolved and fill in the resolved date with the current time', async () => { + const { history } = setup(expectedIncident, [ + Permissions.ViewIncident, + Permissions.ResolveIncident, + ]) - it('should not display a resolve incident button if the incident is resolved', async () => { - const mockIncident = { ...expectedIncident, status: 'resolved' } as Incident - const { wrapper } = await setup(mockIncident, [Permissions.ViewIncident]) + const resolveButton = await screen.findByRole('button', { + name: /incidents\.reports\.resolve/i, + }) - const resolveButton = wrapper.find(Button) - expect(resolveButton).toHaveLength(0) - }) - }) + userEvent.click(resolveButton) - describe('on resolve', () => { - it('should mark the status as resolved and fill in the resolved date with the current time', async () => { - const { wrapper, history } = await setup(expectedIncident, [ - Permissions.ViewIncident, - Permissions.ResolveIncident, - ]) - - const resolveButton = wrapper.find(Button).at(0) - await act(async () => { - const onClick = resolveButton.prop('onClick') as any - await onClick() + await waitFor(() => expect(incidentRepositorySaveSpy).toHaveBeenCalledTimes(1)) + expect(history.location.pathname).toEqual('/incidents') }) - wrapper.update() - - expect(incidentRepositorySaveSpy).toHaveBeenCalledTimes(1) - expect(incidentRepositorySaveSpy).toHaveBeenCalledWith( - expect.objectContaining({ - ...expectedIncident, - status: 'resolved', - resolvedOn: expectedResolveDate.toISOString(), - }), - ) - expect(history.location.pathname).toEqual('/incidents') }) }) }) diff --git a/src/__tests__/labs/Labs.test.tsx b/src/__tests__/labs/Labs.test.tsx index 19077a05a5..38821a8df5 100644 --- a/src/__tests__/labs/Labs.test.tsx +++ b/src/__tests__/labs/Labs.test.tsx @@ -1,5 +1,4 @@ -import { act } from '@testing-library/react' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' import React from 'react' import { Provider } from 'react-redux' import { MemoryRouter } from 'react-router-dom' @@ -7,8 +6,6 @@ import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import Labs from '../../labs/Labs' -import NewLabRequest from '../../labs/requests/NewLabRequest' -import ViewLab from '../../labs/ViewLab' import * as titleUtil from '../../page-header/title/TitleContext' import LabRepository from '../../shared/db/LabRepository' import PatientRepository from '../../shared/db/PatientRepository' @@ -17,68 +14,93 @@ import Patient from '../../shared/model/Patient' import Permissions from '../../shared/model/Permissions' import { RootState } from '../../shared/store' -const { TitleProvider } = titleUtil +const { TitleProvider, useTitle } = titleUtil const mockStore = createMockStore([thunk]) -describe('Labs', () => { - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(LabRepository, 'findAll').mockResolvedValue([]) - jest - .spyOn(LabRepository, 'find') - .mockResolvedValue({ id: '1234', requestedOn: new Date().toISOString() } as Lab) - jest - .spyOn(PatientRepository, 'find') - .mockResolvedValue({ id: '12345', fullName: 'test test' } as Patient) +const Title = () => { + const { title } = useTitle() - const setup = async (initialEntry: string, permissions: Permissions[] = []) => { - const store = mockStore({ - user: { permissions }, - } as any) + return

{title}

+} - let wrapper: any - await act(async () => { - wrapper = await mount( - - - - - - - , - ) - }) +const expectedLab = { + code: 'L-code', + id: '1234', + patient: '1234', + type: 'Type', + requestedOn: new Date().toISOString(), +} as Lab +const expectedPatient = { + fullName: 'fullName', + id: '1234', +} as Patient - wrapper.update() - return { wrapper: wrapper as ReactWrapper } +const setup = (initialPath: string, permissions: Permissions[]) => { + jest.resetAllMocks() + jest.spyOn(LabRepository, 'find').mockResolvedValue(expectedLab) + jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) + const store = mockStore({ user: { permissions } } as any) + + return { + ...render( + + + + + <Labs /> + </TitleProvider> + </MemoryRouter> + </Provider>, + ), } +} +describe('Labs', () => { describe('routing', () => { + describe('/labs', () => { + it('should render the view labs screen when /labs is accessed', async () => { + setup('/labs', [Permissions.ViewLabs]) + expect(screen.getByRole('heading', { name: /labs\.label/i })).toBeInTheDocument() + }) + + it('should not navigate to /labs if the user does not have ViewLabs permissions', async () => { + setup('/labs', []) + expect(screen.queryByRole('heading', { name: /labs\.label/i })).not.toBeInTheDocument() + }) + }) + describe('/labs/new', () => { it('should render the new lab request screen when /labs/new is accessed', async () => { - const { wrapper } = await setup('/labs/new', [Permissions.RequestLab]) - - expect(wrapper.find(NewLabRequest)).toHaveLength(1) + setup('/labs/new', [Permissions.RequestLab]) + expect(screen.getByRole('heading', { name: /labs\.requests\.new/i })).toBeInTheDocument() }) it('should not navigate to /labs/new if the user does not have RequestLab permissions', async () => { - const { wrapper } = await setup('/labs/new') - - expect(wrapper.find(NewLabRequest)).toHaveLength(0) + setup('/labs/new', []) + expect( + screen.queryByRole('heading', { name: /labs\.requests\.new/i }), + ).not.toBeInTheDocument() }) }) describe('/labs/:id', () => { it('should render the view lab screen when /labs/:id is accessed', async () => { - const { wrapper } = await setup('/labs/1234', [Permissions.ViewLab]) - - expect(wrapper.find(ViewLab)).toHaveLength(1) + setup('/labs/1234', [Permissions.ViewLab]) + expect( + await screen.findByRole('heading', { + name: `${expectedLab.type} for ${expectedPatient.fullName}(${expectedLab.code})`, + }), + ).toBeInTheDocument() }) - }) - - it('should not navigate to /labs/:id if the user does not have ViewLab permissions', async () => { - const { wrapper } = await setup('/labs/1234') - expect(wrapper.find(ViewLab)).toHaveLength(0) + it('should not navigate to /labs/:id if the user does not have ViewLab permissions', async () => { + setup('/labs/1234', []) + expect( + screen.queryByRole('heading', { + name: `${expectedLab.type} for ${expectedPatient.fullName}(${expectedLab.code})`, + }), + ).not.toBeInTheDocument() + }) }) }) }) diff --git a/src/__tests__/labs/ViewLab.test.tsx b/src/__tests__/labs/ViewLab.test.tsx index 78f09fbc10..f4a49a1c08 100644 --- a/src/__tests__/labs/ViewLab.test.tsx +++ b/src/__tests__/labs/ViewLab.test.tsx @@ -1,7 +1,7 @@ -import { Badge, Button, Alert } from '@hospitalrun/components' -import { act } from '@testing-library/react' +import { Toaster } from '@hospitalrun/components' +import { render, screen, waitFor, waitForElementToBeRemoved, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import format from 'date-fns/format' -import { mount, ReactWrapper } from 'enzyme' import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' @@ -12,463 +12,353 @@ import thunk from 'redux-thunk' import * as validateUtil from '../../labs/utils/validate-lab' import { LabError } from '../../labs/utils/validate-lab' import ViewLab from '../../labs/ViewLab' -import * as ButtonBarProvider from '../../page-header/button-toolbar/ButtonBarProvider' +import { ButtonBarProvider } from '../../page-header/button-toolbar/ButtonBarProvider' import * as titleUtil from '../../page-header/title/TitleContext' -import TextFieldWithLabelFormGroup from '../../shared/components/input/TextFieldWithLabelFormGroup' import LabRepository from '../../shared/db/LabRepository' import PatientRepository from '../../shared/db/PatientRepository' import Lab from '../../shared/model/Lab' import Patient from '../../shared/model/Patient' import Permissions from '../../shared/model/Permissions' import { RootState } from '../../shared/store' +import { expectOneConsoleError } from '../test-utils/console.utils' const mockStore = createMockStore<RootState, any>([thunk]) -describe('View Lab', () => { - let history: any - const mockPatient = { fullName: 'test' } - const mockLab = { - code: 'L-1234', - id: '12456', - status: 'requested', - patient: '1234', - type: 'lab type', - notes: ['lab notes'], - requestedOn: '2020-03-30T04:43:20.102Z', - } as Lab +const mockPatient = { fullName: 'Full Name' } - let setButtonToolBarSpy: any - let labRepositorySaveSpy: any +const setup = (lab?: Partial<Lab>, permissions = [Permissions.ViewLab], error = {}) => { const expectedDate = new Date() + const mockLab = { + ...{ + code: 'L-1234', + id: '12456', + status: 'requested', + patient: '1234', + type: 'lab type', + notes: [], + requestedOn: '2020-03-30T04:43:20.102Z', + }, + ...lab, + } as Lab - const setup = async (lab: Lab, permissions: Permissions[], error = {}) => { - jest.resetAllMocks() - Date.now = jest.fn(() => expectedDate.valueOf()) - setButtonToolBarSpy = jest.fn() - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) - labRepositorySaveSpy = jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(mockLab) - jest.spyOn(PatientRepository, 'find').mockResolvedValue(mockPatient as Patient) - jest.spyOn(LabRepository, 'find').mockResolvedValue(lab) - - history = createMemoryHistory() - history.push(`labs/${lab.id}`) - const store = mockStore({ - user: { - permissions, - }, - lab: { - lab, - patient: mockPatient, - error, - status: Object.keys(error).length > 0 ? 'error' : 'completed', - }, - } as any) - - let wrapper: any - await act(async () => { - wrapper = await mount( - <ButtonBarProvider.ButtonBarProvider> - <Provider store={store}> - <Router history={history}> - <Route path="/labs/:id"> - <titleUtil.TitleProvider> - <ViewLab /> - </titleUtil.TitleProvider> - </Route> - </Router> - </Provider> - </ButtonBarProvider.ButtonBarProvider>, - ) - }) - wrapper.find(ViewLab).props().updateTitle = jest.fn() - wrapper.update() - return { wrapper: wrapper as ReactWrapper } + jest.resetAllMocks() + Date.now = jest.fn(() => expectedDate.valueOf()) + jest.spyOn(PatientRepository, 'find').mockResolvedValue(mockPatient as Patient) + jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(mockLab) + jest.spyOn(LabRepository, 'find').mockResolvedValue(mockLab) + + const history = createMemoryHistory({ initialEntries: [`/labs/${mockLab.id}`] }) + const store = mockStore({ + user: { + permissions, + }, + lab: { + mockLab, + patient: mockPatient, + error, + status: Object.keys(error).length > 0 ? 'error' : 'completed', + }, + } as any) + + return { + history, + mockLab, + expectedDate, + ...render( + <ButtonBarProvider> + <Provider store={store}> + <Router history={history}> + <Route path="/labs/:id"> + <titleUtil.TitleProvider> + <ViewLab /> + </titleUtil.TitleProvider> + </Route> + </Router> + <Toaster draggable hideProgressBar /> + </Provider> + </ButtonBarProvider>, + ), } +} - describe('title', () => { - it('should have called the useUpdateTitle hook', async () => { - const expectedLab = { ...mockLab } as Lab - await setup(expectedLab, [Permissions.ViewLab]) - expect(titleUtil.useUpdateTitle).toHaveBeenCalled() - }) - }) - +describe('View Lab', () => { describe('page content', () => { - it('should display the patient full name for the for', async () => { - const expectedLab = { ...mockLab } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - const forPatientDiv = wrapper.find('.for-patient') - - expect(forPatientDiv.find('h4').text().trim()).toEqual('labs.lab.for') + it("should display the patients' full name", async () => { + setup() - expect(forPatientDiv.find('h5').text().trim()).toEqual(mockPatient.fullName) + expect(await screen.findByRole('heading', { name: mockPatient.fullName })).toBeInTheDocument() }) - it('should display the lab type for type', async () => { - const expectedLab = { ...mockLab, type: 'expected type' } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - const labTypeDiv = wrapper.find('.lab-type') - expect(labTypeDiv.find('h4').text().trim()).toEqual('labs.lab.type') + it('should display the lab-type', async () => { + const { mockLab } = setup({ type: 'expected type' }) - expect(labTypeDiv.find('h5').text().trim()).toEqual(expectedLab.type) + expect(await screen.findByRole('heading', { name: mockLab.type })).toBeInTheDocument() }) it('should display the requested on date', async () => { - const expectedLab = { ...mockLab, requestedOn: '2020-03-30T04:43:20.102Z' } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - const requestedOnDiv = wrapper.find('.requested-on') - expect(requestedOnDiv.find('h4').text().trim()).toEqual('labs.lab.requestedOn') + const { mockLab } = setup({ requestedOn: '2020-03-30T04:43:20.102Z' }) - expect(requestedOnDiv.find('h5').text().trim()).toEqual( - format(new Date(expectedLab.requestedOn), 'yyyy-MM-dd hh:mm a'), - ) + expect( + await screen.findByRole('heading', { + name: format(new Date(mockLab.requestedOn), 'yyyy-MM-dd hh:mm a'), + }), + ).toBeInTheDocument() }) it('should not display the completed date if the lab is not completed', async () => { - const expectedLab = { ...mockLab } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - const completedOnDiv = wrapper.find('.completed-on') + const completedDate = new Date('2020-10-10T10:10:10.100') // We want a different date than the mocked date + setup({ completedOn: completedDate.toISOString() }) - expect(completedOnDiv).toHaveLength(0) + await waitForElementToBeRemoved(() => screen.queryByText(/loading/i)) + expect( + screen.queryByText(format(completedDate, 'yyyy-MM-dd HH:mm a')), + ).not.toBeInTheDocument() }) - it('should not display the canceled date if the lab is not canceled', async () => { - const expectedLab = { ...mockLab } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - const completedOnDiv = wrapper.find('.canceled-on') + it('should not display the cancelled date if the lab is not cancelled', async () => { + const cancelledDate = new Date('2020-10-10T10:10:10.100') // We want a different date than the mocked date + setup({ canceledOn: cancelledDate.toISOString() }) - expect(completedOnDiv).toHaveLength(0) + await waitForElementToBeRemoved(() => screen.queryByText(/loading/i)) + expect( + screen.queryByText(format(cancelledDate, 'yyyy-MM-dd HH:mm a')), + ).not.toBeInTheDocument() }) it('should render a result text field', async () => { - const expectedLab = { - ...mockLab, - result: 'expected results', - } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - - const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) + const { mockLab } = setup({ result: 'expected results' }) - expect(resultTextField).toBeDefined() - expect(resultTextField.prop('label')).toEqual('labs.lab.result') - expect(resultTextField.prop('value')).toEqual(expectedLab.result) + expect( + await screen.findByRole('textbox', { + name: /labs\.lab\.result/i, + }), + ).toHaveValue(mockLab.result) }) - it('should display the past notes', async () => { - const expectedNotes = 'expected notes' - const expectedLab = { ...mockLab, notes: [expectedNotes] } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - - const notes = wrapper.findWhere((w) => w.prop('data-test') === 'note') - const pastNotesIndex = notes.reduce( - (result: number, item: ReactWrapper, index: number) => - item.text().trim() === expectedNotes ? index : result, - -1, - ) + it('should not display past notes if there is not', async () => { + setup({ notes: [] }) - expect(pastNotesIndex).not.toBe(-1) - expect(notes).toHaveLength(1) + await waitForElementToBeRemoved(() => screen.queryByText(/loading/i)) + expect(screen.queryAllByTestId('note')).toHaveLength(0) }) - it('should not display past notes if there is not', async () => { - const expectedLab = { ...mockLab, notes: undefined } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - - const notes = wrapper.findWhere((w) => w.prop('data-test') === 'note') + it('should display the past notes', async () => { + const expectedNotes = 'expected notes' + setup({ notes: [expectedNotes] }) - expect(notes).toHaveLength(0) + expect(await screen.findByTestId('note')).toHaveTextContent(expectedNotes) }) it('should display the notes text field empty', async () => { - const expectedNotes = 'expected notes' - const expectedLab = { ...mockLab, notes: [expectedNotes] } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - - const notesTextField = wrapper.find(TextFieldWithLabelFormGroup).at(1) + setup() - expect(notesTextField).toBeDefined() - expect(notesTextField.prop('value')).toEqual('') + expect(await screen.findByLabelText('labs.lab.notes')).toHaveValue('') }) it('should display errors', async () => { - const expectedLab = { ...mockLab, status: 'requested' } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab, Permissions.CompleteLab]) - const expectedError = { message: 'some message', result: 'some result feedback' } as LabError + setup({ status: 'requested' }, [Permissions.ViewLab, Permissions.CompleteLab]) + + expectOneConsoleError(expectedError) jest.spyOn(validateUtil, 'validateLabComplete').mockReturnValue(expectedError) - const completeButton = wrapper.find(Button).at(1) - await act(async () => { - const onClick = completeButton.prop('onClick') as any - await onClick() - }) - wrapper.update() - - const alert = wrapper.find(Alert) - const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) - expect(alert.prop('message')).toEqual(expectedError.message) - expect(alert.prop('title')).toEqual('states.error') - expect(alert.prop('color')).toEqual('danger') - expect(resultTextField.prop('isInvalid')).toBeTruthy() - expect(resultTextField.prop('feedback')).toEqual(expectedError.result) + userEvent.click( + await screen.findByRole('button', { + name: /labs\.requests\.complete/i, + }), + ) + + const alert = await screen.findByRole('alert') + + expect(alert).toContainElement(screen.getByText(/states\.error/i)) + expect(alert).toContainElement(screen.getByText(/some message/i)) + expect(screen.getByLabelText(/labs\.lab\.result/i)).toHaveClass('is-invalid') }) describe('requested lab request', () => { it('should display a warning badge if the status is requested', async () => { - const expectedLab = { ...mockLab, status: 'requested' } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - const labStatusDiv = wrapper.find('.lab-status') - const badge = labStatusDiv.find(Badge) - expect(labStatusDiv.find('h4').text().trim()).toEqual('labs.lab.status') - - expect(badge.prop('color')).toEqual('warning') - expect(badge.text().trim()).toEqual(expectedLab.status) + const { mockLab } = setup() + + const status = await screen.findByText(mockLab.status) + expect(status.closest('span')).toHaveClass('badge-warning') }) it('should display a update lab, complete lab, and cancel lab button if the lab is in a requested state', async () => { - const { wrapper } = await setup(mockLab, [ - Permissions.ViewLab, - Permissions.CompleteLab, - Permissions.CancelLab, - ]) - - const buttons = wrapper.find(Button) - expect(buttons.at(0).text().trim()).toEqual('labs.requests.update') - - expect(buttons.at(1).text().trim()).toEqual('labs.requests.complete') - - expect(buttons.at(2).text().trim()).toEqual('labs.requests.cancel') + setup({}, [Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab]) + + expect(await screen.findAllByRole('button')).toEqual( + expect.arrayContaining([ + screen.getByText(/labs\.requests\.update/i), + screen.getByText(/labs\.requests\.complete/i), + screen.getByText(/labs\.requests\.cancel/i), + ]), + ) }) }) describe('canceled lab request', () => { it('should display a danger badge if the status is canceled', async () => { - const expectedLab = { ...mockLab, status: 'canceled' } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) + const { mockLab } = setup({ status: 'canceled' }) - const labStatusDiv = wrapper.find('.lab-status') - const badge = labStatusDiv.find(Badge) - expect(labStatusDiv.find('h4').text().trim()).toEqual('labs.lab.status') - - expect(badge.prop('color')).toEqual('danger') - expect(badge.text().trim()).toEqual(expectedLab.status) + const status = await screen.findByText(mockLab.status) + expect(status.closest('span')).toHaveClass('badge-danger') }) - it('should display the canceled on date if the lab request has been canceled', async () => { - const expectedLab = { - ...mockLab, + it('should display the cancelled on date if the lab request has been cancelled', async () => { + const { mockLab } = setup({ status: 'canceled', canceledOn: '2020-03-30T04:45:20.102Z', - } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - const canceledOnDiv = wrapper.find('.canceled-on') - - expect(canceledOnDiv.find('h4').text().trim()).toEqual('labs.lab.canceledOn') + }) - expect(canceledOnDiv.find('h5').text().trim()).toEqual( - format(new Date(expectedLab.canceledOn as string), 'yyyy-MM-dd hh:mm a'), - ) + expect( + await screen.findByRole('heading', { + name: format(new Date(mockLab.canceledOn as string), 'yyyy-MM-dd hh:mm a'), + }), + ).toBeInTheDocument() }) it('should not display update, complete, and cancel button if the lab is canceled', async () => { - const expectedLab = { ...mockLab, status: 'canceled' } as Lab - - const { wrapper } = await setup(expectedLab, [ - Permissions.ViewLab, - Permissions.CompleteLab, - Permissions.CancelLab, - ]) - - const buttons = wrapper.find(Button) - expect(buttons).toHaveLength(0) - }) - - it('should not display an update button if the lab is canceled', async () => { - const expectedLab = { ...mockLab, status: 'canceled' } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) + setup( + { + status: 'canceled', + }, + [Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab], + ) - const updateButton = wrapper.find(Button) - expect(updateButton).toHaveLength(0) + await waitForElementToBeRemoved(() => screen.queryByText(/loading/i)) + expect(screen.queryByRole('button')).not.toBeInTheDocument() }) it('should not display notes text field if the status is canceled', async () => { - const expectedLab = { ...mockLab, status: 'canceled' } as Lab - - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - - const textsField = wrapper.find(TextFieldWithLabelFormGroup) - const notesTextField = wrapper.find('notesTextField') + setup({ status: 'canceled' }) - expect(textsField.length).toBe(1) - expect(notesTextField).toHaveLength(0) + expect(await screen.findByText(/labs\.lab\.notes/i)).toBeInTheDocument() + expect(screen.queryByLabelText(/labs\.lab\.notes/i)).not.toBeInTheDocument() }) }) describe('completed lab request', () => { it('should display a primary badge if the status is completed', async () => { - jest.resetAllMocks() - const expectedLab = { ...mockLab, status: 'completed' } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - const labStatusDiv = wrapper.find('.lab-status') - const badge = labStatusDiv.find(Badge) - expect(labStatusDiv.find('h4').text().trim()).toEqual('labs.lab.status') - - expect(badge.prop('color')).toEqual('primary') - expect(badge.text().trim()).toEqual(expectedLab.status) + const { mockLab } = setup({ status: 'completed' }) + + const status = await screen.findByText(mockLab.status) + expect(status.closest('span')).toHaveClass('badge-primary') }) it('should display the completed on date if the lab request has been completed', async () => { - const expectedLab = { - ...mockLab, + const { mockLab } = setup({ status: 'completed', completedOn: '2020-03-30T04:44:20.102Z', - } as Lab - const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - const completedOnDiv = wrapper.find('.completed-on') - - expect(completedOnDiv.find('h4').text().trim()).toEqual('labs.lab.completedOn') + }) - expect(completedOnDiv.find('h5').text().trim()).toEqual( - format(new Date(expectedLab.completedOn as string), 'yyyy-MM-dd hh:mm a'), - ) + expect( + await screen.findByText( + format(new Date(mockLab.completedOn as string), 'yyyy-MM-dd hh:mm a'), + ), + ).toBeInTheDocument() }) it('should not display update, complete, and cancel buttons if the lab is completed', async () => { - const expectedLab = { ...mockLab, status: 'completed' } as Lab - - const { wrapper } = await setup(expectedLab, [ - Permissions.ViewLab, - Permissions.CompleteLab, - Permissions.CancelLab, - ]) + setup( + { + status: 'completed', + }, + [Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab], + ) - const buttons = wrapper.find(Button) - expect(buttons).toHaveLength(0) + await waitForElementToBeRemoved(() => screen.queryByText(/loading/i)) + expect(screen.queryByRole('button')).not.toBeInTheDocument() }) it('should not display notes text field if the status is completed', async () => { - const expectedLab = { ...mockLab, status: 'completed' } as Lab - - const { wrapper } = await setup(expectedLab, [ - Permissions.ViewLab, - Permissions.CompleteLab, - Permissions.CancelLab, - ]) - - const textsField = wrapper.find(TextFieldWithLabelFormGroup) - const notesTextField = wrapper.find('notesTextField') + setup( + { + status: 'completed', + }, + [Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab], + ) - expect(textsField.length).toBe(1) - expect(notesTextField).toHaveLength(0) + expect(await screen.findByText(/labs\.lab\.notes/i)).toBeInTheDocument() + expect(screen.queryByLabelText(/labs\.lab\.notes/i)).not.toBeInTheDocument() }) }) }) describe('on update', () => { it('should update the lab with the new information', async () => { - const { wrapper } = await setup(mockLab, [Permissions.ViewLab]) + const { history } = setup() const expectedResult = 'expected result' const newNotes = 'expected notes' - const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) - act(() => { - const onChange = resultTextField.prop('onChange') as any - onChange({ currentTarget: { value: expectedResult } }) - }) - wrapper.update() + const resultTextField = await screen.findByLabelText(/labs\.lab\.result/i) + userEvent.type(resultTextField, expectedResult) - const notesTextField = wrapper.find(TextFieldWithLabelFormGroup).at(1) - act(() => { - const onChange = notesTextField.prop('onChange') as any - onChange({ currentTarget: { value: newNotes } }) - }) - wrapper.update() - const updateButton = wrapper.find(Button) - await act(async () => { - const onClick = updateButton.prop('onClick') as any - onClick() - }) - - const expectedNotes = mockLab.notes ? [...mockLab.notes, newNotes] : [newNotes] + const notesTextField = screen.getByLabelText('labs.lab.notes') + userEvent.type(notesTextField, newNotes) - expect(labRepositorySaveSpy).toHaveBeenCalledTimes(1) - expect(labRepositorySaveSpy).toHaveBeenCalledWith( - expect.objectContaining({ ...mockLab, result: expectedResult, notes: expectedNotes }), + userEvent.click( + screen.getByRole('button', { + name: /labs\.requests\.update/i, + }), ) - expect(history.location.pathname).toEqual('/labs/12456') + + await waitFor(() => { + expect(history.location.pathname).toEqual('/labs/12456') + }) + expect(screen.getByLabelText(/labs\.lab\.result/i)).toHaveTextContent(expectedResult) + expect(screen.getByTestId('note')).toHaveTextContent(newNotes) }) }) describe('on complete', () => { it('should mark the status as completed and fill in the completed date with the current time', async () => { - const { wrapper } = await setup(mockLab, [ + const { history } = setup({}, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, ]) const expectedResult = 'expected result' - const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) - await act(async () => { - const onChange = resultTextField.prop('onChange') as any - await onChange({ currentTarget: { value: expectedResult } }) - }) - wrapper.update() - - const completeButton = wrapper.find(Button).at(1) - await act(async () => { - const onClick = completeButton.prop('onClick') as any - await onClick() - }) - wrapper.update() + userEvent.type(await screen.findByLabelText(/labs\.lab\.result/i), expectedResult) - expect(labRepositorySaveSpy).toHaveBeenCalledTimes(1) - expect(labRepositorySaveSpy).toHaveBeenCalledWith( - expect.objectContaining({ - ...mockLab, - result: expectedResult, - status: 'completed', - completedOn: expectedDate.toISOString(), + userEvent.click( + screen.getByRole('button', { + name: /labs\.requests\.complete/i, }), ) - expect(history.location.pathname).toEqual('/labs/12456') + + await waitFor(() => { + expect(history.location.pathname).toEqual('/labs/12456') + }) + expect(await screen.findByRole('alert')).toBeInTheDocument() + expect( + within(screen.getByRole('alert')).getByText(/labs\.successfullyCompleted/i), + ).toBeInTheDocument() }) }) describe('on cancel', () => { - it('should mark the status as canceled and fill in the cancelled on date with the current time', async () => { - const { wrapper } = await setup(mockLab, [ + // integration test candidate; after 'cancelled' route goes to <Labs /> + // 'should mark the status as canceled and fill in the cancelled on date with the current time' + it('should mark the status as canceled and redirect to /labs', async () => { + const { history } = setup({}, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, ]) const expectedResult = 'expected result' - const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) - await act(async () => { - const onChange = resultTextField.prop('onChange') as any - await onChange({ currentTarget: { value: expectedResult } }) - }) - wrapper.update() + userEvent.type(await screen.findByLabelText(/labs\.lab\.result/i), expectedResult) - const cancelButton = wrapper.find(Button).at(2) - await act(async () => { - const onClick = cancelButton.prop('onClick') as any - await onClick() - }) - wrapper.update() - - expect(labRepositorySaveSpy).toHaveBeenCalledTimes(1) - expect(labRepositorySaveSpy).toHaveBeenCalledWith( - expect.objectContaining({ - ...mockLab, - result: expectedResult, - status: 'canceled', - canceledOn: expectedDate.toISOString(), + userEvent.click( + screen.getByRole('button', { + name: /labs\.requests\.cancel/i, }), ) - expect(history.location.pathname).toEqual('/labs') + + await waitFor(() => { + expect(history.location.pathname).toEqual('/labs') + }) }) }) }) diff --git a/src/__tests__/labs/ViewLabs.test.tsx b/src/__tests__/labs/ViewLabs.test.tsx index 483f111d52..54867a5924 100644 --- a/src/__tests__/labs/ViewLabs.test.tsx +++ b/src/__tests__/labs/ViewLabs.test.tsx @@ -1,6 +1,6 @@ -import { Select, Table, TextInput } from '@hospitalrun/components' -import { act } from '@testing-library/react' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import format from 'date-fns/format' import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' @@ -10,6 +10,7 @@ import thunk from 'redux-thunk' import ViewLabs from '../../labs/ViewLabs' import * as ButtonBarProvider from '../../page-header/button-toolbar/ButtonBarProvider' +import ButtonToolbar from '../../page-header/button-toolbar/ButtonToolBar' import * as titleUtil from '../../page-header/title/TitleContext' import LabRepository from '../../shared/db/LabRepository' import Lab from '../../shared/model/Lab' @@ -18,167 +19,131 @@ import { RootState } from '../../shared/store' const mockStore = createMockStore<RootState, any>([thunk]) -describe('View Labs', () => { - let history: any - const setButtonToolBarSpy = jest.fn() - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) - - const setup = async (permissions: Permissions[] = []) => { - history = createMemoryHistory() - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - - const store = mockStore({ - title: '', - user: { - permissions, - }, - } as any) - - let wrapper: any - await act(async () => { - wrapper = await mount( - <ButtonBarProvider.ButtonBarProvider> - <Provider store={store}> - <Router history={history}> - <titleUtil.TitleProvider> - <ViewLabs /> - </titleUtil.TitleProvider> - </Router> - </Provider> - </ButtonBarProvider.ButtonBarProvider>, - ) - }) - - wrapper.find(ViewLabs).props().updateTitle = jest.fn() - wrapper.update() - return { wrapper: wrapper as ReactWrapper } +const setup = (permissions: Permissions[] = []) => { + const expectedLab = { + code: 'L-1234', + id: '1234', + type: 'lab type', + patient: 'patientId', + status: 'requested', + requestedOn: '2020-03-30T04:43:20.102Z', + } as Lab + + jest.spyOn(LabRepository, 'findAll').mockResolvedValue([expectedLab]) + + const history = createMemoryHistory() + + const store = mockStore({ + title: '', + user: { + permissions, + }, + } as any) + + return { + expectedLab, + history, + ...render( + <Provider store={store}> + <Router history={history}> + <ButtonBarProvider.ButtonBarProvider> + <ButtonToolbar /> + <titleUtil.TitleProvider> + <ViewLabs /> + </titleUtil.TitleProvider> + </ButtonBarProvider.ButtonBarProvider> + </Router> + </Provider>, + ), } +} - describe('title', () => { - it('should have called the useUpdateTitle hook', async () => { - await setup() - expect(titleUtil.useUpdateTitle).toHaveBeenCalled() - }) +describe('View Labs', () => { + beforeEach(() => { + jest.resetAllMocks() }) describe('button bar', () => { - beforeEach(() => { - setButtonToolBarSpy.mockReset() - }) - it('should display button to add new lab request', async () => { - await setup([Permissions.ViewLab, Permissions.RequestLab]) + setup([Permissions.ViewLab, Permissions.RequestLab]) - const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] - expect((actualButtons[0] as any).props.children).toEqual('labs.requests.new') + expect( + await screen.findByRole('button', { name: /labs\.requests\.new/i }), + ).toBeInTheDocument() }) it('should not display button to add new lab request if the user does not have permissions', async () => { - await setup([Permissions.ViewLabs]) + setup([Permissions.ViewLabs]) - const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] - expect(actualButtons).toEqual([]) + expect(screen.queryByRole('button', { name: /labs\.requests\.new/i })).not.toBeInTheDocument() }) }) describe('table', () => { - const expectedLab = { - code: 'L-1234', - id: '1234', - type: 'lab type', - patient: 'patientId', - status: 'requested', - requestedOn: '2020-03-30T04:43:20.102Z', - } as Lab - - jest.spyOn(LabRepository, 'findAll').mockResolvedValue([expectedLab]) - it('should render a table with data', async () => { - const { wrapper } = await setup([Permissions.ViewLabs, Permissions.RequestLab]) - - const table = wrapper.find(Table) - const columns = table.prop('columns') - const actions = table.prop('actions') as any - expect(columns[0]).toEqual(expect.objectContaining({ label: 'labs.lab.code', key: 'code' })) - expect(columns[1]).toEqual(expect.objectContaining({ label: 'labs.lab.type', key: 'type' })) - expect(columns[2]).toEqual( - expect.objectContaining({ label: 'labs.lab.requestedOn', key: 'requestedOn' }), - ) - expect(columns[3]).toEqual( - expect.objectContaining({ label: 'labs.lab.status', key: 'status' }), + const { expectedLab } = setup([Permissions.ViewLabs, Permissions.RequestLab]) + + const headers = await screen.findAllByRole('columnheader') + const cells = screen.getAllByRole('cell') + + expect(headers[0]).toHaveTextContent(/labs\.lab\.code/i) + expect(headers[1]).toHaveTextContent(/labs\.lab\.type/i) + expect(headers[2]).toHaveTextContent(/labs\.lab\.requestedOn/i) + expect(headers[3]).toHaveTextContent(/labs\.lab\.status/i) + expect(cells[0]).toHaveTextContent(expectedLab.code) + expect(cells[1]).toHaveTextContent(expectedLab.type) + expect(cells[2]).toHaveTextContent( + format(new Date(expectedLab.requestedOn), 'yyyy-MM-dd hh:mm a'), ) - - expect(actions[0]).toEqual(expect.objectContaining({ label: 'actions.view' })) - expect(table.prop('actionsHeaderText')).toEqual('actions.label') - expect(table.prop('data')).toEqual([expectedLab]) + expect(cells[3]).toHaveTextContent(expectedLab.status) + expect(screen.getByRole('button', { name: /actions.view/i })).toBeInTheDocument() }) it('should navigate to the lab when the view button is clicked', async () => { - const { wrapper } = await setup([Permissions.ViewLabs, Permissions.RequestLab]) - const tr = wrapper.find('tr').at(1) + const { history, expectedLab } = setup([Permissions.ViewLabs, Permissions.RequestLab]) + + userEvent.click(await screen.findByRole('button', { name: /actions.view/i })) - act(() => { - const onClick = tr.find('button').prop('onClick') as any - onClick({ stopPropagation: jest.fn() }) + await waitFor(() => { + expect(history.location.pathname).toEqual(`/labs/${expectedLab.id}`) }) - expect(history.location.pathname).toEqual(`/labs/${expectedLab.id}`) }) }) describe('dropdown', () => { - const searchLabsSpy = jest.spyOn(LabRepository, 'search') - - beforeEach(() => { - searchLabsSpy.mockClear() - }) - it('should search for labs when dropdown changes', async () => { const expectedStatus = 'requested' - const { wrapper } = await setup([Permissions.ViewLabs]) - - await act(async () => { - const onChange = wrapper.find(Select).prop('onChange') as any - await onChange([expectedStatus]) - }) + setup([Permissions.ViewLabs]) - expect(searchLabsSpy).toHaveBeenCalledTimes(1) - expect(searchLabsSpy).toHaveBeenCalledWith( - expect.objectContaining({ status: expectedStatus }), + userEvent.type( + screen.getByRole('combobox'), + `{selectall}{backspace}${expectedStatus}{arrowdown}{enter}`, ) + expect(screen.getByRole('combobox')).toHaveValue('labs.status.requested') }) }) describe('search functionality', () => { - const searchLabsSpy = jest.spyOn(LabRepository, 'search') - - beforeEach(() => { - searchLabsSpy.mockClear() - }) - - it('should search for labs after the search text has not changed for 500 milliseconds', async () => { - jest.useFakeTimers() - const { wrapper } = await setup([Permissions.ViewLabs]) - - const expectedSearchText = 'search text' - - act(() => { - const onChange = wrapper.find(TextInput).prop('onChange') as any - onChange({ - target: { - value: expectedSearchText, - }, - preventDefault: jest.fn(), - }) - }) - - act(() => { - jest.advanceTimersByTime(500) + it('should search for labs after the search text has not changed for 500 milliseconds', async (): Promise<void> => { + const expectedLab2 = { + code: 'L-5678', + id: '5678', + type: 'another type 2 phaser', + patient: 'patientIdB', + status: 'requested', + requestedOn: '2020-03-30T04:43:20.102Z', + } as Lab + const { expectedLab } = setup([Permissions.ViewLabs, Permissions.RequestLab]) + jest.spyOn(LabRepository, 'search').mockResolvedValue([expectedLab2]) + + expect(await screen.findByRole('cell', { name: expectedLab.code })).toBeInTheDocument() + + await userEvent.type(screen.getByRole('textbox', { name: /labs.search/i }), 'Picard', { + delay: 100, }) - - expect(searchLabsSpy).toHaveBeenCalledTimes(1) - expect(searchLabsSpy).toHaveBeenCalledWith( - expect.objectContaining({ text: expectedSearchText }), - ) + expect(screen.getByRole('textbox', { name: /labs.search/i })).toHaveDisplayValue(/picard/i) + expect(await screen.findByText(/phaser/i)).toBeInTheDocument() + expect(screen.queryByRole('cell', { name: expectedLab.code })).not.toBeInTheDocument() }) }) }) diff --git a/src/__tests__/labs/hooks/useCancelLab.test.ts b/src/__tests__/labs/hooks/useCancelLab.test.ts index e73e9750b6..465826df29 100644 --- a/src/__tests__/labs/hooks/useCancelLab.test.ts +++ b/src/__tests__/labs/hooks/useCancelLab.test.ts @@ -1,5 +1,3 @@ -import { act } from '@testing-library/react-hooks' - import useCancelLab from '../../../labs/hooks/useCancelLab' import LabRepository from '../../../shared/db/LabRepository' import Lab from '../../../shared/model/Lab' @@ -17,14 +15,10 @@ describe('Use Cancel Lab', () => { canceledOn: expectedCanceledOnDate.toISOString(), } as Lab - Date.now = jest.fn(() => expectedCanceledOnDate.valueOf()) - jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(expectedCanceledLab) - it('should cancel a lab', async () => { - let actualData: any - await act(async () => { - actualData = await executeMutation(() => useCancelLab(), lab) - }) + Date.now = jest.fn(() => expectedCanceledOnDate.valueOf()) + jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(expectedCanceledLab) + const actualData = await executeMutation(() => useCancelLab(), lab) expect(LabRepository.saveOrUpdate).toHaveBeenCalledTimes(1) expect(LabRepository.saveOrUpdate).toHaveBeenCalledWith(lab) diff --git a/src/__tests__/labs/hooks/useCompleteLab.test.ts b/src/__tests__/labs/hooks/useCompleteLab.test.ts index b275422f04..46f144d73e 100644 --- a/src/__tests__/labs/hooks/useCompleteLab.test.ts +++ b/src/__tests__/labs/hooks/useCompleteLab.test.ts @@ -1,10 +1,9 @@ -import { act } from '@testing-library/react-hooks' - import useCompleteLab from '../../../labs/hooks/useCompleteLab' import { LabError } from '../../../labs/utils/validate-lab' import * as validateLabUtils from '../../../labs/utils/validate-lab' import LabRepository from '../../../shared/db/LabRepository' import Lab from '../../../shared/model/Lab' +import { expectOneConsoleError } from '../../test-utils/console.utils' import executeMutation from '../../test-utils/use-mutation.util' describe('Use Complete lab', () => { @@ -20,17 +19,13 @@ describe('Use Complete lab', () => { } as Lab Date.now = jest.fn(() => expectedCompletedOnDate.valueOf()) - jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(expectedCompletedLab) beforeEach(() => { - jest.clearAllMocks() + jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(expectedCompletedLab) }) it('should save lab as complete', async () => { - let actualData: any - await act(async () => { - actualData = await executeMutation(() => useCompleteLab(), lab) - }) + const actualData = await executeMutation(() => useCompleteLab(), lab) expect(LabRepository.saveOrUpdate).toHaveBeenCalledTimes(1) expect(LabRepository.saveOrUpdate).toHaveBeenCalledWith(expectedCompletedLab) @@ -44,15 +39,14 @@ describe('Use Complete lab', () => { result: 'some result error message', } as LabError + expectOneConsoleError(expectedLabError) jest.spyOn(validateLabUtils, 'validateLabComplete').mockReturnValue(expectedLabError) - await act(async () => { - try { - await executeMutation(() => useCompleteLab(), lab) - } catch (e) { - expect(e).toEqual(expectedLabError) - expect(LabRepository.saveOrUpdate).not.toHaveBeenCalled() - } - }) + try { + await executeMutation(() => useCompleteLab(), lab) + } catch (e) { + expect(e).toEqual(expectedLabError) + expect(LabRepository.saveOrUpdate).not.toHaveBeenCalled() + } }) }) diff --git a/src/__tests__/labs/hooks/useLab.test.ts b/src/__tests__/labs/hooks/useLab.test.ts index cd98fa268d..93c03dd05c 100644 --- a/src/__tests__/labs/hooks/useLab.test.ts +++ b/src/__tests__/labs/hooks/useLab.test.ts @@ -1,9 +1,7 @@ -import { act, renderHook } from '@testing-library/react-hooks' - import useLab from '../../../labs/hooks/useLab' import LabRepository from '../../../shared/db/LabRepository' import Lab from '../../../shared/model/Lab' -import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util' +import executeQuery from '../../test-utils/use-query.util' describe('Use lab', () => { const expectedLabId = 'lab id' @@ -11,16 +9,9 @@ describe('Use lab', () => { id: expectedLabId, } as Lab - jest.spyOn(LabRepository, 'find').mockResolvedValue(expectedLab) - it('should get a lab by id', async () => { - let actualData: any - await act(async () => { - const renderHookResult = renderHook(() => useLab(expectedLabId)) - const { result } = renderHookResult - await waitUntilQueryIsSuccessful(renderHookResult) - actualData = result.current.data - }) + jest.spyOn(LabRepository, 'find').mockResolvedValue(expectedLab) + const actualData = await executeQuery(() => useLab(expectedLabId)) expect(LabRepository.find).toHaveBeenCalledTimes(1) expect(LabRepository.find).toHaveBeenCalledWith(expectedLabId) diff --git a/src/__tests__/labs/hooks/useLabsSearch.test.ts b/src/__tests__/labs/hooks/useLabsSearch.test.ts index c0bff21528..4a086ae577 100644 --- a/src/__tests__/labs/hooks/useLabsSearch.test.ts +++ b/src/__tests__/labs/hooks/useLabsSearch.test.ts @@ -1,10 +1,8 @@ -import { act, renderHook } from '@testing-library/react-hooks' - import useLabsSearch from '../../../labs/hooks/useLabsSearch' import LabSearchRequest from '../../../labs/model/LabSearchRequest' import LabRepository from '../../../shared/db/LabRepository' import Lab from '../../../shared/model/Lab' -import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util' +import executeQuery from '../../test-utils/use-query.util' describe('Use Labs Search', () => { const expectedLabs = [ @@ -13,13 +11,9 @@ describe('Use Labs Search', () => { }, ] as Lab[] - const labRepositoryFindAllSpy = jest - .spyOn(LabRepository, 'findAll') - .mockResolvedValue(expectedLabs) - const labRepositorySearchSpy = jest.spyOn(LabRepository, 'search').mockResolvedValue(expectedLabs) - beforeEach(() => { - labRepositoryFindAllSpy.mockClear() + jest.spyOn(LabRepository, 'findAll').mockResolvedValue(expectedLabs) + jest.spyOn(LabRepository, 'search').mockResolvedValue(expectedLabs) }) it('should return all labs', async () => { @@ -28,16 +22,10 @@ describe('Use Labs Search', () => { status: 'all', } as LabSearchRequest - let actualData: any - await act(async () => { - const renderHookResult = renderHook(() => useLabsSearch(expectedLabsSearchRequest)) - const { result } = renderHookResult - await waitUntilQueryIsSuccessful(renderHookResult) - actualData = result.current.data - }) + const actualData = await executeQuery(() => useLabsSearch(expectedLabsSearchRequest)) - expect(labRepositoryFindAllSpy).toHaveBeenCalledTimes(1) - expect(labRepositorySearchSpy).not.toHaveBeenCalled() + expect(LabRepository.findAll).toHaveBeenCalledTimes(1) + expect(LabRepository.search).not.toHaveBeenCalled() expect(actualData).toEqual(expectedLabs) }) @@ -47,17 +35,11 @@ describe('Use Labs Search', () => { status: 'all', } as LabSearchRequest - let actualData: any - await act(async () => { - const renderHookResult = renderHook(() => useLabsSearch(expectedLabsSearchRequest)) - const { result } = renderHookResult - await waitUntilQueryIsSuccessful(renderHookResult) - actualData = result.current.data - }) + const actualData = await executeQuery(() => useLabsSearch(expectedLabsSearchRequest)) - expect(labRepositoryFindAllSpy).not.toHaveBeenCalled() - expect(labRepositorySearchSpy).toHaveBeenCalledTimes(1) - expect(labRepositorySearchSpy).toHaveBeenCalledWith( + expect(LabRepository.findAll).not.toHaveBeenCalled() + expect(LabRepository.search).toHaveBeenCalledTimes(1) + expect(LabRepository.search).toHaveBeenCalledWith( expect.objectContaining(expectedLabsSearchRequest), ) expect(actualData).toEqual(expectedLabs) diff --git a/src/__tests__/labs/hooks/useRequestLab.test.ts b/src/__tests__/labs/hooks/useRequestLab.test.ts index fc83aa805c..54ca2f2429 100644 --- a/src/__tests__/labs/hooks/useRequestLab.test.ts +++ b/src/__tests__/labs/hooks/useRequestLab.test.ts @@ -1,10 +1,9 @@ -import { act } from '@testing-library/react-hooks' - import useRequestLab from '../../../labs/hooks/useRequestLab' import * as validateLabRequest from '../../../labs/utils/validate-lab' import { LabError } from '../../../labs/utils/validate-lab' import LabRepository from '../../../shared/db/LabRepository' import Lab from '../../../shared/model/Lab' +import { expectOneConsoleError } from '../../test-utils/console.utils' import executeMutation from '../../test-utils/use-mutation.util' describe('Use Request lab', () => { @@ -19,17 +18,13 @@ describe('Use Request lab', () => { } as Lab Date.now = jest.fn(() => expectedRequestedOnDate.valueOf()) - jest.spyOn(LabRepository, 'save').mockResolvedValue(expectedRequestedLab) beforeEach(() => { - jest.clearAllMocks() + jest.spyOn(LabRepository, 'save').mockResolvedValue(expectedRequestedLab) }) it('should save new request lab', async () => { - let actualData: any - await act(async () => { - actualData = await executeMutation(() => useRequestLab(), lab) - }) + const actualData = await executeMutation(() => useRequestLab(), lab) expect(LabRepository.save).toHaveBeenCalledTimes(1) expect(LabRepository.save).toHaveBeenCalledWith(lab) @@ -45,15 +40,14 @@ describe('Use Request lab', () => { type: 'error type', } as LabError + expectOneConsoleError(expectedError) jest.spyOn(validateLabRequest, 'validateLabRequest').mockReturnValue(expectedError) - await act(async () => { - try { - await executeMutation(() => useRequestLab(), lab) - } catch (e) { - expect(e).toEqual(expectedError) - expect(LabRepository.save).not.toHaveBeenCalled() - } - }) + try { + await executeMutation(() => useRequestLab(), lab) + } catch (e) { + expect(e).toEqual(expectedError) + expect(LabRepository.save).not.toHaveBeenCalled() + } }) }) diff --git a/src/__tests__/labs/hooks/useUpdateLab.test.ts b/src/__tests__/labs/hooks/useUpdateLab.test.ts index 6ab9840284..5ae054a78d 100644 --- a/src/__tests__/labs/hooks/useUpdateLab.test.ts +++ b/src/__tests__/labs/hooks/useUpdateLab.test.ts @@ -1,5 +1,3 @@ -import { act } from '@testing-library/react-hooks' - import useUpdateLab from '../../../labs/hooks/useUpdateLab' import LabRepository from '../../../shared/db/LabRepository' import Lab from '../../../shared/model/Lab' @@ -11,14 +9,9 @@ describe('Use update lab', () => { notes: ['some note'], } as Lab - jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(expectedLab) - it('should update lab', async () => { - let actualData: any - - await act(async () => { - actualData = await executeMutation(() => useUpdateLab(), expectedLab) - }) + jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(expectedLab) + const actualData = await executeMutation(() => useUpdateLab(), expectedLab) expect(LabRepository.saveOrUpdate).toHaveBeenCalledTimes(1) expect(LabRepository.saveOrUpdate).toHaveBeenCalledWith(expectedLab) diff --git a/src/__tests__/labs/requests/NewLabRequest.test.tsx b/src/__tests__/labs/requests/NewLabRequest.test.tsx index 423e7a7b7c..2fa484c32b 100644 --- a/src/__tests__/labs/requests/NewLabRequest.test.tsx +++ b/src/__tests__/labs/requests/NewLabRequest.test.tsx @@ -1,9 +1,9 @@ -import { Button, Typeahead, Label, Alert } from '@hospitalrun/components' +import { Toaster } from '@hospitalrun/components' +import { render, screen, within, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import format from 'date-fns/format' -import { mount, ReactWrapper } from 'enzyme' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' @@ -13,205 +13,169 @@ import NewLabRequest from '../../../labs/requests/NewLabRequest' import * as validationUtil from '../../../labs/utils/validate-lab' import { LabError } from '../../../labs/utils/validate-lab' import * as titleUtil from '../../../page-header/title/TitleContext' -import SelectWithLabelFormGroup from '../../../shared/components/input/SelectWithLabelFormGroup' -import TextFieldWithLabelFormGroup from '../../../shared/components/input/TextFieldWithLabelFormGroup' -import TextInputWithLabelFormGroup from '../../../shared/components/input/TextInputWithLabelFormGroup' import LabRepository from '../../../shared/db/LabRepository' import PatientRepository from '../../../shared/db/PatientRepository' import Lab from '../../../shared/model/Lab' import Patient from '../../../shared/model/Patient' import Visit from '../../../shared/model/Visit' import { RootState } from '../../../shared/store' +import { expectOneConsoleError } from '../../test-utils/console.utils' const mockStore = createMockStore<RootState, any>([thunk]) -describe('New Lab Request', () => { - let history: any - const setup = async ( - store = mockStore({ title: '', user: { user: { id: 'userId' } } } as any), - ) => { - history = createMemoryHistory() - history.push(`/labs/new`) - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - - let wrapper: any - await act(async () => { - wrapper = await mount( - <Provider store={store}> - <Router history={history}> - <titleUtil.TitleProvider> - <NewLabRequest /> - </titleUtil.TitleProvider> - </Router> - </Provider>, - ) - }) - wrapper.find(NewLabRequest).props().updateTitle = jest.fn() - wrapper.update() - return { wrapper: wrapper as ReactWrapper } +const setup = ( + store = mockStore({ + title: '', + user: { user: { id: 'userId' } }, + } as any), +) => { + const expectedDate = new Date() + const expectedNotes = 'expected notes' + const expectedLab = { + patient: '1234567', + type: 'expected type', + status: 'requested', + notes: [expectedNotes], + id: '1234', + requestedOn: expectedDate.toISOString(), + } as Lab + + const expectedVisits = [ + { + startDateTime: new Date().toISOString(), + id: 'visit_id', + type: 'visit_type', + }, + ] as Visit[] + const expectedPatient = { + id: expectedLab.patient, + givenName: 'Jim', + familyName: 'Bob', + fullName: 'Jim Bob', + visits: expectedVisits, + } as Patient + + jest.resetAllMocks() + jest.spyOn(PatientRepository, 'search').mockResolvedValue([expectedPatient]) + jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) + jest.spyOn(LabRepository, 'save').mockResolvedValue(expectedLab) + + const history = createMemoryHistory({ initialEntries: ['/labs/new'] }) + + return { + history, + expectedLab, + expectedPatient, + expectedVisits, + ...render( + <Provider store={store}> + <Router history={history}> + <titleUtil.TitleProvider> + <NewLabRequest /> + </titleUtil.TitleProvider> + </Router> + <Toaster draggable hideProgressBar /> + </Provider>, + ), } +} +describe('New Lab Request', () => { describe('form layout', () => { - it('should have called the useUpdateTitle hook', async () => { - await setup() - expect(titleUtil.useUpdateTitle).toHaveBeenCalledTimes(1) - }) - it('should render a patient typeahead', async () => { - const { wrapper } = await setup() - const typeaheadDiv = wrapper.find('.patient-typeahead') + setup() - expect(typeaheadDiv).toBeDefined() + const typeaheadInput = screen.getByPlaceholderText(/labs.lab.patient/i) - const label = typeaheadDiv.find(Label) - const typeahead = typeaheadDiv.find(Typeahead) - - expect(label).toBeDefined() - expect(label.prop('text')).toEqual('labs.lab.patient') - expect(typeahead).toBeDefined() - expect(typeahead.prop('placeholder')).toEqual('labs.lab.patient') - expect(typeahead.prop('searchAccessor')).toEqual('fullName') + expect(screen.getByText(/labs\.lab\.patient/i)).toBeInTheDocument() + userEvent.type(typeaheadInput, 'Jim Bob') + expect(typeaheadInput).toHaveDisplayValue('Jim Bob') }) it('should render a type input box', async () => { - const { wrapper } = await setup() - const typeInputBox = wrapper.find(TextInputWithLabelFormGroup) + setup() - expect(typeInputBox).toBeDefined() - expect(typeInputBox.prop('label')).toEqual('labs.lab.type') - expect(typeInputBox.prop('isRequired')).toBeTruthy() - expect(typeInputBox.prop('isEditable')).toBeTruthy() + expect(screen.getByText(/labs\.lab\.type/i)).toHaveAttribute( + 'title', + 'This is a required input', + ) + expect(screen.getByLabelText(/labs\.lab\.type/i)).toBeInTheDocument() + expect(screen.getByLabelText(/labs\.lab\.type/i)).not.toBeDisabled() }) it('should render a notes text field', async () => { - const { wrapper } = await setup() - const notesTextField = wrapper.find(TextFieldWithLabelFormGroup) + setup() - expect(notesTextField).toBeDefined() - expect(notesTextField.prop('label')).toEqual('labs.lab.notes') - expect(notesTextField.prop('isRequired')).toBeFalsy() - expect(notesTextField.prop('isEditable')).toBeTruthy() + expect(screen.getByLabelText(/labs\.lab\.notes/i)).not.toBeDisabled() + expect(screen.getByText(/labs\.lab\.notes/i)).not.toHaveAttribute( + 'title', + 'This is a required input', + ) }) it('should render a visit select', async () => { - const { wrapper } = await setup() - const visitSelect = wrapper.find(SelectWithLabelFormGroup) + setup() - expect(visitSelect).toBeDefined() - expect(visitSelect.prop('label') as string).toEqual('patient.visit') - expect(visitSelect.prop('isRequired')).toBeFalsy() - expect(visitSelect.prop('defaultSelected')).toEqual([]) + const selectLabel = screen.getByText(/patient\.visit/i) + const selectInput = within(screen.getByTestId('visitSelect')).getByRole('combobox') + + expect(selectInput).toBeInTheDocument() + expect(selectInput).toHaveDisplayValue(['']) + expect(selectLabel).toBeInTheDocument() + expect(selectLabel).not.toHaveAttribute('title', 'This is a required input') }) - it('should render a save button', async () => { - const { wrapper } = await setup() - const saveButton = wrapper.find(Button).at(0) - expect(saveButton).toBeDefined() - expect(saveButton.text().trim()).toEqual('labs.requests.new') + it('should render a save button', () => { + setup() + + expect(screen.getByRole('button', { name: /labs\.requests\.new/i })).toBeInTheDocument() }) - it('should render a cancel button', async () => { - const { wrapper } = await setup() - const cancelButton = wrapper.find(Button).at(1) - expect(cancelButton).toBeDefined() - expect(cancelButton.text().trim()).toEqual('actions.cancel') + it('should render a cancel button', () => { + setup() + + expect(screen.getByRole('button', { name: /actions\.cancel/i })).toBeInTheDocument() }) it('should clear visit when patient is changed', async () => { - const { wrapper } = await setup() - const patientTypeahead = wrapper.find(Typeahead) - const expectedDate = new Date() - const expectedNotes = 'expected notes' - const expectedLab = { - patient: '1234567', - type: 'expected type', - status: 'requested', - notes: [expectedNotes], - id: '1234', - requestedOn: expectedDate.toISOString(), - } as Lab - - const visits = [ - { - startDateTime: new Date().toISOString(), - id: 'visit_id', - type: 'visit_type', - }, - ] as Visit[] - - await act(async () => { - const onChange = patientTypeahead.prop('onChange') as any - await onChange([{ id: expectedLab.patient, visits }] as Patient[]) - }) - wrapper.update() + const { expectedVisits } = setup() + + const patientTypeahead = screen.getByPlaceholderText(/labs.lab.patient/i) + const visitsInput = within(screen.getByTestId('visitSelect')).getByRole('combobox') + userEvent.type(patientTypeahead, 'Jim Bob') + userEvent.click(await screen.findByText(/Jim Bob/i)) + expect(patientTypeahead).toHaveDisplayValue(/Jim Bob/i) + userEvent.click(visitsInput) // The visits dropdown should be populated with the patient's visits. - expect(wrapper.find(SelectWithLabelFormGroup).prop('options')).toEqual([ - { - label: `${visits[0].type} at ${format( - new Date(visits[0].startDateTime), + userEvent.click( + await screen.findByRole('link', { + name: `${expectedVisits[0].type} at ${format( + new Date(expectedVisits[0].startDateTime), 'yyyy-MM-dd hh:mm a', )}`, - value: 'visit_id', - }, - ]) - await act(async () => { - const onChange = patientTypeahead.prop('onChange') - await onChange([] as Patient[]) - }) - - wrapper.update() - // The visits dropdown option should be reset when the patient is changed. - expect(wrapper.find(SelectWithLabelFormGroup).prop('options')).toEqual([]) - }) + }), + ) + expect(visitsInput).toHaveDisplayValue( + `${expectedVisits[0].type} at ${format( + new Date(expectedVisits[0].startDateTime), + 'yyyy-MM-dd hh:mm a', + )}`, + ) - it('should support selecting a visit', async () => { - const { wrapper } = await setup() - const patientTypeahead = wrapper.find(Typeahead) - const expectedDate = new Date() - const expectedNotes = 'expected notes' - const expectedLab = { - patient: '123456789', - type: 'expected type', - status: 'requested', - notes: [expectedNotes], - id: '1234', - requestedOn: expectedDate.toISOString(), - } as Lab - - const visits = [ - { - startDateTime: new Date().toISOString(), - id: 'visit_id', - type: 'visit_type', - }, - ] as Visit[] - const visitOptions = [ - { - label: `${visits[0].type} at ${format( - new Date(visits[0].startDateTime), + userEvent.clear(patientTypeahead) + await waitFor(() => { + // The visits dropdown option should be reset when the patient is changed. + expect(visitsInput).toHaveDisplayValue('') + }) + expect( + screen.queryByRole('link', { + name: `${expectedVisits[0].type} at ${format( + new Date(expectedVisits[0].startDateTime), 'yyyy-MM-dd hh:mm a', )}`, - value: 'visit_id', - }, - ] - expect(wrapper.find(SelectWithLabelFormGroup).prop('defaultSelected')).toEqual([]) - await act(async () => { - const onChange = patientTypeahead.prop('onChange') as any - await onChange([{ id: expectedLab.patient, visits }] as Patient[]) - }) - wrapper.update() - expect(wrapper.find(SelectWithLabelFormGroup).prop('defaultSelected')).toEqual([]) - const dropdown = wrapper.find(SelectWithLabelFormGroup) - await act(async () => { - // The onChange method takes a list of possible values, - // but our dropdown is single-select, so the argument passed to onChange() - // is a list with just one element. This is why we pass - // the whole array `visitOptions` as opposed to `visitOptions[0]`. - await (dropdown.prop('onChange') as any)(visitOptions.map((option) => option.value)) - }) - wrapper.update() - expect(wrapper.find(SelectWithLabelFormGroup).prop('defaultSelected')).toEqual(visitOptions) + }), + ).not.toBeInTheDocument() }) }) @@ -222,114 +186,53 @@ describe('New Lab Request', () => { type: 'some type error', } as LabError - jest.spyOn(validationUtil, 'validateLabRequest').mockReturnValue(error) - it('should display errors', async () => { - const { wrapper } = await setup() - - const saveButton = wrapper.find(Button).at(0) - await act(async () => { - const onClick = saveButton.prop('onClick') as any - await onClick() - }) - - wrapper.update() - - const alert = wrapper.find(Alert) - const typeInput = wrapper.find(TextInputWithLabelFormGroup) - const patientTypeahead = wrapper.find(Typeahead) - - expect(alert.prop('message')).toEqual(error.message) - expect(alert.prop('title')).toEqual('states.error') - expect(alert.prop('color')).toEqual('danger') - - expect(patientTypeahead.prop('isInvalid')).toBeTruthy() - - expect(typeInput.prop('feedback')).toEqual(error.type) - expect(typeInput.prop('isInvalid')).toBeTruthy() + setup() + jest.spyOn(validationUtil, 'validateLabRequest').mockReturnValue(error) + expectOneConsoleError(error) + const saveButton = screen.getByRole('button', { name: /labs\.requests\.new/i }) + + userEvent.click(saveButton) + + const alert = await screen.findByRole('alert') + const patientInput = screen.getByPlaceholderText(/labs\.lab\.patient/i) + const typeInput = screen.getByLabelText(/labs\.lab\.type/i) + + expect(within(alert).getByText(error.message)).toBeInTheDocument() + expect(within(alert).getByText(/states\.error/i)).toBeInTheDocument() + expect(alert).toHaveClass('alert-danger') + expect(patientInput).toHaveClass('is-invalid') + expect(typeInput).toHaveClass('is-invalid') + expect(typeInput.nextSibling).toHaveTextContent(error.type as string) }) }) describe('on cancel', () => { it('should navigate back to /labs', async () => { - const { wrapper } = await setup() - const cancelButton = wrapper.find(Button).at(1) + const { history } = setup() - act(() => { - const onClick = cancelButton.prop('onClick') as any - onClick({} as React.MouseEvent<HTMLButtonElement>) - }) + userEvent.click(await screen.findByRole('button', { name: /actions\.cancel/i })) expect(history.location.pathname).toEqual('/labs') }) }) describe('on save', () => { - let labRepositorySaveSpy: any - const expectedDate = new Date() - const expectedNotes = 'expected notes' - const expectedLab = { - patient: '12345', - type: 'expected type', - status: 'requested', - notes: [expectedNotes], - id: '1234', - requestedOn: expectedDate.toISOString(), - } as Lab - const store = mockStore({ - lab: { status: 'loading', error: {} }, - user: { user: { id: 'fake id' } }, - } as any) - - beforeEach(() => { - jest.resetAllMocks() - Date.now = jest.fn(() => expectedDate.valueOf()) - labRepositorySaveSpy = jest.spyOn(LabRepository, 'save').mockResolvedValue(expectedLab as Lab) - - jest - .spyOn(PatientRepository, 'search') - .mockResolvedValue([{ id: expectedLab.patient, fullName: 'some full name' }] as Patient[]) - }) - it('should save the lab request and navigate to "/labs/:id"', async () => { - const { wrapper } = await setup(store) - - const patientTypeahead = wrapper.find(Typeahead) - await act(async () => { - const onChange = patientTypeahead.prop('onChange') - await onChange([{ id: expectedLab.patient, visits: [] as Visit[] }] as Patient[]) - }) - - const typeInput = wrapper.find(TextInputWithLabelFormGroup) - act(() => { - const onChange = typeInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedLab.type } }) - }) - - const notesTextField = wrapper.find(TextFieldWithLabelFormGroup) - act(() => { - const onChange = notesTextField.prop('onChange') as any - onChange({ currentTarget: { value: expectedNotes } }) - }) - wrapper.update() - - const saveButton = wrapper.find(Button).at(0) - await act(async () => { - const onClick = saveButton.prop('onClick') as any - await onClick() - }) - - expect(labRepositorySaveSpy).toHaveBeenCalledTimes(1) - expect(labRepositorySaveSpy).toHaveBeenCalledWith( - expect.objectContaining({ - patient: expectedLab.patient, - type: expectedLab.type, - notes: expectedLab.notes, - status: 'requested', - requestedOn: expectedDate.toISOString(), - }), - ) + const { expectedLab, history } = setup() + + userEvent.type(screen.getByPlaceholderText(/labs.lab.patient/i), 'Jim Bob') + expect(await screen.findByText(/jim bob/i)).toBeVisible() + userEvent.click(screen.getByText(/jim bob/i)) + userEvent.type(screen.getByLabelText(/labs\.lab\.type/i), expectedLab.type) + userEvent.type(screen.getByLabelText(/labs\.lab\.notes/i), (expectedLab.notes as string[])[0]) + userEvent.click(screen.getByRole('button', { name: /labs\.requests\.new/i })) + + expect(await screen.findByRole('alert')).toBeInTheDocument() + expect( + within(screen.getByRole('alert')).getByText(/labs\.successfullyCreated/i), + ).toBeInTheDocument() expect(history.location.pathname).toEqual(`/labs/${expectedLab.id}`) - }) + }, 15000) }) }) diff --git a/src/__tests__/medications/Medications.test.tsx b/src/__tests__/medications/Medications.test.tsx index 8bacbf0f05..54df8def20 100644 --- a/src/__tests__/medications/Medications.test.tsx +++ b/src/__tests__/medications/Medications.test.tsx @@ -1,5 +1,4 @@ -import { act } from '@testing-library/react' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' import React from 'react' import { Provider } from 'react-redux' import { MemoryRouter } from 'react-router-dom' @@ -7,8 +6,6 @@ import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import Medications from '../../medications/Medications' -import NewMedicationRequest from '../../medications/requests/NewMedicationRequest' -import ViewMedication from '../../medications/ViewMedication' import { TitleProvider } from '../../page-header/title/TitleContext' import MedicationRepository from '../../shared/db/MedicationRepository' import PatientRepository from '../../shared/db/PatientRepository' @@ -19,8 +16,20 @@ import { RootState } from '../../shared/store' const mockStore = createMockStore<RootState, any>([thunk]) +const expectedMedication = ({ + id: 'medicationId', + patientId: 'patientId', + requestedOn: new Date().toISOString(), + medication: 'medication', + status: 'draft', + intent: 'order', + priority: 'routine', + quantity: { value: 1, unit: 'unit' }, + notes: 'medication notes', +} as unknown) as Medication + describe('Medications', () => { - const setup = (route: string, permissions: Array<string>) => { + const setup = (route: string, permissions: Permissions[] = []) => { jest.resetAllMocks() jest.spyOn(MedicationRepository, 'search').mockResolvedValue([]) jest @@ -36,23 +45,13 @@ describe('Medications', () => { breadcrumbs: { breadcrumbs: [] }, components: { sidebarCollapsed: false }, medication: { - medication: ({ - id: 'medicationId', - patientId: 'patientId', - requestedOn: new Date().toISOString(), - medication: 'medication', - status: 'draft', - intent: 'order', - priority: 'routine', - quantity: { value: 1, unit: 'unit' }, - notes: 'medication notes', - } as unknown) as Medication, + medication: expectedMedication, patient: { id: 'patientId', fullName: 'some name' }, error: {}, }, } as any) - const wrapper = mount( + return render( <Provider store={store}> <MemoryRouter initialEntries={[route]}> <TitleProvider> @@ -61,44 +60,38 @@ describe('Medications', () => { </MemoryRouter> </Provider>, ) - return wrapper as ReactWrapper } describe('routing', () => { describe('/medications/new', () => { it('should render the new medication request screen when /medications/new is accessed', () => { - const route = '/medications/new' - const permissions = [Permissions.RequestMedication] - const wrapper = setup(route, permissions) - expect(wrapper.find(NewMedicationRequest)).toHaveLength(1) + setup('/medications/new', [Permissions.RequestMedication]) + + expect(screen.getByRole('form', { name: /medication request form/i })).toBeInTheDocument() }) it('should not navigate to /medications/new if the user does not have RequestMedication permissions', () => { - const route = '/medications/new' - const permissions: never[] = [] - const wrapper = setup(route, permissions) - expect(wrapper.find(NewMedicationRequest)).toHaveLength(0) + setup('/medications/new') + + expect( + screen.queryByRole('form', { name: /medication request form/i }), + ).not.toBeInTheDocument() }) }) describe('/medications/:id', () => { it('should render the view medication screen when /medications/:id is accessed', async () => { - const route = '/medications/1234' - const permissions = [Permissions.ViewMedication] - let wrapper: any - await act(async () => { - wrapper = setup(route, permissions) + setup('/medications/1234', [Permissions.ViewMedication]) - expect(wrapper.find(ViewMedication)).toHaveLength(1) - }) + expect(screen.getByRole('heading', { name: expectedMedication.status })).toBeInTheDocument() }) it('should not navigate to /medications/:id if the user does not have ViewMedication permissions', async () => { - const route = '/medications/1234' - const permissions: never[] = [] - const wrapper = setup(route, permissions) + setup('/medications/1234') - expect(wrapper.find(ViewMedication)).toHaveLength(0) + expect( + screen.queryByRole('heading', { name: expectedMedication.status }), + ).not.toBeInTheDocument() }) }) }) diff --git a/src/__tests__/medications/ViewMedication.test.tsx b/src/__tests__/medications/ViewMedication.test.tsx index 2b2e1892c4..66e3f9c057 100644 --- a/src/__tests__/medications/ViewMedication.test.tsx +++ b/src/__tests__/medications/ViewMedication.test.tsx @@ -1,7 +1,6 @@ -import { Badge, Button } from '@hospitalrun/components' -import { act } from '@testing-library/react' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import format from 'date-fns/format' -import { mount } from 'enzyme' import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' @@ -12,7 +11,6 @@ import thunk from 'redux-thunk' import ViewMedication from '../../medications/ViewMedication' import * as ButtonBarProvider from '../../page-header/button-toolbar/ButtonBarProvider' import * as titleUtil from '../../page-header/title/TitleContext' -import TextFieldWithLabelFormGroup from '../../shared/components/input/TextFieldWithLabelFormGroup' import MedicationRepository from '../../shared/db/MedicationRepository' import PatientRepository from '../../shared/db/PatientRepository' import Medication from '../../shared/model/Medication' @@ -21,35 +19,32 @@ import Permissions from '../../shared/model/Permissions' import { RootState } from '../../shared/store' const mockStore = createMockStore<RootState, any>([thunk]) + let expectedDate: any -describe('View Medication', () => { - const setup = async (medication: Medication, permissions: Permissions[], error = {}) => { - const mockPatient = { fullName: 'test' } - const mockMedication = { - id: '12456', - status: 'draft', - patient: '1234', - medication: 'medication', - intent: 'order', - priority: 'routine', - quantity: { value: 1, unit: 'unit' }, - notes: 'medication notes', - requestedOn: '2020-03-30T04:43:20.102Z', - } as Medication - - expectedDate = new Date() +describe('View Medication', () => { + const mockPatient = { fullName: 'test' } + const mockMedication = { + id: '12456', + status: 'draft', + patient: '1234', + medication: 'medication', + intent: 'order', + priority: 'routine', + quantity: { value: 1, unit: 'unit' }, + notes: 'medication notes', + requestedOn: '2020-03-30T04:43:20.102Z', + } as Medication + + expectedDate = new Date() + const setup = (medication: Medication, permissions: Permissions[], error = {}) => { jest.resetAllMocks() Date.now = jest.fn(() => expectedDate.valueOf()) const setButtonToolBarSpy = jest.fn() - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) jest.spyOn(MedicationRepository, 'find').mockResolvedValue(medication) - const medicationRepositorySaveSpy = jest - .spyOn(MedicationRepository, 'saveOrUpdate') - .mockResolvedValue(mockMedication) + jest.spyOn(MedicationRepository, 'saveOrUpdate').mockResolvedValue(mockMedication) jest.spyOn(PatientRepository, 'find').mockResolvedValue(mockPatient as Patient) - const history = createMemoryHistory() history.push(`medications/${medication.id}`) const store = mockStore({ @@ -65,224 +60,208 @@ describe('View Medication', () => { }, } as any) - let wrapper: any - await act(async () => { - wrapper = await mount( - <ButtonBarProvider.ButtonBarProvider> - <Provider store={store}> - <Router history={history}> - <Route path="/medications/:id"> - <titleUtil.TitleProvider> - <ViewMedication /> - </titleUtil.TitleProvider> - </Route> - </Router> - </Provider> - </ButtonBarProvider.ButtonBarProvider>, - ) - }) - wrapper.find(ViewMedication).props().updateTitle = jest.fn() - wrapper.update() - return { - wrapper, - mockPatient, - expectedMedication: { ...mockMedication, ...medication }, - medicationRepositorySaveSpy, - history, - } + return render( + <ButtonBarProvider.ButtonBarProvider> + <Provider store={store}> + <Router history={history}> + <Route path="/medications/:id"> + <titleUtil.TitleProvider> + <ViewMedication /> + </titleUtil.TitleProvider> + </Route> + </Router> + </Provider> + </ButtonBarProvider.ButtonBarProvider>, + ) } describe('page content', () => { - it('should display the patient full name for the for', async () => { - const { wrapper, mockPatient } = await setup({} as Medication, [Permissions.ViewMedication]) - const forPatientDiv = wrapper.find('.for-patient') - expect(forPatientDiv.find('h4').text().trim()).toEqual('medications.medication.for') + it('should display the patients full name', async () => { + setup({} as Medication, [Permissions.ViewMedication]) - expect(forPatientDiv.find('h5').text().trim()).toEqual(mockPatient.fullName) + await waitFor(() => { + expect(screen.getByText(/medications.medication.for/i)).toBeInTheDocument() + }) + await waitFor(() => { + expect(screen.getByText(mockPatient.fullName)).toBeInTheDocument() + }) }) + }) + it('should display the medication ', async () => { + const expectedMedication = { ...mockMedication } as Medication - it('should display the medication ', async () => { - const { wrapper, expectedMedication } = await setup({} as Medication, [ - Permissions.ViewMedication, - ]) - const medicationTypeDiv = wrapper.find('.medication-medication') - expect(medicationTypeDiv.find('h4').text().trim()).toEqual( - 'medications.medication.medication', - ) - - expect(medicationTypeDiv.find('h5').text().trim()).toEqual(expectedMedication.medication) + setup({} as Medication, [Permissions.ViewMedication]) + await waitFor(() => { + expect(screen.getByText(/medications.medication.medication/i)).toBeInTheDocument() }) + await waitFor(() => { + expect(screen.getByText(expectedMedication.medication)).toBeInTheDocument() + }) + }) - it('should display the requested on date', async () => { - const { wrapper, expectedMedication } = await setup({} as Medication, [ - Permissions.ViewMedication, - ]) - const requestedOnDiv = wrapper.find('.requested-on') - expect(requestedOnDiv.find('h4').text().trim()).toEqual('medications.medication.requestedOn') + it('should display the requested on date', async () => { + const expectedMedication = { ...mockMedication } as Medication - expect(requestedOnDiv.find('h5').text().trim()).toEqual( - format(new Date(expectedMedication.requestedOn), 'yyyy-MM-dd hh:mm a'), - ) - }) + setup({} as Medication, [Permissions.ViewMedication]) - it('should not display the canceled date if the medication is not canceled', async () => { - const { wrapper } = await setup({} as Medication, [Permissions.ViewMedication]) - const completedOnDiv = wrapper.find('.canceled-on') + await waitFor(() => { + expect( + screen.getByRole('heading', { name: /medications.medication.requestedOn/i }), + ).toBeInTheDocument() - expect(completedOnDiv).toHaveLength(0) + expect( + screen.getByText(format(new Date(expectedMedication.requestedOn), 'yyyy-MM-dd hh:mm a')), + ).toBeInTheDocument() }) + }) - it('should display the notes in the notes text field', async () => { - const { wrapper, expectedMedication } = await setup({} as Medication, [ - Permissions.ViewMedication, - ]) - - const notesTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) + it('should not display the canceled date if the medication is not canceled', async () => { + setup({} as Medication, [Permissions.ViewMedication]) - expect(notesTextField).toBeDefined() - expect(notesTextField.prop('label')).toEqual('medications.medication.notes') - expect(notesTextField.prop('value')).toEqual(expectedMedication.notes) - }) + expect( + screen.queryByRole('heading', { name: /medications\.medication\.canceledOn/i }), + ).not.toBeInTheDocument() + }) - describe('draft medication request', () => { - it('should display a warning badge if the status is draft', async () => { - const { wrapper, expectedMedication } = await setup({} as Medication, [ - Permissions.ViewMedication, - ]) - const medicationStatusDiv = wrapper.find('.medication-status') - const badge = medicationStatusDiv.find(Badge) - expect(medicationStatusDiv.find('h4').text().trim()).toEqual( - 'medications.medication.status', - ) - - expect(badge.prop('color')).toEqual('warning') - expect(badge.text().trim()).toEqual(expectedMedication.status) - }) + it('should display the notes in the notes text field', async () => { + const expectedMedication = { ...mockMedication } as Medication - it('should display a update medication and cancel medication button if the medication is in a draft state', async () => { - const { wrapper } = await setup({} as Medication, [ - Permissions.ViewMedication, - Permissions.CompleteMedication, - Permissions.CancelMedication, - ]) + setup(expectedMedication, [Permissions.ViewMedication]) - const buttons = wrapper.find(Button) - expect(buttons.at(0).text().trim()).toEqual('medications.requests.update') + await waitFor(() => { + expect(screen.getByText(/medications\.medication\.notes/i)).toBeInTheDocument() + }) + await waitFor(() => { + expect(screen.getByRole('textbox', { name: /notes/ })).toHaveValue(expectedMedication.notes) + }) + }) - expect(buttons.at(1).text().trim()).toEqual('medications.requests.cancel') + describe('draft medication request', () => { + it('should display a warning badge if the status is draft', async () => { + const expectedMedication = { ...mockMedication, status: 'draft' } as Medication + setup(expectedMedication, [Permissions.ViewMedication]) + + await waitFor(() => { + expect( + screen.getByRole('heading', { + name: /medications\.medication\.status/i, + }), + ).toBeInTheDocument() + }) + await waitFor(() => { + expect(screen.getByRole('heading', { name: expectedMedication.status })).toBeVisible() }) }) - describe('canceled medication request', () => { - it('should display a danger badge if the status is canceled', async () => { - const { wrapper, expectedMedication } = await setup({ status: 'canceled' } as Medication, [ - Permissions.ViewMedication, - ]) + it('should display a update medication and cancel medication button if the medication is in a draft state', async () => { + const expectedMedication = { ...mockMedication, status: 'draft' } as Medication + setup(expectedMedication, [ + Permissions.ViewMedication, + Permissions.CompleteMedication, + Permissions.CancelMedication, + ]) - const medicationStatusDiv = wrapper.find('.medication-status') - const badge = medicationStatusDiv.find(Badge) - expect(medicationStatusDiv.find('h4').text().trim()).toEqual( - 'medications.medication.status', - ) + expect(screen.getByRole('button', { name: /medications\.requests\.update/i })).toBeVisible() - expect(badge.prop('color')).toEqual('danger') - expect(badge.text().trim()).toEqual(expectedMedication.status) - }) + expect(screen.getByRole('button', { name: /medications\.requests\.cancel/ })).toBeVisible() + }) + }) - it('should display the canceled on date if the medication request has been canceled', async () => { - const { wrapper, expectedMedication } = await setup( - { - status: 'canceled', - canceledOn: '2020-03-30T04:45:20.102Z', - } as Medication, - [Permissions.ViewMedication], - ) - const canceledOnDiv = wrapper.find('.canceled-on') - - expect(canceledOnDiv.find('h4').text().trim()).toEqual('medications.medication.canceledOn') - - expect(canceledOnDiv.find('h5').text().trim()).toEqual( - format(new Date(expectedMedication.canceledOn as string), 'yyyy-MM-dd hh:mm a'), - ) + describe('canceled medication request', () => { + it('should display a danger badge if the status is canceled', async () => { + const expectedMedication = { ...mockMedication, status: 'canceled' } as Medication + setup(expectedMedication as Medication, [Permissions.ViewMedication]) + + await waitFor(() => { + expect( + screen.getByRole('heading', { + name: /medications\.medication\.status/i, + }), + ).toBeInTheDocument() }) + await waitFor(() => { + expect(screen.getByRole('heading', { name: expectedMedication.status })).toBeInTheDocument() + }) + }) - it('should not display update and cancel button if the medication is canceled', async () => { - const { wrapper } = await setup( - { - status: 'canceled', - } as Medication, - [Permissions.ViewMedication, Permissions.CancelMedication], - ) - - const buttons = wrapper.find(Button) - expect(buttons).toHaveLength(0) + it('should display the canceled on date if the medication request has been canceled', async () => { + const expectedMedication = { + ...mockMedication, + status: 'canceled', + canceledOn: '2020-03-30T04:45:20.102Z', + } as Medication + setup(expectedMedication, [Permissions.ViewMedication]) + const date = format(new Date(expectedMedication.canceledOn as string), 'yyyy-MM-dd hh:mm a') + await waitFor(() => { + expect(screen.getByText(/medications.medication.canceledOn/)).toBeInTheDocument() + }) + await waitFor(() => { + expect(screen.getByText(date)).toBeInTheDocument() }) + }) - it('should not display an update button if the medication is canceled', async () => { - const { wrapper } = await setup({ status: 'canceled' } as Medication, [ - Permissions.ViewMedication, - ]) + it('should not display cancel button if the medication is canceled', async () => { + setup({ ...mockMedication, status: 'canceled' } as Medication, [ + Permissions.ViewMedication, + Permissions.CancelMedication, + ]) + await waitFor(() => { + expect( + screen.queryByRole('button', { name: /medications\.requests\.cancel/ }), + ).not.toBeInTheDocument() + }) + }) + it('should not display an update button if the medication is canceled', async () => { + setup({ ...mockMedication, status: 'canceled' } as Medication, [Permissions.ViewMedication]) - const updateButton = wrapper.find(Button) - expect(updateButton).toHaveLength(0) + await waitFor(() => { + expect(screen.queryByRole('button', { name: /actions\.update/ })).not.toBeInTheDocument() }) }) }) - describe('on update', () => { it('should update the medication with the new information', async () => { - const { wrapper, expectedMedication, medicationRepositorySaveSpy, history } = await setup( - {}, - [Permissions.ViewMedication], - ) - const expectedNotes = 'expected notes' - - const notesTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) - act(() => { - const onChange = notesTextField.prop('onChange') - onChange({ currentTarget: { value: expectedNotes } }) - }) - wrapper.update() - const updateButton = wrapper.find(Button) - await act(async () => { - const onClick = updateButton.prop('onClick') - onClick() + const expectedNotes = 'new notes' + const expectedMedication = { ...mockMedication, notes: '' } as Medication + setup(expectedMedication, [Permissions.ViewMedication]) + + userEvent.type(screen.getByRole('textbox', { name: /notes/ }), expectedNotes) + + await waitFor(() => { + expect(screen.getByRole('textbox', { name: /notes/ })).toHaveValue(`${expectedNotes}`) }) - expect(medicationRepositorySaveSpy).toHaveBeenCalled() - expect(medicationRepositorySaveSpy).toHaveBeenCalledWith( - expect.objectContaining({ + userEvent.click(screen.getByRole('button', { name: /medications\.requests\.update/ })) + + await waitFor(() => { + expect(MedicationRepository.saveOrUpdate).toHaveBeenCalled() + expect(MedicationRepository.saveOrUpdate).toHaveBeenCalledWith({ ...expectedMedication, notes: expectedNotes, - }), - ) - expect(history.location.pathname).toEqual('/medications') + }) + }) }) }) describe('on cancel', () => { it('should mark the status as canceled and fill in the cancelled on date with the current time', async () => { - const { wrapper, expectedMedication, medicationRepositorySaveSpy, history } = await setup( - {}, - [Permissions.ViewMedication, Permissions.CompleteMedication, Permissions.CancelMedication], - ) - - const cancelButton = wrapper.find(Button).at(1) - await act(async () => { - const onClick = cancelButton.prop('onClick') - await onClick() - }) - wrapper.update() + const expectedMedication = { ...mockMedication } as Medication + setup(expectedMedication, [ + Permissions.ViewMedication, + Permissions.CompleteMedication, + Permissions.CancelMedication, + ]) - expect(medicationRepositorySaveSpy).toHaveBeenCalled() - expect(medicationRepositorySaveSpy).toHaveBeenCalledWith( - expect.objectContaining({ - ...expectedMedication, - status: 'canceled', - canceledOn: expectedDate.toISOString(), - }), - ) - expect(history.location.pathname).toEqual('/medications') + const cancelButton = screen.getByRole('button', { name: /medications\.requests\.cancel/ }) + + userEvent.click(cancelButton) + + expect(MedicationRepository.saveOrUpdate).toHaveBeenCalled() + expect(MedicationRepository.saveOrUpdate).toHaveBeenCalledWith({ + ...expectedMedication, + status: 'canceled', + canceledOn: expectedDate.toISOString(), + }) }) }) }) diff --git a/src/__tests__/medications/hooks/useMedicationSearch.test.tsx b/src/__tests__/medications/hooks/useMedicationSearch.test.tsx index 570119e022..1803bc800b 100644 --- a/src/__tests__/medications/hooks/useMedicationSearch.test.tsx +++ b/src/__tests__/medications/hooks/useMedicationSearch.test.tsx @@ -1,11 +1,9 @@ -import { act, renderHook } from '@testing-library/react-hooks' - import useMedicationSearch from '../../../medications/hooks/useMedicationSearch' import MedicationSearchRequest from '../../../medications/models/MedicationSearchRequest' import MedicationRepository from '../../../shared/db/MedicationRepository' import SortRequest from '../../../shared/db/SortRequest' import Medication from '../../../shared/model/Medication' -import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util' +import executeQuery from '../../test-utils/use-query.util' const defaultSortRequest: SortRequest = { sorts: [ @@ -25,13 +23,7 @@ describe('useMedicationSearch', () => { const expectedMedicationRequests = [{ id: 'some id' }] as Medication[] jest.spyOn(MedicationRepository, 'search').mockResolvedValue(expectedMedicationRequests) - let actualData: any - await act(async () => { - const renderHookResult = renderHook(() => useMedicationSearch(expectedSearchRequest)) - const { result } = renderHookResult - await waitUntilQueryIsSuccessful(renderHookResult) - actualData = result.current.data - }) + const actualData = await executeQuery(() => useMedicationSearch(expectedSearchRequest)) expect(MedicationRepository.search).toHaveBeenCalledTimes(1) expect(MedicationRepository.search).toBeCalledWith({ diff --git a/src/__tests__/medications/requests/NewMedicationRequest.test.tsx b/src/__tests__/medications/requests/NewMedicationRequest.test.tsx index fcf251a6fc..82057c4532 100644 --- a/src/__tests__/medications/requests/NewMedicationRequest.test.tsx +++ b/src/__tests__/medications/requests/NewMedicationRequest.test.tsx @@ -1,186 +1,203 @@ -import { Button, Typeahead, Label } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router } from 'react-router-dom' +import selectEvent from 'react-select-event' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import NewMedicationRequest from '../../../medications/requests/NewMedicationRequest' import * as titleUtil from '../../../page-header/title/TitleContext' -import TextFieldWithLabelFormGroup from '../../../shared/components/input/TextFieldWithLabelFormGroup' -import TextInputWithLabelFormGroup from '../../../shared/components/input/TextInputWithLabelFormGroup' -import MedicationRepository from '../../../shared/db/MedicationRepository' -import PatientRepository from '../../../shared/db/PatientRepository' -import Medication from '../../../shared/model/Medication' -import Patient from '../../../shared/model/Patient' import { RootState } from '../../../shared/store' const mockStore = createMockStore<RootState, any>([thunk]) +const { TitleProvider } = titleUtil -describe('New Medication Request', () => { - const setup = async ( - store = mockStore({ medication: { status: 'loading', error: {} } } as any), - ) => { - const history = createMemoryHistory() - history.push(`/medications/new`) - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - - const wrapper: ReactWrapper = await mount( +const setup = (store = mockStore({ medication: { status: 'loading', error: {} } } as any)) => { + jest.resetAllMocks() + + const history = createMemoryHistory() + history.push(`/medications/new`) + + return { + history, + ...render( <Provider store={store}> <Router history={history}> - <titleUtil.TitleProvider> + <TitleProvider> <NewMedicationRequest /> - </titleUtil.TitleProvider> + </TitleProvider> </Router> </Provider>, - ) - - wrapper.update() - return { wrapper, history } + ), } +} +describe('New Medication Request', () => { describe('form layout', () => { - it('should have called the useUpdateTitle hook', async () => { - await setup() - expect(titleUtil.useUpdateTitle).toHaveBeenCalledTimes(1) + it('should render a patient typeahead', () => { + setup() + + expect(screen.getByText(/medications\.medication\.patient/i)).toBeInTheDocument() + expect(screen.getByPlaceholderText(/medications\.medication\.patient/i)).toBeInTheDocument() + }) + + it('should render a medication input box with label', async () => { + setup() + + expect(screen.getByText(/medications\.medication\.medication/i)).toBeInTheDocument() + expect(screen.getByLabelText(/medications\.medication\.medication/i)).toBeInTheDocument() }) - it('should render a patient typeahead', async () => { - const { wrapper } = await setup() - const typeaheadDiv = wrapper.find('.patient-typeahead') + it('render medication request status options', async () => { + setup() - expect(typeaheadDiv).toBeDefined() + const medStatus = within(screen.getByTestId('statusSelect')).getByRole('combobox') - const label = typeaheadDiv.find(Label) - const typeahead = typeaheadDiv.find(Typeahead) + expect(screen.getByText(/medications\.medication\.status/i)).toBeInTheDocument() + expect(medStatus.getAttribute('aria-expanded')).toBe('false') + selectEvent.openMenu(medStatus) + expect(medStatus.getAttribute('aria-expanded')).toBe('true') + expect(medStatus).toHaveDisplayValue(/medications\.status\.draft/i) - expect(label).toBeDefined() - expect(label.prop('text')).toEqual('medications.medication.patient') - expect(typeahead).toBeDefined() - expect(typeahead.prop('placeholder')).toEqual('medications.medication.patient') - expect(typeahead.prop('searchAccessor')).toEqual('fullName') + const statusOptions = within(screen.getByTestId('statusSelect')) + .getAllByRole('option') + .map((option) => option.lastElementChild?.innerHTML) + + expect( + statusOptions.includes('medications.status.draft' && 'medications.status.active'), + ).toBe(true) }) - it('should render a medication input box', async () => { - const { wrapper } = await setup() - const typeInputBox = wrapper.find(TextInputWithLabelFormGroup).at(0) + it('render medication intent options', async () => { + setup() + + const medicationIntent = within(screen.getByTestId('intentSelect')).getByRole('combobox') + + expect(screen.getByText(/medications\.medication\.intent/i)).toBeInTheDocument() + expect(medicationIntent.getAttribute('aria-expanded')).toBe('false') + selectEvent.openMenu(medicationIntent) + expect(medicationIntent.getAttribute('aria-expanded')).toBe('true') + expect(medicationIntent).toHaveDisplayValue(/medications\.intent\.proposal/i) + + const intentOptions = within(screen.getByTestId('intentSelect')) + .getAllByRole('option') + .map((option) => option.lastElementChild?.innerHTML) + + expect( + intentOptions.includes( + 'medications.intent.proposal' && + 'medications.intent.plan' && + 'medications.intent.order' && + 'medications.intent.originalOrder' && + 'medications.intent.reflexOrder' && + 'medications.intent.fillerOrder' && + 'medications.intent.instanceOrder' && + 'medications.intent.option', + ), + ).toBe(true) + }) - expect(typeInputBox).toBeDefined() - expect(typeInputBox.prop('label')).toEqual('medications.medication.medication') - expect(typeInputBox.prop('isRequired')).toBeTruthy() - expect(typeInputBox.prop('isEditable')).toBeTruthy() + it('render medication priorty select options', async () => { + setup() + + const medicationPriority = within(screen.getByTestId('prioritySelect')).getByRole('combobox') + + expect(screen.getByText(/medications\.medication\.status/i)).toBeInTheDocument() + expect(medicationPriority.getAttribute('aria-expanded')).toBe('false') + selectEvent.openMenu(medicationPriority) + expect(medicationPriority.getAttribute('aria-expanded')).toBe('true') + expect(medicationPriority).toHaveDisplayValue('medications.priority.routine') + + const priorityOptions = within(screen.getByTestId('prioritySelect')) + .getAllByRole('option') + .map((option) => option.lastElementChild?.innerHTML) + + expect( + priorityOptions.includes( + 'medications.priority.routine' && + 'medications.priority.urgent' && + 'medications.priority.asap' && + 'medications.priority.stat', + ), + ).toBe(true) }) it('should render a notes text field', async () => { - const { wrapper } = await setup() - const notesTextField = wrapper.find(TextFieldWithLabelFormGroup) + setup() - expect(notesTextField).toBeDefined() - expect(notesTextField.prop('label')).toEqual('medications.medication.notes') - expect(notesTextField.prop('isRequired')).toBeFalsy() - expect(notesTextField.prop('isEditable')).toBeTruthy() + const medicationNotes = screen.getByRole('textbox', { + name: /medications\.medication\.notes/i, + }) + + expect(screen.getByLabelText(/medications\.medication\.notes/i)).toBeInTheDocument() + expect(medicationNotes).toBeInTheDocument() + userEvent.type(medicationNotes, 'Bruce Wayne is batman') + expect(medicationNotes).toHaveValue('Bruce Wayne is batman') }) - it('should render a save button', async () => { - const { wrapper } = await setup() - const saveButton = wrapper.find(Button).at(0) - expect(saveButton).toBeDefined() - expect(saveButton.text().trim()).toEqual('medications.requests.new') + it('should render a save button', () => { + setup() + + expect( + screen.getByRole('button', { + name: /medications\.requests\.new/i, + }), + ).toBeInTheDocument() }) - it('should render a cancel button', async () => { - const { wrapper } = await setup() - const cancelButton = wrapper.find(Button).at(1) - expect(cancelButton).toBeDefined() - expect(cancelButton.text().trim()).toEqual('actions.cancel') + it('should render a cancel button', () => { + setup() + + expect( + screen.getByRole('button', { + name: /actions\.cancel/i, + }), + ).toBeInTheDocument() }) }) describe('on cancel', () => { it('should navigate back to /medications', async () => { - const { wrapper, history } = await setup() - const cancelButton = wrapper.find(Button).at(1) + const { history } = setup() - act(() => { - const onClick = cancelButton.prop('onClick') as any - onClick({} as React.MouseEvent<HTMLButtonElement>) + const cancelButton = screen.getByRole('button', { + name: /actions\.cancel/i, }) + expect(history.location.pathname).toEqual('/medications/new') + userEvent.click(cancelButton) expect(history.location.pathname).toEqual('/medications') }) }) describe('on save', () => { - let medicationRepositorySaveSpy: any - const expectedDate = new Date() - const expectedMedication = { - patient: '12345', - medication: 'expected medication', - status: 'draft', - notes: 'expected notes', - id: '1234', - requestedOn: expectedDate.toISOString(), - } as Medication - const store = mockStore({ - medication: { status: 'loading', error: {} }, - user: { user: { id: 'fake id' } }, - } as any) - beforeEach(() => { - jest.resetAllMocks() - Date.now = jest.fn(() => expectedDate.valueOf()) - medicationRepositorySaveSpy = jest - .spyOn(MedicationRepository, 'save') - .mockResolvedValue(expectedMedication as Medication) - - jest - .spyOn(PatientRepository, 'search') - .mockResolvedValue([ - { id: expectedMedication.patient, fullName: 'some full name' }, - ] as Patient[]) - }) - it('should save the medication request and navigate to "/medications/:id"', async () => { - const { wrapper, history } = await setup(store) - - const patientTypeahead = wrapper.find(Typeahead) - await act(async () => { - const onChange = patientTypeahead.prop('onChange') - await onChange([{ id: expectedMedication.patient }] as Patient[]) + const { history } = setup() + const patient = screen.getByPlaceholderText(/medications\.medication\.patient/i) + const medication = screen.getByLabelText(/medications\.medication\.medication/i) + const medicationNotes = screen.getByRole('textbox', { + name: /medications\.medication\.notes/i, }) - - const medicationInput = wrapper.find(TextInputWithLabelFormGroup).at(0) - act(() => { - const onChange = medicationInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedMedication.medication } }) - }) - - const notesTextField = wrapper.find(TextFieldWithLabelFormGroup) - act(() => { - const onChange = notesTextField.prop('onChange') as any - onChange({ currentTarget: { value: expectedMedication.notes } }) - }) - wrapper.update() - - const saveButton = wrapper.find(Button).at(0) - await act(async () => { - const onClick = saveButton.prop('onClick') as any - await onClick() - }) - - expect(medicationRepositorySaveSpy).toHaveBeenCalledTimes(1) - expect(medicationRepositorySaveSpy).toHaveBeenCalledWith( - expect.objectContaining({ - patient: expectedMedication.patient, - medication: expectedMedication.medication, - notes: expectedMedication.notes, - status: 'draft', - requestedOn: expectedDate.toISOString(), + const medStatus = within(screen.getByTestId('statusSelect')).getByRole('combobox') + const medicationIntent = within(screen.getByTestId('intentSelect')).getByRole('combobox') + const medicationPriority = within(screen.getByTestId('prioritySelect')).getByRole('combobox') + + userEvent.type(patient, 'Bruce Wayne') + userEvent.type(medication, 'Ibuprofen') + userEvent.type(medicationNotes, 'Be warned he is Batman') + selectEvent.create(medStatus, 'active') + selectEvent.create(medicationIntent, 'order') + selectEvent.create(medicationPriority, 'urgent') + + userEvent.click( + screen.getByRole('button', { + name: /medications\.requests\.new/i, }), ) - expect(history.location.pathname).toEqual(`/medications/${expectedMedication.id}`) + expect(history.location.pathname).toEqual('/medications/new') }) }) }) diff --git a/src/__tests__/medications/search/MedicationRequestSearch.test.tsx b/src/__tests__/medications/search/MedicationRequestSearch.test.tsx index 981d7358ce..3da0b48403 100644 --- a/src/__tests__/medications/search/MedicationRequestSearch.test.tsx +++ b/src/__tests__/medications/search/MedicationRequestSearch.test.tsx @@ -1,87 +1,81 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import React from 'react' -import { act } from 'react-dom/test-utils' import MedicationSearchRequest from '../../../medications/models/MedicationSearchRequest' import MedicationRequestSearch from '../../../medications/search/MedicationRequestSearch' -import SelectWithLabelFormGroup from '../../../shared/components/input/SelectWithLabelFormGroup' -import TextInputWithLabelFormGroup from '../../../shared/components/input/TextInputWithLabelFormGroup' describe('Medication Request Search', () => { const setup = (givenSearchRequest: MedicationSearchRequest = { text: '', status: 'draft' }) => { const onChangeSpy = jest.fn() - const wrapper = mount( - <MedicationRequestSearch searchRequest={givenSearchRequest} onChange={onChangeSpy} />, - ) - wrapper.update() - - return { wrapper: wrapper as ReactWrapper, onChangeSpy } + return { + onChangeSpy, + ...render( + <MedicationRequestSearch searchRequest={givenSearchRequest} onChange={onChangeSpy} />, + ), + } } - it('should render a select component with the default value', () => { - const expectedSearchRequest: MedicationSearchRequest = { text: '', status: 'draft' } - const { wrapper } = setup(expectedSearchRequest) + it('should render a select component with the default value', async () => { + setup() + const select = screen.getByRole('combobox') + expect(select).not.toBeDisabled() + expect(select).toHaveDisplayValue(/medications\.status\.draft/i) + expect(screen.getByText(/medications\.filterTitle/i)).toBeInTheDocument() + userEvent.click(select) + + const optionsContent = screen + .getAllByRole('option') + .map((option) => option.lastElementChild?.innerHTML) - const select = wrapper.find(SelectWithLabelFormGroup) - expect(select.prop('label')).toEqual('medications.filterTitle') - expect(select.prop('options')).toEqual([ - { label: 'medications.filter.all', value: 'all' }, - { label: 'medications.status.draft', value: 'draft' }, - { label: 'medications.status.active', value: 'active' }, - { label: 'medications.status.onHold', value: 'on hold' }, - { label: 'medications.status.completed', value: 'completed' }, - { label: 'medications.status.enteredInError', value: 'entered in error' }, - { label: 'medications.status.canceled', value: 'canceled' }, - { label: 'medications.status.unknown', value: 'unknown' }, - ]) - expect(select.prop('defaultSelected')).toEqual([ - { - label: 'medications.status.draft', - value: 'draft', - }, - ]) - expect(select.prop('isEditable')).toBeTruthy() + expect( + optionsContent.includes( + 'medications.filter.all' && + 'medications.status.draft' && + 'medications.status.active' && + 'medications.status.onHold' && + 'medications.status.completed' && + 'medications.status.enteredInError' && + 'medications.status.canceled' && + 'medications.status.unknown', + ), + ).toBe(true) }) it('should update the search request when the filter updates', () => { const expectedSearchRequest: MedicationSearchRequest = { text: '', status: 'draft' } const expectedNewValue = 'canceled' - const { wrapper, onChangeSpy } = setup(expectedSearchRequest) + const { onChangeSpy } = setup() + const select = screen.getByRole('combobox') - act(() => { - const select = wrapper.find(SelectWithLabelFormGroup) - const onChange = select.prop('onChange') as any - onChange([expectedNewValue]) - }) + userEvent.type(select, `{selectall}{backspace}${expectedNewValue}`) + userEvent.click(screen.getByText(expectedNewValue)) - expect(onChangeSpy).toHaveBeenCalledTimes(1) + expect(onChangeSpy).toHaveBeenCalled() expect(onChangeSpy).toHaveBeenCalledWith({ ...expectedSearchRequest, status: expectedNewValue }) }) it('should render a text input with the default value', () => { const expectedSearchRequest: MedicationSearchRequest = { text: '', status: 'draft' } - const { wrapper } = setup(expectedSearchRequest) + setup(expectedSearchRequest) + const textInput = screen.getByLabelText(/medications\.search/i) - const textInput = wrapper.find(TextInputWithLabelFormGroup) - expect(textInput.prop('label')).toEqual('medications.search') - expect(textInput.prop('placeholder')).toEqual('medications.search') - expect(textInput.prop('value')).toEqual(expectedSearchRequest.text) - expect(textInput.prop('isEditable')).toBeTruthy() + expect(textInput).toBeInTheDocument() + expect(textInput).toHaveAttribute('placeholder', 'medications.search') + expect(textInput).toHaveAttribute('value', expectedSearchRequest.text) + expect(textInput).not.toBeDisabled() }) it('should update the search request when the text input is updated', () => { const expectedSearchRequest: MedicationSearchRequest = { text: '', status: 'draft' } const expectedNewValue = 'someNewValue' - const { wrapper, onChangeSpy } = setup(expectedSearchRequest) + const { onChangeSpy } = setup(expectedSearchRequest) + const textInput = screen.getByLabelText(/medications\.search/i) - act(() => { - const textInput = wrapper.find(TextInputWithLabelFormGroup) - const onChange = textInput.prop('onChange') as any - onChange({ target: { value: expectedNewValue } }) - }) + userEvent.type(textInput, `{selectall}{backspace}${expectedNewValue}`) - expect(onChangeSpy).toHaveBeenCalledTimes(1) + expect(onChangeSpy).toHaveBeenCalled() expect(onChangeSpy).toHaveBeenCalledWith({ ...expectedSearchRequest, text: expectedNewValue }) }) }) diff --git a/src/__tests__/medications/search/MedicationRequestTable.test.tsx b/src/__tests__/medications/search/MedicationRequestTable.test.tsx index cdadaad467..f14737501d 100644 --- a/src/__tests__/medications/search/MedicationRequestTable.test.tsx +++ b/src/__tests__/medications/search/MedicationRequestTable.test.tsx @@ -1,8 +1,7 @@ -import { Table } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Router } from 'react-router-dom' import MedicationSearchRequest from '../../../medications/models/MedicationSearchRequest' @@ -11,7 +10,7 @@ import MedicationRepository from '../../../shared/db/MedicationRepository' import Medication from '../../../shared/model/Medication' describe('Medication Request Table', () => { - const setup = async ( + const setup = ( givenSearchRequest: MedicationSearchRequest = { text: '', status: 'all' }, givenMedications: Medication[] = [], ) => { @@ -19,73 +18,56 @@ describe('Medication Request Table', () => { jest.spyOn(MedicationRepository, 'search').mockResolvedValue(givenMedications) const history = createMemoryHistory() - let wrapper: any - await act(async () => { - wrapper = await mount( + return { + history, + ...render( <Router history={history}> <MedicationRequestTable searchRequest={givenSearchRequest} /> </Router>, - ) - }) - wrapper.update() - - return { wrapper: wrapper as ReactWrapper, history } + ), + } } it('should render a table with the correct columns', async () => { - const { wrapper } = await setup() + setup() + + expect(await screen.findByRole('table')).toBeInTheDocument() - const table = wrapper.find(Table) - const columns = table.prop('columns') - const actions = table.prop('actions') as any - expect(columns[0]).toEqual( - expect.objectContaining({ label: 'medications.medication.medication', key: 'medication' }), - ) - expect(columns[1]).toEqual( - expect.objectContaining({ label: 'medications.medication.priority', key: 'priority' }), - ) - expect(columns[2]).toEqual( - expect.objectContaining({ label: 'medications.medication.intent', key: 'intent' }), - ) - expect(columns[3]).toEqual( - expect.objectContaining({ - label: 'medications.medication.requestedOn', - key: 'requestedOn', - }), - ) - expect(columns[4]).toEqual( - expect.objectContaining({ label: 'medications.medication.status', key: 'status' }), - ) + const columns = screen.getAllByRole('columnheader') - expect(actions[0]).toEqual(expect.objectContaining({ label: 'actions.view' })) - expect(table.prop('actionsHeaderText')).toEqual('actions.label') + expect(columns[0]).toHaveTextContent(/medications.medication.medication/i) + expect(columns[1]).toHaveTextContent(/medications.medication.priority/i) + expect(columns[2]).toHaveTextContent(/medications.medication.intent/i) + expect(columns[3]).toHaveTextContent(/medications.medication.requestedOn/i) + expect(columns[4]).toHaveTextContent(/medications.medication.status/i) + expect(columns[5]).toHaveTextContent(/actions.label/i) }) it('should fetch medications and display it', async () => { const expectedSearchRequest: MedicationSearchRequest = { text: 'someText', status: 'draft' } - const expectedMedicationRequests: Medication[] = [{ id: 'someId' } as Medication] - - const { wrapper } = await setup(expectedSearchRequest, expectedMedicationRequests) + const expectedMedicationRequests: Medication[] = [ + { + id: 'someId', + medication: expectedSearchRequest.text, + status: expectedSearchRequest.status, + } as Medication, + ] + setup(expectedSearchRequest, expectedMedicationRequests) - const table = wrapper.find(Table) - expect(MedicationRepository.search).toHaveBeenCalledWith( - expect.objectContaining(expectedSearchRequest), - ) - expect(table.prop('data')).toEqual(expectedMedicationRequests) + expect(await screen.findByRole('table')).toBeInTheDocument() + expect(screen.getByText(expectedSearchRequest.text)).toBeInTheDocument() + expect(screen.getByText(expectedSearchRequest.status)).toBeInTheDocument() }) it('should navigate to the medication when the view button is clicked', async () => { const expectedSearchRequest: MedicationSearchRequest = { text: 'someText', status: 'draft' } const expectedMedicationRequests: Medication[] = [{ id: 'someId' } as Medication] + const { history } = setup(expectedSearchRequest, expectedMedicationRequests) - const { wrapper, history } = await setup(expectedSearchRequest, expectedMedicationRequests) + expect(await screen.findByRole('table')).toBeInTheDocument() - const tr = wrapper.find('tr').at(1) - act(() => { - const onClick = tr.find('button').prop('onClick') as any - onClick({ stopPropagation: jest.fn() }) - }) + userEvent.click(screen.getByRole('button', { name: /actions.view/i })) - expect(history.location.pathname).toEqual(`/medications/${expectedMedicationRequests[0].id}`) + expect(history.location.pathname).toBe(`/medications/${expectedMedicationRequests[0].id}`) }) }) diff --git a/src/__tests__/medications/search/ViewMedications.test.tsx b/src/__tests__/medications/search/ViewMedications.test.tsx index f1e083b27c..2a4ad44b20 100644 --- a/src/__tests__/medications/search/ViewMedications.test.tsx +++ b/src/__tests__/medications/search/ViewMedications.test.tsx @@ -1,15 +1,13 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import MedicationSearchRequest from '../../../medications/models/MedicationSearchRequest' -import MedicationRequestSearch from '../../../medications/search/MedicationRequestSearch' -import MedicationRequestTable from '../../../medications/search/MedicationRequestTable' import ViewMedications from '../../../medications/search/ViewMedications' import * as ButtonBarProvider from '../../../page-header/button-toolbar/ButtonBarProvider' import * as titleUtil from '../../../page-header/title/TitleContext' @@ -22,8 +20,11 @@ const { TitleProvider } = titleUtil const mockStore = createMockStore<RootState, any>([thunk]) describe('View Medications', () => { - const setup = async (medication: Medication, permissions: Permissions[] = []) => { - let wrapper: any + const setup = async ( + medication: Medication, + permissions: Permissions[] = [], + givenMedications: Medication[] = [], + ) => { const expectedMedication = ({ id: '1234', medication: 'medication', @@ -39,14 +40,16 @@ describe('View Medications', () => { user: { permissions }, medications: { medications: [{ ...expectedMedication, ...medication }] }, } as any) - const titleSpy = jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) + jest.resetAllMocks() const setButtonToolBarSpy = jest.fn() - jest.spyOn(MedicationRepository, 'search').mockResolvedValue([]) + jest.spyOn(MedicationRepository, 'search').mockResolvedValue(givenMedications) jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) jest.spyOn(MedicationRepository, 'findAll').mockResolvedValue([]) - await act(async () => { - wrapper = await mount( + return { + history, + setButtonToolBarSpy, + ...render( <Provider store={store}> <Router history={history}> <TitleProvider> @@ -54,30 +57,14 @@ describe('View Medications', () => { </TitleProvider> </Router> </Provider>, - ) - }) - wrapper.update() - return { - wrapper: wrapper as ReactWrapper, - history, - titleSpy, - setButtonToolBarSpy, + ), } } - describe('title', () => { - it('should have called the useUpdateTitle hook', async () => { - const { titleSpy } = await setup({} as Medication) - - expect(titleSpy).toHaveBeenCalled() - }) - }) - describe('button bar', () => { it('should display button to add new medication request', async () => { const permissions = [Permissions.ViewMedications, Permissions.RequestMedication] - const { setButtonToolBarSpy } = await setup({} as Medication, permissions) - + const { setButtonToolBarSpy } = await setup({ medication: 'test' } as Medication, permissions) const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] expect((actualButtons[0] as any).props.children).toEqual('medications.requests.new') }) @@ -92,21 +79,20 @@ describe('View Medications', () => { describe('table', () => { it('should render a table with data with the default search', async () => { - const { wrapper } = await setup({} as Medication, [Permissions.ViewMedications]) + setup({} as Medication, [Permissions.ViewMedications]) - const table = wrapper.find(MedicationRequestTable) - expect(table).toHaveLength(1) - expect(table.prop('searchRequest')).toEqual({ text: '', status: 'all' }) + expect(await screen.findByRole('table')).toBeInTheDocument() + expect(screen.getByLabelText(/medications\.search/i)).toHaveDisplayValue('') }) }) describe('search', () => { it('should render a medication request search component', async () => { - const { wrapper } = await setup({} as Medication) + setup({} as Medication) - const search = wrapper.find(MedicationRequestSearch) - expect(search).toHaveLength(1) - expect(search.prop('searchRequest')).toEqual({ text: '', status: 'all' }) + const search = screen.getByLabelText(/medications\.search/i) + expect(search).toBeInTheDocument() + expect(search).toHaveDisplayValue('') }) it('should update the table when the search changes', async () => { @@ -114,17 +100,22 @@ describe('View Medications', () => { text: 'someNewText', status: 'draft', } - const { wrapper } = await setup({} as Medication) - - await act(async () => { - const search = wrapper.find(MedicationRequestSearch) - const onChange = search.prop('onChange') - await onChange(expectedSearchRequest) - }) - wrapper.update() + const expectedMedicationRequests: Medication[] = [ + { + id: 'someId', + medication: expectedSearchRequest.text, + status: expectedSearchRequest.status, + } as Medication, + ] + setup( + { medication: expectedSearchRequest.text } as Medication, + [], + expectedMedicationRequests, + ) + userEvent.type(screen.getByLabelText(/medications\.search/i), expectedSearchRequest.text) + expect(await screen.findByRole('table')).toBeInTheDocument() - const table = wrapper.find(MedicationRequestTable) - expect(table.prop('searchRequest')).toEqual(expectedSearchRequest) + expect(screen.getByText(expectedSearchRequest.text)).toBeInTheDocument() }) }) }) diff --git a/src/__tests__/page-header/breadcrumbs/Breadcrumbs.test.tsx b/src/__tests__/page-header/breadcrumbs/Breadcrumbs.test.tsx index 397630ffc9..b216c29532 100644 --- a/src/__tests__/page-header/breadcrumbs/Breadcrumbs.test.tsx +++ b/src/__tests__/page-header/breadcrumbs/Breadcrumbs.test.tsx @@ -1,8 +1,4 @@ -import { - Breadcrumb as HRBreadcrumb, - BreadcrumbItem as HRBreadcrumbItem, -} from '@hospitalrun/components' -import { mount } from 'enzyme' +import { render, screen } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' @@ -23,22 +19,20 @@ describe('Breadcrumbs', () => { breadcrumbs: { breadcrumbs }, } as any) - const wrapper = mount( + return render( <Provider store={store}> <Router history={history}> <Breadcrumbs /> </Router> </Provider>, ) - - return wrapper } it('should not render the breadcrumb when there are no items in the store', () => { - const wrapper = setup([]) + setup([]) - expect(wrapper.find(HRBreadcrumb)).toHaveLength(0) - expect(wrapper.find(HRBreadcrumbItem)).toHaveLength(0) + expect(screen.queryByRole('list')).toBeNull() + expect(screen.queryByRole('listitem')).toBeNull() }) it('should render breadcrumbs items', () => { @@ -47,13 +41,14 @@ describe('Breadcrumbs', () => { { text: 'Bob', location: '/patient/1' }, { text: 'Edit Patient', location: '/patient/1/edit' }, ] - const wrapper = setup(breadcrumbs) - const items = wrapper.find(HRBreadcrumbItem) + setup(breadcrumbs) + + const breadCrumbItems = screen.getAllByRole('listitem') - expect(items).toHaveLength(3) - expect(items.at(0).text()).toEqual('patient.label') - expect(items.at(1).text()).toEqual('Bob') - expect(items.at(2).text()).toEqual('Edit Patient') + expect(breadCrumbItems).toHaveLength(3) + expect(breadCrumbItems[0]).toHaveTextContent('patient.label') + expect(breadCrumbItems[1]).toHaveTextContent('Bob') + expect(breadCrumbItems[2]).toHaveTextContent('Edit Patient') }) }) diff --git a/src/__tests__/page-header/breadcrumbs/useAddBreadcrumbs.test.tsx b/src/__tests__/page-header/breadcrumbs/useAddBreadcrumbs.test.tsx index 50732bc28d..a6d0994343 100644 --- a/src/__tests__/page-header/breadcrumbs/useAddBreadcrumbs.test.tsx +++ b/src/__tests__/page-header/breadcrumbs/useAddBreadcrumbs.test.tsx @@ -6,23 +6,29 @@ import thunk from 'redux-thunk' import * as breadcrumbsSlice from '../../../page-header/breadcrumbs/breadcrumbs-slice' import useAddBreadcrumbs from '../../../page-header/breadcrumbs/useAddBreadcrumbs' -import { RootState } from '../../../shared/store' -const mockStore = createMockStore<RootState, any>([thunk]) +const mockStore = createMockStore<any, any>([thunk]) describe('useAddBreadcrumbs', () => { beforeEach(() => jest.clearAllMocks()) - it('should call addBreadcrumbs with the correct data', () => { - const wrapper = ({ children }: any) => <Provider store={mockStore({})}>{children}</Provider> - - jest.spyOn(breadcrumbsSlice, 'addBreadcrumbs') + const setup = () => { const breadcrumbs = [ { text: 'Patients', location: '/patients', }, ] + const wrapper: React.FC = ({ children }) => ( + <Provider store={mockStore({})}>{children}</Provider> + ) + + return { breadcrumbs, wrapper } + } + + it('should call addBreadcrumbs with the correct data', () => { + jest.spyOn(breadcrumbsSlice, 'addBreadcrumbs') + const { breadcrumbs, wrapper } = setup() renderHook(() => useAddBreadcrumbs(breadcrumbs), { wrapper } as any) expect(breadcrumbsSlice.addBreadcrumbs).toHaveBeenCalledTimes(1) @@ -30,17 +36,10 @@ describe('useAddBreadcrumbs', () => { }) it('should call addBreadcrumbs with an additional dashboard breadcrumb', () => { - const wrapper = ({ children }: any) => <Provider store={mockStore({})}>{children}</Provider> - jest.spyOn(breadcrumbsSlice, 'addBreadcrumbs') - const breadcrumbs = [ - { - text: 'Patients', - location: '/patients', - }, - ] + const { breadcrumbs, wrapper } = setup() - renderHook(() => useAddBreadcrumbs(breadcrumbs, true), { wrapper } as any) + renderHook(() => useAddBreadcrumbs(breadcrumbs, true), { wrapper }) expect(breadcrumbsSlice.addBreadcrumbs).toHaveBeenCalledTimes(1) expect(breadcrumbsSlice.addBreadcrumbs).toHaveBeenCalledWith([ ...breadcrumbs, @@ -49,18 +48,10 @@ describe('useAddBreadcrumbs', () => { }) it('should call removeBreadcrumbs with the correct data after unmount', () => { - const wrapper = ({ children }: any) => <Provider store={mockStore({})}>{children}</Provider> - - jest.spyOn(breadcrumbsSlice, 'addBreadcrumbs') jest.spyOn(breadcrumbsSlice, 'removeBreadcrumbs') - const breadcrumbs = [ - { - text: 'Patients', - location: '/patients', - }, - ] + const { breadcrumbs, wrapper } = setup() - const { unmount } = renderHook(() => useAddBreadcrumbs(breadcrumbs), { wrapper } as any) + const { unmount } = renderHook(() => useAddBreadcrumbs(breadcrumbs), { wrapper }) unmount() expect(breadcrumbsSlice.removeBreadcrumbs).toHaveBeenCalledTimes(1) expect(breadcrumbsSlice.removeBreadcrumbs).toHaveBeenCalledWith(breadcrumbs) diff --git a/src/__tests__/page-header/button-toolbar/ButtonBarProvider.test.tsx b/src/__tests__/page-header/button-toolbar/ButtonBarProvider.test.tsx index b8310667c3..b726e64687 100644 --- a/src/__tests__/page-header/button-toolbar/ButtonBarProvider.test.tsx +++ b/src/__tests__/page-header/button-toolbar/ButtonBarProvider.test.tsx @@ -1,6 +1,6 @@ import { Button } from '@hospitalrun/components' -import { renderHook } from '@testing-library/react-hooks' -import React, { useEffect } from 'react' +import { renderHook, act } from '@testing-library/react-hooks' +import React from 'react' import { ButtonBarProvider, @@ -11,20 +11,19 @@ import { 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 wrapper: React.FC = ({ children }) => <ButtonBarProvider>{children}</ButtonBarProvider> const { result } = renderHook( () => { const update = useButtonToolbarSetter() - useEffect(() => { - update(expectedButtons) - }, [update]) - - return useButtons() + const buttons = useButtons() + return { buttons, update } }, { wrapper }, ) - expect(result.current).toEqual(expectedButtons) + act(() => result.current.update(expectedButtons)) + + expect(result.current.buttons).toEqual(expectedButtons) }) }) diff --git a/src/__tests__/page-header/button-toolbar/ButtonToolBar.test.tsx b/src/__tests__/page-header/button-toolbar/ButtonToolBar.test.tsx index 71a01bdccd..6f5babd792 100644 --- a/src/__tests__/page-header/button-toolbar/ButtonToolBar.test.tsx +++ b/src/__tests__/page-header/button-toolbar/ButtonToolBar.test.tsx @@ -1,5 +1,5 @@ import { Button } from '@hospitalrun/components' -import { mount } from 'enzyme' +import { render, screen } from '@testing-library/react' import React from 'react' import * as ButtonBarProvider from '../../../page-header/button-toolbar/ButtonBarProvider' @@ -17,17 +17,17 @@ describe('Button Tool Bar', () => { ] jest.spyOn(ButtonBarProvider, 'useButtons').mockReturnValue(buttons) - const wrapper = mount(<ButtonToolBar />).find('.button-toolbar') + render(<ButtonToolBar />) + const renderedButtons = screen.getAllByRole('button') - expect(wrapper.childAt(0).getElement()).toEqual(buttons[0]) - expect(wrapper.childAt(1).getElement()).toEqual(buttons[1]) + expect(renderedButtons[0]).toHaveTextContent('Test 1') + expect(renderedButtons[1]).toHaveTextContent('Test 2') }) it('should return null when there is no button in the provider', () => { jest.spyOn(ButtonBarProvider, 'useButtons').mockReturnValue([]) - const wrapper = mount(<ButtonToolBar />) - - expect(wrapper.html()).toBeNull() + render(<ButtonToolBar />) + expect(screen.queryByRole('button')).not.toBeInTheDocument() }) }) diff --git a/src/__tests__/page-header/title/TitleProvider.test.tsx b/src/__tests__/page-header/title/TitleProvider.test.tsx index 85275956cc..e474228f9e 100644 --- a/src/__tests__/page-header/title/TitleProvider.test.tsx +++ b/src/__tests__/page-header/title/TitleProvider.test.tsx @@ -1,38 +1,24 @@ -import { renderHook } from '@testing-library/react-hooks' +import { renderHook, act } from '@testing-library/react-hooks' import React from 'react' -import { Provider } from 'react-redux' -import createMockStore from 'redux-mock-store' -import thunk from 'redux-thunk' -import { TitleProvider } from '../../../page-header/title/TitleContext' -import { RootState } from '../../../shared/store' - -const mockStore = createMockStore<RootState, any>([thunk]) +import { TitleProvider, useTitle, useUpdateTitle } from '../../../page-header/title/TitleContext' describe('useTitle', () => { - const store = mockStore({ - user: { user: { id: '123' }, permissions: [] }, - appointments: { appointments: [] }, - medications: { medications: [] }, - labs: { labs: [] }, - imagings: { imagings: [] }, - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - } as any) - it('should call the updateTitle with the correct data.', () => { - const wrapper = ({ children }: any) => ( - <TitleProvider> - <Provider store={store}>{children}</Provider> - </TitleProvider> - ) - - const useTitle = jest.fn() const expectedTitle = 'title' + const wrapper: React.FC = ({ children }) => <TitleProvider>{children}</TitleProvider> + + const { result } = renderHook( + () => { + const update = useUpdateTitle() + const title = useTitle() + return { ...title, update } + }, + { wrapper }, + ) - renderHook(() => useTitle(expectedTitle), { wrapper } as any) + act(() => result.current.update(expectedTitle)) - expect(useTitle).toHaveBeenCalledTimes(1) - expect(useTitle).toHaveBeenCalledWith(expectedTitle) + expect(result.current.title).toBe(expectedTitle) }) }) diff --git a/src/__tests__/patients/ContactInfo.test.tsx b/src/__tests__/patients/ContactInfo.test.tsx index debfb82cdd..0dc514ad00 100644 --- a/src/__tests__/patients/ContactInfo.test.tsx +++ b/src/__tests__/patients/ContactInfo.test.tsx @@ -1,12 +1,10 @@ -import { Column, Spinner } from '@hospitalrun/components' -import { mount } from 'enzyme' +import { screen, render, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Router } from 'react-router-dom' import ContactInfo from '../../patients/ContactInfo' -import TextInputWithLabelFormGroup from '../../shared/components/input/TextInputWithLabelFormGroup' import { ContactInfoPiece } from '../../shared/model/ContactInformation' import * as uuid from '../../shared/util/uuid' @@ -20,8 +18,8 @@ describe('Contact Info in its Editable mode', () => { { id: '456', value: ' ', type: undefined }, ] const errors = ['this is an error', ''] - const label = 'this is a label' - const name = 'this is a name' + const label = 'Phone Number' + const name = 'Number' let onChange: jest.Mock const setup = (_data?: ContactInfoPiece[], _errors?: string[]) => { @@ -29,7 +27,7 @@ describe('Contact Info in its Editable mode', () => { history.push('/patients/new') onChange = jest.fn() - const wrapper = mount( + return render( <Router history={history}> <ContactInfo component="TextInputWithLabelFormGroup" @@ -42,16 +40,8 @@ describe('Contact Info in its Editable mode', () => { /> </Router>, ) - return wrapper } - it('should show a spinner if no data is present', () => { - const wrapper = setup() - const spinnerWrapper = wrapper.find(Spinner) - - expect(spinnerWrapper).toHaveLength(1) - }) - it('should call onChange if no data is provided', () => { const newId = 'newId' jest.spyOn(uuid, 'uuid').mockReturnValue(newId) @@ -63,66 +53,57 @@ describe('Contact Info in its Editable mode', () => { }) it('should render the labels if data is provided', () => { - const wrapper = setup(data) - const headerWrapper = wrapper.find('.header') - const columnWrappers = headerWrapper.find(Column) - const expectedTypeLabel = 'patient.contactInfoType.label' + setup(data) - expect(columnWrappers.at(0).text()).toEqual(`${expectedTypeLabel} & ${label}`) - expect(columnWrappers.at(1).text()).toEqual(label) + expect(screen.getByText(/patient\.contactinfotype\.label/i)).toBeInTheDocument() + expect(screen.getByText(label)).toBeInTheDocument() }) it('should display the entries if data is provided', () => { - const wrapper = setup(data) - for (let i = 0; i < wrapper.length; i += 1) { - const inputWrapper = wrapper.findWhere((w: any) => w.prop('name') === `${name}${i}`) + setup(data) - expect(inputWrapper.prop('value')).toEqual(data[i].value) - } + expect(screen.getAllByRole('textbox')[1]).toHaveValue(`${data[0].value}`) + + const selectInput = within(screen.getByTestId('NumberType0Select')).getByRole('combobox') + + expect(selectInput).toHaveValue('patient.contactInfoType.options.home') }) it('should display the error if error is provided', () => { - const wrapper = setup(data, errors) - const feedbackWrappers = wrapper.find('.invalid-feedback') - - expect(feedbackWrappers).toHaveLength(errors.length) + setup(data, errors) - feedbackWrappers.forEach((_, i) => { - expect(feedbackWrappers.at(i).text()).toEqual(errors[i]) - }) + expect(screen.getByText(/this is an error/i)).toBeInTheDocument() }) it('should display the add button', () => { - const wrapper = setup(data) - const buttonWrapper = wrapper.find('button') - - expect(buttonWrapper.text().trim()).toEqual('actions.add') + setup(data) + expect(screen.getByRole('button', { name: /actions\.add/i })).toBeInTheDocument() }) it('should call the onChange callback if input is changed', () => { - const wrapper = setup(data) - const input = wrapper.findWhere((w: any) => w.prop('name') === `${name}0`).find('input') - input.getDOMNode<HTMLInputElement>().value = '777777' - input.simulate('change') + setup(data) const expectedNewData = [ { id: '123', value: '777777', type: 'home' }, { id: '456', value: '789012', type: undefined }, ] - expect(onChange).toHaveBeenCalledTimes(1) + + const inputElement = screen.getAllByRole('textbox')[1] + expect(inputElement).toHaveValue(`${data[0].value}`) + userEvent.clear(inputElement) + userEvent.type(inputElement, expectedNewData[0].value) + expect(inputElement).toHaveValue(`${expectedNewData[0].value}`) + expect(onChange).toHaveBeenCalledWith(expectedNewData) }) it('should call the onChange callback if an add button is clicked with valid entries', () => { - const wrapper = setup(data) - const buttonWrapper = wrapper.find('button') - const onClick = buttonWrapper.prop('onClick') as any + setup(data) const newId = 'newId' jest.spyOn(uuid, 'uuid').mockReturnValue(newId) - act(() => { - onClick() - }) + expect(screen.getByRole('button')).toBeInTheDocument() + userEvent.click(screen.getByRole('button')) const expectedNewData = [...data, { id: newId, value: '' }] @@ -131,15 +112,13 @@ describe('Contact Info in its Editable mode', () => { }) it('should call the onChange callback if an add button is clicked with an empty entry', () => { - const wrapper = setup(dataForNoAdd) - const buttonWrapper = wrapper.find('button') - const onClick = buttonWrapper.prop('onClick') as any + setup(dataForNoAdd) + const newId = 'newId' jest.spyOn(uuid, 'uuid').mockReturnValue(newId) - act(() => { - onClick() - }) + expect(screen.getByRole('button')).toBeInTheDocument() + userEvent.click(screen.getByRole('button')) const expectedNewData = [ { id: '123', value: '123456', type: 'home' }, @@ -156,14 +135,14 @@ describe('Contact Info in its non-Editable mode', () => { { id: '123', value: '123456', type: 'home' }, { id: '456', value: '789012', type: undefined }, ] - const label = 'this is a label' - const name = 'this is a name' + const label = 'Phone Number' + const name = 'Number' const setup = (_data?: ContactInfoPiece[]) => { const history = createMemoryHistory() history.push('/patients/new') - const wrapper = mount( + return render( <Router history={history}> <ContactInfo component="TextInputWithLabelFormGroup" @@ -173,41 +152,39 @@ describe('Contact Info in its non-Editable mode', () => { /> </Router>, ) - return wrapper } it('should render an empty element if no data is present', () => { - const wrapper = setup() - const contactInfoWrapper = wrapper.find(ContactInfo) + const { container } = setup() - expect(contactInfoWrapper.find('div')).toHaveLength(1) - expect(contactInfoWrapper.containsMatchingElement(<div />)).toEqual(true) + expect(container).toMatchInlineSnapshot(` + <div> + <div /> + </div> + `) }) it('should render the labels if data is provided', () => { - const wrapper = setup(data) - const headerWrapper = wrapper.find('.header') - const columnWrappers = headerWrapper.find(Column) - const expectedTypeLabel = 'patient.contactInfoType.label' + setup(data) - expect(columnWrappers.at(0).text()).toEqual(`${expectedTypeLabel} & ${label}`) - expect(columnWrappers.at(1).text()).toEqual(label) + expect(screen.getByText(/patient\.contactInfoType\.label/i)).toBeInTheDocument() }) it('should display the entries if data is provided', () => { - const wrapper = setup(data) - for (let i = 0; i < wrapper.length; i += 1) { - const inputWrapper = wrapper.findWhere((w: any) => w.prop('name') === `${name}${i}`) + setup(data) - expect(inputWrapper.prop('value')).toEqual(data[i].value) - } + const inputElement = screen.getAllByRole('textbox')[1] + const selectInput = within(screen.getByTestId('NumberType0Select')).getByRole('combobox') + expect(selectInput).toHaveDisplayValue('patient.contactInfoType.options.home') + expect(inputElement).toHaveDisplayValue(`${data[0].value}`) }) it('should show inputs that are not editable', () => { - const wrapper = setup(data) - const inputWrappers = wrapper.find(TextInputWithLabelFormGroup) - for (let i = 0; i < inputWrappers.length; i += 1) { - expect(inputWrappers.at(i).prop('isEditable')).toBeFalsy() - } + setup(data) + const inputElement = screen.getAllByRole('textbox')[1] + const selectInput = within(screen.getByTestId('NumberType0Select')).getByRole('combobox') + + expect(selectInput).not.toHaveFocus() + expect(inputElement).not.toHaveFocus() }) }) diff --git a/src/__tests__/patients/GeneralInformation.test.tsx b/src/__tests__/patients/GeneralInformation.test.tsx index e69f06c158..234929a71b 100644 --- a/src/__tests__/patients/GeneralInformation.test.tsx +++ b/src/__tests__/patients/GeneralInformation.test.tsx @@ -1,605 +1,318 @@ -import { Alert } from '@hospitalrun/components' -import { act } from '@testing-library/react' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import format from 'date-fns/format' import startOfDay from 'date-fns/startOfDay' import subYears from 'date-fns/subYears' -import { mount, ReactWrapper } from 'enzyme' -import { createMemoryHistory, MemoryHistory } from 'history' +import { createMemoryHistory } from 'history' import React from 'react' import { Router } from 'react-router-dom' import GeneralInformation from '../../patients/GeneralInformation' import Patient from '../../shared/model/Patient' -describe('Error handling', () => { - it('should display errors', () => { - const error = { - message: 'some message', - givenName: 'given name message', - dateOfBirth: 'date of birth message', - phoneNumbers: ['phone number message'], - emails: ['email message'], - } - - const wrapper = mount( +Date.now = jest.fn().mockReturnValue(new Date().valueOf()) + +// most fields use TextInputWithLabelFormGroup, an uncontrolled <input /> => tests are faster without onChange +function setup(patientArg: Patient, isEditable = true, error?: Record<string, unknown>) { + return render( + <Router history={createMemoryHistory()}> + <GeneralInformation patient={patientArg} isEditable={isEditable} error={error} /> + </Router>, + ) +} + +// however, TextFieldWithLabelFormGroup is controlled and needs explicit onChange +function setupControlled(patientArg: Patient, isEditable = true, error?: Record<string, unknown>) { + const TestComponent = () => { + const [patient, setPatient] = React.useState(patientArg) + return ( <GeneralInformation - patient={ - { - phoneNumbers: [{ value: 'not a phone number', id: '123' }], - emails: [{ value: 'not an email', id: '456' }], - } as Patient - } - isEditable + patient={patient} + isEditable={isEditable} error={error} - />, + onChange={setPatient as (p: Partial<Patient>) => void} + /> ) - wrapper.update() - - const errorMessage = wrapper.find(Alert) - const givenNameInput = wrapper.findWhere((w: any) => w.prop('name') === 'givenName') - const dateOfBirthInput = wrapper.findWhere((w: any) => w.prop('name') === 'dateOfBirth') - const phoneNumberInput = wrapper.findWhere((w: any) => w.prop('name') === 'phoneNumber0') - const emailInput = wrapper.findWhere((w: any) => w.prop('name') === 'email0') - - expect(errorMessage).toBeTruthy() - expect(errorMessage.prop('message')).toMatch(error.message) - - expect(givenNameInput.prop('isInvalid')).toBeTruthy() - expect(givenNameInput.prop('feedback')).toEqual(error.givenName) - - expect(dateOfBirthInput.prop('isInvalid')).toBeTruthy() - expect(dateOfBirthInput.prop('feedback')).toEqual(error.dateOfBirth) - - expect(phoneNumberInput.prop('isInvalid')).toBeTruthy() - expect(phoneNumberInput.prop('feedback')).toEqual(error.phoneNumbers[0]) - - expect(emailInput.prop('isInvalid')).toBeTruthy() - expect(emailInput.prop('feedback')).toEqual(error.emails[0]) - }) + } + render( + <Router history={createMemoryHistory()}> + <TestComponent /> + </Router>, + ) +} + +const patient = { + id: '1234321', + prefix: 'MockPrefix', + givenName: 'givenName', + familyName: 'familyName', + suffix: 'MockSuffix', + sex: 'male', + type: 'charity', + bloodType: 'A-', + dateOfBirth: startOfDay(subYears(new Date(), 30)).toISOString(), + isApproximateDateOfBirth: false, + occupation: 'MockOccupationValue', + preferredLanguage: 'MockPreferredLanguage', + phoneNumbers: [ + { value: '123456789', type: undefined, id: '123' }, + { value: '789012999', type: undefined, id: '456' }, + ], + emails: [ + { value: 'abc@email.com', type: undefined, id: '789' }, + { value: 'xyz@email.com', type: undefined, id: '987' }, + ], + addresses: [ + { value: 'address A', type: undefined, id: '654' }, + { value: 'address B', type: undefined, id: '321' }, + ], + code: 'P00001', +} as Patient + +it('should display errors', () => { + const error = { + message: 'red alert Error Message', + givenName: 'given name Error Message', + dateOfBirth: 'date of birth Error Message', + phoneNumbers: ['phone number Error Message'], + emails: ['email Error Message'], + } + setup( + { + phoneNumbers: [{ value: 'not a phone number', id: '123' }], + emails: [{ value: 'not an email', id: '456' }], + } as Patient, + true, + error, + ) + + expect(screen.getByRole(/alert/i)).toHaveTextContent(error.message) + expect(screen.getByLabelText(/patient\.givenName/i)).toHaveClass('is-invalid') + + expect(screen.getByText(/given name Error Message/i)).toHaveClass('invalid-feedback') + expect(screen.getByText(/date of birth Error Message/i)).toHaveClass('text-danger') + expect(screen.getByText(/phone number Error Message/i)).toHaveClass('invalid-feedback') + expect(screen.getByText(/email Error Message/i)).toHaveClass('invalid-feedback') + + expect(screen.getByDisplayValue(/not an email/i)).toHaveClass('is-invalid') + expect(screen.getByDisplayValue(/not a phone number/i)).toHaveClass('is-invalid') }) -describe('General Information, without isEditable', () => { - let wrapper: ReactWrapper - let history = createMemoryHistory() - const patient = { - id: '123', - prefix: 'prefix', - givenName: 'givenName', - familyName: 'familyName', - suffix: 'suffix', - sex: 'male', - type: 'charity', - bloodType: 'A-', - dateOfBirth: startOfDay(subYears(new Date(), 30)).toISOString(), - isApproximateDateOfBirth: false, - occupation: 'occupation', - preferredLanguage: 'preferredLanguage', - phoneNumbers: [ - { value: '123456', type: undefined, id: '123' }, - { value: '789012', type: undefined, id: '456' }, - ], - emails: [ - { value: 'abc@email.com', type: undefined, id: '789' }, - { value: 'xyz@email.com', type: undefined, id: '987' }, - ], - addresses: [ - { value: 'address A', type: undefined, id: '654' }, - { value: 'address B', type: undefined, id: '321' }, - ], - code: 'P00001', - } as Patient - - beforeEach(() => { - Date.now = jest.fn().mockReturnValue(new Date().valueOf()) - jest.restoreAllMocks() - history = createMemoryHistory() - wrapper = mount( - <Router history={history}> - <GeneralInformation patient={patient} />) - </Router>, - ) - }) +describe('General Information, readonly', () => { + function typeReadonlyAssertion(inputElement: HTMLElement, mockValue: any) { + expect(inputElement).toHaveDisplayValue(mockValue) + userEvent.type(inputElement, 'wontexist') + expect(inputElement).toHaveDisplayValue(mockValue) + expect(inputElement).not.toHaveDisplayValue('wontexist') + } it('should render the prefix', () => { - const prefixInput = wrapper.findWhere((w: any) => w.prop('name') === 'prefix') - expect(prefixInput.prop('value')).toEqual(patient.prefix) - expect(prefixInput.prop('label')).toEqual('patient.prefix') - expect(prefixInput.prop('isEditable')).toBeFalsy() + setup(patient, false) + + typeReadonlyAssertion(screen.getByRole('textbox', { name: /patient\.prefix/i }), patient.prefix) }) it('should render the given name', () => { - const givenNameInput = wrapper.findWhere((w: any) => w.prop('name') === 'givenName') - expect(givenNameInput.prop('value')).toEqual(patient.givenName) - expect(givenNameInput.prop('label')).toEqual('patient.givenName') - expect(givenNameInput.prop('isEditable')).toBeFalsy() + setup(patient, false) + typeReadonlyAssertion(screen.getByLabelText(/patient\.givenName/i), patient.givenName) }) it('should render the family name', () => { - const familyNameInput = wrapper.findWhere((w: any) => w.prop('name') === 'familyName') - expect(familyNameInput.prop('value')).toEqual(patient.familyName) - expect(familyNameInput.prop('label')).toEqual('patient.familyName') - expect(familyNameInput.prop('isEditable')).toBeFalsy() + setup(patient, false) + typeReadonlyAssertion(screen.getByLabelText(/patient\.familyName/i), patient.familyName) }) it('should render the suffix', () => { - const suffixInput = wrapper.findWhere((w: any) => w.prop('name') === 'suffix') - expect(suffixInput.prop('value')).toEqual(patient.suffix) - expect(suffixInput.prop('label')).toEqual('patient.suffix') - expect(suffixInput.prop('isEditable')).toBeFalsy() - }) - - it('should render the sex select', () => { - const sexSelect = wrapper.findWhere((w: any) => w.prop('name') === 'sex') - expect(sexSelect.prop('defaultSelected')[0].value).toEqual(patient.sex) - expect(sexSelect.prop('label')).toEqual('patient.sex') - expect(sexSelect.prop('isEditable')).toBeFalsy() - expect(sexSelect.prop('options')).toHaveLength(4) - expect(sexSelect.prop('options')[0].label).toEqual('sex.male') - expect(sexSelect.prop('options')[0].value).toEqual('male') - expect(sexSelect.prop('options')[1].label).toEqual('sex.female') - expect(sexSelect.prop('options')[1].value).toEqual('female') - expect(sexSelect.prop('options')[2].label).toEqual('sex.other') - expect(sexSelect.prop('options')[2].value).toEqual('other') - expect(sexSelect.prop('options')[3].label).toEqual('sex.unknown') - expect(sexSelect.prop('options')[3].value).toEqual('unknown') - }) - - it('should render the blood type', () => { - const bloodTypeSelect = wrapper.findWhere((w: any) => w.prop('name') === 'bloodType') - expect(bloodTypeSelect.prop('defaultSelected')[0].value).toEqual(patient.bloodType) - expect(bloodTypeSelect.prop('label')).toEqual('patient.bloodType') - expect(bloodTypeSelect.prop('isEditable')).toBeFalsy() - expect(bloodTypeSelect.prop('options')).toHaveLength(9) - expect(bloodTypeSelect.prop('options')[0].label).toEqual('bloodType.apositive') - expect(bloodTypeSelect.prop('options')[0].value).toEqual('A+') - expect(bloodTypeSelect.prop('options')[1].label).toEqual('bloodType.anegative') - expect(bloodTypeSelect.prop('options')[1].value).toEqual('A-') - expect(bloodTypeSelect.prop('options')[2].label).toEqual('bloodType.abpositive') - expect(bloodTypeSelect.prop('options')[2].value).toEqual('AB+') - expect(bloodTypeSelect.prop('options')[3].label).toEqual('bloodType.abnegative') - expect(bloodTypeSelect.prop('options')[3].value).toEqual('AB-') - expect(bloodTypeSelect.prop('options')[4].label).toEqual('bloodType.bpositive') - expect(bloodTypeSelect.prop('options')[4].value).toEqual('B+') - expect(bloodTypeSelect.prop('options')[5].label).toEqual('bloodType.bnegative') - expect(bloodTypeSelect.prop('options')[5].value).toEqual('B-') - expect(bloodTypeSelect.prop('options')[6].label).toEqual('bloodType.opositive') - expect(bloodTypeSelect.prop('options')[6].value).toEqual('O+') - expect(bloodTypeSelect.prop('options')[7].label).toEqual('bloodType.onegative') - expect(bloodTypeSelect.prop('options')[7].value).toEqual('O-') - expect(bloodTypeSelect.prop('options')[8].label).toEqual('bloodType.unknown') - expect(bloodTypeSelect.prop('options')[8].value).toEqual('unknown') - }) - - it('should render the patient type select', () => { - const typeSelect = wrapper.findWhere((w: any) => w.prop('name') === 'type') - expect(typeSelect.prop('defaultSelected')[0].value).toEqual(patient.type) - expect(typeSelect.prop('label')).toEqual('patient.type') - expect(typeSelect.prop('isEditable')).toBeFalsy() - expect(typeSelect.prop('options')).toHaveLength(2) - expect(typeSelect.prop('options')[0].label).toEqual('patient.types.charity') - expect(typeSelect.prop('options')[0].value).toEqual('charity') - expect(typeSelect.prop('options')[1].label).toEqual('patient.types.private') - expect(typeSelect.prop('options')[1].value).toEqual('private') + setup(patient, false) + typeReadonlyAssertion(screen.getByLabelText(/patient\.suffix/i), patient.suffix) }) it('should render the date of the birth of the patient', () => { - const dateOfBirthInput = wrapper.findWhere((w: any) => w.prop('name') === 'dateOfBirth') - expect(dateOfBirthInput.prop('value')).toEqual(new Date(patient.dateOfBirth)) - expect(dateOfBirthInput.prop('label')).toEqual('patient.dateOfBirth') - expect(dateOfBirthInput.prop('maxDate')).toEqual(new Date(Date.now())) - expect(dateOfBirthInput.prop('isEditable')).toBeFalsy() + setup(patient, false) + const expectedDate = format(new Date(patient.dateOfBirth), 'MM/dd/yyyy') + typeReadonlyAssertion(screen.getByDisplayValue(expectedDate), [expectedDate]) }) - it('should render the approximate age if patient.isApproximateDateOfBirth is true', async () => { - patient.isApproximateDateOfBirth = true - await act(async () => { - wrapper = await mount( - <Router history={history}> - <GeneralInformation patient={patient} />) - </Router>, - ) - }) - - const approximateAgeInput = wrapper.findWhere((w: any) => w.prop('name') === 'approximateAge') - - expect(approximateAgeInput.prop('value')).toEqual('30') - expect(approximateAgeInput.prop('label')).toEqual('patient.approximateAge') - expect(approximateAgeInput.prop('isEditable')).toBeFalsy() + it('should render the approximate age if patient.isApproximateDateOfBirth is true', () => { + const newPatient = { ...patient, isApproximateDateOfBirth: true } + setup(newPatient, false) + typeReadonlyAssertion(screen.getByLabelText(/patient.approximateAge/i), '30') }) it('should render the occupation of the patient', () => { - const occupationInput = wrapper.findWhere((w: any) => w.prop('name') === 'occupation') - expect(occupationInput.prop('value')).toEqual(patient.occupation) - expect(occupationInput.prop('label')).toEqual('patient.occupation') - expect(occupationInput.prop('isEditable')).toBeFalsy() + setup(patient, false) + typeReadonlyAssertion(screen.getByLabelText(/patient.occupation/i), patient.occupation) }) it('should render the preferred language of the patient', () => { - const preferredLanguageInput = wrapper.findWhere( - (w: any) => w.prop('name') === 'preferredLanguage', + setup(patient, false) + typeReadonlyAssertion( + screen.getByLabelText(/patient.preferredLanguage/i), + patient.preferredLanguage, ) - expect(preferredLanguageInput.prop('value')).toEqual(patient.preferredLanguage) - expect(preferredLanguageInput.prop('label')).toEqual('patient.preferredLanguage') - expect(preferredLanguageInput.prop('isEditable')).toBeFalsy() }) it('should render the phone numbers of the patient', () => { - patient.phoneNumbers.forEach((phoneNumber, i) => { - const phoneNumberInput = wrapper.findWhere((w: any) => w.prop('name') === `phoneNumber${i}`) - expect(phoneNumberInput.prop('value')).toEqual(phoneNumber.value) - expect(phoneNumberInput.prop('isEditable')).toBeFalsy() - }) + setup(patient, false) + const phoneNumberField = screen.getByDisplayValue(/123456789/i) + expect(phoneNumberField).toHaveProperty('id', 'phoneNumber0TextInput') + typeReadonlyAssertion(phoneNumberField, patient.phoneNumbers[0].value) }) it('should render the emails of the patient', () => { - patient.emails.forEach((email, i) => { - const emailInput = wrapper.findWhere((w: any) => w.prop('name') === `email${i}`) - expect(emailInput.prop('value')).toEqual(email.value) - expect(emailInput.prop('isEditable')).toBeFalsy() - }) + setup(patient, false) + const emailsField = screen.getByDisplayValue(/abc@email.com/i) + expect(emailsField).toHaveProperty('id', 'email0TextInput') + typeReadonlyAssertion(emailsField, patient.emails[0].value) }) it('should render the addresses of the patient', () => { - patient.addresses.forEach((address, i) => { - const addressInput = wrapper.findWhere((w: any) => w.prop('name') === `address${i}`) - expect(addressInput.prop('value')).toEqual(address.value) - expect(addressInput.prop('isEditable')).toBeFalsy() - }) + setup(patient, false) + const phoneNumberField = screen.getByDisplayValue(/address A/i) + expect(phoneNumberField).toHaveProperty('id', 'address0TextField') + typeReadonlyAssertion(phoneNumberField, patient.addresses[0].value) + }) + it('should render the sex select options', () => { + setup(patient, false) + const patientSex = screen.getByDisplayValue(/sex/) + expect(patientSex).toBeDisabled() + expect(patientSex).toHaveDisplayValue([/sex.male/i]) }) -}) -describe('General Information, isEditable', () => { - let wrapper: ReactWrapper - let history: MemoryHistory - let onFieldChange: jest.Mock - const patient = { - id: '123', - prefix: 'prefix', - givenName: 'givenName', - familyName: 'familyName', - suffix: 'suffix', - sex: 'male', - bloodType: 'A-', - type: 'charity', - dateOfBirth: startOfDay(subYears(new Date(), 30)).toISOString(), - isApproximateDateOfBirth: false, - occupation: 'occupation', - preferredLanguage: 'preferredLanguage', - phoneNumbers: [ - { value: '123456', type: undefined, id: '123' }, - { value: '789012', type: undefined, id: '456' }, - ], - emails: [ - { value: 'abc@email.com', type: undefined, id: '789' }, - { value: 'xyz@email.com', type: undefined, id: '987' }, - ], - addresses: [ - { value: 'address A', type: undefined, id: '654' }, - { value: 'address B', type: undefined, id: '321' }, - ], - code: 'P00001', - } as Patient - - beforeEach(() => { - jest.restoreAllMocks() - Date.now = jest.fn().mockReturnValue(new Date().valueOf()) - history = createMemoryHistory() - onFieldChange = jest.fn() - wrapper = mount( - <Router history={history}> - <GeneralInformation patient={patient} onChange={onFieldChange} isEditable />) - </Router>, - ) + it('should render the blood type select options', () => { + setup(patient, false) + const bloodType = screen.getByDisplayValue(/bloodType/) + expect(bloodType).toBeDisabled() + expect(bloodType).toHaveDisplayValue(['bloodType.anegative']) }) + it('should render the patient type select options', () => { + setup(patient, false) + const patientType = screen.getByDisplayValue(/patient.type/) + expect(patientType).toBeDisabled() + expect(patientType).toHaveDisplayValue([/patient.types.charity/i]) + }) +}) - const expectedPrefix = 'expectedPrefix' - const expectedGivenName = 'expectedGivenName' - const expectedFamilyName = 'expectedFamilyName' - const expectedSuffix = 'expectedSuffix' - const expectedDateOfBirth = '1937-06-14T05:00:00.000Z' - const expectedOccupation = 'expectedOccupation' - const expectedPreferredLanguage = 'expectedPreferredLanguage' - const expectedPhoneNumbers = [ - { value: '111111', type: undefined, id: '123' }, - { value: '222222', type: undefined, id: '456' }, - ] - const expectedEmails = [ - { value: 'def@email.com', type: undefined, id: '789' }, - { value: 'uvw@email.com', type: undefined, id: '987' }, - ] - const expectedAddresses = [ - { value: 'address C', type: undefined, id: '654' }, - { value: 'address D', type: undefined, id: '321' }, - ] - const expectedBloodType = 'unknown' +describe('General Information, isEditable', () => { + function typeWritableAssertion(inputElement: HTMLElement, mockValue: any) { + expect(inputElement).toHaveDisplayValue(mockValue) + userEvent.type(inputElement, '{selectall}willexist') + expect(inputElement).toHaveDisplayValue('willexist') + expect(inputElement).not.toHaveDisplayValue(mockValue) + } it('should render the prefix', () => { - const prefixInput = wrapper.findWhere((w: any) => w.prop('name') === 'prefix') - - expect(prefixInput.prop('value')).toEqual(patient.prefix) - expect(prefixInput.prop('label')).toEqual('patient.prefix') - expect(prefixInput.prop('isEditable')).toBeTruthy() - - const input = prefixInput.find('input') - input.getDOMNode<HTMLInputElement>().value = expectedPrefix - input.simulate('change') - - expect(onFieldChange).toHaveBeenCalledTimes(1) - expect(onFieldChange).toHaveBeenCalledWith({ ...patient, prefix: expectedPrefix }) + setup(patient) + typeWritableAssertion(screen.getByRole('textbox', { name: /patient\.prefix/i }), patient.prefix) }) it('should render the given name', () => { - const givenNameInput = wrapper.findWhere((w: any) => w.prop('name') === 'givenName') - - expect(givenNameInput.prop('value')).toEqual(patient.givenName) - expect(givenNameInput.prop('label')).toEqual('patient.givenName') - expect(givenNameInput.prop('isEditable')).toBeTruthy() - - const input = givenNameInput.find('input') - input.getDOMNode<HTMLInputElement>().value = expectedGivenName - input.simulate('change') - - expect(onFieldChange).toHaveBeenCalledTimes(1) - expect(onFieldChange).toHaveBeenCalledWith({ ...patient, givenName: expectedGivenName }) + setup(patient) + typeWritableAssertion(screen.getByPlaceholderText(/patient\.givenName/i), patient.givenName) }) it('should render the family name', () => { - const familyNameInput = wrapper.findWhere((w: any) => w.prop('name') === 'familyName') - - expect(familyNameInput.prop('value')).toEqual(patient.familyName) - expect(familyNameInput.prop('label')).toEqual('patient.familyName') - expect(familyNameInput.prop('isEditable')).toBeTruthy() - - const input = familyNameInput.find('input') - input.getDOMNode<HTMLInputElement>().value = expectedFamilyName - input.simulate('change') - - expect(onFieldChange).toHaveBeenCalledTimes(1) - expect(onFieldChange).toHaveBeenCalledWith({ ...patient, familyName: expectedFamilyName }) + setup(patient) + typeWritableAssertion(screen.getByPlaceholderText(/patient\.familyName/i), patient.familyName) }) it('should render the suffix', () => { - const suffixInput = wrapper.findWhere((w: any) => w.prop('name') === 'suffix') - - expect(suffixInput.prop('value')).toEqual(patient.suffix) - expect(suffixInput.prop('label')).toEqual('patient.suffix') - expect(suffixInput.prop('isEditable')).toBeTruthy() - - const input = suffixInput.find('input') - input.getDOMNode<HTMLInputElement>().value = expectedSuffix - input.simulate('change') - - expect(onFieldChange).toHaveBeenCalledTimes(1) - expect(onFieldChange).toHaveBeenCalledWith({ ...patient, suffix: expectedSuffix }) - }) - - it('should render the sex select', () => { - const sexSelect = wrapper.findWhere((w: any) => w.prop('name') === 'sex') - - expect(sexSelect.prop('defaultSelected')[0].value).toEqual(patient.sex) - expect(sexSelect.prop('label')).toEqual('patient.sex') - expect(sexSelect.prop('isEditable')).toBeTruthy() - expect(sexSelect.prop('options')).toHaveLength(4) - - expect(sexSelect.prop('options')[0].label).toEqual('sex.male') - expect(sexSelect.prop('options')[0].value).toEqual('male') - expect(sexSelect.prop('options')[1].label).toEqual('sex.female') - expect(sexSelect.prop('options')[1].value).toEqual('female') - expect(sexSelect.prop('options')[2].label).toEqual('sex.other') - expect(sexSelect.prop('options')[2].value).toEqual('other') - expect(sexSelect.prop('options')[3].label).toEqual('sex.unknown') - expect(sexSelect.prop('options')[3].value).toEqual('unknown') - }) - - it('should render the blood type select', () => { - const bloodTypeSelect = wrapper.findWhere((w: any) => w.prop('name') === 'bloodType') - expect(bloodTypeSelect.prop('defaultSelected')[0].value).toEqual(patient.bloodType) - expect(bloodTypeSelect.prop('label')).toEqual('patient.bloodType') - expect(bloodTypeSelect.prop('isEditable')).toBeTruthy() - expect(bloodTypeSelect.prop('options')).toHaveLength(9) - expect(bloodTypeSelect.prop('options')[0].label).toEqual('bloodType.apositive') - expect(bloodTypeSelect.prop('options')[0].value).toEqual('A+') - expect(bloodTypeSelect.prop('options')[1].label).toEqual('bloodType.anegative') - expect(bloodTypeSelect.prop('options')[1].value).toEqual('A-') - expect(bloodTypeSelect.prop('options')[2].label).toEqual('bloodType.abpositive') - expect(bloodTypeSelect.prop('options')[2].value).toEqual('AB+') - expect(bloodTypeSelect.prop('options')[3].label).toEqual('bloodType.abnegative') - expect(bloodTypeSelect.prop('options')[3].value).toEqual('AB-') - expect(bloodTypeSelect.prop('options')[4].label).toEqual('bloodType.bpositive') - expect(bloodTypeSelect.prop('options')[4].value).toEqual('B+') - expect(bloodTypeSelect.prop('options')[5].label).toEqual('bloodType.bnegative') - expect(bloodTypeSelect.prop('options')[5].value).toEqual('B-') - expect(bloodTypeSelect.prop('options')[6].label).toEqual('bloodType.opositive') - expect(bloodTypeSelect.prop('options')[6].value).toEqual('O+') - expect(bloodTypeSelect.prop('options')[7].label).toEqual('bloodType.onegative') - expect(bloodTypeSelect.prop('options')[7].value).toEqual('O-') - expect(bloodTypeSelect.prop('options')[8].label).toEqual('bloodType.unknown') - expect(bloodTypeSelect.prop('options')[8].value).toEqual('unknown') - act(() => { - bloodTypeSelect.prop('onChange')([expectedBloodType]) - }) - expect(onFieldChange).toHaveBeenCalledTimes(1) - expect(onFieldChange).toHaveBeenCalledWith({ - ...patient, - bloodType: expectedBloodType, - }) - }) - - it('should render the patient type select', () => { - const typeSelect = wrapper.findWhere((w: any) => w.prop('name') === 'type') - - expect(typeSelect.prop('defaultSelected')[0].value).toEqual(patient.type) - expect(typeSelect.prop('label')).toEqual('patient.type') - expect(typeSelect.prop('isEditable')).toBeTruthy() - - expect(typeSelect.prop('options')).toHaveLength(2) - expect(typeSelect.prop('options')[0].label).toEqual('patient.types.charity') - expect(typeSelect.prop('options')[0].value).toEqual('charity') - expect(typeSelect.prop('options')[1].label).toEqual('patient.types.private') - expect(typeSelect.prop('options')[1].value).toEqual('private') + setup(patient) + typeWritableAssertion(screen.getByPlaceholderText(/patient\.suffix/i), patient.suffix) }) it('should render the date of the birth of the patient', () => { - const dateOfBirthInput = wrapper.findWhere((w: any) => w.prop('name') === 'dateOfBirth') - - expect(dateOfBirthInput.prop('value')).toEqual(new Date(patient.dateOfBirth)) - expect(dateOfBirthInput.prop('label')).toEqual('patient.dateOfBirth') - expect(dateOfBirthInput.prop('isEditable')).toBeTruthy() - expect(dateOfBirthInput.prop('maxDate')).toEqual(new Date(Date.now())) - - act(() => { - dateOfBirthInput.prop('onChange')(new Date(expectedDateOfBirth)) - }) + setup(patient) - expect(onFieldChange).toHaveBeenCalledTimes(1) - expect(onFieldChange).toHaveBeenCalledWith({ ...patient, dateOfBirth: expectedDateOfBirth }) - }) + const expectedDate = format(new Date(patient.dateOfBirth), 'MM/dd/yyyy') + const field = screen.getByDisplayValue(expectedDate) + typeWritableAssertion(field, [expectedDate]) - it('should render the approximate age if patient.isApproximateDateOfBirth is true', async () => { - patient.isApproximateDateOfBirth = true - await act(async () => { - wrapper = await mount( - <Router history={history}> - <GeneralInformation patient={patient} onChange={onFieldChange} isEditable />) - </Router>, - ) - }) - - const approximateAgeInput = wrapper.findWhere((w: any) => w.prop('name') === 'approximateAge') - - expect(approximateAgeInput.prop('value')).toEqual('30') - expect(approximateAgeInput.prop('label')).toEqual('patient.approximateAge') - expect(approximateAgeInput.prop('isEditable')).toBeTruthy() - - const input = approximateAgeInput.find('input') - input.getDOMNode<HTMLInputElement>().value = '20' - input.simulate('change') - - expect(onFieldChange).toHaveBeenCalledTimes(1) - expect(onFieldChange).toHaveBeenCalledWith({ - ...patient, - dateOfBirth: startOfDay(subYears(new Date(Date.now()), 20)).toISOString(), - }) + // workaround for Warning: `NaN` is an invalid value for the `left` css style property. + userEvent.type(field, '{enter}') }) it('should render the occupation of the patient', () => { - const occupationInput = wrapper.findWhere((w: any) => w.prop('name') === 'occupation') - - expect(occupationInput.prop('value')).toEqual(patient.occupation) - expect(occupationInput.prop('label')).toEqual('patient.occupation') - expect(occupationInput.prop('isEditable')).toBeTruthy() - - const input = occupationInput.find('input') - input.getDOMNode<HTMLInputElement>().value = expectedOccupation - input.simulate('change') - - expect(onFieldChange).toHaveBeenCalledTimes(1) - expect(onFieldChange).toHaveBeenCalledWith({ ...patient, occupation: expectedOccupation }) + setup(patient) + typeWritableAssertion(screen.getByLabelText(/patient.occupation/i), [patient.occupation]) }) it('should render the preferred language of the patient', () => { - const preferredLanguageInput = wrapper.findWhere( - (w: any) => w.prop('name') === 'preferredLanguage', + setup(patient) + typeWritableAssertion( + screen.getByLabelText(/patient.preferredLanguage/i), + patient.preferredLanguage, ) - - expect(preferredLanguageInput.prop('value')).toEqual(patient.preferredLanguage) - expect(preferredLanguageInput.prop('label')).toEqual('patient.preferredLanguage') - expect(preferredLanguageInput.prop('isEditable')).toBeTruthy() - - const input = preferredLanguageInput.find('input') - input.getDOMNode<HTMLInputElement>().value = expectedPreferredLanguage - input.simulate('change') - - expect(onFieldChange).toHaveBeenCalledTimes(1) - expect(onFieldChange).toHaveBeenCalledWith({ - ...patient, - preferredLanguage: expectedPreferredLanguage, - }) }) it('should render the phone numbers of the patient', () => { - patient.phoneNumbers.forEach((phoneNumber, i) => { - const phoneNumberInput = wrapper.findWhere((w: any) => w.prop('name') === `phoneNumber${i}`) - expect(phoneNumberInput.prop('value')).toEqual(phoneNumber.value) - expect(phoneNumberInput.prop('isEditable')).toBeTruthy() - - const input = phoneNumberInput.find('input') - input.getDOMNode<HTMLInputElement>().value = expectedPhoneNumbers[i].value - input.simulate('change') - }) - - const calledWith = [] as any - patient.phoneNumbers.forEach((_, i) => { - const newPhoneNumbers = [] as any - patient.phoneNumbers.forEach((__, j) => { - if (j <= i) { - newPhoneNumbers.push(expectedPhoneNumbers[j]) - } else { - newPhoneNumbers.push(patient.phoneNumbers[j]) - } - }) - calledWith.push({ ...patient, phoneNumbers: newPhoneNumbers }) - }) - - expect(onFieldChange).toHaveBeenCalledTimes(calledWith.length) - expect(onFieldChange).toHaveBeenNthCalledWith(1, calledWith[0]) - // expect(onFieldChange).toHaveBeenNthCalledWith(2, calledWith[1]) + setup(patient) + const phoneNumberField = screen.getByDisplayValue(/123456789/i) + expect(phoneNumberField).toHaveProperty('id', 'phoneNumber0TextInput') + typeWritableAssertion(phoneNumberField, patient.phoneNumbers[0].value) }) it('should render the emails of the patient', () => { - patient.emails.forEach((email, i) => { - const emailInput = wrapper.findWhere((w: any) => w.prop('name') === `email${i}`) - expect(emailInput.prop('value')).toEqual(email.value) - expect(emailInput.prop('isEditable')).toBeTruthy() - - const input = emailInput.find('input') - input.getDOMNode<HTMLInputElement>().value = expectedEmails[i].value - input.simulate('change') - }) + setup(patient) + const emailsField = screen.getByDisplayValue(/abc@email.com/i) + expect(emailsField).toHaveProperty('id', 'email0TextInput') + typeWritableAssertion(emailsField, patient.emails[0].value) + }) - const calledWith = [] as any - patient.emails.forEach((_, i) => { - const newEmails = [] as any - patient.emails.forEach((__, j) => { - if (j <= i) { - newEmails.push(expectedEmails[j]) - } else { - newEmails.push(patient.emails[j]) - } - }) - calledWith.push({ ...patient, emails: newEmails }) - }) + it('should render the addresses of the patient', () => { + setupControlled(patient) - expect(onFieldChange).toHaveBeenCalledTimes(calledWith.length) - expect(onFieldChange).toHaveBeenNthCalledWith(1, calledWith[0]) - // expect(onFieldChange).toHaveBeenNthCalledWith(2, calledWith[1]) + const addressField = screen.getByDisplayValue(/address A/i) + expect(addressField).toHaveProperty('id', 'address0TextField') + typeWritableAssertion(addressField, patient.addresses[0].value) }) - it('should render the addresses of the patient', () => { - patient.addresses.forEach((address, i) => { - const addressTextArea = wrapper.findWhere((w: any) => w.prop('name') === `address${i}`) - expect(addressTextArea.prop('value')).toEqual(address.value) - expect(addressTextArea.prop('isEditable')).toBeTruthy() - - const textarea = addressTextArea.find('textarea') - textarea.getDOMNode<HTMLTextAreaElement>().value = expectedAddresses[i].value - textarea.simulate('change') + it('should render the sex select', () => { + setup(patient) + const patientSex = screen.getByDisplayValue(/sex/) + expect(patientSex).not.toBeDisabled() + userEvent.click(patientSex) + const bloodArr = [/sex.male/i, /sex.female/i, /sex.female/i, /sex.unknown/i] + bloodArr.forEach((reg) => { + const sexOption = screen.getByRole('option', { name: reg }) + userEvent.click(sexOption) + expect(sexOption).toBeInTheDocument() }) + }) - const calledWith = [] as any - patient.addresses.forEach((_, i) => { - const newAddresses = [] as any - patient.addresses.forEach((__, j) => { - if (j <= i) { - newAddresses.push(expectedAddresses[j]) - } else { - newAddresses.push(patient.addresses[j]) - } - }) - calledWith.push({ ...patient, addresses: newAddresses }) + it('should render the blood type select', () => { + setup(patient) + const bloodType = screen.getByDisplayValue(/bloodType/) + expect(bloodType).not.toBeDisabled() + userEvent.click(bloodType) + const bloodArr = [ + /bloodType.apositive/i, + /bloodType.anegative/i, + /bloodType.abpositive/i, + /bloodType.abnegative/i, + /bloodType.bpositive/i, + /bloodType.bnegative/i, + /bloodType.opositive/i, + /bloodType.onegative/i, + /bloodType.unknown/i, + ] + bloodArr.forEach((reg) => { + const bloodOption = screen.getByRole('option', { name: reg }) + userEvent.click(bloodOption) + expect(bloodOption).toBeInTheDocument() }) + }) - expect(onFieldChange).toHaveBeenCalledTimes(calledWith.length) - expect(onFieldChange).toHaveBeenNthCalledWith(1, calledWith[0]) - // expect(onFieldChange).toHaveBeenNthCalledWith(2, calledWith[1]) + it('should render the patient type select', () => { + setup(patient) + const patientType = screen.getByDisplayValue(/patient.type/) + expect(patientType).not.toBeDisabled() + userEvent.click(patientType) + const bloodArr = [/patient.types.charity/i, /patient.types.private/i] + bloodArr.forEach((reg) => { + const typeOption = screen.getByRole('option', { name: reg }) + userEvent.click(typeOption) + expect(typeOption).toBeInTheDocument() + }) }) }) diff --git a/src/__tests__/patients/Patients.test.tsx b/src/__tests__/patients/Patients.test.tsx index 4067fc00a3..36d6a30657 100644 --- a/src/__tests__/patients/Patients.test.tsx +++ b/src/__tests__/patients/Patients.test.tsx @@ -1,19 +1,15 @@ -import { mount } from 'enzyme' +import { render, screen } from '@testing-library/react' +import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' -import { MemoryRouter } from 'react-router-dom' +import { Router } 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 { addBreadcrumbs } from '../../page-header/breadcrumbs/breadcrumbs-slice' import * as titleUtil from '../../page-header/title/TitleContext' -import EditPatient from '../../patients/edit/EditPatient' -import NewPatient from '../../patients/new/NewPatient' import * as patientNameUtil from '../../patients/util/patient-util' -import ViewPatient from '../../patients/view/ViewPatient' import PatientRepository from '../../shared/db/PatientRepository' import Patient from '../../shared/model/Patient' import Permissions from '../../shared/model/Permissions' @@ -22,35 +18,39 @@ import { RootState } from '../../shared/store' const { TitleProvider } = titleUtil const mockStore = createMockStore<RootState, any>([thunk]) +const setup = (url: string, permissions: Permissions[] = []) => { + const store = mockStore({ + user: { user: { id: '123' }, permissions }, + breadcrumbs: { breadcrumbs: [] }, + patient: {}, + components: { sidebarCollapsed: false }, + } as any) + const history = createMemoryHistory({ initialEntries: [url] }) + + return { + store, + history, + ...render( + <Provider store={store}> + <Router history={history}> + <TitleProvider> + <HospitalRun /> + </TitleProvider> + </Router> + </Provider>, + ), + } +} + describe('/patients/new', () => { - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - it('should render the new patient screen when /patients/new is accessed', async () => { - const store = mockStore({ - title: 'test', - user: { user: { id: '123' }, permissions: [Permissions.WritePatients] }, - breadcrumbs: { breadcrumbs: [] }, - patient: {}, - components: { sidebarCollapsed: false }, - } as any) - - let wrapper: any - - await act(async () => { - wrapper = await mount( - <Provider store={store}> - <MemoryRouter initialEntries={['/patients/new']}> - <TitleProvider> - <HospitalRun /> - </TitleProvider> - </MemoryRouter> - </Provider>, - ) - }) - - wrapper.update() - - expect(wrapper.find(NewPatient)).toHaveLength(1) + it('sould render the new patient screen when /patients/new is accessed', async () => { + const { store } = setup('/patients/new', [Permissions.WritePatients]) + expect( + await screen.findByRole('heading', { name: /patients\.newPatient/i }), + ).toBeInTheDocument() + + // TODO: Figure out how to select these in the dom instead of checking the store expect(store.getActions()).toContainEqual( addBreadcrumbs([ { i18nKey: 'patients.label', location: '/patients' }, @@ -60,30 +60,15 @@ describe('/patients/new', () => { ) }) - it('should render the Dashboard if the user does not have write patient privileges', () => { - const wrapper = mount( - <Provider - store={mockStore({ - title: 'test', - user: { user: { id: '123' }, permissions: [] }, - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - } as any)} - > - <MemoryRouter initialEntries={['/patients/new']}> - <TitleProvider> - <HospitalRun /> - </TitleProvider> - </MemoryRouter> - </Provider>, - ) + it('should render the Dashboard if the user does not have write patient privileges', async () => { + setup('/patients/new') - expect(wrapper.find(Dashboard)).toHaveLength(1) + expect(await screen.findByRole('heading', { name: /dashboard\.label/i })).toBeInTheDocument() }) }) describe('/patients/edit/:id', () => { - it('should render the edit patient screen when /patients/edit/:id is accessed', () => { + it('should render the edit patient screen when /patients/edit/:id is accessed', async () => { const patient = { id: '123', prefix: 'test', @@ -98,79 +83,36 @@ describe('/patients/edit/:id', () => { .spyOn(patientNameUtil, 'getPatientFullName') .mockReturnValue(`${patient.prefix} ${patient.givenName} ${patient.familyName}`) - const store = mockStore({ - title: 'test', - user: { - user: { id: '123' }, - permissions: [Permissions.WritePatients, Permissions.ReadPatients], - }, - patient: { patient }, - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - } as any) - - const wrapper = mount( - <Provider store={store}> - <MemoryRouter initialEntries={['/patients/edit/123']}> - <TitleProvider> - <HospitalRun /> - </TitleProvider> - </MemoryRouter> - </Provider>, - ) + const { store } = setup('/patients/edit/123', [ + Permissions.WritePatients, + Permissions.ReadPatients, + ]) - expect(wrapper.find(EditPatient)).toHaveLength(1) + expect( + await screen.findByRole('heading', { name: /patients\.editPatient/i }), + ).toBeInTheDocument() - expect(store.getActions()).toContainEqual({ - ...addBreadcrumbs([ + // TODO: Figure out how to select these in the dom instead of checking the store + expect(store.getActions()).toContainEqual( + addBreadcrumbs([ { i18nKey: 'patients.label', location: '/patients' }, { text: 'test test test', location: `/patients/${patient.id}` }, { i18nKey: 'patients.editPatient', location: `/patients/${patient.id}/edit` }, { i18nKey: 'dashboard.label', location: '/' }, ]), - }) + ) }) - it('should render the Dashboard when the user does not have read patient privileges', () => { - const wrapper = mount( - <Provider - store={mockStore({ - title: 'test', - user: { user: { id: '123' }, permissions: [Permissions.WritePatients] }, - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - } as any)} - > - <MemoryRouter initialEntries={['/patients/edit/123']}> - <TitleProvider> - <HospitalRun /> - </TitleProvider> - </MemoryRouter> - </Provider>, - ) + it('should render the Dashboard when the user does not have read patient privileges', async () => { + setup('/patients/edit/123', [Permissions.WritePatients]) - expect(wrapper.find(Dashboard)).toHaveLength(1) + expect(await screen.findByRole('heading', { name: /dashboard\.label/i })).toBeInTheDocument() }) - it('should render the Dashboard when the user does not have write patient privileges', () => { - const wrapper = mount( - <Provider - store={mockStore({ - title: 'test', - user: { user: { id: '123' }, permissions: [Permissions.ReadPatients] }, - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - } as any)} - > - <MemoryRouter initialEntries={['/patients/edit/123']}> - <TitleProvider> - <HospitalRun /> - </TitleProvider> - </MemoryRouter> - </Provider>, - ) + it('should render the Dashboard when the user does not have write patient privileges', async () => { + setup('/patients/edit/123', [Permissions.ReadPatients]) - expect(wrapper.find(Dashboard)).toHaveLength(1) + expect(await screen.findByRole('heading', { name: /dashboard\.label/i })).toBeInTheDocument() }) }) @@ -187,26 +129,11 @@ describe('/patients/:id', () => { jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - const store = mockStore({ - title: 'test', - user: { user: { id: '123' }, permissions: [Permissions.ReadPatients] }, - patient: { patient }, - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - } as any) + const { store } = setup('/patients/123', [Permissions.ReadPatients]) - const wrapper = mount( - <Provider store={store}> - <MemoryRouter initialEntries={['/patients/123']}> - <TitleProvider> - <HospitalRun /> - </TitleProvider> - </MemoryRouter> - </Provider>, - ) - - expect(wrapper.find(ViewPatient)).toHaveLength(1) + expect(await screen.findByRole('heading', { name: /patient\.label/i })).toBeInTheDocument() + // TODO: Figure out how to select these in the dom instead of checking the store expect(store.getActions()).toContainEqual( addBreadcrumbs([ { i18nKey: 'patients.label', location: '/patients' }, @@ -216,24 +143,9 @@ describe('/patients/:id', () => { ) }) - it('should render the Dashboard when the user does not have read patient privileges', () => { - const wrapper = mount( - <Provider - store={mockStore({ - title: 'test', - user: { user: { id: '123' }, permissions: [] }, - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - } as any)} - > - <MemoryRouter initialEntries={['/patients/123']}> - <TitleProvider> - <HospitalRun /> - </TitleProvider> - </MemoryRouter> - </Provider>, - ) + it('should render the Dashboard when the user does not have read patient privileges', async () => { + setup('/patients/123') - expect(wrapper.find(Dashboard)).toHaveLength(1) + expect(await screen.findByRole('heading', { name: /dashboard\.label/i })).toBeInTheDocument() }) }) diff --git a/src/__tests__/patients/allergies/Allergies.test.tsx b/src/__tests__/patients/allergies/Allergies.test.tsx index 39160708e6..99f5d66e06 100644 --- a/src/__tests__/patients/allergies/Allergies.test.tsx +++ b/src/__tests__/patients/allergies/Allergies.test.tsx @@ -1,15 +1,13 @@ -import * as components from '@hospitalrun/components' -import { mount } from 'enzyme' +import { render, screen, within, waitFor, waitForElementToBeRemoved } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import Allergies from '../../../patients/allergies/Allergies' -import AllergiesList from '../../../patients/allergies/AllergiesList' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' import Permissions from '../../../shared/model/Permissions' @@ -26,6 +24,7 @@ const expectedPatient = { ], } as Patient +const newAllergy = 'allergy3' let store: any const setup = async ( @@ -33,66 +32,100 @@ const setup = async ( permissions = [Permissions.AddAllergy], route = '/patients/123/allergies', ) => { + jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) + jest.spyOn(PatientRepository, 'saveOrUpdate') + store = mockStore({ patient: { patient }, user: { permissions } } as any) history.push(route) - let wrapper: any - await act(async () => { - wrapper = await mount( - <Router history={history}> - <Provider store={store}> - <Allergies patient={patient} /> - </Provider> - </Router>, - ) - }) - - return wrapper + return render( + <Router history={history}> + <Provider store={store}> + <Allergies patient={patient} /> + </Provider> + </Router>, + ) } describe('Allergies', () => { beforeEach(() => { jest.resetAllMocks() - jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) - jest.spyOn(PatientRepository, 'saveOrUpdate') }) describe('add new allergy button', () => { - it('should render a button to add new allergies', async () => { - const wrapper = await setup() - - const addAllergyButton = wrapper.find(components.Button) - expect(addAllergyButton).toHaveLength(1) - expect(addAllergyButton.text().trim()).toEqual('patient.allergies.new') + it('should render a button to add new allergies', () => { + setup() + + expect( + screen.getByRole('button', { + name: /patient\.allergies\.new/i, + }), + ).toBeInTheDocument() }) - it('should not render a button to add new allergies if the user does not have permissions', async () => { - const wrapper = await setup(expectedPatient, []) + it('should not render a button to add new allergies if the user does not have permissions', () => { + setup(expectedPatient, []) - const addAllergyButton = wrapper.find(components.Button) - expect(addAllergyButton).toHaveLength(0) + expect( + screen.queryByRole('button', { + name: /patient\.allergies\.new/i, + }), + ).not.toBeInTheDocument() }) + }) - it('should open the New Allergy Modal when clicked', async () => { - const wrapper = await setup() + describe('add new allergy modal ', () => { + it('should open when allergy clicked, close when cancel clicked', async () => { + setup(expectedPatient) - act(() => { - const addAllergyButton = wrapper.find(components.Button) - const onClick = addAllergyButton.prop('onClick') as any - onClick({} as React.MouseEvent<HTMLButtonElement>) - }) + userEvent.click( + screen.getByRole('button', { + name: /patient\.allergies\.new/i, + }), + ) + expect(screen.getByRole('dialog')).toBeInTheDocument() - wrapper.update() + userEvent.click(screen.getByRole('button', { name: /actions\.cancel/i })) + await waitForElementToBeRemoved(() => screen.queryByRole('dialog')) + expect(screen.queryByRole('dialog')).not.toBeInTheDocument() + }) - expect(wrapper.find(components.Modal).prop('show')).toBeTruthy() + it('should add new allergy', async () => { + setup(expectedPatient) + + userEvent.click( + screen.getByRole('button', { + name: /patient\.allergies\.new/i, + }), + ) + userEvent.type( + screen.getByRole('textbox', { + name: /this is a required input/i, + }), + newAllergy, + ) + userEvent.click( + within(screen.getByRole('dialog')).getByRole('button', { + name: /patient\.allergies\.new/i, + }), + ) + + await waitForElementToBeRemoved(() => screen.queryByRole('dialog')) + expect(screen.getByRole('button', { name: newAllergy })).toBeInTheDocument() }) }) describe('allergy list', () => { it('should render allergies', async () => { - const wrapper = await setup() - - expect(wrapper.exists(AllergiesList)).toBeTruthy() + setup() + + await waitFor(() => { + expect( + screen.getAllByRole('button', { + name: /allergy/i, + }), + ).toHaveLength(2) + }) }) }) }) diff --git a/src/__tests__/patients/allergies/AllergiesList.test.tsx b/src/__tests__/patients/allergies/AllergiesList.test.tsx index 3e02da17a0..23d6afe140 100644 --- a/src/__tests__/patients/allergies/AllergiesList.test.tsx +++ b/src/__tests__/patients/allergies/AllergiesList.test.tsx @@ -1,8 +1,7 @@ -import { Alert, List, ListItem } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Router } from 'react-router-dom' import AllergiesList from '../../../patients/allergies/AllergiesList' @@ -11,57 +10,47 @@ import Allergy from '../../../shared/model/Allergy' import Patient from '../../../shared/model/Patient' describe('Allergies list', () => { - const setup = async (allergies: Allergy[]) => { + const setup = (allergies: Allergy[]) => { const mockPatient = { id: '123', allergies } as Patient jest.spyOn(PatientRepository, 'find').mockResolvedValueOnce(mockPatient) const history = createMemoryHistory() history.push(`/patients/${mockPatient.id}/allergies`) - let wrapper: any - await act(async () => { - wrapper = await mount( + + return { + history, + ...render( <Router history={history}> <AllergiesList patientId={mockPatient.id} /> </Router>, - ) - }) - - wrapper.update() - return { wrapper: wrapper as ReactWrapper, history } + ), + } } it('should render a list of allergies', async () => { const expectedAllergies = [{ id: '456', name: 'some name' }] - const { wrapper } = await setup(expectedAllergies) - const listItems = wrapper.find(ListItem) + setup(expectedAllergies) - expect(wrapper.exists(List)).toBeTruthy() + const listItems = await screen.findAllByRole('button') expect(listItems).toHaveLength(expectedAllergies.length) - expect(listItems.at(0).text()).toEqual(expectedAllergies[0].name) + expect(screen.getByText(expectedAllergies[0].name)).toBeInTheDocument() }) it('should display a warning when no allergies are present', async () => { const expectedAllergies: Allergy[] = [] - const { wrapper } = await setup(expectedAllergies) - - const alert = wrapper.find(Alert) - - expect(wrapper.exists(Alert)).toBeTruthy() - expect(wrapper.exists(List)).toBeFalsy() + setup(expectedAllergies) - expect(alert.prop('color')).toEqual('warning') - expect(alert.prop('title')).toEqual('patient.allergies.warning.noAllergies') - expect(alert.prop('message')).toEqual('patient.allergies.addAllergyAbove') + expect(await screen.findByRole('alert')).toHaveTextContent( + /patient\.allergies\.warning.noAllergies/i, + ) + expect(screen.getByRole('alert')).toHaveTextContent(/patient\.allergies\.addAllergyAbove/i) }) - it('should navigate to the allergy view when the allergy is clicked', async () => { + it('should navigate to the allergy when the allergy is clicked', async () => { const expectedAllergies = [{ id: '456', name: 'some name' }] - const { wrapper, history } = await setup(expectedAllergies) - const item = wrapper.find(ListItem) - act(() => { - const onClick = item.prop('onClick') as any - onClick({ stopPropagation: jest.fn() }) - }) + const { history } = setup(expectedAllergies) + const listItems = await screen.findAllByRole('button') + userEvent.click(listItems[0]) expect(history.location.pathname).toEqual(`/patients/123/allergies/${expectedAllergies[0].id}`) }) }) diff --git a/src/__tests__/patients/allergies/NewAllergyModal.test.tsx b/src/__tests__/patients/allergies/NewAllergyModal.test.tsx index 4ba0e8acf2..5f19b17646 100644 --- a/src/__tests__/patients/allergies/NewAllergyModal.test.tsx +++ b/src/__tests__/patients/allergies/NewAllergyModal.test.tsx @@ -1,14 +1,10 @@ -/* eslint-disable no-console */ - -import { Modal, Alert } from '@hospitalrun/components' -import { mount } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import React from 'react' -import { act } from 'react-dom/test-utils' import NewAllergyModal from '../../../patients/allergies/NewAllergyModal' -import TextInputWithLabelFormGroup from '../../../shared/components/input/TextInputWithLabelFormGroup' -import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' +import { expectOneConsoleError } from '../../test-utils/console.utils' describe('New Allergy Modal', () => { const mockPatient = { @@ -16,95 +12,41 @@ describe('New Allergy Modal', () => { givenName: 'someName', } as Patient - const setup = (onCloseSpy = jest.fn()) => { - jest.spyOn(PatientRepository, 'saveOrUpdate').mockResolvedValue(mockPatient) - jest.spyOn(PatientRepository, 'find').mockResolvedValue(mockPatient) - const wrapper = mount( - <NewAllergyModal patientId={mockPatient.id} show onCloseButtonClick={onCloseSpy} />, - ) - - return { wrapper } - } - - beforeEach(() => { - console.error = jest.fn() - }) + const setup = () => + render(<NewAllergyModal patientId={mockPatient.id} show onCloseButtonClick={jest.fn()} />) it('should render a modal with the correct labels', () => { - const { wrapper } = setup() - - const modal = wrapper.find(Modal) - expect(wrapper.exists(Modal)).toBeTruthy() - expect(modal.prop('title')).toEqual('patient.allergies.new') - expect(modal.prop('closeButton')?.children).toEqual('actions.cancel') - expect(modal.prop('closeButton')?.color).toEqual('danger') - expect(modal.prop('successButton')?.children).toEqual('patient.allergies.new') - expect(modal.prop('successButton')?.color).toEqual('success') - expect(modal.prop('successButton')?.icon).toEqual('add') + setup() + const modal = screen.getByRole('dialog') + const cancelButton = screen.getByRole('button', { name: /actions.cancel/i }) + const successButton = screen.getByRole('button', { name: /patient.allergies.new/i }) + + expect(modal).toBeInTheDocument() + expect(screen.getByText('patient.allergies.new', { selector: 'div' })).toBeInTheDocument() + expect(cancelButton).toBeInTheDocument() + expect(cancelButton).toHaveClass('btn-danger') + expect(successButton).toBeInTheDocument() + expect(successButton).toHaveClass('btn-success') + expect(successButton.children[0]).toHaveAttribute('data-icon', 'plus') }) it('should display errors when there is an error saving', async () => { + const expectedErrorMessage = 'patient.allergies.error.unableToAdd' const expectedError = { - message: 'patient.allergies.error.unableToAdd', nameError: 'patient.allergies.error.nameRequired', } - const { wrapper } = setup() - - await act(async () => { - const modal = wrapper.find(Modal) - const onSave = (modal.prop('successButton') as any).onClick - await onSave({} as React.MouseEvent<HTMLButtonElement>) - }) - wrapper.update() - - const alert = wrapper.find(Alert) - const nameField = wrapper.find(TextInputWithLabelFormGroup) - - expect(alert.prop('title')).toEqual('states.error') - expect(alert.prop('message')).toEqual(expectedError.message) - expect(nameField.prop('isInvalid')).toBeTruthy() - expect(nameField.prop('feedback')).toEqual(expectedError.nameError) - }) - - describe('cancel', () => { - it('should call the onCloseButtonClick function when the close button is clicked', () => { - const onCloseButtonClickSpy = jest.fn() - const { wrapper } = setup(onCloseButtonClickSpy) - act(() => { - const modal = wrapper.find(Modal) - const { onClick } = modal.prop('closeButton') as any - onClick() - }) - - expect(onCloseButtonClickSpy).toHaveBeenCalledTimes(1) - }) - }) - - describe('save', () => { - it('should save the allergy for the given patient', async () => { - const expectedName = 'expected name' - const { wrapper } = setup() - - act(() => { - const input = wrapper.findWhere((c) => c.prop('name') === 'name') - const onChange = input.prop('onChange') - onChange({ target: { value: expectedName } }) - }) - - wrapper.update() - - await act(async () => { - const modal = wrapper.find(Modal) - const onSave = (modal.prop('successButton') as any).onClick - await onSave({} as React.MouseEvent<HTMLButtonElement>) - }) - - expect(PatientRepository.saveOrUpdate).toHaveBeenCalledTimes(1) - expect(PatientRepository.saveOrUpdate).toHaveBeenCalledWith( - expect.objectContaining({ - allergies: [expect.objectContaining({ name: expectedName })], - }), - ) - }) + expectOneConsoleError(expectedError) + setup() + const successButton = screen.getByRole('button', { name: /patient.allergies.new/i }) + const nameField = screen.getByLabelText(/patient.allergies.allergyName/i) + + userEvent.click(successButton) + const alert = await screen.findByRole('alert') + + expect(alert).toBeInTheDocument() + expect(screen.getByText(/states.error/i)).toBeInTheDocument() + expect(screen.getByText(expectedErrorMessage)).toBeInTheDocument() + expect(nameField).toHaveClass('is-invalid') + expect(nameField.nextSibling).toHaveTextContent(expectedError.nameError) }) }) diff --git a/src/__tests__/patients/allergies/ViewAllergy.test.tsx b/src/__tests__/patients/allergies/ViewAllergy.test.tsx index f91a7f10db..84f79e41bd 100644 --- a/src/__tests__/patients/allergies/ViewAllergy.test.tsx +++ b/src/__tests__/patients/allergies/ViewAllergy.test.tsx @@ -1,46 +1,40 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, waitFor } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Route, Router } from 'react-router-dom' import ViewAllergy from '../../../patients/allergies/ViewAllergy' -import TextInputWithLabelFormGroup from '../../../shared/components/input/TextInputWithLabelFormGroup' import PatientRepository from '../../../shared/db/PatientRepository' +import Allergy from '../../../shared/model/Allergy' import Patient from '../../../shared/model/Patient' -describe('View Care Plan', () => { +describe('ViewAllergy', () => { const patient = { id: 'patientId', - allergies: [{ id: '123', name: 'some name' }], + allergies: [{ id: '123', name: 'cats' }], } as Patient const setup = async () => { jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) const history = createMemoryHistory() - history.push(`/patients/${patient.id}/allergies/${patient.allergies![0].id}`) - let wrapper: any - - await act(async () => { - wrapper = await mount( - <Router history={history}> - <Route path="/patients/:id/allergies/:allergyId"> - <ViewAllergy /> - </Route> - </Router>, - ) - }) - - wrapper.update() - - return { wrapper: wrapper as ReactWrapper } + history.push(`/patients/${patient.id}/allergies/${(patient.allergies as Allergy[])[0].id}`) + + return render( + <Router history={history}> + <Route path="/patients/:id/allergies/:allergyId"> + <ViewAllergy /> + </Route> + </Router>, + ) } - it('should render a allergy input with the correct data', async () => { - const { wrapper } = await setup() + it('should render an allergy input with the correct data', async () => { + setup() - const allergyName = wrapper.find(TextInputWithLabelFormGroup) - expect(allergyName).toHaveLength(1) - expect(allergyName.prop('value')).toEqual(patient.allergies![0].name) + await waitFor(() => { + expect( + screen.getByRole('textbox', { name: /patient.allergies.allergyName/i }), + ).toHaveDisplayValue('cats') + }) }) }) diff --git a/src/__tests__/patients/appointments/AppointmentsList.test.tsx b/src/__tests__/patients/appointments/AppointmentsList.test.tsx index e4de312670..849bfe2fb3 100644 --- a/src/__tests__/patients/appointments/AppointmentsList.test.tsx +++ b/src/__tests__/patients/appointments/AppointmentsList.test.tsx @@ -1,9 +1,7 @@ -import * as components from '@hospitalrun/components' -import { Table } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' @@ -41,114 +39,90 @@ const expectedAppointments = [ ] as Appointment[] const mockStore = createMockStore<RootState, any>([thunk]) -const history = createMemoryHistory() -let store: any - -const setup = async (patient = expectedPatient, appointments = expectedAppointments) => { +const setup = (patient = expectedPatient, appointments = expectedAppointments) => { jest.resetAllMocks() jest.spyOn(PatientRepository, 'getAppointments').mockResolvedValue(appointments) - store = mockStore({ patient, appointments: { appointments } } as any) - - let wrapper: any + const store = mockStore({ patient, appointments: { appointments } } as any) + const history = createMemoryHistory() - await act(async () => { - wrapper = await mount( + return { + history, + ...render( <Router history={history}> <Provider store={store}> <AppointmentsList patient={patient} /> </Provider> </Router>, - ) - }) - - wrapper.update() - - return { wrapper: wrapper as ReactWrapper } + ), + } } describe('AppointmentsList', () => { describe('Table', () => { it('should render a list of appointments', async () => { - const { wrapper } = await setup() - - const table = wrapper.find(Table) - - const columns = table.prop('columns') - const actions = table.prop('actions') as any - - expect(table).toHaveLength(1) - - expect(columns[0]).toEqual( - expect.objectContaining({ - label: 'scheduling.appointment.startDate', - key: 'startDateTime', - }), - ) - expect(columns[1]).toEqual( - expect.objectContaining({ label: 'scheduling.appointment.endDate', key: 'endDateTime' }), - ) - expect(columns[2]).toEqual( - expect.objectContaining({ label: 'scheduling.appointment.location', key: 'location' }), - ) - expect(columns[3]).toEqual( - expect.objectContaining({ label: 'scheduling.appointment.type', key: 'type' }), - ) - - expect(actions[0]).toEqual(expect.objectContaining({ label: 'actions.view' })) - expect(table.prop('actionsHeaderText')).toEqual('actions.label') + setup() + await waitFor(() => { + expect(screen.getByRole('table')).toBeInTheDocument() + }) + const header = screen.getAllByRole('columnheader') + + expect(header[0]).toHaveTextContent(/scheduling.appointment.startDate/i) + expect(header[1]).toHaveTextContent(/scheduling.appointment.endDate/i) + expect(header[2]).toHaveTextContent(/scheduling.appointment.location/i) + expect(header[3]).toHaveTextContent(/scheduling.appointment.type/i) + expect(header[4]).toHaveTextContent(/actions.label/i) + expect(screen.getAllByRole('button', { name: /actions.view/i })[0]).toBeInTheDocument() }) it('should navigate to appointment profile on appointment click', async () => { - const { wrapper } = await setup() - const tr = wrapper.find('tr').at(1) + const { history } = setup() - act(() => { - const onClick = tr.find('button').at(0).prop('onClick') as any - onClick({ stopPropagation: jest.fn() }) - }) + userEvent.click((await screen.findAllByRole('button', { name: /actions.view/i }))[0]) - expect(history.location.pathname).toEqual('/appointments/456') + await waitFor(() => { + expect(history.location.pathname).toEqual('/appointments/456') + }) }) }) describe('Empty list', () => { it('should render a warning message if there are no appointments', async () => { - const { wrapper } = await setup(expectedPatient, []) - const alert = wrapper.find(components.Alert) + setup(expectedPatient, []) + + const alert = await screen.findByRole('alert') - expect(alert).toHaveLength(1) - expect(alert.prop('title')).toEqual('patient.appointments.warning.noAppointments') - expect(alert.prop('message')).toEqual('patient.appointments.addAppointmentAbove') + expect(alert).toBeInTheDocument() + expect(screen.getByText(/patient.appointments.warning.noAppointments/i)).toBeInTheDocument() + expect(screen.getByText(/patient.appointments.addAppointmentAbove/i)).toBeInTheDocument() }) }) describe('New appointment button', () => { it('should render a new appointment button if there is an appointment', async () => { - const { wrapper } = await setup() + setup() - const addNewAppointmentButton = wrapper.find(components.Button).at(0) - expect(addNewAppointmentButton).toHaveLength(1) - expect(addNewAppointmentButton.text().trim()).toEqual('scheduling.appointments.new') + expect( + await screen.findByRole('button', { name: /scheduling.appointments.new/i }), + ).toBeInTheDocument() }) it('should render a new appointment button if there are no appointments', async () => { - const { wrapper } = await setup(expectedPatient, []) + setup(expectedPatient, []) - const addNewAppointmentButton = wrapper.find(components.Button).at(0) - expect(addNewAppointmentButton).toHaveLength(1) - expect(addNewAppointmentButton.text().trim()).toEqual('scheduling.appointments.new') + expect( + await screen.findByRole('button', { name: /scheduling.appointments.new/i }), + ).toBeInTheDocument() }) it('should navigate to new appointment page', async () => { - const { wrapper } = await setup() + const { history } = setup() - await act(async () => { - await wrapper.find(components.Button).at(0).simulate('click') - }) - wrapper.update() + userEvent.click(await screen.findByRole('button', { name: /scheduling.appointments.new/i })) - expect(history.location.pathname).toEqual('/appointments/new') + await waitFor(() => { + expect(history.location.pathname).toEqual('/appointments/new') + }) }) }) }) diff --git a/src/__tests__/patients/care-goals/AddCareGoalModal.test.tsx b/src/__tests__/patients/care-goals/AddCareGoalModal.test.tsx index b8b23551b0..32cf77a205 100644 --- a/src/__tests__/patients/care-goals/AddCareGoalModal.test.tsx +++ b/src/__tests__/patients/care-goals/AddCareGoalModal.test.tsx @@ -1,14 +1,10 @@ -import { Modal } from '@hospitalrun/components' -import { mount } from 'enzyme' +import { render, screen } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Router } from 'react-router-dom' import AddCareGoalModal from '../../../patients/care-goals/AddCareGoalModal' -import CareGoalForm from '../../../patients/care-goals/CareGoalForm' -import PatientRepository from '../../../shared/db/PatientRepository' -import CareGoal, { CareGoalStatus, CareGoalAchievementStatus } from '../../../shared/model/CareGoal' +import CareGoal from '../../../shared/model/CareGoal' import Patient from '../../../shared/model/Patient' describe('Add Care Goal Modal', () => { @@ -18,84 +14,29 @@ describe('Add Care Goal Modal', () => { careGoals: [] as CareGoal[], } as Patient - const onCloseSpy = jest.fn() const setup = () => { - jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - jest.spyOn(PatientRepository, 'saveOrUpdate') const history = createMemoryHistory() - const wrapper = mount( + + return render( <Router history={history}> - <AddCareGoalModal patient={patient} show onCloseButtonClick={onCloseSpy} /> + <AddCareGoalModal patient={patient} show onCloseButtonClick={jest.fn()} /> </Router>, ) - - wrapper.update() - return { wrapper } } - beforeEach(() => { - jest.resetAllMocks() - }) - it('should render a modal', () => { - const { wrapper } = setup() + setup() - const modal = wrapper.find(Modal) - const sucessButton = modal.prop('successButton') - const closeButton = modal.prop('closeButton') + expect(screen.getByRole('dialog')).toBeInTheDocument() + expect(screen.getAllByText('patient.careGoal.new')[0]).toBeInTheDocument() - expect(modal).toHaveLength(1) - expect(modal.prop('title')).toEqual('patient.careGoal.new') - expect(sucessButton?.children).toEqual('patient.careGoal.new') - expect(sucessButton?.icon).toEqual('add') - expect(closeButton?.children).toEqual('actions.cancel') + expect(screen.getByRole('button', { name: /patient.careGoal.new/i })).toBeInTheDocument() + expect(screen.getByRole('button', { name: /actions.cancel/i })).toBeInTheDocument() }) it('should render a care goal form', () => { - const { wrapper } = setup() - - const careGoalForm = wrapper.find(CareGoalForm) - expect(careGoalForm).toHaveLength(1) - }) - - it('should save care goal when save button is clicked and close', async () => { - const expectedCreatedDate = new Date() - Date.now = jest.fn().mockReturnValue(expectedCreatedDate) - - const expectedCareGoal = { - id: '123', - description: 'some description', - startDate: new Date().toISOString(), - dueDate: new Date().toISOString(), - note: '', - priority: 'medium', - status: CareGoalStatus.Accepted, - achievementStatus: CareGoalAchievementStatus.InProgress, - createdOn: expectedCreatedDate, - } - - const { wrapper } = setup() - await act(async () => { - const careGoalForm = wrapper.find(CareGoalForm) - const onChange = careGoalForm.prop('onChange') as any - await onChange(expectedCareGoal) - }) - - wrapper.update() - - await act(async () => { - const modal = wrapper.find(Modal) - const sucessButton = modal.prop('successButton') - const onClick = sucessButton?.onClick as any - await onClick() - }) - - expect(PatientRepository.saveOrUpdate).toHaveBeenCalledTimes(1) - expect(PatientRepository.saveOrUpdate).toHaveBeenCalledWith({ - ...patient, - careGoals: [expectedCareGoal], - }) + setup() - expect(onCloseSpy).toHaveBeenCalledTimes(1) + expect(screen.getByLabelText('care-goal-form')).toBeInTheDocument() }) }) diff --git a/src/__tests__/patients/care-goals/CareGoalForm.test.tsx b/src/__tests__/patients/care-goals/CareGoalForm.test.tsx index 945cb6d419..3cc5c3f2c2 100644 --- a/src/__tests__/patients/care-goals/CareGoalForm.test.tsx +++ b/src/__tests__/patients/care-goals/CareGoalForm.test.tsx @@ -1,151 +1,150 @@ -import { Alert } from '@hospitalrun/components' -import addDays from 'date-fns/addDays' +import { render, screen, within } from '@testing-library/react' +import userEvent, { specialChars } from '@testing-library/user-event' import addMonths from 'date-fns/addMonths' -import { mount } from 'enzyme' +import format from 'date-fns/format' import React from 'react' -import { act } from 'react-dom/test-utils' import CareGoalForm from '../../../patients/care-goals/CareGoalForm' import CareGoal, { CareGoalStatus, CareGoalAchievementStatus } from '../../../shared/model/CareGoal' -describe('Care Goal Form', () => { +const { arrowDown, enter } = specialChars + +const startDate = new Date() +const dueDate = addMonths(new Date(), 1) +const careGoal = { + description: 'some description', + startDate: startDate.toISOString(), + dueDate: dueDate.toISOString(), + note: '', + priority: 'medium', + status: CareGoalStatus.Accepted, + achievementStatus: CareGoalAchievementStatus.InProgress, +} as CareGoal + +const setup = (disabled = false, initializeCareGoal = true, error?: any) => { const onCareGoalChangeSpy = jest.fn() - const careGoal = { - description: 'some description', - startDate: new Date().toISOString(), - dueDate: addMonths(new Date(), 1).toISOString(), - note: '', - priority: 'medium', - status: CareGoalStatus.Accepted, - achievementStatus: CareGoalAchievementStatus.InProgress, - } as CareGoal - - const setup = (disabled = false, initializeCareGoal = true, error?: any) => { - const wrapper = mount( + + const TestComponent = () => { + const [careGoal2, setCareGoal] = React.useState(initializeCareGoal ? careGoal : {}) + + onCareGoalChangeSpy.mockImplementation(setCareGoal) + + return ( <CareGoalForm - careGoal={initializeCareGoal ? careGoal : {}} + careGoal={careGoal2} disabled={disabled} careGoalError={error} onChange={onCareGoalChangeSpy} - />, + /> ) - - return wrapper } + return { ...render(<TestComponent />), onCareGoalChangeSpy } +} + +describe('Care Goal Form', () => { it('should render a description input', () => { - const wrapper = setup() + setup() + + const descriptionInput = screen.getByLabelText(/patient.careGoal.description/i) - const descriptionInput = wrapper.findWhere((w) => w.prop('name') === 'description') + expect(descriptionInput).toBeInTheDocument() + expect(descriptionInput).toHaveValue(careGoal.description) - expect(descriptionInput).toHaveLength(1) - expect(descriptionInput.prop('label')).toEqual('patient.careGoal.description') - expect(descriptionInput.prop('isRequired')).toBeTruthy() - expect(descriptionInput.prop('value')).toBe(careGoal.description) + // TODO: not using built-in form accessibility features yet: required attribute + // expect((descriptionInput as HTMLInputElement).required).toBeTruthy() }) - it('should call onChange handler when description changes', () => { + it('should call onChange handler when description changes', async () => { const expectedDescription = 'some new description' - const wrapper = setup(false, false) + const { onCareGoalChangeSpy } = setup(false, false) - act(() => { - const descriptionInput = wrapper.findWhere((w) => w.prop('name') === 'description') - const onChange = descriptionInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedDescription } }) - }) + const descriptionInput = screen.getByLabelText(/patient\.careGoal\.description/i) + + userEvent.type(descriptionInput, `${expectedDescription}`) - expect(onCareGoalChangeSpy).toHaveBeenCalledWith({ description: expectedDescription }) + expect(descriptionInput).toBeEnabled() + expect(descriptionInput).toBeInTheDocument() + expect(onCareGoalChangeSpy).toHaveBeenCalledTimes(expectedDescription.length) + expect(descriptionInput).toHaveDisplayValue(expectedDescription) }) it('should render a priority selector', () => { - const wrapper = setup() - - const priority = wrapper.findWhere((w) => w.prop('name') === 'priority') - - expect(priority).toHaveLength(1) - expect(priority.prop('label')).toEqual('patient.careGoal.priority.label') - expect(priority.prop('isRequired')).toBeTruthy() - expect(priority.prop('defaultSelected')[0].value).toBe(careGoal.priority) - expect(priority.prop('options')).toEqual([ - { - label: 'patient.careGoal.priority.low', - value: 'low', - }, - { - label: 'patient.careGoal.priority.medium', - value: 'medium', - }, - { - label: 'patient.careGoal.priority.high', - value: 'high', - }, - ]) + setup() + + expect(screen.getByText(/patient.careGoal.priority.label/i)).toBeInTheDocument() + const priority = within(screen.getByTestId('prioritySelect')).getByRole('combobox') + expect(priority).toBeInTheDocument() + + userEvent.click(priority) // display popup with the options + expect(screen.getByText(/patient.careGoal.priority.low/i)).toBeInTheDocument() + expect(screen.getByText(/patient.careGoal.priority.medium/i)).toBeInTheDocument() + expect(screen.getByText(/patient.careGoal.priority.high/i)).toBeInTheDocument() + + userEvent.click(screen.getByText(/patient.careGoal.priority.low/i)) + expect(priority).toHaveValue('patient.careGoal.priority.low') + expect(screen.queryByText(/patient.careGoal.priority.medium/i)).not.toBeInTheDocument() }) it('should call onChange handler when priority changes', () => { const expectedPriority = 'high' - const wrapper = setup(false, false) + const { onCareGoalChangeSpy } = setup(false, false) - act(() => { - const prioritySelector = wrapper.findWhere((w) => w.prop('name') === 'priority') - const onChange = prioritySelector.prop('onChange') as any - onChange([expectedPriority]) - }) + const priority = within(screen.getByTestId('prioritySelect')).getByRole('combobox') + userEvent.type(priority, `${expectedPriority}${arrowDown}${enter}`) + expect(priority).toHaveDisplayValue([`patient.careGoal.priority.${expectedPriority}`]) + expect(onCareGoalChangeSpy).toHaveBeenCalledTimes(1) expect(onCareGoalChangeSpy).toHaveBeenCalledWith({ priority: expectedPriority }) }) it('should render a status selector', () => { - const wrapper = setup() + setup() + + expect(screen.getByText(/patient.careGoal.status/i)).toBeInTheDocument() - const status = wrapper.findWhere((w) => w.prop('name') === 'status') + const status = within(screen.getByTestId('statusSelect')).getByRole('combobox') + expect(status).toBeInTheDocument() + expect(status).toHaveValue(careGoal.status) - expect(status).toHaveLength(1) - expect(status.prop('label')).toEqual('patient.careGoal.status') - expect(status.prop('isRequired')).toBeTruthy() - expect(status.prop('defaultSelected')[0].value).toBe(careGoal.status) - expect(status.prop('options')).toEqual( - Object.values(CareGoalStatus).map((v) => ({ label: v, value: v })), + userEvent.click(status) // display popup with the options + Object.values(CareGoalStatus).forEach((value) => + expect(screen.getByText(value)).toBeInTheDocument(), ) + + userEvent.click(screen.getByText(CareGoalStatus.Proposed)) + expect(status).toHaveValue(CareGoalStatus.Proposed) + expect(screen.queryByText(CareGoalStatus.Accepted)).not.toBeInTheDocument() }) it('should call onChange handler when status changes', () => { const expectedStatus = CareGoalStatus.OnHold - const wrapper = setup(false, false) + const { onCareGoalChangeSpy } = setup(false, false) - act(() => { - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - const onChange = statusSelector.prop('onChange') as any - onChange([expectedStatus]) - }) + const status = within(screen.getByTestId('statusSelect')).getByRole('combobox') + userEvent.type(status, `${expectedStatus}${arrowDown}${enter}`) expect(onCareGoalChangeSpy).toHaveBeenCalledWith({ status: expectedStatus }) }) it('should render the achievement status selector', () => { - const wrapper = setup() - - const achievementStatus = wrapper.findWhere((w) => w.prop('name') === 'achievementStatus') - expect(achievementStatus).toHaveLength(1) - expect(achievementStatus.prop('label')).toEqual('patient.careGoal.achievementStatus') - expect(achievementStatus.prop('isRequired')).toBeTruthy() - expect(achievementStatus.prop('defaultSelected')[0].value).toBe(careGoal.achievementStatus) - expect(achievementStatus.prop('options')).toEqual( - Object.values(CareGoalAchievementStatus).map((v) => ({ label: v, value: v })), + setup() + + expect(screen.getByText(/patient.careGoal.achievementStatus/i)).toBeInTheDocument() + + const achievementStatus = within(screen.getByTestId('achievementStatusSelect')).getByRole( + 'combobox', ) + expect(achievementStatus).toBeInTheDocument() + expect(achievementStatus).toHaveValue(careGoal.achievementStatus) }) it('should call onChange handler when achievement status change', () => { const expectedAchievementStatus = CareGoalAchievementStatus.Improving - const wrapper = setup(false, false) - - act(() => { - const achievementStatusSelector = wrapper.findWhere( - (w) => w.prop('name') === 'achievementStatus', - ) - const onChange = achievementStatusSelector.prop('onChange') as any - onChange([expectedAchievementStatus]) - }) + const { onCareGoalChangeSpy } = setup(false, false) + + const status = within(screen.getByTestId('achievementStatusSelect')).getByRole('combobox') + userEvent.type(status, `${expectedAchievementStatus}${arrowDown}${enter}`) expect(onCareGoalChangeSpy).toHaveBeenCalledWith({ achievementStatus: expectedAchievementStatus, @@ -153,95 +152,90 @@ describe('Care Goal Form', () => { }) it('should render a start date picker', () => { - const wrapper = setup() + setup() - const startDatePicker = wrapper.findWhere((w) => w.prop('name') === 'startDate') - expect(startDatePicker).toHaveLength(1) - expect(startDatePicker.prop('label')).toEqual('patient.careGoal.startDate') - expect(startDatePicker.prop('isRequired')).toBeTruthy() - expect(startDatePicker.prop('value')).toEqual(new Date(careGoal.startDate)) + expect(screen.getByText(/patient.careGoal.startDate/i)).toBeInTheDocument() + const startDatePicker = within(screen.getByTestId('startDateDatePicker')).getByRole('textbox') + expect(startDatePicker).toBeInTheDocument() + expect(startDatePicker).toHaveValue(format(startDate, 'MM/dd/y')) }) it('should call onChange handler when start date change', () => { - const expectedStartDate = addDays(1, new Date().getDate()) - const wrapper = setup(false, false) - - act(() => { - const startDatePicker = wrapper.findWhere((w) => w.prop('name') === 'startDate') - const onChange = startDatePicker.prop('onChange') as any - onChange(expectedStartDate) - }) + const { onCareGoalChangeSpy } = setup() + const expectedDate = '12/31/2050' - expect(onCareGoalChangeSpy).toHaveBeenCalledWith({ startDate: expectedStartDate.toISOString() }) + const startDatePicker = within(screen.getByTestId('startDateDatePicker')).getByRole('textbox') + userEvent.type(startDatePicker, `{selectall}${expectedDate}{enter}`) + expect(onCareGoalChangeSpy).toHaveBeenCalled() + expect(startDatePicker).toHaveDisplayValue(expectedDate) }) it('should render a due date picker', () => { - const wrapper = setup() + setup() - const dueDatePicker = wrapper.findWhere((w) => w.prop('name') === 'dueDate') - expect(dueDatePicker).toHaveLength(1) - expect(dueDatePicker.prop('label')).toEqual('patient.careGoal.dueDate') - expect(dueDatePicker.prop('isRequired')).toBeTruthy() - expect(dueDatePicker.prop('value')).toEqual(new Date(careGoal.dueDate)) + expect(screen.getByText(/patient.careGoal.dueDate/i)).toBeInTheDocument() + const dueDatePicker = within(screen.getByTestId('dueDateDatePicker')).getByRole('textbox') + expect(dueDatePicker).toBeInTheDocument() + expect(dueDatePicker).toHaveValue(format(dueDate, 'MM/dd/y')) }) - it('should call onChange handler when due date change', () => { - const expectedDueDate = addDays(31, new Date().getDate()) - const wrapper = setup(false, false) - - act(() => { - const dueDatePicker = wrapper.findWhere((w) => w.prop('name') === 'dueDate') - const onChange = dueDatePicker.prop('onChange') as any - onChange(expectedDueDate) - }) + it('should call onChange handler when due date changes', () => { + const { onCareGoalChangeSpy } = setup() + const expectedDate = '12/31/2050' - expect(onCareGoalChangeSpy).toHaveBeenCalledWith({ dueDate: expectedDueDate.toISOString() }) + const dueDatePicker = within(screen.getByTestId('dueDateDatePicker')).getByRole('textbox') + userEvent.type(dueDatePicker, `{selectall}${expectedDate}{enter}`) + expect(onCareGoalChangeSpy).toHaveBeenCalled() + expect(dueDatePicker).toHaveDisplayValue(expectedDate) }) it('should render a note input', () => { - const wrapper = setup() + setup() - const noteInput = wrapper.findWhere((w) => w.prop('name') === 'note') - expect(noteInput).toHaveLength(1) - expect(noteInput.prop('label')).toEqual('patient.careGoal.note') - expect(noteInput.prop('isRequired')).toBeFalsy() - expect(noteInput.prop('value')).toEqual(careGoal.note) + expect(screen.getByText(/patient.careGoal.note/i)).toBeInTheDocument() + const noteInput = screen.getByRole('textbox', { + name: /patient\.caregoal\.note/i, + }) + expect(noteInput).toHaveDisplayValue(careGoal.note) }) it('should call onChange handler when note change', () => { const expectedNote = 'some new note' - const wrapper = setup(false, false) - - act(() => { - const noteInput = wrapper.findWhere((w) => w.prop('name') === 'note') - const onChange = noteInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedNote } }) + const { onCareGoalChangeSpy } = setup(false, false) + const noteInput = screen.getByRole('textbox', { + name: /patient\.caregoal\.note/i, }) + userEvent.type(noteInput, expectedNote) + expect(noteInput).toHaveDisplayValue(expectedNote) - expect(onCareGoalChangeSpy).toHaveBeenCalledWith({ note: expectedNote }) + expect(onCareGoalChangeSpy).toHaveBeenCalledTimes(expectedNote.length) }) it('should render all the forms fields disabled if the form is disabled', () => { - const wrapper = setup(true) - - const description = wrapper.findWhere((w) => w.prop('name') === 'description') - const priority = wrapper.findWhere((w) => w.prop('name') === 'priority') - const status = wrapper.findWhere((w) => w.prop('name') === 'status') - const achievementStatus = wrapper.findWhere((w) => w.prop('name') === 'achievementStatus') - const startDate = wrapper.findWhere((w) => w.prop('name') === 'startDate') - const dueDate = wrapper.findWhere((w) => w.prop('name') === 'dueDate') - const note = wrapper.findWhere((w) => w.prop('name') === 'note') - - expect(description.prop('isEditable')).toBeFalsy() - expect(priority.prop('isEditable')).toBeFalsy() - expect(status.prop('isEditable')).toBeFalsy() - expect(achievementStatus.prop('isEditable')).toBeFalsy() - expect(startDate.prop('isEditable')).toBeFalsy() - expect(dueDate.prop('isEditable')).toBeFalsy() - expect(note.prop('isEditable')).toBeFalsy() + setup(true) + + const descriptionInput = screen.getByLabelText(/patient\.careGoal\.description/i) + const priority = within(screen.getByTestId('prioritySelect')).getByRole('combobox') + const status = within(screen.getByTestId('statusSelect')).getByRole('combobox') + const achievementStatus = within(screen.getByTestId('achievementStatusSelect')).getByRole( + 'combobox', + ) + const startDatePicker = within(screen.getByTestId('startDateDatePicker')).getByRole('textbox') + const dueDatePicker = within(screen.getByTestId('dueDateDatePicker')).getByRole('textbox') + const noteInput = screen.getByRole('textbox', { + name: /patient\.caregoal\.note/i, + }) + + expect(descriptionInput).toBeDisabled() + expect(priority).toBeDisabled() + expect(status).toBeDisabled() + expect(achievementStatus).toBeDisabled() + expect(startDatePicker).toBeDisabled() + expect(dueDatePicker).toBeDisabled() + expect(noteInput).toBeDisabled() }) - it('should render the forms field in an error state', () => { + it('should render the forms field in an error state', async () => { const expectedError = { message: 'some error message', description: 'some description error', @@ -253,41 +247,41 @@ describe('Care Goal Form', () => { note: 'some note error', } - const wrapper = setup(false, false, expectedError) + setup(false, false, expectedError) - const alert = wrapper.find(Alert) - const descriptionInput = wrapper.findWhere((w) => w.prop('name') === 'description') - const prioritySelector = wrapper.findWhere((w) => w.prop('name') === 'priority') - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - const achievementStatusSelector = wrapper.findWhere( - (w) => w.prop('name') === 'achievementStatus', + const alert = await screen.findByRole('alert') + const descriptionInput = screen.getByRole('textbox', { + name: /this is a required input/i, + }) + const priority = within(screen.getByTestId('prioritySelect')).getByRole('combobox') + const status = within(screen.getByTestId('statusSelect')).getByRole('combobox') + const achievementStatus = within(screen.getByTestId('achievementStatusSelect')).getByRole( + 'combobox', ) - const startDatePicker = wrapper.findWhere((w) => w.prop('name') === 'startDate') - const dueDatePicker = wrapper.findWhere((w) => w.prop('name') === 'dueDate') - const noteInput = wrapper.findWhere((w) => w.prop('name') === 'note') - - expect(alert).toHaveLength(1) - expect(alert.prop('message')).toEqual(expectedError.message) - - expect(descriptionInput.prop('isInvalid')).toBeTruthy() - expect(descriptionInput.prop('feedback')).toEqual(expectedError.description) - - expect(prioritySelector.prop('isInvalid')).toBeTruthy() - // expect(prioritySelector.prop('feedback')).toEqual(expectedError.priority) - - expect(statusSelector.prop('isInvalid')).toBeTruthy() - // expect(statusSelector.prop('feedback')).toEqual(expectedError.status) - - expect(achievementStatusSelector.prop('isInvalid')).toBeTruthy() - // expect(achievementStatusSelector.prop('feedback')).toEqual(expectedError.achievementStatus) - - expect(startDatePicker.prop('isInvalid')).toBeTruthy() - expect(startDatePicker.prop('feedback')).toEqual(expectedError.startDate) - - expect(dueDatePicker.prop('isInvalid')).toBeTruthy() - expect(dueDatePicker.prop('feedback')).toEqual(expectedError.dueDate) + const startDatePicker = within(screen.getByTestId('startDateDatePicker')).getByRole('textbox') + const dueDatePicker = within(screen.getByTestId('dueDateDatePicker')).getByRole('textbox') + const noteInput = screen.getByRole('textbox', { + name: /patient\.caregoal\.note/i, + }) - expect(noteInput.prop('isInvalid')).toBeTruthy() - expect(noteInput.prop('feedback')).toEqual(expectedError.note) + expect(screen.getByText(/some description error/i)).toBeInTheDocument() + expect(screen.getByText(/some start date error/i)).toBeInTheDocument() + expect(screen.getByText(/some due date error/i)).toBeInTheDocument() + + expect(alert).toHaveTextContent(expectedError.message) + expect(descriptionInput).toBeInTheDocument() + expect(priority).toBeInTheDocument() + expect(status).toBeInTheDocument() + expect(achievementStatus).toBeInTheDocument() + expect(startDatePicker).toBeInTheDocument() + expect(dueDatePicker).toBeInTheDocument() + expect(noteInput).toBeInTheDocument() + + // TODO: not using built-in form accessibility features yet: HTMLInputElement.setCustomValidity() + // expect((descriptionInput as HTMLInputElement).validity.valid).toBe(false) + expect(descriptionInput).toHaveClass('is-invalid') + expect(priority).toHaveClass('is-invalid') + expect(status).toHaveClass('is-invalid') + expect(achievementStatus).toHaveClass('is-invalid') }) }) diff --git a/src/__tests__/patients/care-goals/CareGoalTab.test.tsx b/src/__tests__/patients/care-goals/CareGoalTab.test.tsx index 2ce1f2e2ae..ec49489bb8 100644 --- a/src/__tests__/patients/care-goals/CareGoalTab.test.tsx +++ b/src/__tests__/patients/care-goals/CareGoalTab.test.tsx @@ -1,113 +1,145 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, waitFor, waitForElementToBeRemoved, within } from '@testing-library/react' +import userEvent, { specialChars } from '@testing-library/user-event' +import format from 'date-fns/format' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router, Route } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' -import AddCareGoalModal from '../../../patients/care-goals/AddCareGoalModal' import CareGoalTab from '../../../patients/care-goals/CareGoalTab' -import CareGoalTable from '../../../patients/care-goals/CareGoalTable' -import ViewCareGoal from '../../../patients/care-goals/ViewCareGoal' import PatientRepository from '../../../shared/db/PatientRepository' +import CareGoal, { CareGoalStatus } from '../../../shared/model/CareGoal' import Patient from '../../../shared/model/Patient' import Permissions from '../../../shared/model/Permissions' import { RootState } from '../../../shared/store' const mockStore = createMockStore<RootState, any>([thunk]) +const { selectAll, arrowDown, enter } = specialChars + +const setup = ( + route: string, + permissions: Permissions[], + wrapper = 'tab', + includeCareGoal = true, +) => { + const expectedCareGoal = { + id: '456', + status: 'accepted', + startDate: new Date().toISOString(), + dueDate: new Date().toISOString(), + achievementStatus: 'improving', + priority: 'high', + description: 'test description', + createdOn: new Date().toISOString(), + note: '', + } as CareGoal + const expectedPatient = { + id: '123', + careGoals: includeCareGoal ? [expectedCareGoal] : [], + } as Patient + + jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) + const history = createMemoryHistory({ initialEntries: [route] }) + const store = mockStore({ user: { permissions } } as any) + const path = + wrapper === 'tab' + ? '/patients/:id' + : wrapper === 'view' + ? '/patients/:id/care-goals/:careGoalId' + : '' + + return render( + <Provider store={store}> + <Router history={history}> + <Route path={path}> + <CareGoalTab /> + </Route> + </Router> + </Provider>, + ) +} describe('Care Goals Tab', () => { - const patient = { id: 'patientId' } as Patient - - const setup = async (route: string, permissions: Permissions[]) => { - jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - const store = mockStore({ user: { permissions } } as any) - const history = createMemoryHistory() - history.push(route) - - let wrapper: any - await act(async () => { - wrapper = await mount( - <Provider store={store}> - <Router history={history}> - <Route path="/patients/:id/care-goals"> - <CareGoalTab /> - </Route> - </Router> - </Provider>, - ) - }) - wrapper.update() - - return wrapper as ReactWrapper - } - - it('should render add care goal button if user has correct permissions', async () => { - const wrapper = await setup('patients/123/care-goals', [Permissions.AddCareGoal]) - - const addNewButton = wrapper.find('Button').at(0) - expect(addNewButton).toHaveLength(1) - expect(addNewButton.text().trim()).toEqual('patient.careGoal.new') - }) - it('should not render add care goal button if user does not have permissions', async () => { - const wrapper = await setup('patients/123/care-goals', []) - - const addNewButton = wrapper.find('Button') - expect(addNewButton).toHaveLength(0) - }) - - it('should open the add care goal modal on click', async () => { - const wrapper = await setup('patients/123/care-goals', [Permissions.AddCareGoal]) - - await act(async () => { - const addNewButton = wrapper.find('Button').at(0) - const onClick = addNewButton.prop('onClick') as any - onClick() - }) + const { container } = setup('/patients/123/care-goals', []) - wrapper.update() - - const modal = wrapper.find(AddCareGoalModal) - expect(modal.prop('show')).toBeTruthy() + // wait for spinner to disappear + await waitForElementToBeRemoved(container.querySelector('.css-0')) + expect(screen.queryByRole('button', { name: /patient.careGoal.new/i })).not.toBeInTheDocument() }) - it('should close the modal when the close button is clicked', async () => { - const wrapper = await setup('patients/123/care-goals', [Permissions.AddCareGoal]) - - await act(async () => { - const addNewButton = wrapper.find('Button').at(0) - const onClick = addNewButton.prop('onClick') as any - onClick() - }) - - wrapper.update() - - await act(async () => { - const modal = wrapper.find(AddCareGoalModal) - const onClose = modal.prop('onCloseButtonClick') as any - onClose() - }) - - wrapper.update() - - const modal = wrapper.find(AddCareGoalModal) - expect(modal.prop('show')).toBeFalsy() + it('should be able to create a new care goal if user has permissions', async () => { + const expectedCareGoal = { + description: 'some description', + status: CareGoalStatus.Accepted, + startDate: new Date('2020-01-01'), + dueDate: new Date('2020-02-01'), + } + + setup('/patients/123/care-goals', [Permissions.AddCareGoal], 'tab', false) + + userEvent.click(await screen.findByRole('button', { name: /patient.careGoal.new/i })) + + const modal = await screen.findByRole('dialog') + + userEvent.type( + screen.getByLabelText(/patient\.careGoal\.description/i), + expectedCareGoal.description, + ) + userEvent.type( + within(screen.getByTestId('statusSelect')).getByRole('combobox'), + `${selectAll}${expectedCareGoal.status}${arrowDown}${enter}`, + ) + userEvent.type( + within(screen.getByTestId('startDateDatePicker')).getByRole('textbox'), + `${selectAll}${format(expectedCareGoal.startDate, 'MM/dd/yyyy')}${enter}`, + ) + userEvent.type( + within(screen.getByTestId('dueDateDatePicker')).getByRole('textbox'), + `${selectAll}${format(expectedCareGoal.dueDate, 'MM/dd/yyyy')}${enter}`, + ) + + userEvent.click(within(modal).getByRole('button', { name: /patient.careGoal.new/i })) + + await waitFor( + () => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument() + }, + { + timeout: 3000, + }, + ) + + const cells = await screen.findAllByRole('cell') + expect(cells[0]).toHaveTextContent(expectedCareGoal.description) + expect(cells[1]).toHaveTextContent(format(expectedCareGoal.startDate, 'yyyy-MM-dd')) + expect(cells[2]).toHaveTextContent(format(expectedCareGoal.dueDate, 'yyyy-MM-dd')) + expect(cells[3]).toHaveTextContent(expectedCareGoal.status) + }, 30000) + + it('should open and close the modal when the add care goal and close buttons are clicked', async () => { + setup('/patients/123/care-goals', [Permissions.AddCareGoal]) + + userEvent.click(await screen.findByRole('button', { name: /patient.careGoal.new/i })) + + expect(screen.getByRole('dialog')).toBeVisible() + + userEvent.click(screen.getByRole('button', { name: /close/i })) + + expect(screen.getByRole('dialog')).not.toBeVisible() }) - it('should render care goal table when on patients/123/care-goals', async () => { - const wrapper = await setup('patients/123/care-goals', [Permissions.ReadCareGoal]) + it('should render care goal table when on patients/:id/care-goals', async () => { + setup('/patients/123/care-goals', [Permissions.ReadCareGoal]) - const careGoalTable = wrapper.find(CareGoalTable) - expect(careGoalTable).toHaveLength(1) + expect(await screen.findByRole('table')).toBeInTheDocument() }) - it('should render care goal view when on patients/123/care-goals/456', async () => { - const wrapper = await setup('patients/123/care-goals/456', [Permissions.ReadCareGoal]) + it('should render care goal view when on patients/:id/care-goals/:careGoalId', async () => { + setup('/patients/123/care-goals/456', [Permissions.ReadCareGoal], 'view') - const viewCareGoal = wrapper.find(ViewCareGoal) - expect(viewCareGoal).toHaveLength(1) + expect(await screen.findByLabelText('care-goal-form')).toBeInTheDocument() }) }) diff --git a/src/__tests__/patients/care-goals/CareGoalTable.test.tsx b/src/__tests__/patients/care-goals/CareGoalTable.test.tsx index 0917055e96..848d59b4ec 100644 --- a/src/__tests__/patients/care-goals/CareGoalTable.test.tsx +++ b/src/__tests__/patients/care-goals/CareGoalTable.test.tsx @@ -1,8 +1,8 @@ -import { Table, Alert } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import format from 'date-fns/format' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Router } from 'react-router-dom' import CareGoalTable from '../../../patients/care-goals/CareGoalTable' @@ -11,15 +11,16 @@ import CareGoal, { CareGoalStatus, CareGoalAchievementStatus } from '../../../sh import Patient from '../../../shared/model/Patient' describe('Care Goal Table', () => { + const expectedDate = new Date().toISOString() const careGoal: CareGoal = { id: '123', description: 'some description', priority: 'medium', status: CareGoalStatus.Accepted, achievementStatus: CareGoalAchievementStatus.Improving, - startDate: new Date().toISOString(), - dueDate: new Date().toISOString(), - createdOn: new Date().toISOString(), + startDate: expectedDate, + dueDate: expectedDate, + createdOn: expectedDate, note: 'some note', } @@ -30,67 +31,56 @@ describe('Care Goal Table', () => { } as Patient const setup = async (expectedPatient = patient) => { + jest.resetAllMocks() jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) const history = createMemoryHistory() history.push(`/patients/${patient.id}/care-goals/${patient.careGoals[0].id}`) - let wrapper: any - await act(async () => { - wrapper = await mount( + return { + history, + ...render( <Router history={history}> <CareGoalTable patientId={expectedPatient.id} /> </Router>, - ) - }) - wrapper.update() - return { wrapper: wrapper as ReactWrapper, history } + ), + } } it('should render a table', async () => { - const { wrapper } = await setup() + setup() - const table = wrapper.find(Table) - const columns = table.prop('columns') + expect(await screen.findByRole('table')).toBeInTheDocument() - expect(columns[0]).toEqual( - expect.objectContaining({ label: 'patient.careGoal.description', key: 'description' }), - ) - expect(columns[1]).toEqual( - expect.objectContaining({ label: 'patient.careGoal.startDate', key: 'startDate' }), - ) - expect(columns[2]).toEqual( - expect.objectContaining({ label: 'patient.careGoal.dueDate', key: 'dueDate' }), - ) - expect(columns[3]).toEqual( - expect.objectContaining({ label: 'patient.careGoal.status', key: 'status' }), - ) + const columns = screen.getAllByRole('columnheader') + expect(columns[0]).toHaveTextContent(/patient.careGoal.description/i) + expect(columns[1]).toHaveTextContent(/patient.careGoal.startDate/i) + expect(columns[2]).toHaveTextContent(/patient.careGoal.dueDate/i) + expect(columns[3]).toHaveTextContent(/patient.careGoal.status/i) + expect(columns[4]).toHaveTextContent(/actions.label/i) + expect(screen.getByRole('button', { name: /actions.view/i })).toBeInTheDocument() - const actions = table.prop('actions') as any - expect(actions[0]).toEqual(expect.objectContaining({ label: 'actions.view' })) - expect(table.prop('actionsHeaderText')).toEqual('actions.label') - expect(table.prop('data')).toEqual(patient.careGoals) + const dates = screen.getAllByText(format(new Date(expectedDate), 'yyyy-MM-dd')) + expect(screen.getByText(careGoal.description)).toBeInTheDocument() + // startDate and dueDate are both rendered with expectedDate + expect(dates).toHaveLength(2) + expect(screen.getByText(careGoal.status)).toBeInTheDocument() }) it('should navigate to the care goal view when the view details button is clicked', async () => { - const { wrapper, history } = await setup() + const { history } = await setup() - const tr = wrapper.find('tr').at(1) - - act(() => { - const onClick = tr.find('button').prop('onClick') as any - onClick({ stopPropagation: jest.fn() }) - }) + expect(await screen.findByRole('table')).toBeInTheDocument() + userEvent.click(screen.getByRole('button', { name: /actions.view/i })) expect(history.location.pathname).toEqual(`/patients/${patient.id}/care-goals/${careGoal.id}`) }) it('should display a warning if there are no care goals', async () => { - const { wrapper } = await setup({ ...patient, careGoals: [] }) - - expect(wrapper.exists(Alert)).toBeTruthy() - const alert = wrapper.find(Alert) - expect(alert.prop('color')).toEqual('warning') - expect(alert.prop('title')).toEqual('patient.careGoals.warning.noCareGoals') - expect(alert.prop('message')).toEqual('patient.careGoals.warning.addCareGoalAbove') + setup({ ...patient, careGoals: [] }) + const alert = await screen.findByRole('alert') + expect(alert).toBeInTheDocument() + expect(alert).toHaveClass('alert-warning') + expect(screen.getByText(/patient.careGoals.warning.noCareGoals/i)).toBeInTheDocument() + expect(screen.getByText(/patient.careGoals.warning.addCareGoalAbove/i)).toBeInTheDocument() }) }) diff --git a/src/__tests__/patients/care-goals/ViewCareGoal.test.tsx b/src/__tests__/patients/care-goals/ViewCareGoal.test.tsx index 380c4f8f20..692219f5b4 100644 --- a/src/__tests__/patients/care-goals/ViewCareGoal.test.tsx +++ b/src/__tests__/patients/care-goals/ViewCareGoal.test.tsx @@ -1,10 +1,8 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Router, Route } from 'react-router-dom' -import CareGoalForm from '../../../patients/care-goals/CareGoalForm' import ViewCareGoal from '../../../patients/care-goals/ViewCareGoal' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' @@ -17,32 +15,23 @@ describe('View Care Goal', () => { careGoals: [{ id: '123', description: 'some description' }], } as Patient - const setup = async () => { + const setup = () => { jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) const history = createMemoryHistory() history.push(`/patients/${patient.id}/care-goals/${patient.careGoals[0].id}`) - let wrapper: any - await act(async () => { - wrapper = await mount( - <Router history={history}> - <Route path="/patients/:id/care-goals/:careGoalId"> - <ViewCareGoal /> - </Route> - </Router>, - ) - }) - - wrapper.update() - - return { wrapper: wrapper as ReactWrapper } + return render( + <Router history={history}> + <Route path="/patients/:id/care-goals/:careGoalId"> + <ViewCareGoal /> + </Route> + </Router>, + ) } it('should render the care goal form', async () => { - const { wrapper } = await setup() - const careGoalForm = wrapper.find(CareGoalForm) + setup() - expect(careGoalForm).toHaveLength(1) - expect(careGoalForm.prop('careGoal')).toEqual(patient.careGoals[0]) + expect(await screen.findByLabelText('care-goal-form')).toBeInTheDocument() }) }) diff --git a/src/__tests__/patients/care-goals/ViewCareGoals.test.tsx b/src/__tests__/patients/care-goals/ViewCareGoals.test.tsx index a80f7a7671..d7205f7a2a 100644 --- a/src/__tests__/patients/care-goals/ViewCareGoals.test.tsx +++ b/src/__tests__/patients/care-goals/ViewCareGoals.test.tsx @@ -1,42 +1,40 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Route, Router } from 'react-router-dom' -import CareGoalTable from '../../../patients/care-goals/CareGoalTable' import ViewCareGoals from '../../../patients/care-goals/ViewCareGoals' import PatientRepository from '../../../shared/db/PatientRepository' import CareGoal from '../../../shared/model/CareGoal' import Patient from '../../../shared/model/Patient' describe('View Care Goals', () => { - const patient = { id: '123', careGoals: [] as CareGoal[] } as Patient + const careGoal = { + id: 'abc', + status: 'accepted', + startDate: new Date().toISOString(), + dueDate: new Date().toISOString(), + } as CareGoal + const patient = { id: '123', careGoals: [careGoal] as CareGoal[] } as Patient const setup = async () => { + jest.resetAllMocks() jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) const history = createMemoryHistory() history.push(`/patients/${patient.id}/care-goals`) - let wrapper: any - await act(async () => { - wrapper = await mount( - <Router history={history}> - <Route path="/patients/:id/care-goals"> - <ViewCareGoals /> - </Route> - </Router>, - ) - }) - - return { wrapper: wrapper as ReactWrapper } + return render( + <Router history={history}> + <Route path="/patients/:id/care-goals"> + <ViewCareGoals /> + </Route> + </Router>, + ) } - it('should render a care goals table with the patient id', async () => { - const { wrapper } = await setup() + it('should render a care goals table', async () => { + setup() - expect(wrapper.exists(CareGoalTable)).toBeTruthy() - const careGoalTable = wrapper.find(CareGoalTable) - expect(careGoalTable.prop('patientId')).toEqual(patient.id) + expect(await screen.findByRole('table')).toBeInTheDocument() }) }) diff --git a/src/__tests__/patients/care-plans/AddCarePlanModal.test.tsx b/src/__tests__/patients/care-plans/AddCarePlanModal.test.tsx index c5af216eb5..29595620e2 100644 --- a/src/__tests__/patients/care-plans/AddCarePlanModal.test.tsx +++ b/src/__tests__/patients/care-plans/AddCarePlanModal.test.tsx @@ -1,20 +1,22 @@ -import { Modal } from '@hospitalrun/components' -import { mount } from 'enzyme' +import { render, screen, waitFor, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Router } from 'react-router-dom' +import selectEvent from 'react-select-event' import AddCarePlanModal from '../../../patients/care-plans/AddCarePlanModal' -import CarePlanForm from '../../../patients/care-plans/CarePlanForm' import PatientRepository from '../../../shared/db/PatientRepository' -import CarePlan, { CarePlanIntent, CarePlanStatus } from '../../../shared/model/CarePlan' +import CarePlan from '../../../shared/model/CarePlan' import Patient from '../../../shared/model/Patient' describe('Add Care Plan Modal', () => { const patient = { - id: 'patientId', - diagnoses: [{ id: '123', name: 'some name', diagnosisDate: new Date().toISOString() }], + id: '0012', + diagnoses: [ + { id: '123', name: 'too skinny', diagnosisDate: new Date().toISOString() }, + { id: '456', name: 'headaches', diagnosisDate: new Date().toISOString() }, + ], carePlans: [] as CarePlan[], } as Patient @@ -23,14 +25,12 @@ describe('Add Care Plan Modal', () => { jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) jest.spyOn(PatientRepository, 'saveOrUpdate') const history = createMemoryHistory() - const wrapper = mount( + + return render( <Router history={history}> <AddCarePlanModal patient={patient} show onCloseButtonClick={onCloseSpy} /> </Router>, ) - - wrapper.update() - return { wrapper } } beforeEach(() => { @@ -38,79 +38,63 @@ describe('Add Care Plan Modal', () => { }) it('should render a modal', () => { - const { wrapper } = setup() - - const modal = wrapper.find(Modal) + setup() + expect(screen.getByRole('dialog')).toBeInTheDocument() + const title = screen.getByText(/patient\.carePlan\.new/i, { selector: 'div' }) + expect(title).toBeInTheDocument() - expect(modal).toHaveLength(1) - - const successButton = modal.prop('successButton') - const cancelButton = modal.prop('closeButton') - expect(modal.prop('title')).toEqual('patient.carePlan.new') - expect(successButton?.children).toEqual('patient.carePlan.new') - expect(successButton?.icon).toEqual('add') - expect(cancelButton?.children).toEqual('actions.cancel') + expect(screen.getByRole('button', { name: /patient\.carePlan\.new/i })).toBeInTheDocument() + expect(screen.getByRole('button', { name: /actions.cancel/i })).toBeInTheDocument() }) it('should render the care plan form', () => { - const { wrapper } = setup() + setup() + expect(screen.getByRole('form')).toBeInTheDocument() + }) - const carePlanForm = wrapper.find(CarePlanForm) - expect(carePlanForm).toHaveLength(1) - expect(carePlanForm.prop('patient')).toEqual(patient) + it('should call the on close function when the cancel button is clicked', async () => { + setup() + userEvent.click( + screen.getByRole('button', { + name: /close/i, + }), + ) + + expect(onCloseSpy).toHaveBeenCalledTimes(1) }) it('should save care plan when the save button is clicked and close', async () => { - const expectedCreatedDate = new Date() - Date.now = jest.fn().mockReturnValue(expectedCreatedDate) const expectedCarePlan = { - id: '123', - title: 'some title', - description: 'some description', - diagnosisId: '123', - startDate: new Date().toISOString(), - endDate: new Date().toISOString(), - status: CarePlanStatus.Active, - intent: CarePlanIntent.Proposal, - createdOn: expectedCreatedDate, + title: 'Feed Harry Potter', + description: 'Get Dobby to feed Harry Potter', + diagnosisId: '123', // condition } - const { wrapper } = setup() - await act(async () => { - const carePlanForm = wrapper.find(CarePlanForm) - const onChange = carePlanForm.prop('onChange') as any - await onChange(expectedCarePlan) - }) - wrapper.update() - - await act(async () => { - const modal = wrapper.find(Modal) - const successButton = modal.prop('successButton') - const onClick = successButton?.onClick as any - await onClick() - }) + setup() - expect(PatientRepository.saveOrUpdate).toHaveBeenCalledTimes(1) - expect(PatientRepository.saveOrUpdate).toHaveBeenCalledWith({ - ...patient, - carePlans: [expectedCarePlan], - }) - expect(onCloseSpy).toHaveBeenCalledTimes(1) - }) + const condition = within(screen.getByTestId('conditionSelect')).getByRole('combobox') + await selectEvent.select(condition, `too skinny`) - it('should call the on close function when the cancel button is clicked', () => { - const { wrapper } = setup() + const title = screen.getByLabelText(/patient\.careplan\.title/i) + const description = screen.getByLabelText(/patient\.careplan\.description/i) - const modal = wrapper.find(Modal) + userEvent.type(title, expectedCarePlan.title) + userEvent.type(description, expectedCarePlan.description) - expect(modal).toHaveLength(1) + userEvent.click( + within(screen.getByRole('dialog')).getByRole('button', { + name: /patient\.carePlan\.new/i, + }), + ) - act(() => { - const cancelButton = modal.prop('closeButton') - const onClick = cancelButton?.onClick as any - onClick() + await waitFor(() => { + expect(PatientRepository.saveOrUpdate).toHaveBeenCalled() }) - expect(onCloseSpy).toHaveBeenCalledTimes(1) - }) + expect(PatientRepository.saveOrUpdate).toHaveBeenCalledWith( + expect.objectContaining({ + carePlans: expect.arrayContaining([expect.objectContaining(expectedCarePlan)]), + }), + ) + }, 30000) }) diff --git a/src/__tests__/patients/care-plans/CarePlanForm.test.tsx b/src/__tests__/patients/care-plans/CarePlanForm.test.tsx index 6d79dbfbea..32544bddb2 100644 --- a/src/__tests__/patients/care-plans/CarePlanForm.test.tsx +++ b/src/__tests__/patients/care-plans/CarePlanForm.test.tsx @@ -1,8 +1,7 @@ -import { Alert } from '@hospitalrun/components' -import addDays from 'date-fns/addDays' -import { mount } from 'enzyme' +import { render, screen, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import format from 'date-fns/format' import React from 'react' -import { act } from 'react-dom/test-utils' import CarePlanForm from '../../../patients/care-plans/CarePlanForm' import CarePlan, { CarePlanIntent, CarePlanStatus } from '../../../shared/model/CarePlan' @@ -19,6 +18,7 @@ describe('Care Plan Form', () => { abatementDate: new Date().toISOString(), status: DiagnosisStatus.Active, note: 'some note', + visit: 'some visit', } const carePlan: CarePlan = { id: 'id', @@ -35,7 +35,8 @@ describe('Care Plan Form', () => { const setup = (disabled = false, initializeCarePlan = true, error?: any) => { onCarePlanChangeSpy = jest.fn() const mockPatient = { id: '123', diagnoses: [diagnosis] } as Patient - const wrapper = mount( + + return render( <CarePlanForm patient={mockPatient} onChange={onCarePlanChangeSpy} @@ -44,226 +45,171 @@ describe('Care Plan Form', () => { carePlanError={error} />, ) - return { wrapper } } - it('should render a title input', () => { - const { wrapper } = setup() - - const titleInput = wrapper.findWhere((w) => w.prop('name') === 'title') + beforeEach(() => { + jest.resetAllMocks() + }) - expect(titleInput).toHaveLength(1) - expect(titleInput.prop('patient.carePlan.title')) - expect(titleInput.prop('isRequired')).toBeTruthy() - expect(titleInput.prop('value')).toEqual(carePlan.title) + it('should render a title input', () => { + setup() + expect(screen.getByLabelText(/patient.carePlan.title/i)).toBeInTheDocument() + expect(screen.getByLabelText(/patient.carePlan.title/i)).toHaveValue(carePlan.title) + expect(screen.getByText(/patient.carePlan.title/i).title).toBe('This is a required input') }) - it('should call the on change handler when condition changes', () => { + it('should call the on change handler when title changes', () => { const expectedNewTitle = 'some new title' - const { wrapper } = setup(false, false) - act(() => { - const titleInput = wrapper.findWhere((w) => w.prop('name') === 'title') - const onChange = titleInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedNewTitle } }) - }) - + setup(false, false) + userEvent.type(screen.getByLabelText(/patient.carePlan.title/i), expectedNewTitle) expect(onCarePlanChangeSpy).toHaveBeenCalledWith({ title: expectedNewTitle }) }) it('should render a description input', () => { - const { wrapper } = setup() - - const descriptionInput = wrapper.findWhere((w) => w.prop('name') === 'description') - - expect(descriptionInput).toHaveLength(1) - expect(descriptionInput.prop('patient.carePlan.description')) - expect(descriptionInput.prop('isRequired')).toBeTruthy() - expect(descriptionInput.prop('value')).toEqual(carePlan.description) + setup() + expect(screen.getByLabelText(/patient.carePlan.description/i)).toBeInTheDocument() + expect(screen.getByLabelText(/patient.carePlan.description/i)).toHaveValue(carePlan.description) + expect(screen.getByText(/patient.carePlan.description/i).title).toBe('This is a required input') }) - it('should call the on change handler when condition changes', () => { + it('should call the on change handler when description changes', () => { const expectedNewDescription = 'some new description' - const { wrapper } = setup(false, false) - act(() => { - const descriptionInput = wrapper.findWhere((w) => w.prop('name') === 'description') - const onChange = descriptionInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedNewDescription } }) - }) - + setup(false, false) + userEvent.paste(screen.getByLabelText(/patient.carePlan.description/i), expectedNewDescription) expect(onCarePlanChangeSpy).toHaveBeenCalledWith({ description: expectedNewDescription }) }) - it('should render a condition selector with the diagnoses from the patient', () => { - const { wrapper } = setup() - - const conditionSelector = wrapper.findWhere((w) => w.prop('name') === 'condition') - - expect(conditionSelector).toHaveLength(1) - expect(conditionSelector.prop('patient.carePlan.condition')) - expect(conditionSelector.prop('isRequired')).toBeTruthy() - expect(conditionSelector.prop('defaultSelected')[0].value).toEqual(carePlan.diagnosisId) - expect(conditionSelector.prop('options')).toEqual([ - { value: diagnosis.id, label: diagnosis.name }, - ]) + it('should render a condition selector with the diagnoses from the patient', async () => { + setup() + const conditionSelector = screen.getByDisplayValue(diagnosis.name) + const conditionSelectorLabel = screen.getByText(/patient.carePlan.condition/i) + expect(conditionSelector).toBeInTheDocument() + expect(conditionSelector).toHaveValue(diagnosis.name) + expect(conditionSelectorLabel).toBeInTheDocument() + expect(conditionSelectorLabel.title).toBe('This is a required input') }) - it('should call the on change handler when condition changes', () => { - const expectedNewCondition = 'some new condition' - const { wrapper } = setup(false, false) - act(() => { - const conditionSelector = wrapper.findWhere((w) => w.prop('name') === 'condition') - const onChange = conditionSelector.prop('onChange') as any - onChange([expectedNewCondition]) - }) - - expect(onCarePlanChangeSpy).toHaveBeenCalledWith({ diagnosisId: expectedNewCondition }) + it('should call the on change handler when condition changes', async () => { + setup(false, false) + const conditionSelector = within(screen.getByTestId('conditionSelect')).getByRole('combobox') + userEvent.type(conditionSelector, `${diagnosis.name}{arrowdown}{enter}`) + expect(onCarePlanChangeSpy).toHaveBeenCalledWith({ diagnosisId: diagnosis.id }) }) it('should render a status selector', () => { - const { wrapper } = setup() - - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - - expect(statusSelector).toHaveLength(1) - expect(statusSelector.prop('patient.carePlan.status')) - expect(statusSelector.prop('isRequired')).toBeTruthy() - expect(statusSelector.prop('defaultSelected')[0].value).toEqual(carePlan.status) - expect(statusSelector.prop('options')).toEqual( - Object.values(CarePlanStatus).map((v) => ({ label: v, value: v })), - ) + setup() + const statusSelector = screen.getByDisplayValue(carePlan.status) + const statusSelectorLabel = screen.getByText(/patient.carePlan.status/i) + expect(statusSelector).toBeInTheDocument() + expect(statusSelector).toHaveValue(carePlan.status) + expect(statusSelectorLabel).toBeInTheDocument() + expect(statusSelectorLabel.title).toBe('This is a required input') + userEvent.click(statusSelector) + const optionsList = screen + .getAllByRole('listbox') + .filter((item) => item.id === 'statusSelect')[0] + const options = Array.prototype.map.call(optionsList.children, (li) => li.textContent) + expect(options).toEqual(Object.values(CarePlanStatus).map((v) => v)) }) it('should call the on change handler when status changes', () => { const expectedNewStatus = CarePlanStatus.Revoked - const { wrapper } = setup(false, false) - act(() => { - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - const onChange = statusSelector.prop('onChange') as any - onChange([expectedNewStatus]) - }) - - expect(onCarePlanChangeSpy).toHaveBeenCalledWith({ status: expectedNewStatus }) + setup() + const statusSelector = screen.getByDisplayValue(carePlan.status) + userEvent.click(statusSelector) + userEvent.click(screen.getByText(expectedNewStatus)) + expect(onCarePlanChangeSpy).toHaveBeenCalledWith( + expect.objectContaining({ status: expectedNewStatus }), + ) }) it('should render an intent selector', () => { - const { wrapper } = setup() - - const intentSelector = wrapper.findWhere((w) => w.prop('name') === 'intent') - - expect(intentSelector).toHaveLength(1) - expect(intentSelector.prop('patient.carePlan.intent')) - expect(intentSelector.prop('isRequired')).toBeTruthy() - expect(intentSelector.prop('defaultSelected')[0].value).toEqual(carePlan.intent) - expect(intentSelector.prop('options')).toEqual( - Object.values(CarePlanIntent).map((v) => ({ label: v, value: v })), - ) + setup() + const intentSelector = screen.getByDisplayValue(carePlan.intent) + const intentSelectorLabel = screen.getByText(/patient.carePlan.intent/i) + expect(intentSelector).toBeInTheDocument() + expect(intentSelector).toHaveValue(carePlan.intent) + expect(intentSelectorLabel).toBeInTheDocument() + expect(intentSelectorLabel.title).toBe('This is a required input') + userEvent.click(intentSelector) + const optionsList = screen + .getAllByRole('listbox') + .filter((item) => item.id === 'intentSelect')[0] + const options = Array.prototype.map.call(optionsList.children, (li) => li.textContent) + expect(options).toEqual(Object.values(CarePlanIntent).map((v) => v)) }) it('should call the on change handler when intent changes', () => { const newIntent = CarePlanIntent.Proposal - const { wrapper } = setup(false, false) - act(() => { - const intentSelector = wrapper.findWhere((w) => w.prop('name') === 'intent') - const onChange = intentSelector.prop('onChange') as any - onChange([newIntent]) - }) - - expect(onCarePlanChangeSpy).toHaveBeenCalledWith({ intent: newIntent }) + setup() + const intentSelector = screen.getByDisplayValue(carePlan.intent) + userEvent.click(intentSelector) + userEvent.click(screen.getByText(newIntent)) + expect(onCarePlanChangeSpy).toHaveBeenCalledWith(expect.objectContaining({ intent: newIntent })) }) it('should render a start date picker', () => { - const { wrapper } = setup() - - const startDatePicker = wrapper.findWhere((w) => w.prop('name') === 'startDate') - - expect(startDatePicker).toHaveLength(1) - expect(startDatePicker.prop('patient.carePlan.startDate')) - expect(startDatePicker.prop('isRequired')).toBeTruthy() - expect(startDatePicker.prop('value')).toEqual(new Date(carePlan.startDate)) + setup() + const date = format(new Date(carePlan.startDate), 'MM/dd/yyyy') + const startDatePicker = within(screen.getByTestId('startDateDatePicker')).getByRole('textbox') + const startDatePickerLabel = screen.getByText(/patient.carePlan.startDate/i) + expect(startDatePicker).toBeInTheDocument() + expect(startDatePicker).toHaveValue(date) + expect(startDatePickerLabel).toBeInTheDocument() + expect(startDatePickerLabel.title).toBe('This is a required input') }) it('should call the on change handler when start date changes', () => { - const expectedNewStartDate = addDays(1, new Date().getDate()) - const { wrapper } = setup(false, false) - - const startDatePicker = wrapper.findWhere((w) => w.prop('name') === 'startDate') - act(() => { - const onChange = startDatePicker.prop('onChange') as any - onChange(expectedNewStartDate) - }) - - expect(onCarePlanChangeSpy).toHaveBeenCalledWith({ - startDate: expectedNewStartDate.toISOString(), - }) + setup() + const startDatePicker = within(screen.getByTestId('startDateDatePicker')).getByRole('textbox') + userEvent.type(startDatePicker, '{arrowdown}{arrowleft}{enter}') + expect(onCarePlanChangeSpy).toHaveBeenCalledTimes(1) }) it('should render an end date picker', () => { - const { wrapper } = setup() - - const endDatePicker = wrapper.findWhere((w) => w.prop('name') === 'endDate') - - expect(endDatePicker).toHaveLength(1) - expect(endDatePicker.prop('patient.carePlan.endDate')) - expect(endDatePicker.prop('isRequired')).toBeTruthy() - expect(endDatePicker.prop('value')).toEqual(new Date(carePlan.endDate)) + setup() + const date = format(new Date(carePlan.endDate), 'MM/dd/yyyy') + const endDatePicker = within(screen.getByTestId('endDateDatePicker')).getByRole('textbox') + const endDatePickerLabel = screen.getByText(/patient.carePlan.endDate/i) + expect(endDatePicker).toBeInTheDocument() + expect(endDatePicker).toHaveValue(date) + expect(endDatePickerLabel).toBeInTheDocument() + expect(endDatePickerLabel.title).toBe('This is a required input') }) it('should call the on change handler when end date changes', () => { - const expectedNewEndDate = addDays(1, new Date().getDate()) - const { wrapper } = setup(false, false) - - const endDatePicker = wrapper.findWhere((w) => w.prop('name') === 'endDate') - act(() => { - const onChange = endDatePicker.prop('onChange') as any - onChange(expectedNewEndDate) - }) - - expect(onCarePlanChangeSpy).toHaveBeenCalledWith({ - endDate: expectedNewEndDate.toISOString(), - }) + setup() + const endDatePicker = within(screen.getByTestId('endDateDatePicker')).getByRole('textbox') + userEvent.type(endDatePicker, '{arrowdown}{arrowleft}{enter}') + expect(onCarePlanChangeSpy).toHaveBeenCalledTimes(1) }) it('should render a note input', () => { - const { wrapper } = setup() - - const noteInput = wrapper.findWhere((w) => w.prop('name') === 'note') - expect(noteInput).toHaveLength(1) - expect(noteInput.prop('patient.carePlan.note')) - expect(noteInput.prop('value')).toEqual(carePlan.note) + setup() + const noteInput = screen.getByLabelText(/patient.carePlan.note/i) + expect(noteInput).toBeInTheDocument() + expect(noteInput).toHaveTextContent(carePlan.note) }) it('should call the on change handler when note changes', () => { const expectedNewNote = 'some new note' - const { wrapper } = setup(false, false) - - const noteInput = wrapper.findWhere((w) => w.prop('name') === 'note') - act(() => { - const onChange = noteInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedNewNote } }) - }) - + setup(false, false) + const noteInput = screen.getByLabelText(/patient.carePlan.note/i) + userEvent.paste(noteInput, expectedNewNote) expect(onCarePlanChangeSpy).toHaveBeenCalledWith({ note: expectedNewNote }) }) it('should render all of the fields as disabled if the form is disabled', () => { - const { wrapper } = setup(true) - const titleInput = wrapper.findWhere((w) => w.prop('name') === 'title') - const descriptionInput = wrapper.findWhere((w) => w.prop('name') === 'description') - const conditionSelector = wrapper.findWhere((w) => w.prop('name') === 'condition') - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - const intentSelector = wrapper.findWhere((w) => w.prop('name') === 'intent') - const startDatePicker = wrapper.findWhere((w) => w.prop('name') === 'startDate') - const endDatePicker = wrapper.findWhere((w) => w.prop('name') === 'endDate') - const noteInput = wrapper.findWhere((w) => w.prop('name') === 'note') - - expect(titleInput.prop('isEditable')).toBeFalsy() - expect(descriptionInput.prop('isEditable')).toBeFalsy() - expect(conditionSelector.prop('isEditable')).toBeFalsy() - expect(statusSelector.prop('isEditable')).toBeFalsy() - expect(intentSelector.prop('isEditable')).toBeFalsy() - expect(startDatePicker.prop('isEditable')).toBeFalsy() - expect(endDatePicker.prop('isEditable')).toBeFalsy() - expect(noteInput.prop('isEditable')).toBeFalsy() + setup(true) + expect(screen.getByLabelText(/patient.carePlan.title/i)).toBeDisabled() + expect(screen.getByLabelText(/patient.carePlan.description/i)).toBeDisabled() + // condition + expect(screen.getByDisplayValue(diagnosis.name)).toBeDisabled() + expect(screen.getByDisplayValue(carePlan.status)).toBeDisabled() + expect(screen.getByDisplayValue(carePlan.intent)).toBeDisabled() + expect(within(screen.getByTestId('startDateDatePicker')).getByRole('textbox')).toBeDisabled() + expect(within(screen.getByTestId('endDateDatePicker')).getByRole('textbox')).toBeDisabled() + expect(screen.getByLabelText(/patient.carePlan.note/i)).toBeDisabled() }) it('should render the form fields in an error state', () => { @@ -278,44 +224,39 @@ describe('Care Plan Form', () => { note: 'some note error', condition: 'some condition error', } - - const { wrapper } = setup(false, false, expectedError) - - const alert = wrapper.find(Alert) - const titleInput = wrapper.findWhere((w) => w.prop('name') === 'title') - const descriptionInput = wrapper.findWhere((w) => w.prop('name') === 'description') - const conditionSelector = wrapper.findWhere((w) => w.prop('name') === 'condition') - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - const intentSelector = wrapper.findWhere((w) => w.prop('name') === 'intent') - const startDatePicker = wrapper.findWhere((w) => w.prop('name') === 'startDate') - const endDatePicker = wrapper.findWhere((w) => w.prop('name') === 'endDate') - const noteInput = wrapper.findWhere((w) => w.prop('name') === 'note') - - expect(alert).toHaveLength(1) - expect(alert.prop('message')).toEqual(expectedError.message) - - expect(titleInput.prop('isInvalid')).toBeTruthy() - expect(titleInput.prop('feedback')).toEqual(expectedError.title) - - expect(descriptionInput.prop('isInvalid')).toBeTruthy() - expect(descriptionInput.prop('feedback')).toEqual(expectedError.description) - - expect(conditionSelector.prop('isInvalid')).toBeTruthy() - // expect(conditionSelector.prop('feedback')).toEqual(expectedError.condition) - - expect(statusSelector.prop('isInvalid')).toBeTruthy() - // expect(statusSelector.prop('feedback')).toEqual(expectedError.status) - - expect(intentSelector.prop('isInvalid')).toBeTruthy() - // expect(intentSelector.prop('feedback')).toEqual(expectedError.intent) - - expect(startDatePicker.prop('isInvalid')).toBeTruthy() - expect(startDatePicker.prop('feedback')).toEqual(expectedError.startDate) - - expect(endDatePicker.prop('isInvalid')).toBeTruthy() - expect(endDatePicker.prop('feedback')).toEqual(expectedError.endDate) - - expect(noteInput.prop('isInvalid')).toBeTruthy() - expect(noteInput.prop('feedback')).toEqual(expectedError.note) + setup(false, false, expectedError) + const alert = screen.getByRole('alert') + const titleInput = screen.getByLabelText(/patient.carePlan.title/i) + const descriptionInput = screen.getByLabelText(/patient.carePlan.description/i) + const conditionInput = within(screen.getByTestId('conditionSelect')).getByRole('combobox') + const statusInput = within(screen.getByTestId('statusSelect')).getByRole('combobox') + const intentInput = within(screen.getByTestId('intentSelect')).getByRole('combobox') + const startDateInput = within(screen.getByTestId('startDateDatePicker')).getByRole('textbox') + const endDateInput = within(screen.getByTestId('endDateDatePicker')).getByRole('textbox') + const noteInput = screen.getByLabelText(/patient.carePlan.note/i) + expect(alert).toBeInTheDocument() + expect(alert).toHaveTextContent(expectedError.message) + expect(titleInput).toHaveClass('is-invalid') + expect(titleInput.nextSibling).toHaveTextContent(expectedError.title) + expect(descriptionInput).toHaveClass('is-invalid') + expect(descriptionInput.nextSibling).toHaveTextContent(expectedError.description) + expect(conditionInput).toHaveClass('is-invalid') + // expect(conditionInput.nextSibling).toHaveTextContent( + // expectedError.condition, + // ) + expect(statusInput).toHaveClass('is-invalid') + // expect(statusInput.nextSibling).toHaveTextContent( + // expectedError.status, + // ) + expect(intentInput).toHaveClass('is-invalid') + // expect(intentInput.nextSibling).toHaveTextContent( + // expectedError.intent, + // ) + expect(startDateInput).toHaveClass('is-invalid') + expect(screen.getByText(expectedError.startDate)).toBeInTheDocument() + expect(endDateInput).toHaveClass('is-invalid') + expect(screen.getByText(expectedError.endDate)).toBeInTheDocument() + expect(noteInput).toHaveClass('is-invalid') + expect(noteInput.nextSibling).toHaveTextContent(expectedError.note) }) }) diff --git a/src/__tests__/patients/care-plans/CarePlanTab.test.tsx b/src/__tests__/patients/care-plans/CarePlanTab.test.tsx index 62b0a30260..0e08092f73 100644 --- a/src/__tests__/patients/care-plans/CarePlanTab.test.tsx +++ b/src/__tests__/patients/care-plans/CarePlanTab.test.tsx @@ -1,18 +1,15 @@ -import { Button } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router, Route } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' -import AddCarePlanModal from '../../../patients/care-plans/AddCarePlanModal' import CarePlanTab from '../../../patients/care-plans/CarePlanTab' -import CarePlanTable from '../../../patients/care-plans/CarePlanTable' -import ViewCarePlan from '../../../patients/care-plans/ViewCarePlan' import PatientRepository from '../../../shared/db/PatientRepository' +import CarePlan, { CarePlanIntent, CarePlanStatus } from '../../../shared/model/CarePlan' import Patient from '../../../shared/model/Patient' import Permissions from '../../../shared/model/Permissions' import { RootState } from '../../../shared/store' @@ -20,87 +17,93 @@ import { RootState } from '../../../shared/store' const mockStore = createMockStore<RootState, any>([thunk]) describe('Care Plan Tab', () => { - const patient = { id: 'patientId' } as Patient + const carePlan = { + id: '679', + title: 'some title', + description: 'some description', + diagnosisId: '123', + startDate: new Date().toISOString(), + endDate: new Date().toISOString(), + note: '', + status: CarePlanStatus.Active, + intent: CarePlanIntent.Proposal, + createdOn: new Date().toISOString(), + } + + const patient = { id: '124', carePlans: [carePlan] as CarePlan[] } as Patient const setup = async (route: string, permissions: Permissions[]) => { + jest.resetAllMocks() jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) const store = mockStore({ user: { permissions } } as any) const history = createMemoryHistory() history.push(route) - let wrapper: any - await act(async () => { - wrapper = await mount( - <Provider store={store}> - <Router history={history}> - <Route path="/patients/:id/care-plans"> - <CarePlanTab /> - </Route> - </Router> - </Provider>, - ) - }) - wrapper.update() - return { wrapper: wrapper as ReactWrapper, history } + return render( + <Provider store={store}> + <Router history={history}> + <Route path="/patients/:id/care-plans"> + <CarePlanTab /> + </Route> + </Router> + </Provider>, + ) } it('should render an add care plan button if user has correct permissions', async () => { - const { wrapper } = await setup('/patients/123/care-plans', [Permissions.AddCarePlan]) + setup('/patients/123/care-plans', [Permissions.AddCarePlan]) - const addNewButton = wrapper.find(Button).at(0) - expect(addNewButton).toHaveLength(1) - expect(addNewButton.text().trim()).toEqual('patient.carePlan.new') + await waitFor(() => { + expect(screen.getByRole('button', { name: /patient\.carePlan\.new/i })).toBeInTheDocument() + }) }) it('should open the add care plan modal on click', async () => { - const { wrapper } = await setup('/patients/123/care-plans', [Permissions.AddCarePlan]) + setup('/patients/123/care-plans', [Permissions.AddCarePlan]) - act(() => { - const addNewButton = wrapper.find(Button).at(0) - const onClick = addNewButton.prop('onClick') as any - onClick() - }) - wrapper.update() + userEvent.click(await screen.findByRole('button', { name: /patient\.carePlan\.new/i })) - const modal = wrapper.find(AddCarePlanModal) - expect(modal.prop('show')).toBeTruthy() + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument() + }) }) it('should close the modal when the close button is clicked', async () => { - const { wrapper } = await setup('/patients/123/care-plans', [Permissions.AddCarePlan]) + setup('/patients/123/care-plans', [Permissions.AddCarePlan]) - act(() => { - const addNewButton = wrapper.find(Button).at(0) - const onClick = addNewButton.prop('onClick') as any - onClick() - }) - wrapper.update() + userEvent.click(await screen.findByRole('button', { name: /patient\.carePlan\.new/i })) - act(() => { - const modal = wrapper.find(AddCarePlanModal) - const onClose = modal.prop('onCloseButtonClick') as any - onClose() - }) - wrapper.update() + expect(screen.getByRole('dialog')).toBeVisible() - expect(wrapper.find(AddCarePlanModal).prop('show')).toBeFalsy() + userEvent.click( + screen.getByRole('button', { + name: /close/i, + }), + ) + await waitFor(() => { + expect(screen.queryByRole('dialog')).not.toBeVisible() + }) }) it('should not render care plan button if user does not have permissions', async () => { - const { wrapper } = await setup('/patients/123/care-plans', []) + setup('/patients/123/care-plans', []) - expect(wrapper.find(Button)).toHaveLength(0) + await waitFor(() => { + expect( + screen.queryByRole('button', { name: /patient\.carePlan\.new/i }), + ).not.toBeInTheDocument() + }) }) it('should render the care plans table when on /patient/:id/care-plans', async () => { - const { wrapper } = await setup('/patients/123/care-plans', [Permissions.ReadCarePlan]) + setup('/patients/123/care-plans', [Permissions.ReadCarePlan]) - expect(wrapper.find(CarePlanTable)).toHaveLength(1) + expect(await screen.findByRole('table')).toBeInTheDocument() }) it('should render the care plan view when on /patient/:id/care-plans/:carePlanId', async () => { - const { wrapper } = await setup('/patients/123/care-plans/456', [Permissions.ReadCarePlan]) + setup('/patients/123/care-plans/679', [Permissions.ReadCarePlan]) - expect(wrapper.find(ViewCarePlan)).toHaveLength(1) + expect(await screen.findByRole('form')).toBeInTheDocument() }) }) diff --git a/src/__tests__/patients/care-plans/CarePlanTable.test.tsx b/src/__tests__/patients/care-plans/CarePlanTable.test.tsx index bfd3b7722e..b0672f857b 100644 --- a/src/__tests__/patients/care-plans/CarePlanTable.test.tsx +++ b/src/__tests__/patients/care-plans/CarePlanTable.test.tsx @@ -1,8 +1,7 @@ -import { Alert, Table } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Router } from 'react-router-dom' import CarePlanTable from '../../../patients/care-plans/CarePlanTable' @@ -12,20 +11,20 @@ import Patient from '../../../shared/model/Patient' describe('Care Plan Table', () => { const carePlan: CarePlan = { - id: 'id', - title: 'title', - description: 'description', + id: '0001', + title: 'chicken pox', + description: 'super itchy spots', status: CarePlanStatus.Active, intent: CarePlanIntent.Option, startDate: new Date(2020, 6, 3).toISOString(), endDate: new Date(2020, 6, 5).toISOString(), - diagnosisId: 'some id', + diagnosisId: '0123', createdOn: new Date().toISOString(), - note: 'note', + note: 'Apply Camomile lotion to spots', } const patient = { id: 'patientId', - diagnoses: [{ id: '123', name: 'some name', diagnosisDate: new Date().toISOString() }], + diagnoses: [{ id: '0123', name: 'chicken pox', diagnosisDate: new Date().toISOString() }], carePlans: [carePlan], } as Patient @@ -34,63 +33,60 @@ describe('Care Plan Table', () => { const history = createMemoryHistory() history.push(`/patients/${patient.id}/care-plans/${patient.carePlans[0].id}`) - let wrapper: any - await act(async () => { - wrapper = await mount( + return { + history, + ...render( <Router history={history}> <CarePlanTable patientId={expectedPatient.id} /> </Router>, - ) - }) - wrapper.update() - - return { wrapper: wrapper as ReactWrapper, history } + ), + } } it('should render a table', async () => { - const { wrapper } = await setup() - - const table = wrapper.find(Table) - const columns = table.prop('columns') - const actions = table.prop('actions') as any - expect(columns[0]).toEqual( - expect.objectContaining({ label: 'patient.carePlan.title', key: 'title' }), - ) - expect(columns[1]).toEqual( - expect.objectContaining({ label: 'patient.carePlan.startDate', key: 'startDate' }), - ) - expect(columns[2]).toEqual( - expect.objectContaining({ label: 'patient.carePlan.endDate', key: 'endDate' }), - ) - expect(columns[3]).toEqual( - expect.objectContaining({ label: 'patient.carePlan.status', key: 'status' }), - ) - - expect(actions[0]).toEqual(expect.objectContaining({ label: 'actions.view' })) - expect(table.prop('actionsHeaderText')).toEqual('actions.label') - expect(table.prop('data')).toEqual(patient.carePlans) + setup() + + await screen.findByRole('table') + + const columns = screen.getAllByRole('columnheader') + + expect(columns[0]).toHaveTextContent(/patient\.carePlan\.title/i) + expect(columns[1]).toHaveTextContent(/patient\.carePlan\.startDate/i) + expect(columns[2]).toHaveTextContent(/patient\.carePlan\.endDate/i) + expect(columns[3]).toHaveTextContent(/patient\.carePlan\.status/i) + + await waitFor(() => { + expect( + screen.getByRole('button', { + name: /actions\.view/i, + }), + ).toBeInTheDocument() + }) }) it('should navigate to the care plan view when the view details button is clicked', async () => { - const { wrapper, history } = await setup() + const { history } = await setup() - const tr = wrapper.find('tr').at(1) + await screen.findByRole('table') - act(() => { - const onClick = tr.find('button').prop('onClick') as any - onClick({ stopPropagation: jest.fn() }) + const actionButton = await screen.findByRole('button', { + name: /actions\.view/i, }) + userEvent.click(actionButton) + expect(history.location.pathname).toEqual(`/patients/${patient.id}/care-plans/${carePlan.id}`) }) it('should display a warning if there are no care plans', async () => { - const { wrapper } = await setup({ ...patient, carePlans: [] }) + await setup({ ...patient, carePlans: [] }) - expect(wrapper.exists(Alert)).toBeTruthy() - const alert = wrapper.find(Alert) - expect(alert.prop('color')).toEqual('warning') - expect(alert.prop('title')).toEqual('patient.carePlans.warning.noCarePlans') - expect(alert.prop('message')).toEqual('patient.carePlans.warning.addCarePlanAbove') + await waitFor(() => { + expect(screen.getByText(/patient\.carePlans\.warning\.noCarePlans/i)).toBeInTheDocument() + }) + + await waitFor(() => { + expect(screen.getByText(/patient\.carePlans\.warning\.addCarePlanAbove/i)).toBeInTheDocument() + }) }) }) diff --git a/src/__tests__/patients/care-plans/ViewCarePlan.test.tsx b/src/__tests__/patients/care-plans/ViewCarePlan.test.tsx index 2145495147..a8cb29bd23 100644 --- a/src/__tests__/patients/care-plans/ViewCarePlan.test.tsx +++ b/src/__tests__/patients/care-plans/ViewCarePlan.test.tsx @@ -1,53 +1,42 @@ -import { mount } from 'enzyme' +import { render, screen } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Route, Router } from 'react-router-dom' -import CarePlanForm from '../../../patients/care-plans/CarePlanForm' import ViewCarePlan from '../../../patients/care-plans/ViewCarePlan' import PatientRepository from '../../../shared/db/PatientRepository' +import CarePlan from '../../../shared/model/CarePlan' import Patient from '../../../shared/model/Patient' describe('View Care Plan', () => { + const carePlan = { + id: '123', + title: 'Feed Harry Potter', + } as CarePlan const patient = { - id: 'patientId', + id: '023', diagnoses: [{ id: '123', name: 'some name', diagnosisDate: new Date().toISOString() }], - carePlans: [{ id: '123', title: 'some title' }], + carePlans: [carePlan], } as Patient const setup = async () => { + jest.resetAllMocks() jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) const history = createMemoryHistory() history.push(`/patients/${patient.id}/care-plans/${patient.carePlans[0].id}`) - let wrapper: any - await act(async () => { - wrapper = await mount( - <Router history={history}> - <Route path="/patients/:id/care-plans/:carePlanId"> - <ViewCarePlan /> - </Route> - </Router>, - ) - }) - wrapper.update() - - return { wrapper } + return render( + <Router history={history}> + <Route path="/patients/:id/care-plans/:carePlanId"> + <ViewCarePlan /> + </Route> + </Router>, + ) } it('should render the care plan title', async () => { - const { wrapper } = await setup() - - expect(wrapper.find('h2').text()).toEqual(patient.carePlans[0].title) - }) - - it('should render a care plan form with the correct data', async () => { - const { wrapper } = await setup() + setup() - const carePlanForm = wrapper.find(CarePlanForm) - expect(carePlanForm).toHaveLength(1) - expect(carePlanForm.prop('carePlan')).toEqual(patient.carePlans[0]) - expect(carePlanForm.prop('patient')).toEqual(patient) + expect(await screen.findByRole('heading', { name: carePlan.title })).toBeInTheDocument() }) }) diff --git a/src/__tests__/patients/care-plans/ViewCarePlans.test.tsx b/src/__tests__/patients/care-plans/ViewCarePlans.test.tsx index 644de9a2ee..44f4e64674 100644 --- a/src/__tests__/patients/care-plans/ViewCarePlans.test.tsx +++ b/src/__tests__/patients/care-plans/ViewCarePlans.test.tsx @@ -1,42 +1,43 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Route, Router } from 'react-router-dom' -import CarePlanTable from '../../../patients/care-plans/CarePlanTable' import ViewCarePlans from '../../../patients/care-plans/ViewCarePlans' import PatientRepository from '../../../shared/db/PatientRepository' -import CarePlan from '../../../shared/model/CarePlan' +import CarePlan, { CarePlanIntent, CarePlanStatus } from '../../../shared/model/CarePlan' import Patient from '../../../shared/model/Patient' describe('View Care Plans', () => { - const patient = { id: '123', carePlans: [] as CarePlan[] } as Patient + const carePlan = { + id: '123', + title: 'Feed Harry Potter', + description: 'Get Dobby to feed Harry Food', + diagnosisId: '123', + startDate: new Date().toISOString(), + endDate: new Date().toISOString(), + status: CarePlanStatus.Active, + intent: CarePlanIntent.Proposal, + } as CarePlan + const patient = { id: '123', carePlans: [carePlan] as CarePlan[] } as Patient const setup = async () => { + jest.resetAllMocks() jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) const history = createMemoryHistory() history.push(`/patients/${patient.id}/careplans`) - let wrapper: any - await act(async () => { - wrapper = await mount( - <Router history={history}> - <Route path="/patients/:id/careplans"> - <ViewCarePlans /> - </Route> - </Router>, - ) - }) - - return { wrapper: wrapper as ReactWrapper } + return render( + <Router history={history}> + <Route path="/patients/:id/careplans"> + <ViewCarePlans /> + </Route> + </Router>, + ) } it('should render a care plans table with the patient id', async () => { - const { wrapper } = await setup() - - expect(wrapper.exists(CarePlanTable)).toBeTruthy() - const carePlanTable = wrapper.find(CarePlanTable) - expect(carePlanTable.prop('patientId')).toEqual(patient.id) + setup() + expect(await screen.findByRole('table')).toBeInTheDocument() }) }) diff --git a/src/__tests__/patients/diagnoses/AddDiagnosisModal.test.tsx b/src/__tests__/patients/diagnoses/AddDiagnosisModal.test.tsx index 0ff107dfb8..3953062525 100644 --- a/src/__tests__/patients/diagnoses/AddDiagnosisModal.test.tsx +++ b/src/__tests__/patients/diagnoses/AddDiagnosisModal.test.tsx @@ -1,11 +1,8 @@ -/* eslint-disable no-console */ -import { Modal } from '@hospitalrun/components' -import { mount } from 'enzyme' +import { render, screen, waitFor, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import React from 'react' -import { act } from 'react-dom/test-utils' import AddDiagnosisModal from '../../../patients/diagnoses/AddDiagnosisModal' -import DiagnosisForm from '../../../patients/diagnoses/DiagnosisForm' import PatientRepository from '../../../shared/db/PatientRepository' import { CarePlanIntent, CarePlanStatus } from '../../../shared/model/CarePlan' import Diagnosis, { DiagnosisStatus } from '../../../shared/model/Diagnosis' @@ -43,75 +40,65 @@ describe('Add Diagnosis Modal', () => { jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) jest.spyOn(PatientRepository, 'saveOrUpdate').mockResolvedValue(patient) - const wrapper = mount( - <AddDiagnosisModal patient={patient} show onCloseButtonClick={onCloseSpy} />, - ) - - wrapper.update() - return { wrapper } + return render(<AddDiagnosisModal patient={patient} show onCloseButtonClick={onCloseSpy} />) } - beforeEach(() => { - console.error = jest.fn() - }) - it('should render a modal', () => { - const { wrapper } = setup() - const modal = wrapper.find(Modal) + it('should render a modal', () => { + setup() - expect(modal).toHaveLength(1) + expect(screen.getByRole('dialog')).toBeInTheDocument() - const successButton = modal.prop('successButton') - const cancelButton = modal.prop('closeButton') - expect(modal.prop('title')).toEqual('patient.diagnoses.new') - expect(successButton?.children).toEqual('patient.diagnoses.new') - expect(successButton?.icon).toEqual('add') - expect(cancelButton?.children).toEqual('actions.cancel') + expect(screen.getByRole('button', { name: /patient\.diagnoses\.new/i })).toBeInTheDocument() + expect(screen.getByRole('button', { name: /actions\.cancel/i })).toBeInTheDocument() }) it('should render the diagnosis form', () => { - const { wrapper } = setup() + setup() - const diagnosisForm = wrapper.find(DiagnosisForm) - expect(diagnosisForm).toHaveLength(1) + expect(screen.getByRole('form')).toBeInTheDocument() }) it('should dispatch add diagnosis when the save button is clicked', async () => { const patient = mockPatient patient.diagnoses = [] - const { wrapper } = setup(patient) + setup(patient) const newDiagnosis = mockDiagnosis - newDiagnosis.name = 'New Diagnosis Name' - - act(() => { - const diagnosisForm = wrapper.find(DiagnosisForm) - const onChange = diagnosisForm.prop('onChange') as any - onChange(newDiagnosis) - }) - wrapper.update() - - await act(async () => { - const modal = wrapper.find(Modal) - const onSave = (modal.prop('successButton') as any).onClick - await onSave({} as React.MouseEvent<HTMLButtonElement>) - }) + newDiagnosis.name = 'yellow polka dot spots' + + userEvent.type( + screen.getByPlaceholderText(/patient\.diagnoses\.diagnosisName/i), + newDiagnosis.name, + ) + + await waitFor(() => + userEvent.click( + within(screen.getByRole('dialog')).getByRole('button', { + name: /patient\.diagnoses\.new/i, + }), + ), + ) + expect(PatientRepository.saveOrUpdate).toHaveBeenCalledTimes(1) expect(PatientRepository.saveOrUpdate).toHaveBeenCalledWith( expect.objectContaining({ - diagnoses: [expect.objectContaining({ name: 'New Diagnosis Name' })], + diagnoses: [expect.objectContaining({ name: 'yellow polka dot spots' })], }), ) + expect(await screen.queryByRole('dialogue')).not.toBeInTheDocument() }) it('should call the on close function when the cancel button is clicked', async () => { const onCloseButtonClickSpy = jest.fn() - const { wrapper } = setup(mockPatient, onCloseButtonClickSpy) - const modal = wrapper.find(Modal) - act(() => { - const { onClick } = modal.prop('closeButton') as any - onClick() - }) - expect(modal).toHaveLength(1) + setup(mockPatient, onCloseButtonClickSpy) + + await waitFor(() => + userEvent.click( + within(screen.getByRole('dialog')).getByRole('button', { + name: /actions\.cancel/i, + }), + ), + ) expect(onCloseButtonClickSpy).toHaveBeenCalledTimes(1) }) }) diff --git a/src/__tests__/patients/diagnoses/Diagnoses.test.tsx b/src/__tests__/patients/diagnoses/Diagnoses.test.tsx index f60b1a58ff..d239fade69 100644 --- a/src/__tests__/patients/diagnoses/Diagnoses.test.tsx +++ b/src/__tests__/patients/diagnoses/Diagnoses.test.tsx @@ -1,10 +1,7 @@ -/* eslint-disable no-console */ - -import * as components from '@hospitalrun/components' -import { mount } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' @@ -20,7 +17,7 @@ import { RootState } from '../../../shared/store' const expectedPatient = { id: '123', diagnoses: [ - { id: '123', name: 'diagnosis1', diagnosisDate: new Date().toISOString() } as Diagnosis, + { id: '123', name: 'Hayfever', diagnosisDate: new Date().toISOString() } as Diagnosis, ], } as Patient @@ -33,48 +30,51 @@ let store: any const setup = (patient = expectedPatient, permissions = [Permissions.AddDiagnosis]) => { user = { permissions } store = mockStore({ patient, user } as any) - const wrapper = mount( + return render( <Router history={history}> <Provider store={store}> <Diagnoses patient={patient} /> </Provider> </Router>, ) - - return wrapper } + describe('Diagnoses', () => { describe('add diagnoses button', () => { beforeEach(() => { jest.resetAllMocks() jest.spyOn(PatientRepository, 'saveOrUpdate') - console.error = jest.fn() }) + it('should render a add diagnoses button', () => { - const wrapper = setup() + setup() - const addDiagnosisButton = wrapper.find(components.Button) - expect(addDiagnosisButton).toHaveLength(1) - expect(addDiagnosisButton.text().trim()).toEqual('patient.diagnoses.new') + expect( + screen.getByRole('button', { + name: /patient\.diagnoses\.new/i, + }), + ).toBeInTheDocument() }) it('should not render a diagnoses button if the user does not have permissions', () => { - const wrapper = setup(expectedPatient, []) - - const addDiagnosisButton = wrapper.find(components.Button) - expect(addDiagnosisButton).toHaveLength(0) + setup(expectedPatient, []) + expect( + screen.queryByRole('button', { + name: /patient\.diagnoses\.new/i, + }), + ).not.toBeInTheDocument() }) it('should open the Add Diagnosis Modal', () => { - const wrapper = setup() + setup() - act(() => { - const onClick = wrapper.find(components.Button).prop('onClick') as any - onClick() - }) - wrapper.update() + userEvent.click( + screen.getByRole('button', { + name: /patient\.diagnoses\.new/i, + }), + ) - expect(wrapper.find(components.Modal).prop('show')).toBeTruthy() + expect(screen.getByRole('dialog')).toBeInTheDocument() }) }) }) diff --git a/src/__tests__/patients/diagnoses/DiagnosesList.test.tsx b/src/__tests__/patients/diagnoses/DiagnosesList.test.tsx index e47be82b3d..5c4bf8fddc 100644 --- a/src/__tests__/patients/diagnoses/DiagnosesList.test.tsx +++ b/src/__tests__/patients/diagnoses/DiagnosesList.test.tsx @@ -1,7 +1,5 @@ -import { Alert, List, ListItem } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, waitFor } from '@testing-library/react' import React from 'react' -import { act } from 'react-dom/test-utils' import DiagnosesList from '../../../patients/diagnoses/DiagnosesList' import PatientRepository from '../../../shared/db/PatientRepository' @@ -14,38 +12,31 @@ const expectedDiagnoses = [ describe('Diagnoses list', () => { const setup = async (diagnoses: Diagnosis[]) => { + jest.resetAllMocks() const mockPatient = { id: '123', diagnoses } as Patient jest.spyOn(PatientRepository, 'find').mockResolvedValueOnce(mockPatient) - let wrapper: any - await act(async () => { - wrapper = await mount(<DiagnosesList patientId={mockPatient.id} />) - }) - wrapper.update() - return { wrapper: wrapper as ReactWrapper } + return render(<DiagnosesList patientId={mockPatient.id} />) } it('should list the patients diagnoses', async () => { const diagnoses = expectedDiagnoses as Diagnosis[] - const { wrapper } = await setup(diagnoses) - - const listItems = wrapper.find(ListItem) - - expect(wrapper.exists(List)).toBeTruthy() + const { container } = await setup(diagnoses) + await waitFor(() => { + expect(container.querySelector('.list-group')).toBeInTheDocument() + }) + const listItems = container.querySelectorAll('.list-group-item') expect(listItems).toHaveLength(expectedDiagnoses.length) - expect(listItems.at(0).text()).toEqual(expectedDiagnoses[0].name) + expect(listItems[0]).toHaveTextContent(expectedDiagnoses[0].name) }) it('should render a warning message if the patient does not have any diagnoses', async () => { - const { wrapper } = await setup([]) - - const alert = wrapper.find(Alert) - - expect(wrapper.exists(Alert)).toBeTruthy() - expect(wrapper.exists(List)).toBeFalsy() - - expect(alert.prop('color')).toEqual('warning') - expect(alert.prop('title')).toEqual('patient.diagnoses.warning.noDiagnoses') - expect(alert.prop('message')).toEqual('patient.diagnoses.addDiagnosisAbove') + const { container } = await setup([]) + const alert = await screen.findByRole('alert') + expect(alert).toBeInTheDocument() + expect(container.querySelector('.list-group')).not.toBeInTheDocument() + expect(alert).toHaveClass('alert-warning') + expect(screen.getByText(/patient.diagnoses.warning.noDiagnoses/i)).toBeInTheDocument() + expect(screen.getByText(/patient.diagnoses.addDiagnosisAbove/i)).toBeInTheDocument() }) }) diff --git a/src/__tests__/patients/diagnoses/DiagnosisForm.test.tsx b/src/__tests__/patients/diagnoses/DiagnosisForm.test.tsx index 452206a209..fdb4b2fe61 100644 --- a/src/__tests__/patients/diagnoses/DiagnosisForm.test.tsx +++ b/src/__tests__/patients/diagnoses/DiagnosisForm.test.tsx @@ -1,12 +1,13 @@ -import { Alert } from '@hospitalrun/components' -import addDays from 'date-fns/addDays' -import { mount } from 'enzyme' +import { render, screen, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import format from 'date-fns/format' import React from 'react' -import { act } from 'react-dom/test-utils' import DiagnosisForm from '../../../patients/diagnoses/DiagnosisForm' +import PatientRepository from '../../../shared/db/PatientRepository' import Diagnosis, { DiagnosisStatus } from '../../../shared/model/Diagnosis' import Patient from '../../../shared/model/Patient' +import Visit from '../../../shared/model/Visit' describe('Diagnosis Form', () => { let onDiagnosisChangeSpy: any @@ -20,15 +21,34 @@ describe('Diagnosis Form', () => { note: 'some note', visit: 'some visit', } - + const date = new Date(Date.now()).toString() + const visits = [ + { + id: 'some visit', + createdAt: '', + updatedAt: '', + startDateTime: date, + endDateTime: '', + type: 'type', + status: 'arrived', + reason: 'reason', + location: '', + rev: 'revValue', + }, + ] as Visit[] const patient = { + id: '12345', givenName: 'first', fullName: 'first', + visits, } as Patient const setup = (disabled = false, initializeDiagnosis = true, error?: any) => { + jest.resetAllMocks() onDiagnosisChangeSpy = jest.fn() - const wrapper = mount( + jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) + + return render( <DiagnosisForm onChange={onDiagnosisChangeSpy} diagnosis={initializeDiagnosis ? diagnosis : {}} @@ -37,196 +57,164 @@ describe('Diagnosis Form', () => { patient={patient} />, ) - return { wrapper } } it('should render a name input', () => { - const { wrapper } = setup() - - const nameInput = wrapper.findWhere((w) => w.prop('name') === 'name') - - expect(nameInput).toHaveLength(1) - expect(nameInput.prop('patient.diagnoses.name')) - expect(nameInput.prop('isRequired')).toBeTruthy() - expect(nameInput.prop('value')).toEqual(diagnosis.name) + setup() + const nameInput = screen.getByLabelText(/patient.diagnoses.diagnosisName/i) + const nameInputLabel = screen.getByText(/patient.diagnoses.diagnosisName/i) + expect(nameInput).toBeInTheDocument() + expect(nameInput).toHaveDisplayValue(diagnosis.name) + expect(nameInputLabel.title).toBe('This is a required input') }) it('should call the on change handler when name changes', () => { const expectedNewname = 'some new name' - const { wrapper } = setup(false, false) - act(() => { - const nameInput = wrapper.findWhere((w) => w.prop('name') === 'name') - const onChange = nameInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedNewname } }) - }) - + setup(false, false) + const nameInput = screen.getByLabelText(/patient.diagnoses.diagnosisName/i) + userEvent.paste(nameInput, expectedNewname) expect(onDiagnosisChangeSpy).toHaveBeenCalledWith({ name: expectedNewname }) }) it('should render a visit selector', () => { - const { wrapper } = setup() - - const visitSelector = wrapper.findWhere((w) => w.prop('name') === 'visit') - - expect(visitSelector).toHaveLength(1) - expect(visitSelector.prop('patient.diagnoses.visit')) - expect(visitSelector.prop('isRequired')).toBeFalsy() - expect(visitSelector.prop('defaultSelected')).toEqual([]) - }) - - it('should call the on change handler when visit changes', () => { - const expectedNewVisit = patient.visits - const { wrapper } = setup(false, false) - act(() => { - const visitSelector = wrapper.findWhere((w) => w.prop('name') === 'visit') - const onChange = visitSelector.prop('onChange') as any - onChange([expectedNewVisit]) - }) - - expect(onDiagnosisChangeSpy).toHaveBeenCalledWith({ status: expectedNewVisit }) - }) + setup() + const visitSelector = within(screen.getByTestId('visitSelect')).getByRole('combobox') + const visitSelectorLabel = screen.getByText(/patient.diagnoses.visit/i) + expect(visitSelector).toBeInTheDocument() + expect(visitSelector).toHaveDisplayValue('') + expect(visitSelectorLabel).toBeInTheDocument() + expect(visitSelectorLabel.title).not.toBe('This is a required input') + }) + + // it.only('should call the on change handler when visit changes', () => { + // const expectedNewVisit = patient.visits + // const { container } = setup() + // const visitSelector = screen.getAllByRole('combobox')[0] + // userEvent.click(visitSelector) + // act(() => { + // const visitSelector = wrapper.findWhere((w) => w.prop('name') === 'visit') + // const onChange = visitSelector.prop('onChange') as any + // onChange([expectedNewVisit]) + // }) + + // expect(onDiagnosisChangeSpy).toHaveBeenCalledWith({ status: expectedNewVisit }) + // }) it('should render a status selector', () => { - const { wrapper } = setup() - - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - - expect(statusSelector).toHaveLength(1) - expect(statusSelector.prop('patient.diagnoses.status')) - expect(statusSelector.prop('isRequired')).toBeTruthy() - expect(statusSelector.prop('defaultSelected')[0].value).toEqual(diagnosis.status) - expect(statusSelector.prop('options')).toEqual( - Object.values(DiagnosisStatus).map((v) => ({ label: v, value: v })), - ) + setup() + const statusSelector = within(screen.getByTestId('statusSelect')).getByRole('combobox') + const statusSelectorLabel = screen.getByText(/patient.diagnoses.status/i) + expect(statusSelector).toBeInTheDocument() + expect(statusSelector).toHaveValue(diagnosis.status) + expect(statusSelectorLabel).toBeInTheDocument() + expect(statusSelectorLabel.title).toBe('This is a required input') + userEvent.click(statusSelector) + const optionsList = screen + .getAllByRole('listbox') + .filter((item) => item.id === 'statusSelect')[0] + const options = Array.prototype.map.call(optionsList.children, (li) => li.textContent) + expect(options).toEqual(Object.values(DiagnosisStatus).map((v) => v)) }) it('should call the on change handler when status changes', () => { const expectedNewStatus = DiagnosisStatus.Active - const { wrapper } = setup(false, false) - act(() => { - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - const onChange = statusSelector.prop('onChange') as any - onChange([expectedNewStatus]) - }) - + setup(false, false) + const statusSelector = within(screen.getByTestId('statusSelect')).getByRole('combobox') + userEvent.click(statusSelector) + userEvent.click(screen.getByText(expectedNewStatus)) expect(onDiagnosisChangeSpy).toHaveBeenCalledWith({ status: expectedNewStatus }) }) it('should render a diagnosis date picker', () => { - const { wrapper } = setup() - - const diagnosisDatePicker = wrapper.findWhere((w) => w.prop('name') === 'diagnosisDate') - - expect(diagnosisDatePicker).toHaveLength(1) - expect(diagnosisDatePicker.prop('patient.diagnoses.diagnosisDate')) - expect(diagnosisDatePicker.prop('isRequired')).toBeTruthy() - expect(diagnosisDatePicker.prop('value')).toEqual(new Date(diagnosis.diagnosisDate)) + setup() + const diagnosisDatePicker = within(screen.getByTestId('diagnosisDateDatePicker')).getByRole( + 'textbox', + ) + const diagnosisDatePickerLabel = screen.getByText(/patient.diagnoses.diagnosisDate/i) + expect(diagnosisDatePicker).toBeInTheDocument() + expect(diagnosisDatePickerLabel).toBeInTheDocument() + expect(diagnosisDatePickerLabel.title).toBe('This is a required input') }) it('should call the on change handler when diagnosis date changes', () => { - const expectedNewDiagnosisDate = addDays(1, new Date().getDate()) - const { wrapper } = setup(false, false) - - const diagnosisDatePicker = wrapper.findWhere((w) => w.prop('name') === 'diagnosisDate') - act(() => { - const onChange = diagnosisDatePicker.prop('onChange') as any - onChange(expectedNewDiagnosisDate) - }) - - expect(onDiagnosisChangeSpy).toHaveBeenCalledWith({ - diagnosisDate: expectedNewDiagnosisDate.toISOString(), - }) + setup(false, false) + const diagnosisDatePicker = within(screen.getByTestId('diagnosisDateDatePicker')).getByRole( + 'textbox', + ) + userEvent.click(diagnosisDatePicker) + userEvent.type(diagnosisDatePicker, '{backspace}1{enter}') + expect(onDiagnosisChangeSpy).toHaveBeenCalled() }) it('should render a onset date picker', () => { - const { wrapper } = setup() - - const onsetDatePicker = wrapper.findWhere((w) => w.prop('name') === 'onsetDate') - - expect(onsetDatePicker).toHaveLength(1) - expect(onsetDatePicker.prop('patient.diagnoses.onsetDate')) - expect(onsetDatePicker.prop('isRequired')).toBeTruthy() - expect(onsetDatePicker.prop('value')).toEqual(new Date(diagnosis.onsetDate)) + setup() + const onsetDatePicker = within(screen.getByTestId('onsetDateDatePicker')).getByRole('textbox') + const onsetDatePickerLabel = screen.getByText(/patient.diagnoses.onsetDate/i) + expect(onsetDatePicker).toBeInTheDocument() + expect(onsetDatePickerLabel).toBeInTheDocument() + expect(onsetDatePickerLabel.title).toBe('This is a required input') }) it('should call the on change handler when onset date changes', () => { - const expectedNewOnsetDate = addDays(1, new Date().getDate()) - const { wrapper } = setup(false, false) - - const onsetDatePicker = wrapper.findWhere((w) => w.prop('name') === 'onsetDate') - act(() => { - const onChange = onsetDatePicker.prop('onChange') as any - onChange(expectedNewOnsetDate) - }) - - expect(onDiagnosisChangeSpy).toHaveBeenCalledWith({ - onsetDate: expectedNewOnsetDate.toISOString(), - }) + setup(false, false) + const onsetDatePicker = within(screen.getByTestId('onsetDateDatePicker')).getByRole('textbox') + userEvent.click(onsetDatePicker) + userEvent.type(onsetDatePicker, '{backspace}1{enter}') + expect(onDiagnosisChangeSpy).toHaveBeenCalled() }) it('should render a abatement date picker', () => { - const { wrapper } = setup() - - const abatementDatePicker = wrapper.findWhere((w) => w.prop('name') === 'abatementDate') - - expect(abatementDatePicker).toHaveLength(1) - expect(abatementDatePicker.prop('patient.diagnoses.abatementDate')) - expect(abatementDatePicker.prop('isRequired')).toBeTruthy() - expect(abatementDatePicker.prop('value')).toEqual(new Date(diagnosis.abatementDate)) + setup() + const abatementDatePicker = within(screen.getByTestId('abatementDateDatePicker')).getByRole( + 'textbox', + ) + const abatementDatePickerLabel = screen.getByText(/patient.diagnoses.abatementDate/i) + expect(abatementDatePicker).toBeInTheDocument() + expect(abatementDatePickerLabel).toBeInTheDocument() + expect(abatementDatePickerLabel.title).toBe('This is a required input') }) it('should call the on change handler when abatementDate date changes', () => { - const expectedNewAbatementDate = addDays(1, new Date().getDate()) - const { wrapper } = setup(false, false) - - const abatementDatePicker = wrapper.findWhere((w) => w.prop('name') === 'abatementDate') - act(() => { - const onChange = abatementDatePicker.prop('onChange') as any - onChange(expectedNewAbatementDate) - }) - - expect(onDiagnosisChangeSpy).toHaveBeenCalledWith({ - abatementDate: expectedNewAbatementDate.toISOString(), - }) + setup(false, false) + const abatementDatePicker = within(screen.getByTestId('abatementDateDatePicker')).getByRole( + 'textbox', + ) + userEvent.click(abatementDatePicker) + userEvent.type(abatementDatePicker, '{backspace}1{enter}') + expect(onDiagnosisChangeSpy).toHaveBeenCalled() }) it('should render a note input', () => { - const { wrapper } = setup() - - const noteInput = wrapper.findWhere((w) => w.prop('name') === 'note') - expect(noteInput).toHaveLength(1) - expect(noteInput.prop('patient.diagnoses.note')) - expect(noteInput.prop('value')).toEqual(diagnosis.note) + setup() + expect(screen.getByLabelText(/patient.diagnoses.note/i)).toBeInTheDocument() + expect(screen.getByLabelText(/patient.diagnoses.note/i)).toHaveValue(diagnosis.note) }) it('should call the on change handler when note changes', () => { const expectedNewNote = 'some new note' - const { wrapper } = setup(false, false) - - const noteInput = wrapper.findWhere((w) => w.prop('name') === 'note') - act(() => { - const onChange = noteInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedNewNote } }) - }) - + setup(false, false) + const noteInput = screen.getByLabelText(/patient.diagnoses.note/i) + userEvent.type(noteInput, '{selectall}') + userEvent.paste(noteInput, expectedNewNote) expect(onDiagnosisChangeSpy).toHaveBeenCalledWith({ note: expectedNewNote }) }) it('should render all of the fields as disabled if the form is disabled', () => { - const { wrapper } = setup(true) - const nameInput = wrapper.findWhere((w) => w.prop('name') === 'name') - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - const diagnosisDatePicker = wrapper.findWhere((w) => w.prop('name') === 'diagnosisDate') - const onsetDatePicker = wrapper.findWhere((w) => w.prop('name') === 'onsetDate') - const abatementeDatePicker = wrapper.findWhere((w) => w.prop('name') === 'abatementDate') - const noteInput = wrapper.findWhere((w) => w.prop('name') === 'note') - - expect(nameInput.prop('isEditable')).toBeFalsy() - expect(statusSelector.prop('isEditable')).toBeFalsy() - expect(diagnosisDatePicker.prop('isEditable')).toBeFalsy() - expect(abatementeDatePicker.prop('isEditable')).toBeFalsy() - expect(onsetDatePicker.prop('isEditable')).toBeFalsy() - expect(noteInput.prop('isEditable')).toBeFalsy() + setup(true) + const nameInput = screen.getByLabelText(/patient.diagnoses.diagnosisName/i) + const statusSelector = screen.getAllByRole('combobox')[1] + const diagnosisDatePicker = screen.getAllByDisplayValue(format(new Date(), 'MM/dd/yyyy'))[0] + const onsetDatePicker = screen.getAllByDisplayValue(format(new Date(), 'MM/dd/yyyy'))[1] + const abatementDatePicker = within(screen.getByTestId('abatementDateDatePicker')).getByRole( + 'textbox', + ) + const noteInput = screen.getByLabelText(/patient.diagnoses.note/i) + expect(nameInput).toBeDisabled() + expect(statusSelector).toBeDisabled() + expect(diagnosisDatePicker).toBeDisabled() + expect(onsetDatePicker).toBeDisabled() + expect(abatementDatePicker).toBeDisabled() + expect(noteInput).toBeDisabled() }) it('should render the form fields in an error state', () => { @@ -240,35 +228,25 @@ describe('Diagnosis Form', () => { note: 'some note error', } - const { wrapper } = setup(false, false, expectedError) - - const alert = wrapper.find(Alert) - const nameInput = wrapper.findWhere((w) => w.prop('name') === 'name') - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - const diagnosisDatePicker = wrapper.findWhere((w) => w.prop('name') === 'diagnosisDate') - const onsetDatePicker = wrapper.findWhere((w) => w.prop('name') === 'onsetDate') - const abatementDatePicker = wrapper.findWhere((w) => w.prop('name') === 'abatementDate') - - const noteInput = wrapper.findWhere((w) => w.prop('name') === 'note') - - expect(alert).toHaveLength(1) - expect(alert.prop('message')).toEqual(expectedError.message) - - expect(nameInput.prop('isInvalid')).toBeTruthy() - expect(nameInput.prop('feedback')).toEqual(expectedError.name) - - expect(statusSelector.prop('isInvalid')).toBeTruthy() - - expect(diagnosisDatePicker.prop('isInvalid')).toBeTruthy() - expect(diagnosisDatePicker.prop('feedback')).toEqual(expectedError.diagnosisDate) - - expect(onsetDatePicker.prop('isInvalid')).toBeTruthy() - expect(onsetDatePicker.prop('feedback')).toEqual(expectedError.onsetDate) - - expect(abatementDatePicker.prop('isInvalid')).toBeTruthy() - expect(abatementDatePicker.prop('feedback')).toEqual(expectedError.abatementDate) - - expect(noteInput.prop('isInvalid')).toBeTruthy() - expect(noteInput.prop('feedback')).toEqual(expectedError.note) + setup(false, false, expectedError) + const alert = screen.getByRole('alert') + const nameInput = screen.getByLabelText(/patient.diagnoses.diagnosisName/i) + const statusSelector = screen.getAllByRole('combobox')[1] + const diagnosisDatePicker = screen.getAllByDisplayValue(format(new Date(), 'MM/dd/yyyy'))[0] + const onsetDatePicker = screen.getAllByDisplayValue(format(new Date(), 'MM/dd/yyyy'))[1] + const abatementDatePicker = screen.getAllByDisplayValue(format(new Date(), 'MM/dd/yyyy'))[2] + const noteInput = screen.getByLabelText(/patient.diagnoses.note/i) + + expect(alert).toBeInTheDocument() + expect(screen.getByText(expectedError.message)).toBeInTheDocument() + expect(nameInput).toHaveClass('is-invalid') + expect(nameInput.nextSibling).toHaveTextContent(expectedError.name) + expect(statusSelector).toHaveClass('is-invalid') + expect(diagnosisDatePicker).toHaveClass('is-invalid') + expect(onsetDatePicker).toHaveClass('is-invalid') + expect(abatementDatePicker).toHaveClass('is-invalid') + expect(screen.getAllByText(expectedError.diagnosisDate)).toHaveLength(3) + expect(noteInput).toHaveClass('is-invalid') + expect(noteInput.nextSibling).toHaveTextContent(expectedError.note) }) }) diff --git a/src/__tests__/patients/edit/EditPatient.test.tsx b/src/__tests__/patients/edit/EditPatient.test.tsx index ece733f994..cce690b662 100644 --- a/src/__tests__/patients/edit/EditPatient.test.tsx +++ b/src/__tests__/patients/edit/EditPatient.test.tsx @@ -1,55 +1,50 @@ +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import subDays from 'date-fns/subDays' -import { mount } from 'enzyme' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router, Route } from 'react-router-dom' -import createMockStore, { MockStore } from 'redux-mock-store' +import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import * as titleUtil from '../../../page-header/title/TitleContext' import EditPatient from '../../../patients/edit/EditPatient' -import GeneralInformation from '../../../patients/GeneralInformation' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' import { RootState } from '../../../shared/store' const mockStore = createMockStore<RootState, any>([thunk]) -describe('Edit Patient', () => { - const patient = { - id: '123', - prefix: 'prefix', - givenName: 'givenName', - familyName: 'familyName', - suffix: 'suffix', - fullName: 'givenName familyName suffix', - sex: 'male', - type: 'charity', - occupation: 'occupation', - preferredLanguage: 'preferredLanguage', - phoneNumbers: [{ value: '123456789', id: '789' }], - emails: [{ value: 'email@email.com', id: '456' }], - addresses: [{ value: 'address', id: '123' }], - code: 'P00001', - dateOfBirth: subDays(new Date(), 2).toISOString(), - index: 'givenName familyName suffixP00001', - } as Patient - - let history: any - let store: MockStore - - const setup = () => { - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(PatientRepository, 'saveOrUpdate').mockResolvedValue(patient) - jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - - history = createMemoryHistory() - store = mockStore({ patient: { patient } } as any) - - history.push('/patients/edit/123') - const wrapper = mount( +const patient = { + id: '123', + prefix: 'Dr', + givenName: 'Bruce', + familyName: 'Banner', + suffix: 'MD', + fullName: 'Bruce Banner MD', + sex: 'male', + type: 'charity', + occupation: 'The Hulk', + preferredLanguage: 'Hulk lingo', + phoneNumbers: [{ value: '123456789', id: '789' }], + emails: [{ value: 'theHulk@theAvengers.com', id: '456' }], + addresses: [{ value: 'address', id: '123' }], + code: 'P00001', + dateOfBirth: subDays(new Date(), 2).toISOString(), + index: 'Bruce Banner MDP00001', +} as Patient + +const setup = () => { + jest.spyOn(PatientRepository, 'saveOrUpdate').mockResolvedValue(patient) + jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) + + const history = createMemoryHistory({ initialEntries: ['/patients/edit/123'] }) + const store = mockStore({ patient: { patient } } as any) + + return { + history, + ...render( <Provider store={store}> <Router history={history}> <Route path="/patients/edit/:id"> @@ -59,79 +54,48 @@ describe('Edit Patient', () => { </Route> </Router> </Provider>, - ) - - wrapper.find(EditPatient).props().updateTitle = jest.fn() - - wrapper.update() - - return wrapper + ), } +} +describe('Edit Patient', () => { beforeEach(() => { jest.restoreAllMocks() }) - it('should have called the useUpdateTitle hook', async () => { - await act(async () => { - await setup() - }) - expect(titleUtil.useUpdateTitle).toHaveBeenCalled() - }) - it('should render an edit patient form', async () => { - let wrapper: any - await act(async () => { - wrapper = await setup() - }) + setup() - expect(wrapper.find(GeneralInformation)).toHaveLength(1) + expect(await screen.findByLabelText(/patient\.prefix/i)).toBeInTheDocument() }) it('should load a Patient when component loads', async () => { - await act(async () => { - await setup() + setup() + + await waitFor(() => { + expect(PatientRepository.find).toHaveBeenCalledWith(patient.id) }) - expect(PatientRepository.find).toHaveBeenCalledWith(patient.id) + expect(screen.getByPlaceholderText(/patient.givenName/i)).toHaveValue(patient.givenName) }) it('should dispatch updatePatient when save button is clicked', async () => { - let wrapper: any - await act(async () => { - wrapper = await setup() - }) - - wrapper.update() + setup() - const saveButton = wrapper.find('.btn-save').at(0) - const onClick = saveButton.prop('onClick') as any - expect(saveButton.text().trim()).toEqual('patients.updatePatient') + userEvent.click(await screen.findByRole('button', { name: /patients\.updatePatient/i })) - await act(async () => { - await onClick() + await waitFor(() => { + expect(PatientRepository.saveOrUpdate).toHaveBeenCalledWith(patient) }) - - expect(PatientRepository.saveOrUpdate).toHaveBeenCalledWith(patient) }) it('should navigate to /patients/:id when cancel is clicked', async () => { - let wrapper: any - await act(async () => { - wrapper = await setup() - }) + const { history } = setup() - wrapper.update() + userEvent.click(await screen.findByRole('button', { name: /actions\.cancel/i })) - const cancelButton = wrapper.find('.btn-cancel').at(1) - const onClick = cancelButton.prop('onClick') as any - expect(cancelButton.text().trim()).toEqual('actions.cancel') - - act(() => { - onClick() + await waitFor(() => { + expect(history.location.pathname).toEqual('/patients/123') }) - - wrapper.update() - expect(history.location.pathname).toEqual('/patients/123') }) }) diff --git a/src/__tests__/patients/hooks/useAddAllergy.test.tsx b/src/__tests__/patients/hooks/useAddAllergy.test.tsx index d04b8103e1..a58569d9d0 100644 --- a/src/__tests__/patients/hooks/useAddAllergy.test.tsx +++ b/src/__tests__/patients/hooks/useAddAllergy.test.tsx @@ -1,26 +1,25 @@ -/* eslint-disable no-console */ - import useAddAllergy from '../../../patients/hooks/useAddAllergy' import * as validateAllergy from '../../../patients/util/validate-allergy' import PatientRepository from '../../../shared/db/PatientRepository' import Allergy from '../../../shared/model/Allergy' import Patient from '../../../shared/model/Patient' import * as uuid from '../../../shared/util/uuid' +import { expectOneConsoleError } from '../../test-utils/console.utils' import executeMutation from '../../test-utils/use-mutation.util' describe('use add allergy', () => { beforeEach(() => { jest.resetAllMocks() - console.error = jest.fn() }) it('should throw an error if allergy validation fails', async () => { const expectedError = { nameError: 'some error' } + expectOneConsoleError(expectedError) jest.spyOn(validateAllergy, 'default').mockReturnValue(expectedError) jest.spyOn(PatientRepository, 'saveOrUpdate') try { - await executeMutation(() => useAddAllergy(), { patientId: '123', allergy: {} }) + await executeMutation(() => useAddAllergy(), { patientId: '123', allergy: {} as Allergy }) } catch (e) { expect(e).toEqual(expectedError) } diff --git a/src/__tests__/patients/hooks/useAddCareGoal.test.tsx b/src/__tests__/patients/hooks/useAddCareGoal.test.tsx index be5885447e..703fa0edc4 100644 --- a/src/__tests__/patients/hooks/useAddCareGoal.test.tsx +++ b/src/__tests__/patients/hooks/useAddCareGoal.test.tsx @@ -5,6 +5,7 @@ import PatientRepository from '../../../shared/db/PatientRepository' import CareGoal, { CareGoalStatus, CareGoalAchievementStatus } from '../../../shared/model/CareGoal' import Patient from '../../../shared/model/Patient' import * as uuid from '../../../shared/util/uuid' +import { expectOneConsoleError } from '../../test-utils/console.utils' import executeMutation from '../../test-utils/use-mutation.util' describe('use add care goal', () => { @@ -59,12 +60,13 @@ describe('use add care goal', () => { const expectedError = { message: 'patient.careGoal.error.unableToAdd', description: 'some error', - } - jest.spyOn(validateCareGoal, 'default').mockReturnValue(expectedError as CareGoalError) + } as CareGoalError + expectOneConsoleError(expectedError) + jest.spyOn(validateCareGoal, 'default').mockReturnValue(expectedError) jest.spyOn(PatientRepository, 'saveOrUpdate') try { - await executeMutation(() => useAddCareGoal(), { patientId: '123', careGoal: {} }) + await executeMutation(() => useAddCareGoal(), { patientId: '123', careGoal: {} as CareGoal }) } catch (e) { expect(e).toEqual(expectedError) } diff --git a/src/__tests__/patients/hooks/useAddCarePlan.test.tsx b/src/__tests__/patients/hooks/useAddCarePlan.test.tsx index 9df72591c1..c74b5c32d0 100644 --- a/src/__tests__/patients/hooks/useAddCarePlan.test.tsx +++ b/src/__tests__/patients/hooks/useAddCarePlan.test.tsx @@ -5,6 +5,7 @@ import PatientRepository from '../../../shared/db/PatientRepository' import CarePlan, { CarePlanIntent, CarePlanStatus } from '../../../shared/model/CarePlan' import Patient from '../../../shared/model/Patient' import * as uuid from '../../../shared/util/uuid' +import { expectOneConsoleError } from '../../test-utils/console.utils' import executeMutation from '../../test-utils/use-mutation.util' describe('use add care plan', () => { @@ -45,12 +46,16 @@ describe('use add care plan', () => { }) it('should throw an error if validation fails', async () => { - const expectedError = { message: 'patient.carePlan.error.unableToAdd', title: 'some error' } - jest.spyOn(validateCarePlan, 'default').mockReturnValue(expectedError as CarePlanError) + const expectedError = { + message: 'patient.carePlan.error.unableToAdd', + title: 'some error', + } as CarePlanError + expectOneConsoleError(expectedError) + jest.spyOn(validateCarePlan, 'default').mockReturnValue(expectedError) jest.spyOn(PatientRepository, 'saveOrUpdate') try { - await executeMutation(() => useAddCarePlan(), { patientId: '123', carePlan: {} }) + await executeMutation(() => useAddCarePlan(), { patientId: '123', carePlan: {} as CarePlan }) } catch (e) { expect(e).toEqual(expectedError) } diff --git a/src/__tests__/patients/hooks/useAddPatientDiagnosis.test.tsx b/src/__tests__/patients/hooks/useAddPatientDiagnosis.test.tsx index c234ae3144..a63e6dcc67 100644 --- a/src/__tests__/patients/hooks/useAddPatientDiagnosis.test.tsx +++ b/src/__tests__/patients/hooks/useAddPatientDiagnosis.test.tsx @@ -1,26 +1,28 @@ -/* eslint-disable no-console */ - import useAddPatientDiagnosis from '../../../patients/hooks/useAddPatientDiagnosis' import * as validateDiagnosis from '../../../patients/util/validate-diagnosis' import PatientRepository from '../../../shared/db/PatientRepository' import Diagnosis, { DiagnosisStatus } from '../../../shared/model/Diagnosis' import Patient from '../../../shared/model/Patient' import * as uuid from '../../../shared/util/uuid' +import { expectOneConsoleError } from '../../test-utils/console.utils' import executeMutation from '../../test-utils/use-mutation.util' describe('use add diagnosis', () => { beforeEach(() => { jest.resetAllMocks() - console.error = jest.fn() }) it('should throw an error if diagnosis validation fails', async () => { const expectedError = { name: 'some error' } + expectOneConsoleError(expectedError as Error) jest.spyOn(validateDiagnosis, 'default').mockReturnValue(expectedError) jest.spyOn(PatientRepository, 'saveOrUpdate') try { - await executeMutation(() => useAddPatientDiagnosis(), { patientId: '123', note: {} }) + await executeMutation(() => useAddPatientDiagnosis(), { + patientId: '123', + diagnosis: {} as Diagnosis, + }) } catch (e) { expect(e).toEqual(expectedError) } diff --git a/src/__tests__/patients/hooks/useAddPatientNote.test.ts b/src/__tests__/patients/hooks/useAddPatientNote.test.ts index 5444799dcd..1665d8f593 100644 --- a/src/__tests__/patients/hooks/useAddPatientNote.test.ts +++ b/src/__tests__/patients/hooks/useAddPatientNote.test.ts @@ -1,26 +1,25 @@ -/* eslint-disable no-console */ - import useAddPatientNote from '../../../patients/hooks/useAddPatientNote' import * as validateNote from '../../../patients/util/validate-note' import PatientRepository from '../../../shared/db/PatientRepository' import Note from '../../../shared/model/Note' import Patient from '../../../shared/model/Patient' import * as uuid from '../../../shared/util/uuid' +import { expectOneConsoleError } from '../../test-utils/console.utils' import executeMutation from '../../test-utils/use-mutation.util' describe('use add note', () => { beforeEach(() => { jest.resetAllMocks() - console.error = jest.fn() }) it('should throw an error if note validation fails', async () => { const expectedError = { nameError: 'some error' } + expectOneConsoleError(expectedError) jest.spyOn(validateNote, 'default').mockReturnValue(expectedError) jest.spyOn(PatientRepository, 'saveOrUpdate') try { - await executeMutation(() => useAddPatientNote(), { patientId: '123', note: {} }) + await executeMutation(() => useAddPatientNote(), { patientId: '123', note: {} as Note }) } catch (e) { expect(e).toEqual(expectedError) } diff --git a/src/__tests__/patients/hooks/useAddPatientRelatedPerson.test.tsx b/src/__tests__/patients/hooks/useAddPatientRelatedPerson.test.tsx index 6857a00815..59356fea5f 100644 --- a/src/__tests__/patients/hooks/useAddPatientRelatedPerson.test.tsx +++ b/src/__tests__/patients/hooks/useAddPatientRelatedPerson.test.tsx @@ -4,6 +4,7 @@ import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' import RelatedPerson from '../../../shared/model/RelatedPerson' import * as uuid from '../../../shared/util/uuid' +import { expectOneConsoleError } from '../../test-utils/console.utils' import executeMutation from '../../test-utils/use-mutation.util' describe('use add patient related person', () => { @@ -13,13 +14,14 @@ describe('use add patient related person', () => { it('should throw an error if related person not specified', async () => { const expectedError = { relatedPersonError: 'some error' } + expectOneConsoleError(expectedError) jest.spyOn(validateRelatedPerson, 'default').mockReturnValue(expectedError) jest.spyOn(PatientRepository, 'saveOrUpdate') try { await executeMutation(() => useAddPatientRelatedPerson(), { patientId: '123', - relatedPerson: {}, + relatedPerson: {} as RelatedPerson, }) } catch (e) { expect(e).toEqual(expectedError) @@ -30,13 +32,14 @@ describe('use add patient related person', () => { it('should throw an error if the relation type is not specified', async () => { const expectedError = { relationshipTypeError: 'some error' } + expectOneConsoleError(expectedError) jest.spyOn(validateRelatedPerson, 'default').mockReturnValue(expectedError) jest.spyOn(PatientRepository, 'saveOrUpdate') try { await executeMutation(() => useAddPatientRelatedPerson(), { patientId: '123', - relatedPerson: { patientId: '456' }, + relatedPerson: { patientId: '456' } as RelatedPerson, }) } catch (e) { expect(e).toEqual(expectedError) diff --git a/src/__tests__/patients/hooks/useAddPatientVisit.test.tsx b/src/__tests__/patients/hooks/useAddPatientVisit.test.tsx index 091df6fa18..afe5a2883d 100644 --- a/src/__tests__/patients/hooks/useAddPatientVisit.test.tsx +++ b/src/__tests__/patients/hooks/useAddPatientVisit.test.tsx @@ -2,7 +2,8 @@ import useAddVisit from '../../../patients/hooks/useAddVisit' import * as validateVisit from '../../../patients/util/validate-visit' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' -import Visit from '../../../shared/model/Visit' +import Visit, { VisitStatus } from '../../../shared/model/Visit' +import { expectOneConsoleError } from '../../test-utils/console.utils' import executeMutation from '../../test-utils/use-mutation.util' describe('use add visit', () => { @@ -17,14 +18,14 @@ describe('use add visit', () => { const expectedVisit: Visit[] = [ { id: '123', - reason: 'reason for visit', + rev: '1', createdAt: expectedDate.toISOString(), updatedAt: expectedDate.toISOString(), startDateTime: new Date().toISOString(), endDateTime: new Date().toISOString(), type: 'type', - status: 'planned', - reason: 'given reason', + status: VisitStatus.Planned, + reason: 'reason for visit', location: 'give location', }, ] @@ -46,12 +47,14 @@ describe('use add visit', () => { }) it('should throw an error if validation fails', async () => { - const expectedError = { message: 'patient.visit.error.unableToAdd', title: 'some error' } + const expectedError: Error = { message: 'patient.visit.error.unableToAdd', name: 'some error' } + expectOneConsoleError(expectedError) + jest.spyOn(validateVisit, 'default').mockReturnValue(expectedError) jest.spyOn(PatientRepository, 'saveOrUpdate') try { - await executeMutation(() => useAddVisit(), { patientId: '123', visit: {} }) + await executeMutation(() => useAddVisit(), { patientId: '123', visit: {} as Visit }) } catch (e) { expect(e).toEqual(expectedError) } diff --git a/src/__tests__/patients/hooks/useAllergy.test.tsx b/src/__tests__/patients/hooks/useAllergy.test.tsx index 27e9c282e0..a29c0bd119 100644 --- a/src/__tests__/patients/hooks/useAllergy.test.tsx +++ b/src/__tests__/patients/hooks/useAllergy.test.tsx @@ -4,6 +4,17 @@ import Patient from '../../../shared/model/Patient' import executeQuery from '../../test-utils/use-query.util' describe('use allergy', () => { + let errorMock: jest.SpyInstance + + beforeEach(() => { + jest.resetAllMocks() + errorMock = jest.spyOn(console, 'error').mockImplementation() + }) + + afterEach(() => { + errorMock.mockRestore() + }) + it('should return an allergy given a patient id and allergy id', async () => { const expectedPatientId = '123' const expectedAllergy = { id: '456', name: 'some name' } @@ -22,11 +33,17 @@ describe('use allergy', () => { it('should throw an error if patient does not have allergy with id', async () => { const expectedPatientId = '123' const expectedAllergy = { id: '456', name: 'some name' } - const expectedPatient = { id: expectedPatientId, allergies: [expectedAllergy] } as Patient + const expectedPatient = { + id: 'expectedPatientId', + allergies: [{ id: '426', name: 'some name' }], + } as Patient jest.spyOn(PatientRepository, 'find').mockResolvedValueOnce(expectedPatient) try { - await executeQuery(() => useAllergy(expectedPatientId, expectedAllergy.id)) + await executeQuery( + () => useAllergy(expectedPatientId, expectedAllergy.id), + (query) => query.isError, + ) } catch (e) { expect(e).toEqual(new Error('Allergy not found')) } diff --git a/src/__tests__/patients/hooks/useCarePlan.test.tsx b/src/__tests__/patients/hooks/useCarePlan.test.tsx index cf5e762c99..231517c0b1 100644 --- a/src/__tests__/patients/hooks/useCarePlan.test.tsx +++ b/src/__tests__/patients/hooks/useCarePlan.test.tsx @@ -5,6 +5,17 @@ import Patient from '../../../shared/model/Patient' import executeQuery from '../../test-utils/use-query.util' describe('use care plan', () => { + let errorMock: jest.SpyInstance + + beforeEach(() => { + jest.resetAllMocks() + errorMock = jest.spyOn(console, 'error').mockImplementation() + }) + + afterEach(() => { + errorMock.mockRestore() + }) + it('should return a care plan given a patient id and care plan id', async () => { const expectedPatientId = '123' const expectedCarePlan = { id: '456', title: 'some title' } as CarePlan @@ -23,11 +34,17 @@ describe('use care plan', () => { it('should throw an error if patient does not have care plan with id', async () => { const expectedPatientId = '123' const expectedCarePlan = { id: '456', title: 'some title' } as CarePlan - const expectedPatient = { id: expectedPatientId, carePlans: [expectedCarePlan] } as Patient + const expectedPatient = { + id: expectedPatientId, + carePlans: [{ id: '426', title: 'some title' }], + } as Patient jest.spyOn(PatientRepository, 'find').mockResolvedValueOnce(expectedPatient) try { - await executeQuery(() => useCarePlan(expectedPatientId, expectedCarePlan.id)) + await executeQuery( + () => useCarePlan(expectedPatientId, expectedCarePlan.id), + (query) => query.isError, + ) } catch (e) { expect(e).toEqual(new Error('Care Plan not found')) } diff --git a/src/__tests__/patients/hooks/usePatientNote.test.ts b/src/__tests__/patients/hooks/usePatientNote.test.ts index acfbe68f6d..580239c3bd 100644 --- a/src/__tests__/patients/hooks/usePatientNote.test.ts +++ b/src/__tests__/patients/hooks/usePatientNote.test.ts @@ -1,14 +1,18 @@ -/* eslint-disable no-console */ - import usePatientNote from '../../../patients/hooks/usePatientNote' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' import executeQuery from '../../test-utils/use-query.util' describe('use note', () => { + let errorMock: jest.SpyInstance + beforeEach(() => { jest.resetAllMocks() - console.error = jest.fn() + errorMock = jest.spyOn(console, 'error').mockImplementation() + }) + + afterEach(() => { + errorMock.mockRestore() }) it('should return a note given a patient id and note id', async () => { @@ -31,12 +35,15 @@ describe('use note', () => { id: expectedPatientId, notes: [{ id: '426', text: 'eome name', date: '1947-09-09T14:48:00.000Z' }], } as Patient - jest.spyOn(PatientRepository, 'find').mockResolvedValueOnce(expectedPatient) + jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) try { - await executeQuery(() => usePatientNote(expectedPatientId, expectedNote.id)) + await executeQuery( + () => usePatientNote(expectedPatientId, expectedNote.id), + (query) => query.isError, + ) } catch (e) { - expect(e).toEqual(new Error('Timed out in waitFor after 1000ms.')) + expect(e).toEqual(new Error('Note not found')) } }) }) diff --git a/src/__tests__/patients/hooks/usePatientRelatedPersons.test.tsx b/src/__tests__/patients/hooks/usePatientRelatedPersons.test.tsx index bbbd60e450..ac1c161975 100644 --- a/src/__tests__/patients/hooks/usePatientRelatedPersons.test.tsx +++ b/src/__tests__/patients/hooks/usePatientRelatedPersons.test.tsx @@ -1,9 +1,7 @@ -import { act, renderHook } from '@testing-library/react-hooks' - import usePatientRelatedPersons from '../../../patients/hooks/usePatientRelatedPersons' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' -import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util' +import executeQuery from '../../test-utils/use-query.util' describe('use patient related persons', () => { beforeEach(() => { @@ -27,13 +25,7 @@ describe('use patient related persons', () => { } as Patient) .mockResolvedValueOnce(expectedRelatedPersonPatientDetails) - let actualData: any - await act(async () => { - const renderHookResult = renderHook(() => usePatientRelatedPersons(expectedPatientId)) - await waitUntilQueryIsSuccessful(renderHookResult) - const { result } = renderHookResult - actualData = result.current.data - }) + const actualData = await executeQuery(() => usePatientRelatedPersons(expectedPatientId)) expect(PatientRepository.find).toHaveBeenNthCalledWith(1, expectedPatientId) expect(PatientRepository.find).toHaveBeenNthCalledWith(2, expectedRelatedPatientId) diff --git a/src/__tests__/patients/labs/Labs.test.tsx b/src/__tests__/patients/labs/Labs.test.tsx index 8959d4deb4..29dd39c0bc 100644 --- a/src/__tests__/patients/labs/Labs.test.tsx +++ b/src/__tests__/patients/labs/Labs.test.tsx @@ -1,4 +1,4 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' @@ -7,21 +7,36 @@ import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import Labs from '../../../patients/labs/Labs' -import LabsList from '../../../patients/labs/LabsList' import PatientRepository from '../../../shared/db/PatientRepository' +import Lab from '../../../shared/model/Lab' import Patient from '../../../shared/model/Patient' import Permissions from '../../../shared/model/Permissions' import { RootState } from '../../../shared/store' const mockStore = createMockStore<RootState, any>([thunk]) const history = createMemoryHistory() +const expectedLabs = [ + { + id: '456', + rev: '1', + patient: '1234', + requestedOn: new Date(2020, 1, 1, 9, 0, 0, 0).toISOString(), + requestedBy: 'someone', + type: 'lab type', + }, + { + id: '123', + rev: '1', + patient: '1234', + requestedOn: new Date(2020, 1, 1, 9, 0, 0, 0).toISOString(), + requestedBy: 'someone', + type: 'lab type', + }, +] as Lab[] const expectedPatient = ({ id: '123', rev: '123', - labs: [ - { id: '1', type: 'lab type 1' }, - { id: '2', type: 'lab type 2' }, - ], + labs: expectedLabs, } as unknown) as Patient let store: any @@ -33,29 +48,27 @@ const setup = async ( ) => { store = mockStore({ patient: { patient }, user: { permissions } } as any) history.push(route) - - const wrapper = await mount( + return render( <Router history={history}> <Provider store={store}> <Labs patient={patient} /> </Provider> </Router>, ) - return { wrapper: wrapper as ReactWrapper } } describe('Labs', () => { beforeEach(() => { jest.resetAllMocks() jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) + jest.spyOn(PatientRepository, 'getLabs').mockResolvedValue(expectedLabs) jest.spyOn(PatientRepository, 'saveOrUpdate') }) describe('patient labs list', () => { it('should render patient labs', async () => { - const { wrapper } = await setup() - - expect(wrapper.exists(LabsList)).toBeTruthy() + setup() + expect(await screen.findByRole('table')).toBeInTheDocument() }) }) }) diff --git a/src/__tests__/patients/labs/LabsList.test.tsx b/src/__tests__/patients/labs/LabsList.test.tsx index 959ddf8edb..e59c225922 100644 --- a/src/__tests__/patients/labs/LabsList.test.tsx +++ b/src/__tests__/patients/labs/LabsList.test.tsx @@ -1,9 +1,7 @@ -import * as components from '@hospitalrun/components' -import { Table } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, waitFor, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' @@ -25,16 +23,16 @@ const expectedLabs = [ rev: '1', patient: '1234', requestedOn: new Date(2020, 1, 1, 9, 0, 0, 0).toISOString(), - requestedBy: 'someone', - type: 'lab type', + requestedBy: 'Dr Strange', + type: 'Blood type', }, { id: '123', rev: '1', patient: '1234', requestedOn: new Date(2020, 1, 1, 9, 0, 0, 0).toISOString(), - requestedBy: 'someone', - type: 'lab type', + requestedBy: 'Dr Meredith Gray', + type: 'another type ', }, ] as Lab[] @@ -48,71 +46,79 @@ const setup = async (patient = expectedPatient, labs = expectedLabs) => { jest.spyOn(PatientRepository, 'getLabs').mockResolvedValue(labs) store = mockStore({ patient, labs: { labs } } as any) - let wrapper: any - - await act(async () => { - wrapper = await mount( - <Router history={history}> - <Provider store={store}> - <LabsList patient={patient} /> - </Provider> - </Router>, - ) - }) - - wrapper.update() - - return { wrapper: wrapper as ReactWrapper } + return render( + <Router history={history}> + <Provider store={store}> + <LabsList patient={patient} /> + </Provider> + </Router>, + ) } -describe('LabsList', () => { - describe('Table', () => { - it('should render a list of labs', async () => { - const { wrapper } = await setup() - - const table = wrapper.find(Table) - - const columns = table.prop('columns') - const actions = table.prop('actions') as any +describe('Table', () => { + it('should render a list of labs', async () => { + await setup() + await waitFor(() => { + expect(screen.getByRole('table')).toBeInTheDocument() + }) + expect(screen.getAllByRole('columnheader')).toHaveLength(4) + + expect( + screen.getByRole('columnheader', { + name: /labs\.lab\.type/i, + }), + ).toBeInTheDocument() + + expect( + screen.getByRole('columnheader', { + name: /labs\.lab\.requestedon/i, + }), + ).toBeInTheDocument() + + expect( + screen.getByRole('columnheader', { + name: /labs\.lab\.status/i, + }), + ).toBeInTheDocument() + + expect( + screen.getByRole('columnheader', { + name: /actions\.label/i, + }), + ).toBeInTheDocument() + }) + it('should navigate to lab view on lab click', async () => { + let row: any + await setup() - expect(table).toHaveLength(1) + await waitFor(() => { + row = screen.getByRole('row', { + name: /blood type 2020-02-01 09:00 am actions\.view/i, + }) + }) - expect(columns[0]).toEqual(expect.objectContaining({ label: 'labs.lab.type', key: 'type' })) - expect(columns[1]).toEqual( - expect.objectContaining({ label: 'labs.lab.requestedOn', key: 'requestedOn' }), - ) - expect(columns[2]).toEqual( - expect.objectContaining({ - label: 'labs.lab.status', - key: 'status', + await waitFor(() => + userEvent.click( + within(row).getByRole('button', { + name: /actions\.view/i, }), - ) - expect(actions[0]).toEqual(expect.objectContaining({ label: 'actions.view' })) - expect(table.prop('actionsHeaderText')).toEqual('actions.label') - expect(table.prop('data')).toEqual(expectedLabs) - }) + ), + ) - it('should navigate to lab view on lab click', async () => { - const { wrapper } = await setup() - const tr = wrapper.find('tr').at(1) + expect(history.location.pathname).toEqual('/labs/456') + }) +}) - act(() => { - const onClick = tr.find('button').at(0).prop('onClick') as any - onClick({ stopPropagation: jest.fn() }) - }) +describe('no patient labs', () => { + it('should render a warning message if there are no labs', async () => { + await setup(expectedPatient, []) - expect(history.location.pathname).toEqual('/labs/456') + await waitFor(() => { + expect(screen.getByText(/patient\.labs\.warning\.noLabs/i)).toBeInTheDocument() }) - }) - - describe('no patient labs', () => { - it('should render a warning message if there are no labs', async () => { - const { wrapper } = await setup(expectedPatient, []) - const alert = wrapper.find(components.Alert) - expect(alert).toHaveLength(1) - expect(alert.prop('title')).toEqual('patient.labs.warning.noLabs') - expect(alert.prop('message')).toEqual('patient.labs.noLabsMessage') + await waitFor(() => { + expect(screen.getByText(/patient\.labs\.noLabsMessage/i)) }) }) }) diff --git a/src/__tests__/patients/medications/Medications.test.tsx b/src/__tests__/patients/medications/Medications.test.tsx index 37e715be36..a4b82baf34 100644 --- a/src/__tests__/patients/medications/Medications.test.tsx +++ b/src/__tests__/patients/medications/Medications.test.tsx @@ -1,4 +1,4 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' @@ -7,21 +7,30 @@ import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import Medications from '../../../patients/medications/Medications' -import MedicationsList from '../../../patients/medications/MedicationsList' import PatientRepository from '../../../shared/db/PatientRepository' +import Medication from '../../../shared/model/Medication' import Patient from '../../../shared/model/Patient' import Permissions from '../../../shared/model/Permissions' import { RootState } from '../../../shared/store' const mockStore = createMockStore<RootState, any>([thunk]) const history = createMemoryHistory() +const expectedMedications = [ + { + id: '1', + medication: 'medication name', + status: 'active', + }, + { + id: '2', + medication: 'medication name2', + status: 'active', + }, +] as Medication[] const expectedPatient = ({ id: '123', rev: '123', - medications: [ - { id: '1', type: 'medication type 1' }, - { id: '2', type: 'medication type 2' }, - ], + medications: expectedMedications, } as unknown) as Patient let store: any @@ -34,28 +43,33 @@ const setup = async ( store = mockStore({ patient: { patient }, user: { permissions } } as any) history.push(route) - const wrapper = await mount( + return render( <Router history={history}> <Provider store={store}> <Medications patient={patient} /> </Provider> </Router>, ) - return { wrapper: wrapper as ReactWrapper } } describe('Medications', () => { beforeEach(() => { jest.resetAllMocks() jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) + jest.spyOn(PatientRepository, 'getMedications').mockResolvedValue(expectedMedications) jest.spyOn(PatientRepository, 'saveOrUpdate') }) describe('patient medications list', () => { it('should render patient medications', async () => { - const { wrapper } = await setup() - - expect(wrapper.exists(MedicationsList)).toBeTruthy() + setup() + expect(await screen.findByRole('table')).toBeInTheDocument() + expect( + screen.getByRole('cell', { name: expectedMedications[0].medication }), + ).toBeInTheDocument() + expect( + screen.getByRole('cell', { name: expectedMedications[1].medication }), + ).toBeInTheDocument() }) }) }) diff --git a/src/__tests__/patients/medications/MedicationsList.test.tsx b/src/__tests__/patients/medications/MedicationsList.test.tsx index a60859925b..4c4745273f 100644 --- a/src/__tests__/patients/medications/MedicationsList.test.tsx +++ b/src/__tests__/patients/medications/MedicationsList.test.tsx @@ -1,9 +1,7 @@ -import * as components from '@hospitalrun/components' -import { Table } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, waitFor, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' @@ -21,20 +19,20 @@ const expectedPatient = { const expectedMedications = [ { - id: '123456', - rev: '1', - patient: '1234', - requestedOn: new Date(2020, 1, 1, 9, 0, 0, 0).toISOString(), - requestedBy: 'someone', + id: '1234', + medication: 'Ibuprofen', + status: 'active', + intent: 'order', priority: 'routine', + requestedOn: new Date().toISOString(), }, { - id: '456789', - rev: '1', - patient: '1234', - requestedOn: new Date(2020, 1, 1, 9, 0, 0, 0).toISOString(), - requestedBy: 'someone', - priority: 'routine', + id: '2', + medication: 'Hydrocortisone', + status: 'active', + intent: 'reflex order', + priority: 'asap', + requestedOn: new Date().toISOString(), }, ] as Medication[] @@ -48,82 +46,68 @@ const setup = async (patient = expectedPatient, medications = expectedMedication jest.spyOn(PatientRepository, 'getMedications').mockResolvedValue(medications) store = mockStore({ patient, medications: { medications } } as any) - let wrapper: any + return render( + <Router history={history}> + <Provider store={store}> + <MedicationsList patient={patient} /> + </Provider> + </Router>, + ) +} + +describe('Medications table', () => { + it('should render patient medications table headers', async () => { + setup() + + expect(await screen.findByRole('table')).toBeInTheDocument() + const headers = screen.getAllByRole('columnheader') - await act(async () => { - wrapper = await mount( - <Router history={history}> - <Provider store={store}> - <MedicationsList patient={patient} /> - </Provider> - </Router>, - ) + expect(headers[0]).toHaveTextContent(/medications\.medication\.medication/i) + expect(headers[1]).toHaveTextContent(/medications\.medication\.priority/i) + expect(headers[2]).toHaveTextContent(/medications\.medication\.intent/i) + expect(headers[3]).toHaveTextContent(/medications\.medication\.requestedOn/i) + expect(headers[4]).toHaveTextContent(/medications\.medication\.status/i) + expect(headers[5]).toHaveTextContent(/actions\.label/i) }) - wrapper.update() + it('should render patient medication list', async () => { + setup() - return { wrapper: wrapper as ReactWrapper } -} + await screen.findByRole('table') -describe('MedicationsList', () => { - describe('Table', () => { - it('should render a list of medications', async () => { - const { wrapper } = await setup() - - const table = wrapper.find(Table) - - const columns = table.prop('columns') - const actions = table.prop('actions') as any - - expect(table).toHaveLength(1) - - expect(columns[0]).toEqual( - expect.objectContaining({ label: 'medications.medication.medication', key: 'medication' }), - ) - expect(columns[1]).toEqual( - expect.objectContaining({ label: 'medications.medication.priority', key: 'priority' }), - ) - expect(columns[2]).toEqual( - expect.objectContaining({ label: 'medications.medication.intent', key: 'intent' }), - ) - expect(columns[3]).toEqual( - expect.objectContaining({ - label: 'medications.medication.requestedOn', - key: 'requestedOn', - }), - ) - expect(columns[4]).toEqual( - expect.objectContaining({ - label: 'medications.medication.status', - key: 'status', - }), - ) - expect(actions[0]).toEqual(expect.objectContaining({ label: 'actions.view' })) - expect(table.prop('actionsHeaderText')).toEqual('actions.label') - expect(table.prop('data')).toEqual(expectedMedications) - }) + const cells = screen.getAllByRole('cell') + expect(cells[0]).toHaveTextContent('Ibuprofen') + expect(cells[1]).toHaveTextContent('routine') + expect(cells[2]).toHaveTextContent('order') + }) - it('should navigate to medication view on medication click', async () => { - const { wrapper } = await setup() - const tr = wrapper.find('tr').at(1) + it('render an action button', async () => { + setup() - act(() => { - const onClick = tr.find('button').at(0).prop('onClick') as any - onClick({ stopPropagation: jest.fn() }) - }) + await waitFor(() => { + const row = screen.getAllByRole('row') - expect(history.location.pathname).toEqual('/medications/123456') + expect( + within(row[1]).getByRole('button', { + name: /actions\.view/i, + }), + ).toBeInTheDocument() }) }) - describe('no patient medications', () => { - it('should render a warning message if there are no medications', async () => { - const { wrapper } = await setup(expectedPatient, []) - const alert = wrapper.find(components.Alert) + it('should navigate to medication view on medication click', async () => { + setup() + expect(await screen.findByRole('table')).toBeInTheDocument() + userEvent.click(screen.getAllByRole('button', { name: /actions.view/i })[0]) + expect(history.location.pathname).toEqual('/medications/1234') + }) +}) - expect(alert).toHaveLength(1) - expect(alert.prop('title')).toEqual('patient.medications.warning.noMedications') - expect(alert.prop('message')).toEqual('patient.medications.noMedicationsMessage') - }) +describe('no patient medications', () => { + it('should render a warning message if there are no medications', async () => { + setup(expectedPatient, []) + expect(await screen.findByRole('alert')).toBeInTheDocument() + expect(screen.getByText(/patient.medications.warning.noMedications/i)).toBeInTheDocument() + expect(screen.getByText(/patient.medications.noMedicationsMessage/i)).toBeInTheDocument() }) }) diff --git a/src/__tests__/patients/new/DuplicateNewPatientModal.test.tsx b/src/__tests__/patients/new/DuplicateNewPatientModal.test.tsx index 1e305bc373..cd1c131f33 100644 --- a/src/__tests__/patients/new/DuplicateNewPatientModal.test.tsx +++ b/src/__tests__/patients/new/DuplicateNewPatientModal.test.tsx @@ -1,6 +1,5 @@ -import { Modal } from '@hospitalrun/components' -import { act } from '@testing-library/react' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' import createMockStore from 'redux-mock-store' @@ -11,7 +10,7 @@ import { RootState } from '../../../shared/store' const mockStore = createMockStore<RootState, any>([thunk]) -const setupOnClick = (onClose: any, onContinue: any, prop: string) => { +const setup = (onClose: any, onContinue: any) => { const store = mockStore({ patient: { patient: { @@ -20,7 +19,7 @@ const setupOnClick = (onClose: any, onContinue: any, prop: string) => { }, } as any) - const wrapper = mount( + return render( <Provider store={store}> <DuplicateNewPatientModal show @@ -30,51 +29,43 @@ const setupOnClick = (onClose: any, onContinue: any, prop: string) => { /> </Provider>, ) - wrapper.update() - - act(() => { - const modal = wrapper.find(Modal) - const { onClick } = modal.prop(prop) as any - onClick() - }) - - return { wrapper: wrapper as ReactWrapper } } describe('Duplicate New Patient Modal', () => { it('should render a modal with the correct labels', () => { - const store = mockStore({ - patient: { - patient: { - id: '1234', - }, - }, - } as any) - const wrapper = mount( - <Provider store={store}> - <DuplicateNewPatientModal - show - toggle={jest.fn()} - onCloseButtonClick={jest.fn()} - onContinueButtonClick={jest.fn()} - /> - </Provider>, - ) - wrapper.update() - const modal = wrapper.find(Modal) - expect(modal).toHaveLength(1) - expect(modal.prop('title')).toEqual('patients.newPatient') - expect(modal.prop('closeButton')?.children).toEqual('actions.cancel') - expect(modal.prop('closeButton')?.color).toEqual('danger') - expect(modal.prop('successButton')?.children).toEqual('actions.save') - expect(modal.prop('successButton')?.color).toEqual('success') + const onClose = jest.fn + const onContinue = jest.fn + setup(onClose, onContinue) + + expect(screen.getByRole('dialog')).toBeInTheDocument() + expect(screen.getByRole('alert')).toBeInTheDocument() + expect(screen.getByRole('alert')).toHaveClass('alert-warning') + + expect(screen.getByText(/patients\.warning/i)).toBeInTheDocument() + + expect( + screen.getByRole('button', { + name: /actions\.cancel/i, + }), + ).toBeInTheDocument() + + expect( + screen.getByRole('button', { + name: /actions\.save/i, + }), + ).toBeInTheDocument() }) describe('cancel', () => { it('should call the onCloseButtonClick function when the close button is clicked', () => { const onCloseButtonClickSpy = jest.fn() - const closeButtonProp = 'closeButton' - setupOnClick(onCloseButtonClickSpy, jest.fn(), closeButtonProp) + setup(onCloseButtonClickSpy, jest.fn()) + + userEvent.click( + screen.getByRole('button', { + name: /actions\.cancel/i, + }), + ) expect(onCloseButtonClickSpy).toHaveBeenCalledTimes(1) }) }) @@ -82,8 +73,13 @@ describe('Duplicate New Patient Modal', () => { describe('on save', () => { it('should call the onContinueButtonClick function when the continue button is clicked', () => { const onContinueButtonClickSpy = jest.fn() - const continueButtonProp = 'successButton' - setupOnClick(jest.fn(), onContinueButtonClickSpy, continueButtonProp) + setup(jest.fn(), onContinueButtonClickSpy) + + userEvent.click( + screen.getByRole('button', { + name: /actions\.save/i, + }), + ) expect(onContinueButtonClickSpy).toHaveBeenCalledTimes(1) }) }) diff --git a/src/__tests__/patients/new/NewPatient.test.tsx b/src/__tests__/patients/new/NewPatient.test.tsx index 943253d0d7..ec3fd5cf10 100644 --- a/src/__tests__/patients/new/NewPatient.test.tsx +++ b/src/__tests__/patients/new/NewPatient.test.tsx @@ -1,17 +1,16 @@ import * as components from '@hospitalrun/components' -import { mount } from 'enzyme' +import { Toaster } from '@hospitalrun/components' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router, Route } from 'react-router-dom' import createMockStore, { MockStore } from 'redux-mock-store' import thunk from 'redux-thunk' import * as titleUtil from '../../../page-header/title/TitleContext' -import GeneralInformation from '../../../patients/GeneralInformation' import NewPatient from '../../../patients/new/NewPatient' -import * as patientSlice from '../../../patients/patient-slice' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' import { RootState } from '../../../shared/store' @@ -21,22 +20,26 @@ const mockStore = createMockStore<RootState, any>([thunk]) describe('New Patient', () => { const patient = { - givenName: 'first', - fullName: 'first', + id: '123', + givenName: 'givenName', + fullName: 'givenName', + familyName: 'familyName', + sex: 'male', + dateOfBirth: '01/01/2020', } as Patient let history: any let store: MockStore const setup = (error?: any) => { - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) jest.spyOn(PatientRepository, 'save').mockResolvedValue(patient) history = createMemoryHistory() store = mockStore({ patient: { patient: {} as Patient, createError: error } } as any) history.push('/patients/new') - const wrapper = mount( + + return render( <Provider store={store}> <Router history={history}> <Route path="/patients/new"> @@ -45,11 +48,9 @@ describe('New Patient', () => { </TitleProvider> </Route> </Router> + <Toaster draggable hideProgressBar /> </Provider>, ) - - wrapper.update() - return wrapper } beforeEach(() => { @@ -57,116 +58,62 @@ describe('New Patient', () => { }) it('should render a general information form', async () => { - let wrapper: any - await act(async () => { - wrapper = await setup() - }) - - expect(wrapper.find(GeneralInformation)).toHaveLength(1) + setup() + expect(screen.getByText(/patient\.basicInformation/i)) }) it('should pass the error object to general information', async () => { const expectedError = { message: 'some message' } - let wrapper: any - await act(async () => { - wrapper = await setup(expectedError) - }) - wrapper.update() - - const generalInformationForm = wrapper.find(GeneralInformation) - expect(generalInformationForm.prop('error')).toEqual(expectedError) + setup(expectedError) + expect(screen.getByRole('alert')).toHaveTextContent(expectedError.message) }) it('should dispatch createPatient when save button is clicked', async () => { - let wrapper: any - await act(async () => { - wrapper = await setup() - }) - - const generalInformationForm = wrapper.find(GeneralInformation) - - act(() => { - generalInformationForm.prop('onChange')(patient) - }) - - wrapper.update() - - const saveButton = wrapper.find('.btn-save').at(0) - const onClick = saveButton.prop('onClick') as any - expect(saveButton.text().trim()).toEqual('patients.createPatient') - - await act(async () => { - await onClick() - }) - - expect(PatientRepository.save).toHaveBeenCalledWith(patient) - expect(store.getActions()).toContainEqual(patientSlice.createPatientStart()) - expect(store.getActions()).toContainEqual(patientSlice.createPatientSuccess()) + setup() + userEvent.type(screen.getByLabelText(/patient\.givenName/i), patient.givenName as string) + userEvent.click(screen.getByRole('button', { name: /patients\.createPatient/i })) + expect(PatientRepository.save).toHaveBeenCalledWith( + expect.objectContaining({ fullName: patient.fullName, givenName: patient.givenName }), + ) }) - it('should reveal modal (return true) when save button is clicked if an existing patient has the same information', async () => { - let wrapper: any - await act(async () => { - wrapper = await setup() - }) - - const saveButton = wrapper.find('.btn-save').at(0) - const onClick = saveButton.prop('onClick') as any - expect(saveButton.text().trim()).toEqual('patients.createPatient') - - act(() => { - onClick() - }) - wrapper.update() - - expect(onClick()).toEqual(true) - }) + // TODO: https://github.com/HospitalRun/hospitalrun-frontend/pull/2516#issuecomment-753378004 + // it('should reveal modal (return true) when save button is clicked if an existing patient has the same information', async () => { + // const { container } = setup() + // userEvent.type(screen.getByLabelText(/patient\.givenName/i), patient.givenName as string) + // userEvent.type(screen.getByLabelText(/patient\.familyName/i), patient.familyName as string) + // userEvent.type( + // screen.getAllByPlaceholderText('-- Choose --')[0], + // `${patient.sex}{arrowdown}{enter}`, + // ) + // userEvent.type( + // (container.querySelector('.react-datepicker__input-container') as HTMLInputElement) + // .children[0], + // '01/01/2020', + // ) + // userEvent.click(screen.getByRole('button', { name: /patients\.createPatient/i })) + // expect(await screen.findByRole('alert')).toBeInTheDocument() + // expect(screen.getByText(/patients.duplicatePatientWarning/i)).toBeInTheDocument() + // }) it('should navigate to /patients/:id and display a message after a new patient is successfully created', async () => { jest.spyOn(components, 'Toast').mockImplementation(jest.fn()) - let wrapper: any - await act(async () => { - wrapper = await setup() + const { container } = setup() + userEvent.type(screen.getByLabelText(/patient\.givenName/i), patient.givenName as string) + userEvent.click(screen.getByRole('button', { name: /patients\.createPatient/i })) + await waitFor(() => { + expect(history.location.pathname).toEqual(`/patients/${patient.id}`) }) - - const generalInformationForm = wrapper.find(GeneralInformation) - - act(() => { - generalInformationForm.prop('onChange')(patient) + await waitFor(() => { + expect(container.querySelector('.Toastify')).toBeInTheDocument() }) - - wrapper.update() - - const saveButton = wrapper.find('.btn-save').at(0) - const onClick = saveButton.prop('onClick') as any - expect(saveButton.text().trim()).toEqual('patients.createPatient') - - await act(async () => { - await onClick() - }) - - expect(history.location.pathname).toEqual(`/patients/${patient.id}`) - expect(components.Toast).toHaveBeenCalledWith( - 'success', - 'states.success', - `patients.successfullyCreated ${patient.fullName}`, - ) }) it('should navigate to /patients when cancel is clicked', async () => { - let wrapper: any - await act(async () => { - wrapper = await setup() + setup() + userEvent.click(screen.getByRole('button', { name: /actions.cancel/i })) + await waitFor(() => { + expect(history.location.pathname).toEqual('/patients') }) - - const cancelButton = wrapper.find('.btn-cancel').at(0) - const onClick = cancelButton.prop('onClick') as any - expect(cancelButton.text().trim()).toEqual('actions.cancel') - - act(() => { - onClick() - }) - - expect(history.location.pathname).toEqual('/patients') }) }) diff --git a/src/__tests__/patients/notes/NewNoteModal.test.tsx b/src/__tests__/patients/notes/NewNoteModal.test.tsx index 59cf0c35a9..dcc7d7eeca 100644 --- a/src/__tests__/patients/notes/NewNoteModal.test.tsx +++ b/src/__tests__/patients/notes/NewNoteModal.test.tsx @@ -1,14 +1,11 @@ -/* eslint-disable no-console */ - -import { Alert, Modal } from '@hospitalrun/components' -import { act } from '@testing-library/react' -import { mount } from 'enzyme' +import { screen, render, waitFor, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import React from 'react' import NewNoteModal from '../../../patients/notes/NewNoteModal' -import TextFieldWithLabelFormGroup from '../../../shared/components/input/TextFieldWithLabelFormGroup' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' +import { expectOneConsoleError } from '../../test-utils/console.utils' describe('New Note Modal', () => { const mockPatient = { @@ -16,10 +13,11 @@ describe('New Note Modal', () => { givenName: 'someName', } as Patient - const setup = (onCloseSpy = jest.fn()) => { + const onCloseSpy = jest.fn() + const setup = () => { jest.spyOn(PatientRepository, 'saveOrUpdate').mockResolvedValue(mockPatient) jest.spyOn(PatientRepository, 'find').mockResolvedValue(mockPatient) - const wrapper = mount( + return render( <NewNoteModal show onCloseButtonClick={onCloseSpy} @@ -27,101 +25,96 @@ describe('New Note Modal', () => { patientId={mockPatient.id} />, ) - return { wrapper } } - beforeEach(() => { - console.error = jest.fn() - }) + it('should render a modal with the correct labels', async () => { + setup() + + expect(await screen.findByRole('dialog')).toBeInTheDocument() + expect( + screen.getByText(/patient\.notes\.new/i, { selector: 'script, style, button' }), + ).toBeInTheDocument() + + const successButton = screen.getByRole('button', { + name: /patient\.notes\.new/i, + }) + const cancelButton = screen.getByRole('button', { + name: /actions\.cancel/i, + }) - it('should render a modal with the correct labels', () => { - const { wrapper } = setup() - - const modal = wrapper.find(Modal) - expect(modal).toHaveLength(1) - expect(modal.prop('title')).toEqual('patient.notes.new') - expect(modal.prop('closeButton')?.children).toEqual('actions.cancel') - expect(modal.prop('closeButton')?.color).toEqual('danger') - expect(modal.prop('successButton')?.children).toEqual('patient.notes.new') - expect(modal.prop('successButton')?.color).toEqual('success') - expect(modal.prop('successButton')?.icon).toEqual('add') + expect(cancelButton).toHaveClass('btn-danger') + expect(successButton).toHaveClass('btn-success') + expect(within(successButton).getByRole('img')).toHaveAttribute('data-icon', 'plus') }) it('should render a notes rich text editor', () => { - const { wrapper } = setup() + setup() - const noteTextField = wrapper.find(TextFieldWithLabelFormGroup) - expect(noteTextField.prop('label')).toEqual('patient.note') - expect(noteTextField.prop('isRequired')).toBeTruthy() - expect(noteTextField).toHaveLength(1) + expect(screen.getByRole('textbox')).toBeInTheDocument() + expect(within(screen.getByText('patient.note')).getByRole('img')).toHaveAttribute( + 'data-icon', + 'asterisk', + ) }) it('should render note error', async () => { + const expectedErrorMessage = 'patient.notes.error.unableToAdd' const expectedError = { - message: 'patient.notes.error.unableToAdd', - note: 'patient.notes.error.noteRequired', + noteError: 'patient.notes.error.noteRequired', } - const { wrapper } = setup() + expectOneConsoleError(expectedError) - await act(async () => { - const modal = wrapper.find(Modal) - const onSave = (modal.prop('successButton') as any).onClick - await onSave({} as React.MouseEvent<HTMLButtonElement>) - }) - wrapper.update() - const alert = wrapper.find(Alert) - const noteTextField = wrapper.find(TextFieldWithLabelFormGroup) - - expect(alert.prop('title')).toEqual('states.error') - expect(alert.prop('message')).toEqual(expectedError.message) - expect(noteTextField.prop('isInvalid')).toBeTruthy() - expect(noteTextField.prop('feedback')).toEqual(expectedError.note) + setup() + + userEvent.click( + screen.getByRole('button', { + name: /patient\.notes\.new/i, + }), + ) + + expect(await screen.findByRole('alert')).toBeInTheDocument() + expect(screen.getByText(/states.error/i)).toBeInTheDocument() + expect(screen.getByText(expectedErrorMessage)).toBeInTheDocument() + expect(screen.getByText(expectedError.noteError)).toBeInTheDocument() + expect(screen.getByRole('textbox')).toHaveClass('is-invalid') }) describe('on cancel', () => { - it('should call the onCloseButtonCLick function when the cancel button is clicked', () => { - const onCloseButtonClickSpy = jest.fn() - const { wrapper } = setup(onCloseButtonClickSpy) - - act(() => { - const modal = wrapper.find(Modal) - const { onClick } = modal.prop('closeButton') as any - onClick() - }) + it('should call the onCloseButtonCLick function when the cancel button is clicked', async () => { + setup() - expect(onCloseButtonClickSpy).toHaveBeenCalledTimes(1) + userEvent.click( + screen.getByRole('button', { + name: /actions\.cancel/i, + }), + ) + await waitFor(() => expect(onCloseSpy).toHaveBeenCalledTimes(1)) }) }) describe('on save', () => { it('should dispatch add note', async () => { const expectedNote = 'some note' - const { wrapper } = setup() - const noteTextField = wrapper.find(TextFieldWithLabelFormGroup) - - await act(async () => { - const onChange = noteTextField.prop('onChange') as any - await onChange({ currentTarget: { value: expectedNote } }) - }) + setup() - wrapper.update() + const noteTextField = screen.getByRole('textbox') + userEvent.type(noteTextField, expectedNote) - await act(async () => { - const modal = wrapper.find(Modal) - const onSave = (modal.prop('successButton') as any).onClick - await onSave({} as React.MouseEvent<HTMLButtonElement>) - wrapper.update() + userEvent.click( + screen.getByRole('button', { + name: /patient\.notes\.new/i, + }), + ) + await waitFor(() => { + expect(PatientRepository.saveOrUpdate).toHaveBeenCalledTimes(1) }) - - expect(PatientRepository.saveOrUpdate).toHaveBeenCalledTimes(1) expect(PatientRepository.saveOrUpdate).toHaveBeenCalledWith( expect.objectContaining({ notes: [expect.objectContaining({ text: expectedNote })], }), ) - // Does the form reset value back to blank? - expect(noteTextField.prop('value')).toEqual('') + expect(noteTextField).toHaveValue('') }) }) }) diff --git a/src/__tests__/patients/notes/NotesList.test.tsx b/src/__tests__/patients/notes/NotesList.test.tsx index 860572c139..bffd8f81be 100644 --- a/src/__tests__/patients/notes/NotesList.test.tsx +++ b/src/__tests__/patients/notes/NotesList.test.tsx @@ -1,8 +1,7 @@ -import { Alert, List, ListItem } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { screen, render, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Router } from 'react-router-dom' import NotesList from '../../../patients/notes/NotesList' @@ -11,22 +10,20 @@ import Note from '../../../shared/model/Note' import Patient from '../../../shared/model/Patient' describe('Notes list', () => { - const setup = async (notes: Note[]) => { + const setup = (notes: Note[]) => { const mockPatient = { id: '123', notes } as Patient jest.spyOn(PatientRepository, 'find').mockResolvedValueOnce(mockPatient) const history = createMemoryHistory() history.push(`/patients/${mockPatient.id}/notes`) - let wrapper: any - await act(async () => { - wrapper = await mount( + + return { + history, + ...render( <Router history={history}> <NotesList patientId={mockPatient.id} /> </Router>, - ) - }) - - wrapper.update() - return { wrapper: wrapper as ReactWrapper, history } + ), + } } it('should render a list of notes', async () => { @@ -36,39 +33,49 @@ describe('Notes list', () => { text: 'some name', date: '1947-09-09T14:48:00.000Z', }, + { + id: '457', + text: 'some name', + date: '1947-09-10T14:48:00.000Z', + }, ] - const { wrapper } = await setup(expectedNotes) - const listItems = wrapper.find(ListItem) + setup(expectedNotes) - expect(wrapper.exists(List)).toBeTruthy() - expect(listItems).toHaveLength(expectedNotes.length) - expect(listItems.at(0).find('.ref__note-item-date').text().length) - expect(listItems.at(0).find('.ref__note-item-text').text()).toEqual(expectedNotes[0].text) + const dateString = new Date(expectedNotes[0].date).toLocaleString() + await waitFor(() => { + expect( + screen.getByRole('button', { + name: `${dateString} some name`, + }), + ).toBeInTheDocument() + }) + + expect(screen.getAllByRole('listitem')).toHaveLength(2) }) it('should display a warning when no notes are present', async () => { const expectedNotes: Note[] = [] - const { wrapper } = await setup(expectedNotes) - - const alert = wrapper.find(Alert) + setup(expectedNotes) - expect(wrapper.exists(Alert)).toBeTruthy() - expect(wrapper.exists(List)).toBeFalsy() + await waitFor(() => { + expect(screen.getByRole('alert')).toBeInTheDocument() + }) - expect(alert.prop('color')).toEqual('warning') - expect(alert.prop('title')).toEqual('patient.notes.warning.noNotes') - expect(alert.prop('message')).toEqual('patient.notes.addNoteAbove') + expect(screen.getByText(/patient\.notes\.warning\.nonotes/i)).toBeInTheDocument() + expect(screen.getByText(/patient\.notes\.addnoteabove/i)).toBeInTheDocument() }) it('should navigate to the note view when the note is clicked', async () => { const expectedNotes = [{ id: '456', text: 'some name', date: '1947-09-09T14:48:00.000Z' }] - const { wrapper, history } = await setup(expectedNotes) - const item = wrapper.find(ListItem) - act(() => { - const onClick = item.prop('onClick') as any - onClick({ stopPropagation: jest.fn() }) + const { history } = setup(expectedNotes) + + const dateString = new Date(expectedNotes[0].date).toLocaleString() + const item = await screen.findByRole('button', { + name: `${dateString} some name`, }) + userEvent.click(item) + expect(history.location.pathname).toEqual(`/patients/123/notes/${expectedNotes[0].id}`) }) }) diff --git a/src/__tests__/patients/notes/NotesTab.test.tsx b/src/__tests__/patients/notes/NotesTab.test.tsx index e91eae2d17..446e047692 100644 --- a/src/__tests__/patients/notes/NotesTab.test.tsx +++ b/src/__tests__/patients/notes/NotesTab.test.tsx @@ -1,11 +1,8 @@ -/* eslint-disable no-console */ - -import * as components from '@hospitalrun/components' -import { mount } from 'enzyme' +import { screen, render } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import assign from 'lodash/assign' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' @@ -43,18 +40,13 @@ const setup = (props: any = {}) => { user = { permissions } store = mockStore({ patient, user } as any) history.push(route) - let wrapper: any - act(() => { - wrapper = mount( - <Router history={history}> - <Provider store={store}> - <NoteTab patient={patient} /> - </Provider> - </Router>, - ) - }) - - return wrapper + return render( + <Router history={history}> + <Provider store={store}> + <NoteTab patient={patient} /> + </Provider> + </Router>, + ) } describe('Notes Tab', () => { @@ -62,43 +54,38 @@ describe('Notes Tab', () => { beforeEach(() => { jest.resetAllMocks() jest.spyOn(PatientRepository, 'saveOrUpdate') - console.error = jest.fn() }) it('should render a add notes button', () => { - const wrapper = setup() + setup() - const addNoteButton = wrapper.find(components.Button) - expect(addNoteButton).toHaveLength(1) - expect(addNoteButton.text().trim()).toEqual('patient.notes.new') + expect(screen.getByRole('button', { name: /patient\.notes\.new/i })).toBeInTheDocument() }) it('should not render a add notes button if the user does not have permissions', () => { - const wrapper = setup({ permissions: [] }) + setup({ permissions: [] }) - const addNotesButton = wrapper.find(components.Button) - expect(addNotesButton).toHaveLength(0) + expect(screen.queryByRole('button', { name: /patient\.notes\.new/i })).not.toBeInTheDocument() }) it('should open the Add Notes Modal', () => { - const wrapper = setup() - act(() => { - const onClick = wrapper.find(components.Button).prop('onClick') as any - onClick() - }) - wrapper.update() - - expect(wrapper.find(components.Modal).prop('show')).toBeTruthy() + setup() + + expect(screen.queryByRole('dialog')).not.toBeInTheDocument() + + const addButton = screen.getByRole('button', { name: /patient\.notes\.new/i }) + userEvent.click(addButton) + + expect(screen.getByRole('dialog')).toBeInTheDocument() }) }) describe('/patients/:id/notes', () => { it('should render the view notes screen when /patients/:id/notes is accessed', () => { const route = '/patients/123/notes' const permissions = [Permissions.WritePatients] - const wrapper = setup({ route, permissions }) - act(() => { - expect(wrapper.exists(NoteTab)).toBeTruthy() - }) + setup({ route, permissions }) + + expect(screen.getByText(/patient\.notes\.new/i)).toBeInTheDocument() }) }) }) diff --git a/src/__tests__/patients/notes/ViewNote.test.tsx b/src/__tests__/patients/notes/ViewNote.test.tsx index 74172470c7..2a6a22edd4 100644 --- a/src/__tests__/patients/notes/ViewNote.test.tsx +++ b/src/__tests__/patients/notes/ViewNote.test.tsx @@ -1,46 +1,34 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Route, Router } from 'react-router-dom' import ViewNote from '../../../patients/notes/ViewNote' -import TextInputWithLabelFormGroup from '../../../shared/components/input/TextInputWithLabelFormGroup' import PatientRepository from '../../../shared/db/PatientRepository' +import Note from '../../../shared/model/Note' import Patient from '../../../shared/model/Patient' describe('View Note', () => { const patient = { id: 'patientId', - notes: [{ id: '123', text: 'some name', date: '1947-09-09T14:48:00.000Z' }], + notes: [{ id: '123', text: 'some name', date: '1947-09-09T14:48:00.000Z' }] as Note[], } as Patient - const setup = async () => { + it('should render a note input with the correct data', async () => { jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - const history = createMemoryHistory() - history.push(`/patients/${patient.id}/notes/${patient.notes![0].id}`) - let wrapper: any - - await act(async () => { - wrapper = await mount( - <Router history={history}> - <Route path="/patients/:id/notes/:noteId"> - <ViewNote /> - </Route> - </Router>, - ) + const history = createMemoryHistory({ + initialEntries: [`/patients/${patient.id}/notes/${(patient.notes as Note[])[0].id}`], }) - wrapper.update() - - return { wrapper: wrapper as ReactWrapper } - } - - it('should render a note input with the correct data', async () => { - const { wrapper } = await setup() + render( + <Router history={history}> + <Route path="/patients/:id/notes/:noteId"> + <ViewNote /> + </Route> + </Router>, + ) - const noteText = wrapper.find(TextInputWithLabelFormGroup) - expect(noteText).toHaveLength(1) - expect(noteText.prop('value')).toEqual(patient.notes![0].text) + const input = await screen.findByLabelText(/patient.note/i) + expect(input).toHaveValue((patient.notes as Note[])[0].text) }) }) diff --git a/src/__tests__/patients/related-persons/AddRelatedPersonModal.test.tsx b/src/__tests__/patients/related-persons/AddRelatedPersonModal.test.tsx deleted file mode 100644 index 5069f8ed24..0000000000 --- a/src/__tests__/patients/related-persons/AddRelatedPersonModal.test.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { Modal, Alert, Typeahead } from '@hospitalrun/components' -import { act } from '@testing-library/react' -import { mount } from 'enzyme' -import React from 'react' - -import AddRelatedPersonModal from '../../../patients/related-persons/AddRelatedPersonModal' -import TextInputWithLabelFormGroup from '../../../shared/components/input/TextInputWithLabelFormGroup' -import PatientRepository from '../../../shared/db/PatientRepository' -import Patient from '../../../shared/model/Patient' - -describe('Add Related Person Modal', () => { - const patient = { - id: '123', - prefix: 'prefix', - givenName: 'givenName', - familyName: 'familyName', - suffix: 'suffix', - sex: 'male', - type: 'charity', - occupation: 'occupation', - preferredLanguage: 'preferredLanguage', - phoneNumber: 'phoneNumber', - email: 'email@email.com', - address: 'address', - code: 'P00001', - dateOfBirth: new Date().toISOString(), - } as Patient - - const setup = () => { - jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - jest.spyOn(PatientRepository, 'saveOrUpdate').mockResolvedValue(patient) - - return mount( - <AddRelatedPersonModal - show - patientId={patient.id} - onCloseButtonClick={jest.fn()} - toggle={jest.fn()} - />, - ) - } - - describe('layout', () => { - it('should render a modal', () => { - const wrapper = setup() - const modal = wrapper.find(Modal) - expect(modal).toHaveLength(1) - expect(modal.prop('show')).toBeTruthy() - }) - - it('should render a patient search typeahead', () => { - const wrapper = setup() - const patientSearchTypeahead = wrapper.find(Typeahead) - - expect(patientSearchTypeahead).toHaveLength(1) - expect(patientSearchTypeahead.prop('placeholder')).toEqual('patient.relatedPerson') - }) - - it('should render a relationship type text input', () => { - const wrapper = setup() - const relationshipTypeTextInput = wrapper.findWhere((w: any) => w.prop('name') === 'type') - - expect(relationshipTypeTextInput).toHaveLength(1) - expect(relationshipTypeTextInput.type()).toBe(TextInputWithLabelFormGroup) - expect(relationshipTypeTextInput.prop('name')).toEqual('type') - expect(relationshipTypeTextInput.prop('isEditable')).toBeTruthy() - expect(relationshipTypeTextInput.prop('label')).toEqual( - 'patient.relatedPersons.relationshipType', - ) - }) - - it('should render a cancel button', () => { - const wrapper = setup() - const cancelButton = wrapper.findWhere( - (w: { text: () => string }) => w.text() === 'actions.cancel', - ) - - expect(cancelButton).toHaveLength(1) - }) - - it('should render an add new related person button button', () => { - const wrapper = setup() - const modal = wrapper.find(Modal) as any - expect(modal.prop('successButton').children).toEqual('patient.relatedPersons.add') - }) - - it('should render the error when there is an error saving', async () => { - const wrapper = setup() - const expectedError = { - message: 'patient.relatedPersons.error.unableToAddRelatedPerson', - relatedPersonError: 'patient.relatedPersons.error.relatedPersonRequired', - relationshipTypeError: 'patient.relatedPersons.error.relationshipTypeRequired', - } - - await act(async () => { - const modal = wrapper.find(Modal) - const onSave = (modal.prop('successButton') as any).onClick - await onSave({} as React.MouseEvent<HTMLButtonElement>) - }) - wrapper.update() - - const alert = wrapper.find(Alert) - const typeahead = wrapper.find(Typeahead) - const relationshipTypeInput = wrapper.find(TextInputWithLabelFormGroup) - - expect(alert.prop('message')).toEqual(expectedError.message) - expect(alert.prop('title')).toEqual('states.error') - expect(typeahead.prop('isInvalid')).toBeTruthy() - expect(relationshipTypeInput.prop('isInvalid')).toBeTruthy() - expect(relationshipTypeInput.prop('feedback')).toEqual(expectedError.relationshipTypeError) - }) - }) - - describe('save', () => { - it('should call the save function with the correct data', async () => { - const wrapper = setup() - act(() => { - const patientTypeahead = wrapper.find(Typeahead) - patientTypeahead.prop('onChange')([{ id: '123' }]) - }) - wrapper.update() - - act(() => { - const relationshipTypeTextInput = wrapper.findWhere((w: any) => w.prop('name') === 'type') - relationshipTypeTextInput.prop('onChange')({ target: { value: 'relationship' } }) - }) - wrapper.update() - - await act(async () => { - const { onClick } = wrapper.find(Modal).prop('successButton') as any - await onClick({} as React.MouseEvent<HTMLButtonElement, MouseEvent>) - }) - - expect(PatientRepository.saveOrUpdate).toHaveBeenCalledTimes(1) - expect(PatientRepository.saveOrUpdate).toHaveBeenCalledWith( - expect.objectContaining({ - relatedPersons: [ - expect.objectContaining({ - patientId: '123', - type: 'relationship', - }), - ], - }), - ) - }) - }) -}) diff --git a/src/__tests__/patients/related-persons/RelatedPersonsTab.test.tsx b/src/__tests__/patients/related-persons/RelatedPersonsTab.test.tsx index 958d2cc6d0..0d9e753399 100644 --- a/src/__tests__/patients/related-persons/RelatedPersonsTab.test.tsx +++ b/src/__tests__/patients/related-persons/RelatedPersonsTab.test.tsx @@ -1,217 +1,242 @@ -import * as components from '@hospitalrun/components' -import { Table } from '@hospitalrun/components' -import { act } from '@testing-library/react' -import { mount } from 'enzyme' +import { render, screen, within, waitFor, waitForElementToBeRemoved } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' -import { Router } from 'react-router-dom' +import { Route, Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' -import AddRelatedPersonModal from '../../../patients/related-persons/AddRelatedPersonModal' import RelatedPersonTab from '../../../patients/related-persons/RelatedPersonTab' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' import Permissions from '../../../shared/model/Permissions' +import RelatedPerson from '../../../shared/model/RelatedPerson' import { RootState } from '../../../shared/store' +import { expectOneConsoleError } from '../../test-utils/console.utils' const mockStore = createMockStore<RootState, any>([thunk]) -describe('Related Persons Tab', () => { - let wrapper: any - let history = createMemoryHistory() +const setup = ({ + permissions = [Permissions.WritePatients, Permissions.ReadPatients], + patientOverrides = {}, +}: { + permissions?: Permissions[] + patientOverrides?: Partial<Patient> +} = {}) => { + const expectedPatient = { + id: '123', + rev: '123', + ...patientOverrides, + } as Patient + const expectedRelatedPerson = { + givenName: 'Related', + familyName: 'Patient', + id: '123001', + } as Patient + const newRelatedPerson = { + id: 'patient2', + fullName: 'fullName2', + givenName: 'Patient', + familyName: 'PatientFamily', + code: 'code2', + } as Patient + + jest.spyOn(PatientRepository, 'find').mockImplementation(async (id: string) => { + if (id === expectedRelatedPerson.id) { + return expectedRelatedPerson + } + if (id === newRelatedPerson.id) { + return newRelatedPerson + } + return expectedPatient + }) + jest.spyOn(PatientRepository, 'saveOrUpdate').mockResolvedValue(expectedPatient) + jest.spyOn(PatientRepository, 'getLabs').mockResolvedValue([]) + jest.spyOn(PatientRepository, 'search').mockResolvedValue([newRelatedPerson]) + jest.spyOn(PatientRepository, 'count').mockResolvedValue(1) + + const history = createMemoryHistory({ initialEntries: ['/patients/123/relatedpersons'] }) + const store = mockStore({ + user: { + permissions, + }, + patient: {}, + } as any) + + return { + expectedPatient, + expectedRelatedPerson, + newRelatedPerson, + history, + ...render( + <Provider store={store}> + <Router history={history}> + <Route path="/patients/:id"> + <RelatedPersonTab patient={expectedPatient} /> + </Route> + </Router> + </Provider>, + ), + } +} +describe('Related Persons Tab', () => { describe('Add New Related Person', () => { - let patient: any - let user: any - jest.spyOn(components, 'Toast') + it('should render a New Related Person button', async () => { + setup() - beforeEach(() => { - jest.resetAllMocks() - history = createMemoryHistory() + expect(await screen.findByRole('button', { name: /patient\.relatedPersons\.add/i })) + }) - patient = { - id: '123', - rev: '123', - } as Patient + it('should not render a New Related Person button if the user does not have write privileges for a patient', async () => { + const { container } = setup({ permissions: [Permissions.ReadPatients] }) - jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - jest.spyOn(PatientRepository, 'saveOrUpdate').mockResolvedValue(patient) - jest.spyOn(PatientRepository, 'getLabs').mockResolvedValue([]) + // wait for spinner to disappear + await waitForElementToBeRemoved(container.querySelector(`[class^='css']`)) - user = { - permissions: [Permissions.WritePatients, Permissions.ReadPatients], - } - act(() => { - wrapper = mount( - <Router history={history}> - <Provider store={mockStore({ patient, user } as any)}> - <RelatedPersonTab patient={patient} /> - </Provider> - </Router>, - ) - }) + expect( + screen.queryByRole('button', { name: /patient\.relatedPersons\.add/i }), + ).not.toBeInTheDocument() }) - it('should render a New Related Person button', () => { - const newRelatedPersonButton = wrapper.find(components.Button) + it('should show the New Related Person modal when the New Related Person button is clicked', async () => { + setup() - expect(newRelatedPersonButton).toHaveLength(1) - expect(newRelatedPersonButton.text().trim()).toEqual('patient.relatedPersons.add') + userEvent.click(await screen.findByRole('button', { name: /patient\.relatedPersons\.add/i })) + + expect(await screen.findByRole('dialog')).toBeInTheDocument() }) - it('should not render a New Related Person button if the user does not have write privileges for a patient', () => { - user = { permissions: [Permissions.ReadPatients] } - act(() => { - wrapper = mount( - <Router history={history}> - <Provider store={mockStore({ patient, user } as any)}> - <RelatedPersonTab patient={patient} /> - </Provider> - </Router>, - ) - }) - const newRelatedPersonButton = wrapper.find(components.Button) - expect(newRelatedPersonButton).toHaveLength(0) + it('should render a modal with expected input fields', async () => { + setup() + + userEvent.click(await screen.findByRole('button', { name: /patient\.relatedPersons\.add/i })) + const modal = await screen.findByRole('dialog') + + expect(modal).toBeInTheDocument() + expect(within(modal).getByPlaceholderText(/^patient.relatedPerson$/i)).toBeInTheDocument() + + const relationshipTypeInput = within(modal).getByLabelText( + /^patient.relatedPersons.relationshipType$/i, + ) + expect(relationshipTypeInput).toBeInTheDocument() + expect(relationshipTypeInput).not.toBeDisabled() + expect(within(modal).getByRole('button', { name: /close/i })).toBeInTheDocument() + expect( + within(modal).getByRole('button', { name: /patient.relatedPersons.add/i }), + ).toBeInTheDocument() }) - it('should render a New Related Person modal', () => { - const newRelatedPersonModal = wrapper.find(AddRelatedPersonModal) + it('should render the error when there is an error saving', async () => { + setup() - expect(newRelatedPersonModal.prop('show')).toBeFalsy() - expect(newRelatedPersonModal).toHaveLength(1) + userEvent.click(await screen.findByRole('button', { name: /patient\.relatedPersons\.add/i })) + const modal = await screen.findByRole('dialog') + const expectedErrorMessage = 'patient.relatedPersons.error.unableToAddRelatedPerson' + const expectedError = { + relatedPersonError: 'patient.relatedPersons.error.relatedPersonRequired', + relationshipTypeError: 'patient.relatedPersons.error.relationshipTypeRequired', + } + expectOneConsoleError(expectedError) + + userEvent.click(within(modal).getByRole('button', { name: /patient.relatedPersons.add/i })) + expect(await screen.findByRole('alert')).toBeInTheDocument() + expect(screen.getByText(expectedErrorMessage)).toBeInTheDocument() + expect(screen.getByText(/states.error/i)).toBeInTheDocument() + expect(screen.getByPlaceholderText(/^patient.relatedPerson$/i)).toHaveClass('is-invalid') + expect(screen.getByLabelText(/^patient.relatedPersons.relationshipType$/i)).toHaveClass( + 'is-invalid', + ) + expect(screen.getByText(expectedError.relatedPersonError)).toBeInTheDocument() + expect(screen.getByText(expectedError.relationshipTypeError)).toBeInTheDocument() }) - it('should show the New Related Person modal when the New Related Person button is clicked', () => { - const newRelatedPersonButton = wrapper.find(components.Button) + it('should add a related person to the table with the correct data', async () => { + const { newRelatedPerson } = setup() - act(() => { - const onClick = newRelatedPersonButton.prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByRole('button', { name: /patient\.relatedPersons\.add/i })) + const modal = await screen.findByRole('dialog') - wrapper.update() + userEvent.type( + within(modal).getByPlaceholderText(/^patient.relatedPerson$/i), + newRelatedPerson.fullName as string, + ) - const newRelatedPersonModal = wrapper.find(AddRelatedPersonModal) - expect(newRelatedPersonModal.prop('show')).toBeTruthy() - }) + userEvent.click(await within(modal).findByText(/^fullname2/i)) + + userEvent.type( + within(modal).getByLabelText(/^patient.relatedPersons.relationshipType$/i), + 'new relationship', + ) + + userEvent.click(within(modal).getByRole('button', { name: /patient.relatedPersons.add/i })) + + await waitFor(() => { + expect(screen.getByRole('cell', { name: newRelatedPerson.familyName })).toBeInTheDocument() + expect(screen.getByRole('cell', { name: newRelatedPerson.givenName })).toBeInTheDocument() + expect(screen.getByRole('cell', { name: /new relationship/i })).toBeInTheDocument() + }) + }, 30000) }) describe('Table', () => { - const patient = { - id: '123', - rev: '123', - relatedPersons: [{ patientId: '123001', type: 'type' }], - } as Patient - const expectedRelatedPerson = { - givenName: 'test', - familyName: 'test', - fullName: 'test test', - id: '123001', - type: 'type', - } as Patient - - const user = { - permissions: [Permissions.WritePatients, Permissions.ReadPatients], + const relationShipType = 'Sibling' + const patientOverrides = { + relatedPersons: [{ patientId: '123001', type: relationShipType } as RelatedPerson], } - beforeEach(async () => { - jest.spyOn(PatientRepository, 'saveOrUpdate') - jest - .spyOn(PatientRepository, 'find') - .mockResolvedValueOnce(patient) - .mockResolvedValueOnce(expectedRelatedPerson) - - await act(async () => { - wrapper = await mount( - <Router history={history}> - <Provider store={mockStore({ patient, user } as any)}> - <RelatedPersonTab patient={patient} /> - </Provider> - </Router>, - ) - }) - wrapper.update() - }) + it('should render a list of related persons with their full name being displayed', async () => { + const { expectedRelatedPerson } = setup({ patientOverrides }) - it('should render a list of related persons with their full name being displayed', () => { - const table = wrapper.find(Table) - const columns = table.prop('columns') - const actions = table.prop('actions') as any - expect(columns[0]).toEqual( - expect.objectContaining({ label: 'patient.givenName', key: 'givenName' }), - ) - expect(columns[1]).toEqual( - expect.objectContaining({ label: 'patient.familyName', key: 'familyName' }), - ) - expect(columns[2]).toEqual( - expect.objectContaining({ - label: 'patient.relatedPersons.relationshipType', - key: 'type', - }), - ) + expect(await screen.findByRole('table')).toBeInTheDocument() + + expect(screen.getByRole('columnheader', { name: /patient\.givenName/i })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /patient\.familyName/i })).toBeInTheDocument() + expect( + screen.getByRole('columnheader', { name: /patient\.relatedPersons\.relationshipType/i }), + ).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /actions\.label/i })).toBeInTheDocument() - expect(actions[0]).toEqual(expect.objectContaining({ label: 'actions.view' })) - expect(actions[1]).toEqual(expect.objectContaining({ label: 'actions.delete' })) - expect(table.prop('actionsHeaderText')).toEqual('actions.label') - expect(table.prop('data')).toEqual([expectedRelatedPerson]) + expect( + screen.getByRole('cell', { name: expectedRelatedPerson.givenName as string }), + ).toBeInTheDocument() + expect( + screen.getByRole('cell', { name: expectedRelatedPerson.familyName as string }), + ).toBeInTheDocument() + expect(screen.getByRole('cell', { name: relationShipType })).toBeInTheDocument() + + expect(screen.getByRole('button', { name: /actions\.view/i })).toBeInTheDocument() + expect(screen.getByRole('button', { name: /actions\.delete/i })).toBeInTheDocument() }) it('should remove the related person when the delete button is clicked', async () => { - const removeRelatedPersonSpy = jest.spyOn(PatientRepository, 'saveOrUpdate') - const tr = wrapper.find('tr').at(1) + setup({ patientOverrides }) - await act(async () => { - const onClick = tr.find('button').at(1).prop('onClick') as any - await onClick({ stopPropagation: jest.fn() }) - }) - expect(removeRelatedPersonSpy).toHaveBeenCalledWith({ ...patient, relatedPersons: [] }) + userEvent.click(await screen.findByRole('button', { name: /actions\.delete/i })) + + expect( + await screen.findByText(/patient\.relatedPersons\.warning\.noRelatedPersons/i), + ).toBeInTheDocument() }) it('should navigate to related person patient profile on related person click', async () => { - const tr = wrapper.find('tr').at(1) + const { history } = setup({ patientOverrides }) - act(() => { - const onClick = tr.find('button').at(0).prop('onClick') as any - onClick({ stopPropagation: jest.fn() }) - }) + userEvent.click(await screen.findByRole('button', { name: /actions\.view/i })) expect(history.location.pathname).toEqual('/patients/123001') }) }) - describe('EmptyList', () => { - const patient = { - id: '123', - rev: '123', - } as Patient - - const user = { - permissions: [Permissions.WritePatients, Permissions.ReadPatients], - } + it('should display a warning if patient has no related persons', async () => { + setup() - beforeEach(async () => { - jest.spyOn(PatientRepository, 'find').mockResolvedValue({ - fullName: 'test test', - id: '123001', - } as Patient) - - await act(async () => { - wrapper = await mount( - <Router history={history}> - <Provider store={mockStore({ patient, user } as any)}> - <RelatedPersonTab patient={patient} /> - </Provider> - </Router>, - ) - }) - wrapper.update() - }) - - it('should display a warning if patient has no related persons', () => { - const warning = wrapper.find(components.Alert) - expect(warning).toBeDefined() - }) + expect( + await screen.findByText(/patient\.relatedPersons\.warning\.noRelatedPersons/i), + ).toBeInTheDocument() + expect( + await screen.findByText(/patient\.relatedPersons\.addRelatedPersonAbove/i), + ).toBeInTheDocument() }) }) diff --git a/src/__tests__/patients/search/NoPatientsExist.test.tsx b/src/__tests__/patients/search/NoPatientsExist.test.tsx index cf59129d1b..a0015216ed 100644 --- a/src/__tests__/patients/search/NoPatientsExist.test.tsx +++ b/src/__tests__/patients/search/NoPatientsExist.test.tsx @@ -1,33 +1,22 @@ -import { Icon, Typography, Button } from '@hospitalrun/components' -import { mount } from 'enzyme' +import { render, screen } from '@testing-library/react' import React from 'react' import NoPatientsExist from '../../../patients/search/NoPatientsExist' describe('NoPatientsExist', () => { - const setup = () => mount(<NoPatientsExist />) - it('should render an icon and a button with typography', () => { - const wrapper = setup() - - const addNewPatient = wrapper.find(NoPatientsExist) - expect(addNewPatient).toHaveLength(1) + render(<NoPatientsExist />) - const icon = wrapper.find(Icon).first() - const typography = wrapper.find(Typography) - const button = wrapper.find(Button) - const iconType = icon.prop('icon') - const iconSize = icon.prop('size') - const typographyText = typography.prop('children') - const typographyVariant = typography.prop('variant') - const buttonIcon = button.prop('icon') - const buttonText = button.prop('children') + expect( + screen.getByRole('heading', { + name: /patients\.nopatients/i, + }), + ).toBeInTheDocument() - expect(iconType).toEqual('patients') - expect(iconSize).toEqual('6x') - expect(typographyText).toEqual('patients.noPatients') - expect(typographyVariant).toEqual('h5') - expect(buttonIcon).toEqual('patient-add') - expect(buttonText).toEqual('patients.newPatient') + expect( + screen.getByRole('button', { + name: /patients\.newpatient/i, + }), + ).toBeInTheDocument() }) }) diff --git a/src/__tests__/patients/search/PatientSearchInput.test.tsx b/src/__tests__/patients/search/PatientSearchInput.test.tsx index 02274bc79b..11159bfcf2 100644 --- a/src/__tests__/patients/search/PatientSearchInput.test.tsx +++ b/src/__tests__/patients/search/PatientSearchInput.test.tsx @@ -1,40 +1,25 @@ -import { TextInput } from '@hospitalrun/components' -import { mount } from 'enzyme' +import { render, screen, act } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import React from 'react' -import { act } from 'react-dom/test-utils' -import PatientSearchRequest from '../../../patients/models/PatientSearchRequest' import PatientSearchInput from '../../../patients/search/PatientSearchInput' describe('Patient Search Input', () => { - const setup = (onChange: (s: PatientSearchRequest) => void) => { - const wrapper = mount(<PatientSearchInput onChange={onChange} />) - - return { wrapper } - } - it('should render a text box', () => { - const { wrapper } = setup(jest.fn()) - - const textInput = wrapper.find(TextInput) - expect(wrapper.exists(TextInput)).toBeTruthy() - expect(textInput.prop('size')).toEqual('lg') - expect(textInput.prop('type')).toEqual('text') - expect(textInput.prop('placeholder')).toEqual('actions.search') + render(<PatientSearchInput onChange={jest.fn()} />) + expect(screen.getByRole('textbox')).toBeInTheDocument() }) it('should call the on change function when the search input changes after debounce time', () => { jest.useFakeTimers() const expectedNewQueryString = 'some new query string' const onChangeSpy = jest.fn() - const { wrapper } = setup(onChangeSpy) - act(() => { - const textInput = wrapper.find(TextInput) - const onChange = textInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedNewQueryString } }) - }) - wrapper.update() + render(<PatientSearchInput onChange={onChangeSpy} />) + + const textbox = screen.getByRole('textbox') + userEvent.type(textbox, expectedNewQueryString) + onChangeSpy.mockReset() expect(onChangeSpy).not.toHaveBeenCalled() diff --git a/src/__tests__/patients/search/SearchPatients.test.tsx b/src/__tests__/patients/search/SearchPatients.test.tsx index 448cdf50f3..222dd29ce8 100644 --- a/src/__tests__/patients/search/SearchPatients.test.tsx +++ b/src/__tests__/patients/search/SearchPatients.test.tsx @@ -1,47 +1,98 @@ -import { mount } from 'enzyme' +import { screen, render, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import format from 'date-fns/format' import React from 'react' -import { act } from 'react-dom/test-utils' -import PatientSearchInput from '../../../patients/search/PatientSearchInput' import SearchPatients from '../../../patients/search/SearchPatients' -import ViewPatientsTable from '../../../patients/search/ViewPatientsTable' import PatientRepository from '../../../shared/db/PatientRepository' +import Patient from '../../../shared/model/Patient' describe('Search Patients', () => { - const setup = () => { - const wrapper = mount(<SearchPatients />) - - return { wrapper } - } + const dateOfBirth = new Date(2010, 1, 1, 1, 1, 1, 1) + const expectedPatient = { + id: '123', + givenName: 'givenName', + familyName: 'familyName', + code: 'test code', + sex: 'sex', + dateOfBirth: dateOfBirth.toISOString(), + } as Patient beforeEach(() => { - jest.spyOn(PatientRepository, 'search').mockResolvedValueOnce([]) + jest.resetAllMocks() }) it('should render a patient search input', () => { - const { wrapper } = setup() + jest.spyOn(PatientRepository, 'search').mockResolvedValueOnce([]) + render(<SearchPatients />) - expect(wrapper.exists(PatientSearchInput)).toBeTruthy() + expect(screen.getByPlaceholderText(/actions\.search/i)).toBeInTheDocument() }) - it('should render a view patients table', () => { - const { wrapper } = setup() + it('should render a view patients table', async () => { + jest.spyOn(PatientRepository, 'search').mockResolvedValueOnce([]) + render(<SearchPatients />) - expect(wrapper.exists(ViewPatientsTable)).toBeTruthy() + await waitFor(() => { + expect(screen.getByRole('heading', { name: /patients\.nopatients/i })).toBeInTheDocument() + }) + await waitFor(() => { + expect(screen.getByRole('button', { name: /patients\.newpatient/i })).toBeInTheDocument() + }) }) - it('should update view patients table search request when patient search input changes', () => { - const expectedSearch = { queryString: 'someQueryString' } - const { wrapper } = setup() + it('should update view patients table search request when patient search input changes', async () => { + const countSpyOn = jest + .spyOn(PatientRepository, 'count') + .mockResolvedValueOnce(0) + .mockResolvedValueOnce(1) - act(() => { - const patientSearch = wrapper.find(PatientSearchInput) - const onChange = patientSearch.prop('onChange') - onChange(expectedSearch) + const searchSpyOn = jest + .spyOn(PatientRepository, 'search') + .mockResolvedValueOnce([]) + .mockResolvedValueOnce([expectedPatient]) + + const expectedSearch = 'someQueryString' + render(<SearchPatients />) + + await waitFor(() => { + expect(screen.getByRole('heading', { name: /patients\.nopatients/i })).toBeInTheDocument() }) - wrapper.update() - const patientsTable = wrapper.find(ViewPatientsTable) - expect(patientsTable.prop('searchRequest')).toEqual(expectedSearch) + await waitFor(() => { + expect(screen.getByRole('button', { name: /patients\.newpatient/i })).toBeInTheDocument() + }) + + const patientSearch = screen.getByPlaceholderText(/actions\.search/i) + userEvent.type(patientSearch, expectedSearch) + + await waitFor(() => { + expect(patientSearch).toHaveDisplayValue(expectedSearch) + }) + + await waitFor(() => { + expect(screen.getByRole('cell', { name: expectedPatient.code })).toBeInTheDocument() + }) + + await waitFor(() => { + expect(screen.getByRole('cell', { name: expectedPatient.givenName })).toBeInTheDocument() + }) + await waitFor(() => { + expect(screen.getByRole('cell', { name: expectedPatient.familyName })).toBeInTheDocument() + }) + await waitFor(() => { + expect(screen.getByRole('cell', { name: expectedPatient.sex })).toBeInTheDocument() + }) + await waitFor(() => { + expect( + screen.getByRole('cell', { name: format(dateOfBirth, 'MM/dd/yyyy') }), + ).toBeInTheDocument() + }) + + searchSpyOn.mockReset() + searchSpyOn.mockRestore() + + countSpyOn.mockReset() + countSpyOn.mockRestore() }) }) diff --git a/src/__tests__/patients/search/ViewPatients.test.tsx b/src/__tests__/patients/search/ViewPatients.test.tsx index 7d9f333fd1..b03a3a36e4 100644 --- a/src/__tests__/patients/search/ViewPatients.test.tsx +++ b/src/__tests__/patients/search/ViewPatients.test.tsx @@ -1,4 +1,5 @@ -import { mount } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import React from 'react' import { Provider } from 'react-redux' import { MemoryRouter } from 'react-router-dom' @@ -6,7 +7,6 @@ import configureStore from 'redux-mock-store' import thunk from 'redux-thunk' import { TitleProvider } from '../../../page-header/title/TitleContext' -import SearchPatients from '../../../patients/search/SearchPatients' import ViewPatients from '../../../patients/search/ViewPatients' import PatientRepository from '../../../shared/db/PatientRepository' @@ -16,7 +16,8 @@ const mockStore = configureStore(middlewares) describe('Patients', () => { const setup = () => { const store = mockStore({}) - return mount( + + return render( <Provider store={store}> <MemoryRouter> <TitleProvider> @@ -33,8 +34,11 @@ describe('Patients', () => { }) it('should render the search patients component', () => { - const wrapper = setup() + setup() + userEvent.type(screen.getByRole('textbox'), 'Jean Luc Picard') + expect(screen.getByRole('textbox')).toHaveValue('Jean Luc Picard') - expect(wrapper.exists(SearchPatients)).toBeTruthy() + userEvent.clear(screen.getByRole('textbox')) + expect(screen.queryByRole('textbox')).not.toHaveValue('Jean Luc Picard') }) }) diff --git a/src/__tests__/patients/search/ViewPatientsTable.test.tsx b/src/__tests__/patients/search/ViewPatientsTable.test.tsx index 6f7272672b..9ad1e2fcce 100644 --- a/src/__tests__/patients/search/ViewPatientsTable.test.tsx +++ b/src/__tests__/patients/search/ViewPatientsTable.test.tsx @@ -1,54 +1,35 @@ -import { Table } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' -import { createMemoryHistory } from 'history' +import { screen, render, waitFor } from '@testing-library/react' +import format from 'date-fns/format' import React from 'react' -import { act } from 'react-dom/test-utils' -import { Router } from 'react-router-dom' import PatientSearchRequest from '../../../patients/models/PatientSearchRequest' -import NoPatientsExist from '../../../patients/search/NoPatientsExist' import ViewPatientsTable from '../../../patients/search/ViewPatientsTable' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' describe('View Patients Table', () => { - const setup = async ( - expectedSearchRequest: PatientSearchRequest, - expectedPatients: Patient[], - ) => { + const setup = (expectedSearchRequest: PatientSearchRequest, expectedPatients: Patient[]) => { jest.spyOn(PatientRepository, 'search').mockResolvedValueOnce(expectedPatients) jest.spyOn(PatientRepository, 'count').mockResolvedValueOnce(expectedPatients.length) - let wrapper: any - const history = createMemoryHistory() - - await act(async () => { - wrapper = await mount( - <Router history={history}> - <ViewPatientsTable searchRequest={expectedSearchRequest} /> - </Router>, - ) - }) - wrapper.update() - return { wrapper: wrapper as ReactWrapper, history } + return render(<ViewPatientsTable searchRequest={expectedSearchRequest} />) } beforeEach(() => { jest.resetAllMocks() }) - it('should search for patients given a search request', async () => { + it('should search for patients given a search request', () => { const expectedSearchRequest = { queryString: 'someQueryString' } - await setup(expectedSearchRequest, []) + setup(expectedSearchRequest, []) expect(PatientRepository.search).toHaveBeenCalledTimes(1) expect(PatientRepository.search).toHaveBeenCalledWith(expectedSearchRequest.queryString) }) it('should display no patients exist if total patient count is 0', async () => { - const { wrapper } = await setup({ queryString: '' }, []) - - expect(wrapper.exists(NoPatientsExist)).toBeTruthy() + setup({ queryString: '' }, []) + expect(await screen.findByRole('heading', { name: /patients.noPatients/i })).toBeInTheDocument() }) it('should render a table', async () => { @@ -56,32 +37,34 @@ describe('View Patients Table', () => { id: '123', givenName: 'givenName', familyName: 'familyName', + code: 'test code', sex: 'sex', dateOfBirth: new Date(2010, 1, 1, 1, 1, 1, 1).toISOString(), } as Patient const expectedPatients = [expectedPatient] - const { wrapper } = await setup({ queryString: '' }, expectedPatients) - const table = wrapper.find(Table) - const columns = table.prop('columns') - const actions = table.prop('actions') as any + setup({ queryString: '' }, expectedPatients) - expect(table).toHaveLength(1) + await waitFor(() => screen.getByText('familyName')) + const cells = screen.getAllByRole('cell') - expect(columns[0]).toEqual(expect.objectContaining({ label: 'patient.code', key: 'code' })) - expect(columns[1]).toEqual( - expect.objectContaining({ label: 'patient.givenName', key: 'givenName' }), - ) - expect(columns[2]).toEqual( - expect.objectContaining({ label: 'patient.familyName', key: 'familyName' }), - ) - expect(columns[3]).toEqual(expect.objectContaining({ label: 'patient.sex', key: 'sex' })) - expect(columns[4]).toEqual( - expect.objectContaining({ label: 'patient.dateOfBirth', key: 'dateOfBirth' }), - ) + expect(screen.getByRole('columnheader', { name: /patient\.code/i })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /patient\.givenName/i })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /patient\.familyName/i })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /patient\.sex/i })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /patient\.dateOfBirth/i })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /actions\.label/i })).toBeInTheDocument() + + Object.keys(expectedPatient) + .filter((key) => key !== 'id' && key !== 'dateOfBirth') + .forEach((key) => { + expect( + screen.getByRole('cell', { name: expectedPatient[key as keyof Patient] as string }), + ).toBeInTheDocument() + }) - expect(actions[0]).toEqual(expect.objectContaining({ label: 'actions.view' })) - expect(table.prop('data')).toEqual(expectedPatients) - expect(table.prop('actionsHeaderText')).toEqual('actions.label') + expect(cells[4]).toHaveTextContent( + format(Date.parse(expectedPatient.dateOfBirth), 'MM/dd/yyyy'), + ) }) }) diff --git a/src/__tests__/patients/view/ImportantPatientInfo.test.tsx b/src/__tests__/patients/view/ImportantPatientInfo.test.tsx index 23e39da3a1..815effb7ff 100644 --- a/src/__tests__/patients/view/ImportantPatientInfo.test.tsx +++ b/src/__tests__/patients/view/ImportantPatientInfo.test.tsx @@ -1,18 +1,14 @@ -import * as components from '@hospitalrun/components' +import { render, screen, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import format from 'date-fns/format' -import { mount } from 'enzyme' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' -import NewAllergyModal from '../../../patients/allergies/NewAllergyModal' -import AddDiagnosisModal from '../../../patients/diagnoses/AddDiagnosisModal' import ImportantPatientInfo from '../../../patients/view/ImportantPatientInfo' -import AddVisitModal from '../../../patients/visits/AddVisitModal' import PatientRepository from '../../../shared/db/PatientRepository' import CarePlan from '../../../shared/model/CarePlan' import Diagnosis from '../../../shared/model/Diagnosis' @@ -56,201 +52,134 @@ describe('Important Patient Info Panel', () => { ], } as Patient - const setup = async (patient = expectedPatient, permissions: Permissions[]) => { + const setup = (patient = expectedPatient, permissions: Permissions[]) => { jest.resetAllMocks() jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) history = createMemoryHistory() user = { permissions } store = mockStore({ patient, user } as any) - let wrapper: any - - await act(async () => { - wrapper = await mount( - <Provider store={store}> - <Router history={history}> - <ImportantPatientInfo patient={patient} /> - </Router> - </Provider>, - ) - }) - wrapper.update() - return wrapper + return render( + <Provider store={store}> + <Router history={history}> + <ImportantPatientInfo patient={patient} /> + </Router> + </Provider>, + ) } describe("patient's full name, patient's code, sex, and date of birth", () => { it("should render patient's full name", async () => { - const wrapper = await setup(expectedPatient, []) - const code = wrapper.find('.col-2') - expect(code.at(0).text()).toEqual(expectedPatient.fullName) + setup(expectedPatient, []) + if (typeof expectedPatient.fullName !== 'undefined') { + expect(screen.getByText(expectedPatient.fullName)).toBeInTheDocument() + } }) - it("should render patient's code", async () => { - const wrapper = await setup(expectedPatient, []) - const code = wrapper.find('.col-2') - expect(code.at(1).text()).toEqual(`patient.code${expectedPatient.code}`) + it("should render patient's code", () => { + setup(expectedPatient, []) + expect(screen.getByText(expectedPatient.code)).toBeInTheDocument() }) - it("should render patient's sex", async () => { - const wrapper = await setup(expectedPatient, []) - const sex = wrapper.find('.patient-sex') - expect(sex.text()).toEqual(`patient.sex${expectedPatient.sex}`) + it("should render patient's sex", () => { + setup(expectedPatient, []) + expect(screen.getByText(expectedPatient.sex)).toBeInTheDocument() }) - it("should render patient's dateOfDate", async () => { - const wrapper = await setup(expectedPatient, []) - const dateOfBirth = wrapper.find('.patient-dateOfBirth') - expect(dateOfBirth.text()).toEqual(`patient.dateOfBirth${expectedPatient.dateOfBirth}`) + it("should render patient's dateOfDate", () => { + setup(expectedPatient, []) + expect(screen.getAllByText(expectedPatient.dateOfBirth)[0]).toBeInTheDocument() }) }) describe('add new visit button', () => { - it('should render an add visit button if user has correct permissions', async () => { - const wrapper = await setup(expectedPatient, [Permissions.AddVisit]) - - const addNewButton = wrapper.find(components.Button) - expect(addNewButton).toHaveLength(1) - expect(addNewButton.text().trim()).toEqual('patient.visits.new') + it('should render an add visit button if user has correct permissions', () => { + setup(expectedPatient, [Permissions.AddVisit]) + expect(screen.getByRole('button', { name: /patient.visits.new/i })).toBeInTheDocument() }) it('should open the add visit modal on click', async () => { - const wrapper = await setup(expectedPatient, [Permissions.AddVisit]) - - act(() => { - const addNewButton = wrapper.find(components.Button) - const onClick = addNewButton.prop('onClick') as any - onClick() - }) - wrapper.update() - - const modal = wrapper.find(AddVisitModal) - expect(modal.prop('show')).toBeTruthy() + setup(expectedPatient, [Permissions.AddVisit]) + userEvent.click(screen.getByText(/patient.visits.new/i)) + expect(await screen.findByRole('dialog')).toBeInTheDocument() + const { getByText: getByTextModal } = within(screen.getByRole('dialog')) + expect(getByTextModal(/patient.visits.new/i, { selector: 'div' })).toBeInTheDocument() }) it('should close the modal when the close button is clicked', async () => { - const wrapper = await setup(expectedPatient, [Permissions.AddVisit]) - - act(() => { - const addNewButton = wrapper.find(components.Button) - const onClick = addNewButton.prop('onClick') as any - onClick() - }) - wrapper.update() - - act(() => { - const modal = wrapper.find(AddVisitModal) - const onClose = modal.prop('onCloseButtonClick') as any - onClose() - }) - wrapper.update() - - expect(wrapper.find(AddVisitModal).prop('show')).toBeFalsy() + setup(expectedPatient, [Permissions.AddVisit]) + userEvent.click(screen.getByText(/patient.visits.new/i)) + expect(await screen.findByRole('dialog')).toBeInTheDocument() + const { getAllByRole: getAllByRoleModal } = within(screen.getByRole('dialog')) + userEvent.click(getAllByRoleModal('button')[0]) + expect(await screen.findByRole('dialog')).not.toBeInTheDocument() }) - it('should not render new visit button if user does not have permissions', async () => { - const wrapper = await setup(expectedPatient, []) - - expect(wrapper.find(components.Button)).toHaveLength(0) + it('should not render new visit button if user does not have permissions', () => { + setup(expectedPatient, []) + expect(screen.queryByRole('button', { name: /patient.visits.new/i })).not.toBeInTheDocument() }) }) describe('add new allergy button', () => { - it('should render an add allergy button if user has correct permissions', async () => { - const wrapper = await setup(expectedPatient, [Permissions.AddAllergy]) - - const addNewButton = wrapper.find(components.Button) - expect(addNewButton).toHaveLength(1) - expect(addNewButton.text().trim()).toEqual('patient.allergies.new') + it('should render an add allergy button if user has correct permissions', () => { + setup(expectedPatient, [Permissions.AddAllergy]) + expect(screen.getByRole('button', { name: /patient.allergies.new/i })).toBeInTheDocument() }) it('should open the add allergy modal on click', async () => { - const wrapper = await setup(expectedPatient, [Permissions.AddAllergy]) - - act(() => { - const addNewButton = wrapper.find(components.Button) - const onClick = addNewButton.prop('onClick') as any - onClick() - }) - wrapper.update() - - const modal = wrapper.find(NewAllergyModal) - expect(modal.prop('show')).toBeTruthy() + setup(expectedPatient, [Permissions.AddAllergy]) + userEvent.click(screen.getByRole('button', { name: /patient.allergies.new/i })) + expect(await screen.findByRole('dialog')).toBeInTheDocument() + const { getByLabelText: getByLabelTextModal } = within(screen.getByRole('dialog')) + expect(getByLabelTextModal(/patient.allergies.allergyName/i)).toBeInTheDocument() }) it('should close the modal when the close button is clicked', async () => { - const wrapper = await setup(expectedPatient, [Permissions.AddAllergy]) - - act(() => { - const addNewButton = wrapper.find(components.Button) - const onClick = addNewButton.prop('onClick') as any - onClick() - }) - wrapper.update() - - act(() => { - const modal = wrapper.find(NewAllergyModal) - const onClose = modal.prop('onCloseButtonClick') as any - onClose() - }) - wrapper.update() - - expect(wrapper.find(NewAllergyModal).prop('show')).toBeFalsy() + setup(expectedPatient, [Permissions.AddAllergy]) + userEvent.click(screen.getByRole('button', { name: /patient.allergies.new/i })) + expect(await screen.findByRole('dialog')).toBeInTheDocument() + const { getAllByRole: getAllByRoleModal } = within(screen.getByRole('dialog')) + userEvent.click(getAllByRoleModal('button')[0]) + expect(await screen.findByRole('dialog')).not.toBeInTheDocument() }) - it('should not render new allergy button if user does not have permissions', async () => { - const wrapper = await setup(expectedPatient, []) - - expect(wrapper.find(components.Button)).toHaveLength(0) + it('should not render new allergy button if user does not have permissions', () => { + setup(expectedPatient, []) + expect( + screen.queryByRole('button', { name: /patient.allergies.new/i }), + ).not.toBeInTheDocument() }) }) describe('add diagnoses button', () => { - it('should render an add diagnosis button if user has correct permissions', async () => { - const wrapper = await setup(expectedPatient, [Permissions.AddDiagnosis]) - - const addNewButton = wrapper.find(components.Button) - expect(addNewButton).toHaveLength(1) - expect(addNewButton.text().trim()).toEqual('patient.diagnoses.new') + it('should render an add diagnosis button if user has correct permissions', () => { + setup(expectedPatient, [Permissions.AddDiagnosis]) + expect(screen.getByRole('button', { name: /patient.diagnoses.new/i })).toBeInTheDocument() }) it('should open the add diagnosis modal on click', async () => { - const wrapper = await setup(expectedPatient, [Permissions.AddDiagnosis]) - - act(() => { - const addNewButton = wrapper.find(components.Button) - const onClick = addNewButton.prop('onClick') as any - onClick() - }) - wrapper.update() - - const modal = wrapper.find(AddDiagnosisModal) - expect(modal.prop('show')).toBeTruthy() + setup(expectedPatient, [Permissions.AddDiagnosis]) + userEvent.click(screen.getByRole('button', { name: /patient.diagnoses.new/i })) + expect(await screen.findByRole('dialog')).toBeInTheDocument() + const { getByLabelText: getByLabelTextModal } = within(screen.getByRole('dialog')) + expect(getByLabelTextModal(/patient.diagnoses.diagnosisName/i)).toBeInTheDocument() }) it('should close the modal when the close button is clicked', async () => { - const wrapper = await setup(expectedPatient, [Permissions.AddDiagnosis]) - - act(() => { - const addNewButton = wrapper.find(components.Button) - const onClick = addNewButton.prop('onClick') as any - onClick() - }) - wrapper.update() - - act(() => { - const modal = wrapper.find(AddDiagnosisModal) - const onClose = modal.prop('onCloseButtonClick') as any - onClose() - }) - wrapper.update() - - expect(wrapper.find(AddDiagnosisModal).prop('show')).toBeFalsy() - }) - - it('should not render new diagnosis button if user does not have permissions', async () => { - const wrapper = await setup(expectedPatient, []) - - expect(wrapper.find(components.Button)).toHaveLength(0) + setup(expectedPatient, [Permissions.AddDiagnosis]) + userEvent.click(screen.getByRole('button', { name: /patient.diagnoses.new/i })) + expect(await screen.findByRole('dialog')).toBeInTheDocument() + const { getAllByRole: getAllByRoleModal } = within(screen.getByRole('dialog')) + userEvent.click(getAllByRoleModal('button')[0]) + expect(await screen.findByRole('dialog')).not.toBeInTheDocument() + }) + + it('should not render new diagnosis button if user does not have permissions', () => { + setup(expectedPatient, []) + expect( + screen.queryByRole('button', { name: /patient.diagnoses.new/i }), + ).not.toBeInTheDocument() }) }) }) diff --git a/src/__tests__/patients/view/ViewPatient.test.tsx b/src/__tests__/patients/view/ViewPatient.test.tsx index 88ce50b9a8..5c8f160a80 100644 --- a/src/__tests__/patients/view/ViewPatient.test.tsx +++ b/src/__tests__/patients/view/ViewPatient.test.tsx @@ -1,24 +1,15 @@ -import { TabsHeader, Tab } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' -import { Route, Router } from 'react-router-dom' -import createMockStore, { MockStore } from 'redux-mock-store' +import { Router, Route } from 'react-router' +import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' -import * as ButtonBarProvider from '../../../page-header/button-toolbar/ButtonBarProvider' +import { ButtonBarProvider } from '../../../page-header/button-toolbar/ButtonBarProvider' +import ButtonToolbar from '../../../page-header/button-toolbar/ButtonToolBar' import * as titleUtil from '../../../page-header/title/TitleContext' -import Allergies from '../../../patients/allergies/Allergies' -import AppointmentsList from '../../../patients/appointments/AppointmentsList' -import CarePlanTab from '../../../patients/care-plans/CarePlanTab' -import Diagnoses from '../../../patients/diagnoses/Diagnoses' -import GeneralInformation from '../../../patients/GeneralInformation' -import Labs from '../../../patients/labs/Labs' -import Medications from '../../../patients/medications/Medications' -import NotesTab from '../../../patients/notes/NoteTab' -import RelatedPersonTab from '../../../patients/related-persons/RelatedPersonTab' import ViewPatient from '../../../patients/view/ViewPatient' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' @@ -29,7 +20,7 @@ const { TitleProvider } = titleUtil const mockStore = createMockStore<RootState, any>([thunk]) describe('ViewPatient', () => { - const patient = ({ + const testPatient = ({ id: '123', prefix: 'prefix', givenName: 'givenName', @@ -46,316 +37,260 @@ describe('ViewPatient', () => { dateOfBirth: new Date().toISOString(), } as unknown) as Patient - let history: any - let store: MockStore - let setButtonToolBarSpy: any - - const setup = async (permissions = [Permissions.ReadPatients]) => { - setButtonToolBarSpy = jest.fn() - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) + interface SetupProps { + permissions?: string[] + startPath?: string + } + const setup = ({ + permissions = [], + startPath = `/patients/${testPatient.id}`, + }: SetupProps = {}) => { + jest.spyOn(PatientRepository, 'find').mockResolvedValue(testPatient) jest.spyOn(PatientRepository, 'getLabs').mockResolvedValue([]) jest.spyOn(PatientRepository, 'getMedications').mockResolvedValue([]) - history = createMemoryHistory() - store = mockStore({ - patient: { patient }, - user: { permissions }, - appointments: { appointments: [] }, - labs: { labs: [] }, - medications: { medications: [] }, + + const history = createMemoryHistory({ initialEntries: [startPath] }) + const store = mockStore({ + user: { permissions: [Permissions.ReadPatients, ...permissions] }, } as any) - history.push('/patients/123') - let wrapper: any - await act(async () => { - wrapper = await mount( + return { + history, + store, + ...render( <Provider store={store}> - <Router history={history}> - <Route path="/patients/:id"> - <TitleProvider> - <ViewPatient /> - </TitleProvider> - </Route> - </Router> + <ButtonBarProvider> + <ButtonToolbar /> + <Router history={history}> + <Route path="/patients/:id"> + <TitleProvider> + <ViewPatient /> + </TitleProvider> + </Route> + </Router> + </ButtonBarProvider> </Provider>, - ) - }) - wrapper.find(ViewPatient).props().updateTitle = jest.fn() - wrapper.update() - - return { wrapper: wrapper as ReactWrapper } + ), + } } - beforeEach(() => { - jest.restoreAllMocks() - }) - it('should dispatch fetchPatient when component loads', async () => { - await setup() - - expect(PatientRepository.find).toHaveBeenCalledWith(patient.id) - }) - - it('should have called useUpdateTitle hook', async () => { - await setup() + setup() - expect(titleUtil.useUpdateTitle).toHaveBeenCalled() + await waitFor(() => { + expect(PatientRepository.find).toHaveBeenCalledWith(testPatient.id) + }) }) - it('should add a "Edit Patient" button to the button tool bar if has WritePatients permissions', async () => { - await setup([Permissions.WritePatients]) + it('should render an "Edit Patient" button to the button tool bar if user has WritePatients permissions', async () => { + setup({ permissions: [Permissions.WritePatients] }) - const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] - expect((actualButtons[0] as any).props.children).toEqual('actions.edit') + await waitFor(() => { + expect(screen.getByText(/actions\.edit/i)).toBeInTheDocument() + }) }) - it('button toolbar empty if only has ReadPatients permission', async () => { - await setup() + it('should render an empty button toolbar if the user has only ReadPatients permissions', async () => { + setup() - const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] - expect(actualButtons.length).toEqual(0) + expect(screen.queryByText(/actions\.edit/i)).not.toBeInTheDocument() }) it('should render a tabs header with the correct tabs', async () => { - const { wrapper } = await setup() - - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - expect(tabsHeader).toHaveLength(1) - - expect(tabs).toHaveLength(11) - expect(tabs.at(0).prop('label')).toEqual('patient.generalInformation') - expect(tabs.at(1).prop('label')).toEqual('patient.relatedPersons.label') - expect(tabs.at(2).prop('label')).toEqual('scheduling.appointments.label') - expect(tabs.at(3).prop('label')).toEqual('patient.allergies.label') - expect(tabs.at(4).prop('label')).toEqual('patient.diagnoses.label') - expect(tabs.at(5).prop('label')).toEqual('patient.notes.label') - expect(tabs.at(6).prop('label')).toEqual('patient.medications.label') - expect(tabs.at(7).prop('label')).toEqual('patient.labs.label') - expect(tabs.at(8).prop('label')).toEqual('patient.carePlan.label') - expect(tabs.at(9).prop('label')).toEqual('patient.careGoal.label') - expect(tabs.at(10).prop('label')).toEqual('patient.visits.label') + setup() + + await waitFor(() => { + expect(screen.getAllByRole('tab')).toHaveLength(11) + }) + expect(screen.getByRole('tab', { name: /patient\.generalInformation/i })).toBeInTheDocument() + expect(screen.getByRole('tab', { name: /patient\.relatedPersons\.label/i })).toBeInTheDocument() + expect( + screen.getByRole('tab', { name: /scheduling\.appointments\.label/i }), + ).toBeInTheDocument() + expect(screen.getByRole('tab', { name: /patient\.allergies\.label/i })).toBeInTheDocument() + expect(screen.getByRole('tab', { name: /patient\.diagnoses\.label/i })).toBeInTheDocument() + expect(screen.getByRole('tab', { name: /patient\.notes\.label/i })).toBeInTheDocument() + expect(screen.getByRole('tab', { name: /patient\.medications\.label/i })).toBeInTheDocument() + expect(screen.getByRole('tab', { name: /patient\.labs\.label/i })).toBeInTheDocument() + expect(screen.getByRole('tab', { name: /patient\.carePlan\.label/i })).toBeInTheDocument() + expect(screen.getByRole('tab', { name: /patient\.careGoal\.label/i })).toBeInTheDocument() + expect(screen.getByRole('tab', { name: /patient\.visits\.label/i })).toBeInTheDocument() }) it('should mark the general information tab as active and render the general information component when route is /patients/:id', async () => { - const { wrapper } = await setup() - - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const generalInformation = wrapper.find(GeneralInformation) - expect(tabs.at(0).prop('active')).toBeTruthy() - expect(generalInformation).toHaveLength(1) - expect(generalInformation.prop('patient')).toEqual(patient) + setup() + + await waitFor(() => { + expect(screen.getByRole('button', { name: /patient\.generalInformation/i })).toHaveClass( + 'active', + ) + }) + expect(screen.getByText(/patient\.basicinformation/i)).toBeInTheDocument() }) it('should navigate /patients/:id when the general information tab is clicked', async () => { - const { wrapper } = await setup() + const { history } = setup({ startPath: `/patients/${testPatient.id}/relatedpersons` }) // Start from NOT the General Information tab - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - await act(async () => { - await (tabs.at(0).prop('onClick') as any)() + await waitFor(() => { + userEvent.click(screen.getByRole('button', { name: /patient\.generalInformation/i })) }) - wrapper.update() - - expect(history.location.pathname).toEqual('/patients/123') + await waitFor(() => { + expect(history.location.pathname).toEqual(`/patients/${testPatient.id}`) + }) }) it('should mark the related persons tab as active when it is clicked and render the Related Person Tab component when route is /patients/:id/relatedpersons', async () => { - const { wrapper } = await setup() + const { history } = setup() - await act(async () => { - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const onClick = tabs.at(1).prop('onClick') as any - onClick() + await waitFor(() => { + userEvent.click(screen.getByRole('button', { name: /patient\.relatedPersons\.label/i })) }) - wrapper.update() - - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const relatedPersonTab = wrapper.find(RelatedPersonTab) - - expect(history.location.pathname).toEqual(`/patients/${patient.id}/relatedpersons`) - expect(tabs.at(1).prop('active')).toBeTruthy() - expect(relatedPersonTab).toHaveLength(1) - expect(relatedPersonTab.prop('patient')).toEqual(patient) + await waitFor(() => { + expect(history.location.pathname).toEqual(`/patients/${testPatient.id}/relatedpersons`) + }) + expect(screen.getByRole('button', { name: /patient\.relatedPersons\.label/i })).toHaveClass( + 'active', + ) + await waitFor(() => { + expect( + screen.getByText(/patient\.relatedPersons\.warning\.noRelatedPersons/i), + ).toBeInTheDocument() + }) }) it('should mark the appointments tab as active when it is clicked and render the appointments tab component when route is /patients/:id/appointments', async () => { - const { wrapper } = await setup() + const { history } = setup() - await act(async () => { - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const onClick = tabs.at(2).prop('onClick') as any - onClick() + await waitFor(() => { + userEvent.click(screen.getByRole('button', { name: /scheduling\.appointments\.label/i })) }) - wrapper.update() - - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const appointmentsTab = wrapper.find(AppointmentsList) - - expect(history.location.pathname).toEqual(`/patients/${patient.id}/appointments`) - expect(tabs.at(2).prop('active')).toBeTruthy() - expect(appointmentsTab).toHaveLength(1) - expect(appointmentsTab.prop('patient')).toEqual(patient) + await waitFor(() => { + expect(history.location.pathname).toEqual(`/patients/${testPatient.id}/appointments`) + }) + expect(screen.getByRole('button', { name: /scheduling\.appointments\.label/i })).toHaveClass( + 'active', + ) + await waitFor(() => { + expect( + screen.getByText(/patient\.appointments\.warning\.noAppointments/i), + ).toBeInTheDocument() + }) }) it('should mark the allergies tab as active when it is clicked and render the allergies component when route is /patients/:id/allergies', async () => { - const { wrapper } = await setup() + const { history } = setup() - await act(async () => { - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const onClick = tabs.at(3).prop('onClick') as any - onClick() + await waitFor(() => { + userEvent.click(screen.getByRole('button', { name: /patient\.allergies\.label/i })) }) - wrapper.update() - - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const allergiesTab = wrapper.find(Allergies) - - expect(history.location.pathname).toEqual(`/patients/${patient.id}/allergies`) - expect(tabs.at(3).prop('active')).toBeTruthy() - expect(allergiesTab).toHaveLength(1) - expect(allergiesTab.prop('patient')).toEqual(patient) + await waitFor(() => { + expect(history.location.pathname).toEqual(`/patients/${testPatient.id}/allergies`) + }) + expect(screen.getByRole('button', { name: /patient\.allergies\.label/i })).toHaveClass('active') + await waitFor(() => { + expect(screen.getByText(/patient\.allergies\.warning\.noAllergies/i)).toBeInTheDocument() + }) }) it('should mark the diagnoses tab as active when it is clicked and render the diagnoses component when route is /patients/:id/diagnoses', async () => { - const { wrapper } = await setup() + const { history } = setup() - await act(async () => { - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const onClick = tabs.at(4).prop('onClick') as any - onClick() + await waitFor(() => { + userEvent.click(screen.getByRole('button', { name: /patient\.diagnoses\.label/i })) }) - wrapper.update() - - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const diagnosesTab = wrapper.find(Diagnoses) - - expect(history.location.pathname).toEqual(`/patients/${patient.id}/diagnoses`) - expect(tabs.at(4).prop('active')).toBeTruthy() - expect(diagnosesTab).toHaveLength(1) - expect(diagnosesTab.prop('patient')).toEqual(patient) + await waitFor(() => { + expect(history.location.pathname).toEqual(`/patients/${testPatient.id}/diagnoses`) + }) + expect(screen.getByRole('button', { name: /patient\.diagnoses\.label/i })).toHaveClass('active') + await waitFor(() => { + expect(screen.getByText(/patient\.diagnoses\.warning\.noDiagnoses/i)).toBeInTheDocument() + }) }) it('should mark the notes tab as active when it is clicked and render the note component when route is /patients/:id/notes', async () => { - const { wrapper } = await setup() + const { history } = setup() - await act(async () => { - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const onClick = tabs.at(5).prop('onClick') as any - onClick() + await waitFor(() => { + userEvent.click(screen.getByRole('button', { name: /patient\.notes\.label/i })) }) - wrapper.update() - - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const notesTab = wrapper.find(NotesTab) - - expect(history.location.pathname).toEqual(`/patients/${patient.id}/notes`) - expect(tabs.at(5).prop('active')).toBeTruthy() - expect(notesTab).toHaveLength(1) - expect(notesTab.prop('patient')).toEqual(patient) + await waitFor(() => { + expect(history.location.pathname).toEqual(`/patients/${testPatient.id}/notes`) + }) + expect(screen.getByRole('button', { name: /patient\.notes\.label/i })).toHaveClass('active') + await waitFor(() => { + expect(screen.getByText(/patient\.notes\.warning\.noNotes/i)).toBeInTheDocument() + }) }) it('should mark the medications tab as active when it is clicked and render the medication component when route is /patients/:id/medications', async () => { - const { wrapper } = await setup() + const { history } = setup() - await act(async () => { - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const onClick = tabs.at(6).prop('onClick') as any - onClick() + await waitFor(() => { + userEvent.click(screen.getByRole('button', { name: /patient\.medications\.label/i })) }) - wrapper.update() - - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const medicationsTab = wrapper.find(Medications) - - expect(history.location.pathname).toEqual(`/patients/${patient.id}/medications`) - expect(tabs.at(6).prop('active')).toBeTruthy() - expect(medicationsTab).toHaveLength(1) - expect(medicationsTab.prop('patient')).toEqual(patient) + await waitFor(() => { + expect(history.location.pathname).toEqual(`/patients/${testPatient.id}/medications`) + }) + expect(screen.getByRole('button', { name: /patient\.medications\.label/i })).toHaveClass( + 'active', + ) + await waitFor(() => { + expect(screen.getByText(/patient\.medications\.warning\.noMedications/i)).toBeInTheDocument() + }) }) it('should mark the labs tab as active when it is clicked and render the lab component when route is /patients/:id/labs', async () => { - const { wrapper } = await setup() + const { history } = setup() - await act(async () => { - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const onClick = tabs.at(7).prop('onClick') as any - onClick() + await waitFor(() => { + userEvent.click(screen.getByRole('button', { name: /patient\.labs\.label/i })) }) - wrapper.update() - - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const labsTab = wrapper.find(Labs) - - expect(history.location.pathname).toEqual(`/patients/${patient.id}/labs`) - expect(tabs.at(7).prop('active')).toBeTruthy() - expect(labsTab).toHaveLength(1) - expect(labsTab.prop('patient')).toEqual(patient) + await waitFor(() => { + expect(history.location.pathname).toEqual(`/patients/${testPatient.id}/labs`) + }) + expect(screen.getByRole('button', { name: /patient\.labs\.label/i })).toHaveClass('active') + await waitFor(() => { + expect(screen.getByText(/patient\.labs\.warning\.noLabs/i)).toBeInTheDocument() + }) }) it('should mark the care plans tab as active when it is clicked and render the care plan tab component when route is /patients/:id/care-plans', async () => { - const { wrapper } = await setup() + const { history } = setup() - await act(async () => { - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const onClick = tabs.at(8).prop('onClick') as any - onClick() + await waitFor(() => { + userEvent.click(screen.getByRole('button', { name: /patient\.carePlan\.label/i })) }) - wrapper.update() - - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const carePlansTab = wrapper.find(CarePlanTab) - - expect(history.location.pathname).toEqual(`/patients/${patient.id}/care-plans`) - expect(tabs.at(8).prop('active')).toBeTruthy() - expect(carePlansTab).toHaveLength(1) + await waitFor(() => { + expect(history.location.pathname).toEqual(`/patients/${testPatient.id}/care-plans`) + }) + expect(screen.getByRole('button', { name: /patient\.carePlan\.label/i })).toHaveClass('active') + await waitFor(() => { + expect(screen.getByText(/patient\.carePlans\.warning\.noCarePlans/i)).toBeInTheDocument() + }) }) it('should mark the care goals tab as active when it is clicked and render the care goal tab component when route is /patients/:id/care-goals', async () => { - const { wrapper } = await setup() + const { history } = setup() - await act(async () => { - const tabHeader = wrapper.find(TabsHeader) - const tabs = tabHeader.find(Tab) - const onClick = tabs.at(9).prop('onClick') as any - onClick() + await waitFor(() => { + userEvent.click(screen.getByRole('button', { name: /patient\.careGoal\.label/i })) }) - wrapper.update() - - const tabsHeader = wrapper.find(TabsHeader) - const tabs = tabsHeader.find(Tab) - const careGoalsTab = tabs.at(9) - - expect(history.location.pathname).toEqual(`/patients/${patient.id}/care-goals`) - expect(careGoalsTab.prop('active')).toBeTruthy() - expect(careGoalsTab).toHaveLength(1) + await waitFor(() => { + expect(history.location.pathname).toEqual(`/patients/${testPatient.id}/care-goals`) + }) + expect(screen.getByRole('button', { name: /patient\.careGoal\.label/i })).toHaveClass('active') + await waitFor(() => { + expect(screen.getByText(/patient\.careGoals\.warning\.noCareGoals/i)).toBeInTheDocument() + }) }) }) diff --git a/src/__tests__/patients/visits/AddVisitModal.test.tsx b/src/__tests__/patients/visits/AddVisitModal.test.tsx index 3890cccdda..ab9a1d8401 100644 --- a/src/__tests__/patients/visits/AddVisitModal.test.tsx +++ b/src/__tests__/patients/visits/AddVisitModal.test.tsx @@ -1,12 +1,10 @@ -import { Modal } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { screen, render, fireEvent, waitFor, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Router } from 'react-router-dom' import AddVisitModal from '../../../patients/visits/AddVisitModal' -import VisitForm from '../../../patients/visits/VisitForm' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' import { VisitStatus } from '../../../shared/model/Visit' @@ -32,73 +30,64 @@ describe('Add Visit Modal', () => { jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) jest.spyOn(PatientRepository, 'saveOrUpdate') const history = createMemoryHistory() - const wrapper = mount( + + return render( <Router history={history}> <AddVisitModal show onCloseButtonClick={onCloseSpy} patientId={patient.id} /> </Router>, ) - - wrapper.update() - - return { wrapper: wrapper as ReactWrapper } } - it('should render a modal', () => { - const { wrapper } = setup() - - const modal = wrapper.find(Modal) - - expect(modal).toHaveLength(1) + it('should render a modal and within a form', () => { + setup() - const successButton = modal.prop('successButton') - const cancelButton = modal.prop('closeButton') - expect(modal.prop('title')).toEqual('patient.visits.new') - expect(successButton?.children).toEqual('patient.visits.new') - expect(successButton?.icon).toEqual('add') - expect(cancelButton?.children).toEqual('actions.cancel') + expect(within(screen.getByRole('dialog')).getByLabelText('visit form')).toBeInTheDocument() }) - it('should render the visit form', () => { - const { wrapper } = setup() - - const addVisitModal = wrapper.find(AddVisitModal) - expect(addVisitModal).toHaveLength(1) + it('should call the on close function when the cancel button is clicked', () => { + setup() + userEvent.click( + screen.getByRole('button', { + name: /close/i, + }), + ) + expect(onCloseSpy).toHaveBeenCalledTimes(1) }) - it('should call the on close function when the cancel button is clicked', () => { - const { wrapper } = setup() + it('should save the visit when the save button is clicked', async () => { + setup() + const testPatient = patient.visits[0] - const modal = wrapper.find(Modal) + /* Date Pickers */ + const startDateInput = within(screen.getByTestId('startDateTimeDateTimePicker')).getByRole( + 'textbox', + ) + const endDateInput = within(screen.getByTestId('endDateTimeDateTimePicker')).getByRole( + 'textbox', + ) - expect(modal).toHaveLength(1) + fireEvent.change(startDateInput, { target: { value: testPatient.startDateTime } }) + fireEvent.change(endDateInput, { target: { value: testPatient.endDateTime } }) - act(() => { - const cancelButton = modal.prop('closeButton') - const onClick = cancelButton?.onClick as any - onClick() - }) + /* Text */ + const typeInput = screen.getByPlaceholderText(/patient.visits.type/i) + userEvent.type(typeInput, testPatient.type) - expect(onCloseSpy).toHaveBeenCalledTimes(1) - }) + const statusInput = screen.getByRole('combobox') + userEvent.type(statusInput, `${testPatient.status}{arrowdown}{enter}`) - it('should save the visit when the save button is clicked', async () => { - const { wrapper } = setup() + const textareaReason = screen.getByLabelText(/patient\.visits\.reason/i) + userEvent.type(textareaReason, testPatient.reason) - act(() => { - const visitForm = wrapper.find(VisitForm) - const onChange = visitForm.prop('onChange') as any - onChange(patient.visits[0]) - }) - wrapper.update() + const locationInput = screen.getByLabelText(/patient.visits.location/i) + userEvent.type(locationInput, testPatient.location) - await act(async () => { - const modal = wrapper.find(Modal) - const successButton = modal.prop('successButton') - const onClick = successButton?.onClick as any - await onClick() - }) + const saveButton = screen.getByRole('button', { name: /patient.visits.new/i }) + userEvent.click(saveButton) - expect(PatientRepository.saveOrUpdate).toHaveBeenCalledTimes(1) + await waitFor(() => { + expect(PatientRepository.saveOrUpdate).toHaveBeenCalledTimes(1) + }) expect(PatientRepository.saveOrUpdate).toHaveBeenCalledWith(patient) }) }) diff --git a/src/__tests__/patients/visits/ViewVisit.test.tsx b/src/__tests__/patients/visits/ViewVisit.test.tsx index 3f35166b2a..bcb911759d 100644 --- a/src/__tests__/patients/visits/ViewVisit.test.tsx +++ b/src/__tests__/patients/visits/ViewVisit.test.tsx @@ -1,52 +1,82 @@ -import { mount, ReactWrapper } from 'enzyme' +import { screen, render, waitFor, within } from '@testing-library/react' +import format from 'date-fns/format' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Route, Router } from 'react-router-dom' import ViewVisit from '../../../patients/visits/ViewVisit' -import VisitForm from '../../../patients/visits/VisitForm' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' +import Visit, { VisitStatus } from '../../../shared/model/Visit' describe('View Visit', () => { + const visit: Visit = { + id: '123', + startDateTime: new Date().toISOString(), + endDateTime: new Date().toISOString(), + type: 'emergency', + status: VisitStatus.Arrived, + reason: 'routine visit', + location: 'main', + } as Visit const patient = { id: 'patientId', - visits: [{ id: '123', reason: 'reason for visit' }], + visits: [visit], } as Patient - const setup = async () => { + const setup = () => { jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) const history = createMemoryHistory() history.push(`/patients/${patient.id}/visits/${patient.visits[0].id}`) - let wrapper: any - - await act(async () => { - wrapper = await mount( - <Router history={history}> - <Route path="/patients/:id/visits/:visitId"> - <ViewVisit patientId={patient.id} /> - </Route> - </Router>, - ) - }) - - wrapper.update() - return { wrapper: wrapper as ReactWrapper } + return render( + <Router history={history}> + <Route path="/patients/:id/visits/:visitId"> + <ViewVisit patientId={patient.id} /> + </Route> + </Router>, + ) } it('should render the visit reason', async () => { - const { wrapper } = await setup() + setup() - expect(wrapper.find('h2').text()).toEqual(patient.visits[0].reason) + await waitFor(() => { + expect(screen.getByRole('heading', { name: patient.visits[0].reason })).toBeInTheDocument() + }) }) it('should render a visit form with the correct data', async () => { - const { wrapper } = await setup() + setup() + + expect(await screen.findByLabelText('visit form')).toBeInTheDocument() + + const startDateTimePicker = within(screen.getByTestId('startDateTimeDateTimePicker')).getByRole( + 'textbox', + ) + const endDateTimePicker = within(screen.getByTestId('endDateTimeDateTimePicker')).getByRole( + 'textbox', + ) + const typeInput = screen.getByPlaceholderText(/patient.visits.type/i) + const statusSelector = screen.getByPlaceholderText('-- Choose --') + const reasonInput = screen.getAllByRole('textbox', { hidden: false })[3] + const locationInput = screen.getByPlaceholderText(/patient.visits.location/i) - const visitForm = wrapper.find(VisitForm) - expect(visitForm).toHaveLength(1) - expect(visitForm.prop('visit')).toEqual(patient.visits[0]) + expect(startDateTimePicker).toHaveDisplayValue( + format(new Date(visit.startDateTime), 'MM/dd/yyyy h:mm aa'), + ) + expect(startDateTimePicker).toBeDisabled() + expect(endDateTimePicker).toHaveDisplayValue( + format(new Date(visit.endDateTime), 'MM/dd/yyyy h:mm aa'), + ) + expect(endDateTimePicker).toBeDisabled() + expect(typeInput).toHaveDisplayValue(visit.type) + expect(typeInput).toBeDisabled() + expect(statusSelector).toHaveDisplayValue(visit.status) + expect(statusSelector).toBeDisabled() + expect(reasonInput).toHaveDisplayValue(visit.reason) + expect(reasonInput).toBeDisabled() + expect(locationInput).toHaveDisplayValue(visit.location) + expect(locationInput).toBeDisabled() }) }) diff --git a/src/__tests__/patients/visits/VisitForm.test.tsx b/src/__tests__/patients/visits/VisitForm.test.tsx deleted file mode 100644 index 2569b17460..0000000000 --- a/src/__tests__/patients/visits/VisitForm.test.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import { Alert } from '@hospitalrun/components' -import addDays from 'date-fns/addDays' -import { mount } from 'enzyme' -import React from 'react' -import { act } from 'react-dom/test-utils' - -import VisitForm from '../../../patients/visits/VisitForm' -import Patient from '../../../shared/model/Patient' -import Visit, { VisitStatus } from '../../../shared/model/Visit' - -describe('Visit Form', () => { - let onVisitChangeSpy: any - - const visit: Visit = { - startDateTime: new Date().toISOString(), - endDateTime: new Date().toISOString(), - type: 'emergency', - status: VisitStatus.Arrived, - reason: 'routine visit', - location: 'main', - } - const setup = (disabled = false, initializeVisit = true, error?: any) => { - onVisitChangeSpy = jest.fn() - const mockPatient = { id: '123' } as Patient - const wrapper = mount( - <VisitForm - patient={mockPatient} - onChange={onVisitChangeSpy} - visit={initializeVisit ? visit : {}} - disabled={disabled} - visitError={error} - />, - ) - return { wrapper } - } - - it('should render a start date picker', () => { - const { wrapper } = setup() - - const startDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'startDateTime') - - expect(startDateTimePicker).toHaveLength(1) - expect(startDateTimePicker.prop('patient.visit.startDateTime')) - expect(startDateTimePicker.prop('isRequired')).toBeTruthy() - expect(startDateTimePicker.prop('value')).toEqual(new Date(visit.startDateTime)) - }) - - it('should call the on change handler when start date changes', () => { - const expectedNewStartDateTime = addDays(1, new Date().getDate()) - const { wrapper } = setup(false, false) - - const startDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'startDateTime') - act(() => { - const onChange = startDateTimePicker.prop('onChange') as any - onChange(expectedNewStartDateTime) - }) - - expect(onVisitChangeSpy).toHaveBeenCalledWith({ - startDateTime: expectedNewStartDateTime.toISOString(), - }) - }) - - it('should render an end date picker', () => { - const { wrapper } = setup() - - const endDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'endDateTime') - - expect(endDateTimePicker).toHaveLength(1) - expect(endDateTimePicker.prop('patient.visit.endDateTime')) - expect(endDateTimePicker.prop('isRequired')).toBeTruthy() - expect(endDateTimePicker.prop('value')).toEqual(new Date(visit.endDateTime)) - }) - - it('should call the on change handler when end date changes', () => { - const expectedNewEndDateTime = addDays(1, new Date().getDate()) - const { wrapper } = setup(false, false) - - const endDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'endDateTime') - act(() => { - const onChange = endDateTimePicker.prop('onChange') as any - onChange(expectedNewEndDateTime) - }) - - expect(onVisitChangeSpy).toHaveBeenCalledWith({ - endDateTime: expectedNewEndDateTime.toISOString(), - }) - }) - - it('should render a type input', () => { - const { wrapper } = setup() - - const typeInput = wrapper.findWhere((w) => w.prop('name') === 'type') - expect(typeInput).toHaveLength(1) - expect(typeInput.prop('patient.visit.type')) - expect(typeInput.prop('value')).toEqual(visit.type) - }) - - it('should call the on change handler when type changes', () => { - const expectedNewType = 'some new type' - const { wrapper } = setup(false, false) - - const typeInput = wrapper.findWhere((w) => w.prop('name') === 'type') - act(() => { - const onChange = typeInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedNewType } }) - }) - - expect(onVisitChangeSpy).toHaveBeenCalledWith({ type: expectedNewType }) - }) - - it('should render a status selector', () => { - const { wrapper } = setup() - - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - - expect(statusSelector).toHaveLength(1) - expect(statusSelector.prop('patient.visit.status')) - expect(statusSelector.prop('isRequired')).toBeTruthy() - expect(statusSelector.prop('defaultSelected')[0].value).toEqual(visit.status) - expect(statusSelector.prop('options')).toEqual( - Object.values(VisitStatus).map((v) => ({ label: v, value: v })), - ) - }) - - it('should call the on change handler when status changes', () => { - const expectedNewStatus = VisitStatus.Finished - const { wrapper } = setup(false, false) - act(() => { - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - const onChange = statusSelector.prop('onChange') as any - onChange([expectedNewStatus]) - }) - - expect(onVisitChangeSpy).toHaveBeenCalledWith({ status: expectedNewStatus }) - }) - - it('should render a reason input', () => { - const { wrapper } = setup() - - const reasonInput = wrapper.findWhere((w) => w.prop('name') === 'reason') - - expect(reasonInput).toHaveLength(1) - expect(reasonInput.prop('patient.visit.reason')) - expect(reasonInput.prop('isRequired')).toBeTruthy() - expect(reasonInput.prop('value')).toEqual(visit.reason) - }) - - it('should call the on change handler when reason changes', () => { - const expectedNewReason = 'some new reason' - const { wrapper } = setup(false, false) - act(() => { - const reasonInput = wrapper.findWhere((w) => w.prop('name') === 'reason') - const onChange = reasonInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedNewReason } }) - }) - - expect(onVisitChangeSpy).toHaveBeenCalledWith({ reason: expectedNewReason }) - }) - - it('should render a location input', () => { - const { wrapper } = setup() - - const locationInput = wrapper.findWhere((w) => w.prop('name') === 'location') - - expect(locationInput).toHaveLength(1) - expect(locationInput.prop('patient.visit.location')) - expect(locationInput.prop('isRequired')).toBeTruthy() - expect(locationInput.prop('value')).toEqual(visit.location) - }) - - it('should call the on change handler when location changes', () => { - const expectedNewLocation = 'some new location' - const { wrapper } = setup(false, false) - act(() => { - const locationInput = wrapper.findWhere((w) => w.prop('name') === 'location') - const onChange = locationInput.prop('onChange') as any - onChange({ currentTarget: { value: expectedNewLocation } }) - }) - - expect(onVisitChangeSpy).toHaveBeenCalledWith({ location: expectedNewLocation }) - }) - - it('should render all of the fields as disabled if the form is disabled', () => { - const { wrapper } = setup(true) - const startDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'startDateTime') - const endDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'endDateTime') - const typeInput = wrapper.findWhere((w) => w.prop('name') === 'type') - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - const reasonInput = wrapper.findWhere((w) => w.prop('name') === 'reason') - const locationInput = wrapper.findWhere((w) => w.prop('name') === 'location') - - expect(startDateTimePicker.prop('isEditable')).toBeFalsy() - expect(endDateTimePicker.prop('isEditable')).toBeFalsy() - expect(typeInput.prop('isEditable')).toBeFalsy() - expect(statusSelector.prop('isEditable')).toBeFalsy() - expect(reasonInput.prop('isEditable')).toBeFalsy() - expect(locationInput.prop('isEditable')).toBeFalsy() - }) - - it('should render the form fields in an error state', () => { - const expectedError = { - message: 'error message', - startDateTime: 'start date error', - endDateTime: 'end date error', - type: 'type error', - status: 'status error', - reason: 'reason error', - location: 'location error', - } - - const { wrapper } = setup(false, false, expectedError) - - const alert = wrapper.find(Alert) - const startDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'startDateTime') - const endDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'endDateTime') - const typeInput = wrapper.findWhere((w) => w.prop('name') === 'type') - const statusSelector = wrapper.findWhere((w) => w.prop('name') === 'status') - const reasonInput = wrapper.findWhere((w) => w.prop('name') === 'reason') - const locationInput = wrapper.findWhere((w) => w.prop('name') === 'location') - - expect(alert).toHaveLength(1) - expect(alert.prop('message')).toEqual(expectedError.message) - - expect(startDateTimePicker.prop('isInvalid')).toBeTruthy() - expect(startDateTimePicker.prop('feedback')).toEqual(expectedError.startDateTime) - - expect(endDateTimePicker.prop('isInvalid')).toBeTruthy() - expect(endDateTimePicker.prop('feedback')).toEqual(expectedError.endDateTime) - - expect(typeInput.prop('isInvalid')).toBeTruthy() - expect(typeInput.prop('feedback')).toEqual(expectedError.type) - - expect(statusSelector.prop('isInvalid')).toBeTruthy() - - expect(reasonInput.prop('isInvalid')).toBeTruthy() - expect(reasonInput.prop('feedback')).toEqual(expectedError.reason) - - expect(locationInput.prop('isInvalid')).toBeTruthy() - expect(locationInput.prop('feedback')).toEqual(expectedError.location) - }) -}) diff --git a/src/__tests__/patients/visits/VisitTab.test.tsx b/src/__tests__/patients/visits/VisitTab.test.tsx index bdf46f65fd..1d3bce4375 100644 --- a/src/__tests__/patients/visits/VisitTab.test.tsx +++ b/src/__tests__/patients/visits/VisitTab.test.tsx @@ -1,105 +1,118 @@ -import { Button } from '@hospitalrun/components' -import { mount } from 'enzyme' +import { screen, render, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' -import AddVisitModal from '../../../patients/visits/AddVisitModal' -import ViewVisit from '../../../patients/visits/ViewVisit' import VisitTab from '../../../patients/visits/VisitTab' -import VisitTable from '../../../patients/visits/VisitTable' +import PatientRepository from '../../../shared/db/PatientRepository' +import Patient from '../../../shared/model/Patient' import Permissions from '../../../shared/model/Permissions' +import Visit, { VisitStatus } from '../../../shared/model/Visit' import { RootState } from '../../../shared/store' const mockStore = createMockStore<RootState, any>([thunk]) describe('Visit Tab', () => { + const visit = { + id: '456', + startDateTime: new Date(2020, 6, 3).toISOString(), + endDateTime: new Date(2020, 6, 5).toISOString(), + type: 'standard type', + status: VisitStatus.Arrived, + reason: 'some reason', + location: 'main building', + } as Visit + const patient = { - id: 'patientId', - } + id: '123', + visits: [visit], + } as Patient - const setup = async (route: string, permissions: Permissions[]) => { + const setup = (route: string, permissions: Permissions[]) => { + jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) const store = mockStore({ user: { permissions } } as any) const history = createMemoryHistory() history.push(route) - let wrapper: any - await act(async () => { - wrapper = await mount( + return { + history, + ...render( <Provider store={store}> <Router history={history}> <VisitTab patientId={patient.id} /> </Router> </Provider>, - ) - }) - - wrapper.update() - - return { wrapper, history } + ), + } } - it('should render an add visit button if user has correct permissions', async () => { - const { wrapper } = await setup('/patients/123/visits', [Permissions.AddVisit]) + it('should render an add visit button if user has correct permissions', () => { + setup(`/patients/${patient.id}/visits`, [Permissions.AddVisit]) - const addNewButton = wrapper.find(Button).at(0) - expect(addNewButton).toHaveLength(1) - expect(addNewButton.text().trim()).toEqual('patient.visits.new') + expect(screen.queryByRole('button', { name: /patient.visits.new/i })).toBeInTheDocument() }) - it('should open the add visit modal on click', async () => { - const { wrapper } = await setup('/patients/123/visits', [Permissions.AddVisit]) + it('should open the add visit modal on click', () => { + setup(`/patients/${patient.id}/visits`, [Permissions.AddVisit]) - act(() => { - const addNewButton = wrapper.find(Button).at(0) - const onClick = addNewButton.prop('onClick') as any - onClick() - }) - wrapper.update() + const addNewButton = screen.getByRole('button', { name: /patient.visits.new/i }) + userEvent.click(addNewButton) - const modal = wrapper.find(AddVisitModal) - expect(modal.prop('show')).toBeTruthy() + expect(screen.getByRole('dialog')).toBeInTheDocument() + expect(screen.getByRole('dialog').classList).toContain('show') }) it('should close the modal when the close button is clicked', async () => { - const { wrapper } = await setup('/patients/123/visits', [Permissions.AddVisit]) + setup(`/patients/${patient.id}/visits`, [Permissions.AddVisit]) - act(() => { - const addNewButton = wrapper.find(Button).at(0) - const onClick = addNewButton.prop('onClick') as any - onClick() - }) - wrapper.update() + const addNewButton = screen.getByRole('button', { name: /patient.visits.new/i }) + userEvent.click(addNewButton) - act(() => { - const modal = wrapper.find(AddVisitModal) - const onClose = modal.prop('onCloseButtonClick') as any - onClose() - }) - wrapper.update() + expect(screen.getByRole('dialog')).toBeInTheDocument() + expect(screen.getByRole('dialog').classList).toContain('show') + + const closeButton = screen.getByRole('button', { name: /close/i }) - expect(wrapper.find(AddVisitModal).prop('show')).toBeFalsy() + userEvent.click(closeButton) + expect(screen.getByRole('dialog').classList).not.toContain('show') }) - it('should not render visit button if user does not have permissions', async () => { - const { wrapper } = await setup('/patients/123/visits', []) + it('should not render visit button if user does not have permissions', () => { + setup(`/patients/${patient.id}/visits`, []) - expect(wrapper.find(Button)).toHaveLength(0) + expect(screen.queryByRole('button', { name: /patient.visits.new/i })).not.toBeInTheDocument() }) it('should render the visits table when on /patient/:id/visits', async () => { - const { wrapper } = await setup('/patients/123/visits', [Permissions.ReadVisits]) + setup(`/patients/${patient.id}/visits`, [Permissions.ReadVisits]) - expect(wrapper.find(VisitTable)).toHaveLength(1) + await waitFor(() => { + expect(screen.queryByRole('table')).toBeInTheDocument() + }) + + expect( + screen.getByRole('columnheader', { name: /patient.visits.startDateTime/i }), + ).toBeInTheDocument() + expect( + screen.getByRole('columnheader', { name: /patient.visits.endDateTime/i }), + ).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /patient.visits.type/i })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /patient.visits.status/i })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /patient.visits.reason/i })).toBeInTheDocument() + expect( + screen.getByRole('columnheader', { name: /patient.visits.location/i }), + ).toBeInTheDocument() }) it('should render the visit view when on /patient/:id/visits/:visitId', async () => { - const { wrapper } = await setup('/patients/123/visits/456', [Permissions.ReadVisits]) + setup(`/patients/${patient.id}/visits/${visit.id}`, [Permissions.ReadVisits]) - expect(wrapper.find(ViewVisit)).toHaveLength(1) + await waitFor(() => { + expect(screen.queryByRole('heading', { name: visit.reason })).toBeInTheDocument() + }) }) }) diff --git a/src/__tests__/patients/visits/VisitTable.test.tsx b/src/__tests__/patients/visits/VisitTable.test.tsx index 0df60931dd..fb5c1644d7 100644 --- a/src/__tests__/patients/visits/VisitTable.test.tsx +++ b/src/__tests__/patients/visits/VisitTable.test.tsx @@ -1,8 +1,8 @@ -import { Table } from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { screen, render } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import format from 'date-fns/format' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Router } from 'react-router-dom' import VisitTable from '../../../patients/visits/VisitTable' @@ -10,81 +10,79 @@ import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' import Visit, { VisitStatus } from '../../../shared/model/Visit' -describe('Visit Table', () => { - const visit: Visit = { - id: 'id', - startDateTime: new Date(2020, 6, 3).toISOString(), - endDateTime: new Date(2020, 6, 5).toISOString(), - type: 'standard type', - status: VisitStatus.Arrived, - reason: 'some reason', - location: 'main building', - } - const patient = { - id: 'patientId', - diagnoses: [{ id: '123', name: 'some name', diagnosisDate: new Date().toISOString() }], - visits: [visit], - } as Patient - - const setup = async () => { - jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - const history = createMemoryHistory() - history.push(`/patients/${patient.id}/visits/${patient.visits[0].id}`) - - let wrapper: any - await act(async () => { - wrapper = await mount( - <Router history={history}> - <VisitTable patientId={patient.id} /> - </Router>, - ) - }) +const visit: Visit = { + id: 'id', + startDateTime: new Date(2020, 6, 3).toISOString(), + endDateTime: new Date(2020, 6, 5).toISOString(), + type: 'standard type', + status: VisitStatus.Arrived, + reason: 'some reason', + location: 'main building', +} as Visit +const patient = { + id: 'patientId', + diagnoses: [{ id: '123', name: 'some name', diagnosisDate: new Date().toISOString() }], + visits: [visit], +} as Patient - wrapper.update() +const setup = () => { + jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) + const history = createMemoryHistory() + history.push(`/patients/${patient.id}/visits/${patient.visits[0].id}`) - return { wrapper: wrapper as ReactWrapper, history } + return { + history, + ...render( + <Router history={history}> + <VisitTable patientId={patient.id} /> + </Router>, + ), } +} +describe('Visit Table', () => { it('should render a table', async () => { - const { wrapper } = await setup() + setup() + + await screen.findByRole('table') + + expect( + screen.getByRole('columnheader', { name: /patient.visits.startDateTime/i }), + ).toBeInTheDocument() + expect( + screen.getByRole('columnheader', { name: /patient.visits.endDateTime/i }), + ).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /patient.visits.type/i })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /patient.visits.status/i })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /patient.visits.reason/i })).toBeInTheDocument() + expect( + screen.getByRole('columnheader', { name: /patient.visits.location/i }), + ).toBeInTheDocument() - const table = wrapper.find(Table) - const columns = table.prop('columns') - const actions = table.prop('actions') as any - expect(columns[0]).toEqual( - expect.objectContaining({ label: 'patient.visits.startDateTime', key: 'startDateTime' }), - ) - expect(columns[1]).toEqual( - expect.objectContaining({ label: 'patient.visits.endDateTime', key: 'endDateTime' }), - ) - expect(columns[2]).toEqual( - expect.objectContaining({ label: 'patient.visits.type', key: 'type' }), - ) - expect(columns[3]).toEqual( - expect.objectContaining({ label: 'patient.visits.status', key: 'status' }), - ) - expect(columns[4]).toEqual( - expect.objectContaining({ label: 'patient.visits.reason', key: 'reason' }), - ) - expect(columns[5]).toEqual( - expect.objectContaining({ label: 'patient.visits.location', key: 'location' }), - ) + expect( + screen.getByRole('button', { + name: /actions\.view/i, + }), + ).toBeInTheDocument() - expect(actions[0]).toEqual(expect.objectContaining({ label: 'actions.view' })) - expect(table.prop('actionsHeaderText')).toEqual('actions.label') - expect(table.prop('data')).toEqual(patient.visits) + const formatter = (dt: string) => format(new Date(dt), 'yyyy-MM-dd hh:mm a') + expect(screen.getByRole('cell', { name: formatter(visit.startDateTime) })).toBeInTheDocument() + expect(screen.getByRole('cell', { name: formatter(visit.endDateTime) })).toBeInTheDocument() + expect(screen.getByRole('cell', { name: visit.type })).toBeInTheDocument() + expect(screen.getByRole('cell', { name: visit.status })).toBeInTheDocument() + expect(screen.getByRole('cell', { name: visit.reason })).toBeInTheDocument() + expect(screen.getByRole('cell', { name: visit.location })).toBeInTheDocument() }) it('should navigate to the visit view when the view details button is clicked', async () => { - const { wrapper, history } = await setup() + const { history } = setup() - const tr = wrapper.find('tr').at(1) - - act(() => { - const onClick = tr.find('button').prop('onClick') as any - onClick({ stopPropagation: jest.fn() }) + const actionButton = await screen.findByRole('button', { + name: /actions\.view/i, }) + userEvent.click(actionButton) + expect(history.location.pathname).toEqual(`/patients/${patient.id}/visits/${visit.id}`) }) }) diff --git a/src/__tests__/scheduling/appointments/AppointmentDetailForm.test.tsx b/src/__tests__/scheduling/appointments/AppointmentDetailForm.test.tsx index 7bfc5b39dd..4104b86c14 100644 --- a/src/__tests__/scheduling/appointments/AppointmentDetailForm.test.tsx +++ b/src/__tests__/scheduling/appointments/AppointmentDetailForm.test.tsx @@ -1,48 +1,21 @@ -import { Typeahead, Alert } from '@hospitalrun/components' -import { act } from '@testing-library/react' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import addMinutes from 'date-fns/addMinutes' import roundToNearestMinutes from 'date-fns/roundToNearestMinutes' -import { mount, ReactWrapper } from 'enzyme' import React from 'react' import AppointmentDetailForm from '../../../scheduling/appointments/AppointmentDetailForm' -import PatientRepository from '../../../shared/db/PatientRepository' import Appointment from '../../../shared/model/Appointment' import Patient from '../../../shared/model/Patient' -describe('AppointmentDetailForm', () => { - describe('Error handling', () => { - it('should display errors', () => { - const error = { - message: 'some message', - patient: 'patient message', - startDateTime: 'start date time message', - } - - const wrapper = mount( - <AppointmentDetailForm - appointment={{} as Appointment} - patient={{} as Patient} - isEditable - error={error} - />, - ) - wrapper.update() - - const errorMessage = wrapper.find(Alert) - const patientTypeahead = wrapper.find(Typeahead) - const startDateInput = wrapper.findWhere((w: any) => w.prop('name') === 'startDate') - expect(errorMessage).toBeTruthy() - expect(errorMessage.prop('message')).toMatch(error.message) - expect(patientTypeahead.prop('isInvalid')).toBeTruthy() - expect(patientTypeahead.prop('feedback')).toEqual(error.patient) - expect(startDateInput.prop('isInvalid')).toBeTruthy() - expect(startDateInput.prop('feedback')).toEqual(error.startDateTime) - }) - }) +const setup = (appointment: Appointment, patient?: Patient, error = {}) => ({ + ...render( + <AppointmentDetailForm appointment={appointment} patient={patient} isEditable error={error} />, + ), +}) - describe('layout - editable', () => { - let wrapper: ReactWrapper +describe('AppointmentDetailForm', () => { + it('should render a type select box', () => { const expectedAppointment = { startDateTime: roundToNearestMinutes(new Date(), { nearestTo: 15 }).toISOString(), endDateTime: addMinutes( @@ -54,256 +27,35 @@ describe('AppointmentDetailForm', () => { type: 'emergency', } as Appointment - beforeEach(() => { - wrapper = mount( - <AppointmentDetailForm appointment={expectedAppointment} onFieldChange={jest.fn()} />, - ) - }) + setup(expectedAppointment) - it('should render a typeahead for patients', () => { - const patientTypeahead = wrapper.find(Typeahead) - - expect(patientTypeahead).toHaveLength(1) - expect(patientTypeahead.prop('placeholder')).toEqual('scheduling.appointment.patient') - expect(patientTypeahead.prop('value')).toEqual(expectedAppointment.patient) - }) + const types = ['checkup', 'emergency', 'followUp', 'routine', 'walkIn'] - it('should render as start date date time picker', () => { - const startDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'startDate') - - expect(startDateTimePicker).toHaveLength(1) - expect(startDateTimePicker.prop('label')).toEqual('scheduling.appointment.startDate') - expect(startDateTimePicker.prop('value')).toEqual( - roundToNearestMinutes(new Date(), { nearestTo: 15 }), - ) - }) - - it('should render an end date time picker', () => { - const endDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'endDate') - - expect(endDateTimePicker).toHaveLength(1) - expect(endDateTimePicker.prop('label')).toEqual('scheduling.appointment.endDate') - expect(endDateTimePicker.prop('value')).toEqual( - addMinutes(roundToNearestMinutes(new Date(), { nearestTo: 15 }), 60), - ) - }) - - it('should render a location text input box', () => { - const locationTextInputBox = wrapper.findWhere((w) => w.prop('name') === 'location') - - expect(locationTextInputBox).toHaveLength(1) - expect(locationTextInputBox.prop('label')).toEqual('scheduling.appointment.location') - expect(locationTextInputBox.prop('value')).toEqual(expectedAppointment.location) - }) + userEvent.click(screen.getByPlaceholderText('-- Choose --')) - it('should render a type select box', () => { - const typeSelect = wrapper.findWhere((w) => w.prop('name') === 'type') - - expect(typeSelect).toHaveLength(1) - expect(typeSelect.prop('label')).toEqual('scheduling.appointment.type') - expect(typeSelect.prop('options')[0].label).toEqual('scheduling.appointment.types.checkup') - expect(typeSelect.prop('options')[0].value).toEqual('checkup') - expect(typeSelect.prop('options')[1].label).toEqual('scheduling.appointment.types.emergency') - expect(typeSelect.prop('options')[1].value).toEqual('emergency') - expect(typeSelect.prop('options')[2].label).toEqual('scheduling.appointment.types.followUp') - expect(typeSelect.prop('options')[2].value).toEqual('follow up') - expect(typeSelect.prop('options')[3].label).toEqual('scheduling.appointment.types.routine') - expect(typeSelect.prop('options')[3].value).toEqual('routine') - expect(typeSelect.prop('options')[4].label).toEqual('scheduling.appointment.types.walkIn') - expect(typeSelect.prop('options')[4].value).toEqual('walk in') - expect(typeSelect.prop('defaultSelected')[0].value).toEqual(expectedAppointment.type) - }) - - it('should render a reason text field input', () => { - const reasonTextField = wrapper.findWhere((w) => w.prop('name') === 'reason') - - expect(reasonTextField).toHaveLength(1) - expect(reasonTextField.prop('label')).toEqual('scheduling.appointment.reason') - }) - }) - - describe('layout - editable but patient prop passed (Edit Appointment functionality)', () => { - it('should disable patient typeahead if patient prop passed', () => { - const expectedAppointment = { - startDateTime: roundToNearestMinutes(new Date(), { nearestTo: 15 }).toISOString(), - endDateTime: addMinutes( - roundToNearestMinutes(new Date(), { nearestTo: 15 }), - 60, - ).toISOString(), - } as Appointment - - const wrapper = mount( - <AppointmentDetailForm - isEditable - appointment={expectedAppointment} - patient={{} as Patient} - onFieldChange={jest.fn()} - />, - ) - const patientTypeahead = wrapper.find(Typeahead) - expect(patientTypeahead.prop('disabled')).toBeTruthy() + types.forEach((type) => { + const typeOption = screen.getByRole('option', { + name: `scheduling.appointment.types.${type}`, + }) + expect(typeOption).toBeInTheDocument() + userEvent.click(typeOption) }) }) - describe('layout - not editable', () => { - let wrapper: ReactWrapper + it('should disable patient typeahead if patient prop passed', () => { const expectedAppointment = { - patient: 'patientId', startDateTime: roundToNearestMinutes(new Date(), { nearestTo: 15 }).toISOString(), endDateTime: addMinutes( roundToNearestMinutes(new Date(), { nearestTo: 15 }), 60, ).toISOString(), } as Appointment - const expectedPatient = { - id: '123', - fullName: 'full name', + fullName: 'Mr Popo', } as Patient - beforeEach(() => { - wrapper = mount( - <AppointmentDetailForm - isEditable={false} - appointment={expectedAppointment} - patient={expectedPatient} - onFieldChange={jest.fn()} - />, - ) - }) - it('should disable fields', () => { - const patientTypeahead = wrapper.find(Typeahead) - const startDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'startDate') - const endDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'endDate') - const locationTextInputBox = wrapper.findWhere((w) => w.prop('name') === 'location') - const reasonTextField = wrapper.findWhere((w) => w.prop('name') === 'reason') - const typeSelect = wrapper.findWhere((w) => w.prop('name') === 'type') - - expect(patientTypeahead).toHaveLength(1) - expect(patientTypeahead.prop('disabled')).toBeTruthy() - expect(patientTypeahead.prop('value')).toEqual(expectedPatient.fullName) - expect(startDateTimePicker.prop('isEditable')).toBeFalsy() - expect(endDateTimePicker.prop('isEditable')).toBeFalsy() - expect(locationTextInputBox.prop('isEditable')).toBeFalsy() - expect(reasonTextField.prop('isEditable')).toBeFalsy() - expect(typeSelect.prop('isEditable')).toBeFalsy() - }) - }) + setup(expectedAppointment, expectedPatient) - describe('change handlers', () => { - let wrapper: ReactWrapper - const appointment = { - startDateTime: roundToNearestMinutes(new Date(), { nearestTo: 15 }).toISOString(), - endDateTime: addMinutes( - roundToNearestMinutes(new Date(), { nearestTo: 15 }), - 30, - ).toISOString(), - } as Appointment - const onFieldChange = jest.fn() - - beforeEach(() => { - wrapper = mount( - <AppointmentDetailForm appointment={appointment} onFieldChange={onFieldChange} />, - ) - }) - - it('should call onFieldChange when patient input changes', () => { - const expectedPatientId = '123' - - act(() => { - const patientTypeahead = wrapper.find(Typeahead) - patientTypeahead.prop('onChange')([{ id: expectedPatientId }] as Patient[]) - }) - wrapper.update() - - expect(onFieldChange).toHaveBeenLastCalledWith('patient', expectedPatientId) - }) - - it('should call onFieldChange when start date time changes', () => { - const expectedStartDateTime = roundToNearestMinutes(new Date(), { nearestTo: 15 }) - - act(() => { - const startDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'startDate') - startDateTimePicker.prop('onChange')(expectedStartDateTime) - }) - wrapper.update() - - expect(onFieldChange).toHaveBeenLastCalledWith( - 'startDateTime', - expectedStartDateTime.toISOString(), - ) - }) - - it('should call onFieldChange when end date time changes', () => { - const expectedStartDateTime = roundToNearestMinutes(new Date(), { nearestTo: 15 }) - const expectedEndDateTime = addMinutes(expectedStartDateTime, 30) - - act(() => { - const endDateTimePicker = wrapper.findWhere((w) => w.prop('name') === 'endDate') - endDateTimePicker.prop('onChange')(expectedEndDateTime) - }) - wrapper.update() - - expect(onFieldChange).toHaveBeenLastCalledWith( - 'endDateTime', - expectedEndDateTime.toISOString(), - ) - }) - - it('should call onFieldChange when location changes', () => { - const expectedLocation = 'location' - - act(() => { - const locationTextInputBox = wrapper.findWhere((w) => w.prop('name') === 'location') - locationTextInputBox.prop('onChange')({ target: { value: expectedLocation } }) - }) - wrapper.update() - - expect(onFieldChange).toHaveBeenLastCalledWith('location', expectedLocation) - }) - - it('should call onFieldChange when reason changes', () => { - const expectedReason = 'reason' - - act(() => { - const reasonTextField = wrapper.findWhere((w) => w.prop('name') === 'reason') - reasonTextField.prop('onChange')({ currentTarget: { value: expectedReason } }) - }) - wrapper.update() - - expect(onFieldChange).toHaveBeenLastCalledWith('reason', expectedReason) - }) - }) - - describe('typeahead search', () => { - let wrapper: ReactWrapper - beforeEach(() => { - wrapper = mount( - <AppointmentDetailForm - appointment={ - { - startDateTime: roundToNearestMinutes(new Date(), { nearestTo: 15 }).toISOString(), - endDateTime: addMinutes( - roundToNearestMinutes(new Date(), { nearestTo: 15 }), - 60, - ).toISOString(), - } as Appointment - } - onFieldChange={jest.fn()} - />, - ) - }) - - it('should call the PatientRepository search when typeahead changes', () => { - const patientTypeahead = wrapper.find(Typeahead) - const patientRepositorySearch = jest.spyOn(PatientRepository, 'search') - const expectedSearchString = 'search' - - act(() => { - patientTypeahead.prop('onSearch')(expectedSearchString) - }) - - expect(patientRepositorySearch).toHaveBeenCalledWith(expectedSearchString) - }) + expect(screen.getByDisplayValue(expectedPatient.fullName as string)).toBeDisabled() }) }) diff --git a/src/__tests__/scheduling/appointments/Appointments.test.tsx b/src/__tests__/scheduling/appointments/Appointments.test.tsx index 5f8c50c2a1..a0ad1af519 100644 --- a/src/__tests__/scheduling/appointments/Appointments.test.tsx +++ b/src/__tests__/scheduling/appointments/Appointments.test.tsx @@ -1,77 +1,50 @@ -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' +import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' -import { MemoryRouter, Route } from 'react-router-dom' +import { Router } 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 { addBreadcrumbs } from '../../../page-header/breadcrumbs/breadcrumbs-slice' import * as titleUtil from '../../../page-header/title/TitleContext' -import Appointments from '../../../scheduling/appointments/Appointments' -import EditAppointment from '../../../scheduling/appointments/edit/EditAppointment' -import NewAppointment from '../../../scheduling/appointments/new/NewAppointment' -import ViewAppointments from '../../../scheduling/appointments/ViewAppointments' -import AppointmentRepository from '../../../shared/db/AppointmentRepository' -import PatientRepository from '../../../shared/db/PatientRepository' -import Appointment from '../../../shared/model/Appointment' -import Patient from '../../../shared/model/Patient' import Permissions from '../../../shared/model/Permissions' import { RootState } from '../../../shared/store' const { TitleProvider } = titleUtil const mockStore = createMockStore<RootState, any>([thunk]) -let route: any -describe('/appointments', () => { - // eslint-disable-next-line no-shadow - - const setup = (setupRoute: string, permissions: Permissions[], renderHr: boolean = false) => { - const appointment = { - id: '123', - patient: '456', - } as Appointment - - const patient = { - id: '456', - } as Patient - - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(AppointmentRepository, 'find').mockResolvedValue(appointment) - jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - - const store = mockStore({ - title: 'test', - user: { user: { id: '123' }, permissions }, - appointment: { appointment, patient: { id: '456' } as Patient }, - appointments: [{ appointment, patient: { id: '456' } as Patient }], - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - } as any) - jest.useFakeTimers() - const wrapper = mount( +const setup = (url: string, permissions: Permissions[]) => { + const history = createMemoryHistory({ initialEntries: [url] }) + const store = mockStore({ + user: { user: { id: '123' }, permissions }, + breadcrumbs: { breadcrumbs: [] }, + components: { sidebarCollapsed: false }, + } as any) + + return { + history, + store, + ...render( <Provider store={store}> - <MemoryRouter initialEntries={[setupRoute]}> - <TitleProvider>{renderHr ? <HospitalRun /> : <Appointments />}</TitleProvider> - </MemoryRouter> + <Router history={history}> + <TitleProvider> + <HospitalRun /> + </TitleProvider> + </Router> </Provider>, - ) - jest.advanceTimersByTime(100) - if (!renderHr) { - wrapper.find(Appointments).props().updateTitle = jest.fn() - } - wrapper.update() - - return { wrapper: wrapper as ReactWrapper, store } + ), } +} +describe('/appointments', () => { it('should render the appointments screen when /appointments is accessed', async () => { - route = '/appointments' - const permissions: Permissions[] = [Permissions.ReadAppointments] - const { wrapper, store } = await setup(route, permissions) + const { store } = setup('/appointments', [Permissions.ReadAppointments]) - expect(wrapper.find(ViewAppointments)).toHaveLength(1) + expect( + await screen.findByRole('heading', { name: /scheduling\.appointments\.label/i }), + ).toBeInTheDocument() expect(store.getActions()).toContainEqual( addBreadcrumbs([ @@ -82,62 +55,20 @@ describe('/appointments', () => { }) it('should render the Dashboard when the user does not have read appointment privileges', async () => { - route = '/appointments' - const permissions: Permissions[] = [] - const { wrapper } = await setup(route, permissions, true) + setup('/appointments', []) - wrapper.update() - expect(wrapper.find(Dashboard)).toHaveLength(1) + expect(await screen.findByRole('heading', { name: /dashboard\.label/i })).toBeInTheDocument() }) }) describe('/appointments/new', () => { - // eslint-disable-next-line no-shadow - const setup = (route: string, permissions: Permissions[], renderHr: boolean = false) => { - const appointment = { - id: '123', - patient: '456', - } as Appointment - - const patient = { - id: '456', - } as Patient - - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(AppointmentRepository, 'find').mockResolvedValue(appointment) - jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - - const store = mockStore({ - title: 'test', - user: { user: { id: '123' }, permissions }, - appointment: { appointment, patient: { id: '456' } as Patient }, - appointments: [{ appointment, patient: { id: '456' } as Patient }], - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - } as any) - - const wrapper = mount( - <Provider store={store}> - <MemoryRouter initialEntries={[route]}> - <TitleProvider>{renderHr ? <HospitalRun /> : <NewAppointment />}</TitleProvider> - </MemoryRouter> - </Provider>, - ) - if (!renderHr) { - wrapper.find(NewAppointment).props().updateTitle = jest.fn() - } - wrapper.update() - - return { wrapper: wrapper as ReactWrapper, store } - } it('should render the new appointment screen when /appointments/new is accessed', async () => { - route = '/appointments/new' - const permissions: Permissions[] = [Permissions.WriteAppointments] - const { wrapper, store } = setup(route, permissions, false) + const { store } = setup('/appointments/new', [Permissions.WriteAppointments]) - wrapper.update() + expect( + await screen.findByRole('heading', { name: /scheduling\.appointments\.new/i }), + ).toBeInTheDocument() - expect(wrapper.find(NewAppointment)).toHaveLength(1) expect(store.getActions()).toContainEqual( addBreadcrumbs([ { i18nKey: 'scheduling.appointments.label', location: '/appointments' }, @@ -147,140 +78,31 @@ describe('/appointments/new', () => { ) }) - it('should render the Dashboard when the user does not have read appointment privileges', () => { - route = '/appointments/new' - const permissions: Permissions[] = [] - const { wrapper } = setup(route, permissions, true) + it('should render the Dashboard when the user does not have read appointment privileges', async () => { + setup('/appointments/new', []) - expect(wrapper.find(Dashboard)).toHaveLength(1) + expect(await screen.findByRole('heading', { name: /dashboard\.label/i })).toBeInTheDocument() }) }) describe('/appointments/edit/:id', () => { - // eslint-disable-next-line no-shadow - const setup = async (route: string, permissions: Permissions[], renderHr: boolean = false) => { - const appointment = { - id: '123', - patient: '456', - } as Appointment - - const patient = { - id: '456', - } as Patient - - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(AppointmentRepository, 'find').mockResolvedValue(appointment) - jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - - const store = mockStore({ - title: 'test', - user: { user: { id: '123' }, permissions }, - appointment: { appointment, patient: { id: '456' } as Patient }, - appointments: [{ appointment, patient: { id: '456' } as Patient }], - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - } as any) - - const wrapper = await mount( - <Provider store={store}> - <MemoryRouter initialEntries={[route]}> - <Route path="/appointments/edit/:id"> - <TitleProvider>{renderHr ? <HospitalRun /> : <EditAppointment />}</TitleProvider> - </Route> - </MemoryRouter> - </Provider>, - ) - if (!renderHr) { - wrapper.find(EditAppointment).props().updateTitle = jest.fn() - } - wrapper.update() - - return { wrapper: wrapper as ReactWrapper, store } - } - it('should render the edit appointment screen when /appointments/edit/:id is accessed', async () => { - route = '/appointments/edit/123' - const permissions: Permissions[] = [Permissions.WriteAppointments, Permissions.ReadAppointments] - const { wrapper } = await setup(route, permissions, false) + setup('/appointments/edit/123', [Permissions.WriteAppointments, Permissions.ReadAppointments]) - expect(wrapper.find(EditAppointment)).toHaveLength(1) - expect(AppointmentRepository.find).toHaveBeenCalledTimes(1) - expect(AppointmentRepository.find).toHaveBeenCalledWith('123') + expect( + await screen.findByRole('heading', { name: /scheduling\.appointments\.editAppointment/i }), + ).toBeInTheDocument() }) it('should render the Dashboard when the user does not have read appointment privileges', async () => { - route = '/appointments/edit/123' - const permissions: Permissions[] = [] - const appointment = { - id: '123', - patient: '456', - } as Appointment - - const patient = { - id: '456', - } as Patient - - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(AppointmentRepository, 'find').mockResolvedValue(appointment) - jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - const store = mockStore({ - title: 'test', - user: { user: { id: '123' }, permissions }, - appointment: { appointment, patient: { id: '456' } as Patient }, - appointments: [{ appointment, patient: { id: '456' } as Patient }], - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - } as any) - - const wrapper = await mount( - <Provider store={store}> - <MemoryRouter initialEntries={[route]}> - <TitleProvider> - <HospitalRun /> - </TitleProvider> - </MemoryRouter> - </Provider>, - ) - await wrapper.update() + setup('/appointments/edit/123', [Permissions.WriteAppointments]) - expect(wrapper.find(Dashboard)).toHaveLength(1) + expect(await screen.findByRole('heading', { name: /dashboard\.label/i })).toBeInTheDocument() }) it('should render the Dashboard when the user does not have write appointment privileges', async () => { - route = '/appointments/edit/123' - const permissions: Permissions[] = [] - const appointment = { - id: '123', - patient: '456', - } as Appointment - - const patient = { - id: '456', - } as Patient - - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(AppointmentRepository, 'find').mockResolvedValue(appointment) - jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - const store = mockStore({ - title: 'test', - user: { user: { id: '123' }, permissions }, - appointment: { appointment, patient: { id: '456' } as Patient }, - appointments: [{ appointment, patient: { id: '456' } as Patient }], - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - } as any) - const wrapper = await mount( - <Provider store={store}> - <MemoryRouter initialEntries={[route]}> - <TitleProvider> - <HospitalRun /> - </TitleProvider> - </MemoryRouter> - </Provider>, - ) - - await wrapper.update() + setup('/appointments/edit/123', [Permissions.ReadAppointments]) - expect(wrapper.find(Dashboard)).toHaveLength(1) + expect(await screen.findByRole('heading', { name: /dashboard\.label/i })).toBeInTheDocument() }) }) diff --git a/src/__tests__/scheduling/appointments/ViewAppointments.test.tsx b/src/__tests__/scheduling/appointments/ViewAppointments.test.tsx index 6ab9f81b4c..fc97cf53c7 100644 --- a/src/__tests__/scheduling/appointments/ViewAppointments.test.tsx +++ b/src/__tests__/scheduling/appointments/ViewAppointments.test.tsx @@ -1,6 +1,6 @@ -import { Calendar } from '@hospitalrun/components' -import { act } from '@testing-library/react' -import { mount } from 'enzyme' +import { render, waitFor, screen } from '@testing-library/react' +import addMinutes from 'date-fns/addMinutes' +import format from 'date-fns/format' import React from 'react' import { Provider } from 'react-redux' import { MemoryRouter } from 'react-router-dom' @@ -18,77 +18,63 @@ import { RootState } from '../../../shared/store' const { TitleProvider } = titleUtil -describe('ViewAppointments', () => { - const expectedAppointments = [ - { - id: '123', - rev: '1', - patient: '1234', - startDateTime: new Date().toISOString(), - endDateTime: new Date().toISOString(), - location: 'location', - reason: 'reason', - }, - ] as Appointment[] +const now = new Date() + +const setup = (start = new Date(now.setHours(14, 30))) => { + const expectedAppointment = { + id: '123', + rev: '1', + patient: '1234', + startDateTime: start.toISOString(), + endDateTime: addMinutes(start, 60).toISOString(), + location: 'location', + reason: 'reason', + } as Appointment const expectedPatient = { id: '123', fullName: 'patient full name', } as Patient - const setup = async () => { - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(AppointmentRepository, 'findAll').mockResolvedValue(expectedAppointments) - jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) - const mockStore = createMockStore<RootState, any>([thunk]) - return mount( - <Provider store={mockStore({ appointments: { appointments: expectedAppointments } } as any)}> + jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockImplementation(() => jest.fn()) + jest.spyOn(AppointmentRepository, 'findAll').mockResolvedValue([expectedAppointment]) + jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) + + const mockStore = createMockStore<RootState, any>([thunk]) + + return { + expectedPatient, + expectedAppointment, + ...render( + <Provider store={mockStore({ appointments: { appointments: [expectedAppointment] } } as any)}> <MemoryRouter initialEntries={['/appointments']}> <TitleProvider> <ViewAppointments /> </TitleProvider> </MemoryRouter> </Provider>, - ) + ), } +} - it('should have called the useUpdateTitle hook', async () => { - await act(async () => { - await setup() - }) - expect(titleUtil.useUpdateTitle).toHaveBeenCalled() - }) - +describe('ViewAppointments', () => { it('should add a "New Appointment" button to the button tool bar', async () => { - const setButtonToolBarSpy = jest.fn() - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) + setup() - await act(async () => { - await setup() + await waitFor(() => { + expect(ButtonBarProvider.useButtonToolbarSetter).toHaveBeenCalled() }) - - 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 () => { - wrapper = await setup() + const { expectedPatient, expectedAppointment } = setup() + + await waitFor(() => { + expect(screen.getAllByText(expectedPatient.fullName as string)[0]).toBeInTheDocument() }) - wrapper.update() - const expectedEvents = [ - { - id: expectedAppointments[0].id, - start: new Date(expectedAppointments[0].startDateTime), - end: new Date(expectedAppointments[0].endDateTime), - title: 'patient full name', - allDay: false, - }, - ] + const expectedStart = format(new Date(expectedAppointment.startDateTime), 'h:mm') + const expectedEnd = format(new Date(expectedAppointment.endDateTime), 'h:mm') - const calendar = wrapper.find(Calendar) - expect(calendar).toHaveLength(1) - expect(calendar.prop('events')).toEqual(expectedEvents) + expect(screen.getByText(`${expectedStart} - ${expectedEnd}`)).toBeInTheDocument() }) }) diff --git a/src/__tests__/scheduling/appointments/edit/EditAppointment.test.tsx b/src/__tests__/scheduling/appointments/edit/EditAppointment.test.tsx index f3f5aa0848..16b11f8260 100644 --- a/src/__tests__/scheduling/appointments/edit/EditAppointment.test.tsx +++ b/src/__tests__/scheduling/appointments/edit/EditAppointment.test.tsx @@ -1,17 +1,15 @@ -import { Button } from '@hospitalrun/components' +import { render, waitFor, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import addMinutes from 'date-fns/addMinutes' import roundToNearestMinutes from 'date-fns/roundToNearestMinutes' -import { mount, ReactWrapper } from 'enzyme' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router, Route } from 'react-router-dom' import createMockStore, { MockStore } from 'redux-mock-store' import thunk from 'redux-thunk' import * as titleUtil from '../../../../page-header/title/TitleContext' -import AppointmentDetailForm from '../../../../scheduling/appointments/AppointmentDetailForm' import EditAppointment from '../../../../scheduling/appointments/edit/EditAppointment' import AppointmentRepository from '../../../../shared/db/AppointmentRepository' import PatientRepository from '../../../../shared/db/PatientRepository' @@ -56,7 +54,6 @@ describe('Edit Appointment', () => { const setup = async (mockAppointment: Appointment, mockPatient: Patient) => { jest.resetAllMocks() - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) jest.spyOn(AppointmentRepository, 'saveOrUpdate').mockResolvedValue(mockAppointment) jest.spyOn(AppointmentRepository, 'find').mockResolvedValue(mockAppointment) jest.spyOn(PatientRepository, 'find').mockResolvedValue(mockPatient) @@ -65,7 +62,8 @@ describe('Edit Appointment', () => { store = mockStore({ appointment: { mockAppointment, mockPatient } } as any) history.push('/appointments/edit/123') - const wrapper = await mount( + + return render( <Provider store={store}> <Router history={history}> <Route path="/appointments/edit/:id"> @@ -76,10 +74,6 @@ describe('Edit Appointment', () => { </Router> </Provider>, ) - - wrapper.find(EditAppointment).props().updateTitle = jest.fn() - wrapper.update() - return { wrapper: wrapper as ReactWrapper } } beforeEach(() => { @@ -87,69 +81,67 @@ describe('Edit Appointment', () => { }) it('should load an appointment when component loads', async () => { - const { wrapper } = await setup(expectedAppointment, expectedPatient) - await act(async () => { - await wrapper.update() - }) - - expect(AppointmentRepository.find).toHaveBeenCalledWith(expectedAppointment.id) - expect(PatientRepository.find).toHaveBeenCalledWith(expectedAppointment.patient) - }) + setup(expectedAppointment, expectedPatient) - it('should have called useUpdateTitle hook', async () => { - await setup(expectedAppointment, expectedPatient) - expect(titleUtil.useUpdateTitle).toHaveBeenCalled() + await waitFor(() => { + expect(AppointmentRepository.find).toHaveBeenCalledWith(expectedAppointment.id) + }) + await waitFor(() => { + expect(PatientRepository.find).toHaveBeenCalledWith(expectedAppointment.patient) + }) }) it('should updateAppointment when save button is clicked', async () => { - const { wrapper } = await setup(expectedAppointment, expectedPatient) - await act(async () => { - await wrapper.update() + setup(expectedAppointment, expectedPatient) + + await waitFor(() => { + expect( + screen.getByRole('button', { name: /scheduling.appointments.updateAppointment/i }), + ).toBeInTheDocument() }) - const saveButton = wrapper.find(Button).at(0) - const onClick = saveButton.prop('onClick') as any - expect(saveButton.text().trim()).toEqual('scheduling.appointments.updateAppointment') + userEvent.click( + await screen.findByRole('button', { name: /scheduling.appointments.updateAppointment/i }), + ) - await act(async () => { - await onClick() + await waitFor(() => { + expect(AppointmentRepository.saveOrUpdate).toHaveBeenCalledWith(expectedAppointment) }) - - expect(AppointmentRepository.saveOrUpdate).toHaveBeenCalledWith(expectedAppointment) }) it('should navigate to /appointments/:id when save is successful', async () => { - const { wrapper } = await setup(expectedAppointment, expectedPatient) + setup(expectedAppointment, expectedPatient) - const saveButton = wrapper.find(Button).at(0) - const onClick = saveButton.prop('onClick') as any + userEvent.click( + await screen.findByRole('button', { name: /scheduling.appointments.updateAppointment/i }), + ) - await act(async () => { - await onClick() + await waitFor(() => { + expect(history.location.pathname).toEqual('/appointments/123') }) - - expect(history.location.pathname).toEqual('/appointments/123') }) it('should navigate to /appointments/:id when cancel is clicked', async () => { - const { wrapper } = await setup(expectedAppointment, expectedPatient) - - const cancelButton = wrapper.find(Button).at(1) - const onClick = cancelButton.prop('onClick') as any - expect(cancelButton.text().trim()).toEqual('actions.cancel') + setup(expectedAppointment, expectedPatient) - act(() => { - onClick() + await waitFor(() => { + expect(screen.getByRole('button', { name: /actions.cancel/i })).toBeInTheDocument() }) - wrapper.update() - expect(history.location.pathname).toEqual('/appointments/123') + userEvent.click(await screen.findByRole('button', { name: /actions.cancel/i })) + + await waitFor(() => { + expect(history.location.pathname).toEqual('/appointments/123') + }) }) + it('should render an edit appointment form', async () => { - const { wrapper } = await setup(expectedAppointment, expectedPatient) - await act(async () => { - await wrapper.update() + setup(expectedAppointment, expectedPatient) + + await waitFor(() => { + expect( + screen.getByRole('button', { name: /scheduling.appointments.updateAppointment/i }), + ).toBeInTheDocument() }) - expect(wrapper.find(AppointmentDetailForm)).toHaveLength(1) }) }) diff --git a/src/__tests__/scheduling/appointments/new/NewAppointment.test.tsx b/src/__tests__/scheduling/appointments/new/NewAppointment.test.tsx index e462b388b7..f64fcc3f22 100644 --- a/src/__tests__/scheduling/appointments/new/NewAppointment.test.tsx +++ b/src/__tests__/scheduling/appointments/new/NewAppointment.test.tsx @@ -1,21 +1,20 @@ -import * as components from '@hospitalrun/components' -import { Alert, Button, Typeahead } from '@hospitalrun/components' -import { act } from '@testing-library/react' +import { Toaster } from '@hospitalrun/components' +import { render, screen, waitFor, fireEvent, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import addMinutes from 'date-fns/addMinutes' import roundToNearestMinutes from 'date-fns/roundToNearestMinutes' -import { mount } from 'enzyme' -import { createMemoryHistory, MemoryHistory } from 'history' +import { createMemoryHistory } from 'history' import React from 'react' +import { ReactQueryConfigProvider } from 'react-query' import { Provider } from 'react-redux' -import { Router, Route } from 'react-router-dom' -import createMockStore, { MockStore } from 'redux-mock-store' +import { Router } from 'react-router' +import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import * as titleUtil from '../../../../page-header/title/TitleContext' -import AppointmentDetailForm from '../../../../scheduling/appointments/AppointmentDetailForm' import NewAppointment from '../../../../scheduling/appointments/new/NewAppointment' -import DateTimePickerWithLabelFormGroup from '../../../../shared/components/input/DateTimePickerWithLabelFormGroup' import AppointmentRepository from '../../../../shared/db/AppointmentRepository' +import PatientRepository from '../../../../shared/db/PatientRepository' import Appointment from '../../../../shared/model/Appointment' import Patient from '../../../../shared/model/Patient' import { RootState } from '../../../../shared/store' @@ -24,71 +23,76 @@ const { TitleProvider } = titleUtil const mockStore = createMockStore<RootState, any>([thunk]) describe('New Appointment', () => { - let history: MemoryHistory - let store: MockStore - const expectedNewAppointment = { id: '123' } - - const setup = () => { - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest - .spyOn(AppointmentRepository, 'save') - .mockResolvedValue(expectedNewAppointment as Appointment) - history = createMemoryHistory() - store = mockStore({ - appointment: { - appointment: {} as Appointment, - patient: {} as Patient, - }, - } as any) - - history.push('/appointments/new') - const wrapper = mount( - <Provider store={store}> - <Router history={history}> - <Route path="/appointments/new"> - <TitleProvider> - <NewAppointment /> - </TitleProvider> - </Route> - </Router> - </Provider>, - ) - - wrapper.update() - return wrapper + const expectedPatient: Patient = { + addresses: [], + bloodType: 'o', + careGoals: [], + carePlans: [], + code: 'P-qrQc3FkCO', + createdAt: new Date().toISOString(), + dateOfBirth: new Date(0).toISOString(), + emails: [], + id: '123', + index: '', + isApproximateDateOfBirth: false, + phoneNumbers: [], + rev: '', + sex: 'female', + updatedAt: new Date().toISOString(), + visits: [], + givenName: 'Popo', + prefix: 'Mr', + fullName: 'Mr Popo', } - describe('header', () => { - it('should have called useUpdateTitle hook', async () => { - await act(async () => { - await setup() - }) + const noRetryConfig = { + queries: { + retry: false, + }, + } - expect(titleUtil.useUpdateTitle).toHaveBeenCalled() - }) - }) + const setup = () => { + const expectedAppointment = { id: '123' } as Appointment + jest.spyOn(AppointmentRepository, 'save').mockResolvedValue(expectedAppointment) + jest.spyOn(PatientRepository, 'search').mockResolvedValue([expectedPatient]) + + const history = createMemoryHistory({ initialEntries: ['/appointments/new'] }) + + return { + expectedAppointment, + history, + ...render( + <ReactQueryConfigProvider config={noRetryConfig}> + <Provider store={mockStore({} as any)}> + <Router history={history}> + <TitleProvider> + <NewAppointment /> + </TitleProvider> + </Router> + <Toaster draggable hideProgressBar /> + </Provider> + </ReactQueryConfigProvider>, + ), + } + } describe('layout', () => { it('should render an Appointment Detail Component', async () => { - let wrapper: any - await act(async () => { - wrapper = await setup() - }) + setup() - expect(wrapper.find(AppointmentDetailForm)).toHaveLength(1) + expect(await screen.findByLabelText('new appointment form')).toBeInTheDocument() }) }) describe('on save click', () => { it('should have error when error saving without patient', async () => { - let wrapper: any - await act(async () => { - wrapper = await setup() - }) + setup() + const expectedError = { message: 'scheduling.appointment.errors.createAppointmentError', patient: 'scheduling.appointment.errors.patientRequired', } + const expectedAppointment = { patient: '', startDateTime: roundToNearestMinutes(new Date(), { nearestTo: 15 }).toISOString(), @@ -98,88 +102,68 @@ describe('New Appointment', () => { ).toISOString(), location: 'location', reason: 'reason', - type: 'type', + type: 'routine', } as Appointment - act(() => { - const appointmentDetailForm = wrapper.find(AppointmentDetailForm) - const onFieldChange = appointmentDetailForm.prop('onFieldChange') - onFieldChange('patient', expectedAppointment.patient) - }) - - wrapper.update() - - const saveButton = wrapper.find(Button).at(0) - expect(saveButton.text().trim()).toEqual('scheduling.appointments.createAppointment') - const onClick = saveButton.prop('onClick') as any + userEvent.type( + screen.getByPlaceholderText(/scheduling\.appointment\.patient/i), + expectedAppointment.patient, + ) - await act(async () => { - await onClick() - }) - wrapper.update() - const alert = wrapper.find(Alert) - const typeahead = wrapper.find(Typeahead) + userEvent.click(screen.getByText(/scheduling.appointments.createAppointment/i)) - expect(AppointmentRepository.save).toHaveBeenCalledTimes(0) - expect(alert.prop('message')).toEqual(expectedError.message) - expect(typeahead.prop('isInvalid')).toBeTruthy() + expect(screen.getByText(expectedError.message)).toBeInTheDocument() + expect(screen.getByPlaceholderText(/scheduling\.appointment\.patient/i)).toHaveClass( + 'is-invalid', + ) + expect(AppointmentRepository.save).not.toHaveBeenCalled() }) it('should have error when error saving with end time earlier than start time', async () => { - let wrapper: any - await act(async () => { - wrapper = await setup() - }) + setup() + const expectedError = { message: 'scheduling.appointment.errors.createAppointmentError', startDateTime: 'scheduling.appointment.errors.startDateMustBeBeforeEndDate', } + const expectedAppointment = { - patient: 'Mr Popo', + patient: expectedPatient.fullName, startDateTime: new Date(2020, 10, 10, 0, 0, 0, 0).toISOString(), endDateTime: new Date(1957, 10, 10, 0, 0, 0, 0).toISOString(), location: 'location', reason: 'reason', - type: 'type', + type: 'routine', } as Appointment - act(() => { - const appointmentDetailForm = wrapper.find(AppointmentDetailForm) - const onFieldChange = appointmentDetailForm.prop('onFieldChange') - onFieldChange('patient', expectedAppointment.patient) - onFieldChange('startDateTime', expectedAppointment.startDateTime) - onFieldChange('endDateTime', expectedAppointment.endDateTime) + userEvent.type( + screen.getByPlaceholderText(/scheduling\.appointment\.patient/i), + expectedAppointment.patient, + ) + fireEvent.change(within(screen.getByTestId('startDateDateTimePicker')).getByRole('textbox'), { + target: { value: expectedAppointment.startDateTime }, }) - - wrapper.update() - - const saveButton = wrapper.find(Button).at(0) - expect(saveButton.text().trim()).toEqual('scheduling.appointments.createAppointment') - const onClick = saveButton.prop('onClick') as any - - await act(async () => { - await onClick() + fireEvent.change(within(screen.getByTestId('endDateDateTimePicker')).getByRole('textbox'), { + target: { value: expectedAppointment.endDateTime }, }) - wrapper.update() - const alert = wrapper.find(Alert) - const typeahead = wrapper.find(Typeahead) - const dateInput = wrapper.find(DateTimePickerWithLabelFormGroup).at(0) + userEvent.click(screen.getByText(/scheduling.appointments.createAppointment/i)) + expect(screen.getByText(expectedError.message)).toBeInTheDocument() + expect(screen.getByPlaceholderText(/scheduling\.appointment\.patient/i)).toHaveClass( + 'is-invalid', + ) + expect( + within(screen.getByTestId('startDateDateTimePicker')).getByRole('textbox'), + ).toHaveClass('is-invalid') + expect(screen.getByText(expectedError.startDateTime)).toBeInTheDocument() expect(AppointmentRepository.save).toHaveBeenCalledTimes(0) - expect(alert.prop('message')).toEqual(expectedError.message) - expect(typeahead.prop('isInvalid')).toBeTruthy() - expect(dateInput.prop('isInvalid')).toBeTruthy() - expect(dateInput.prop('feedback')).toEqual(expectedError.startDateTime) }) it('should call AppointmentRepo.save when save button is clicked', async () => { - let wrapper: any - await act(async () => { - wrapper = await setup() - }) + setup() const expectedAppointment = { - patient: '123', + patient: expectedPatient.fullName, startDateTime: roundToNearestMinutes(new Date(), { nearestTo: 15 }).toISOString(), endDateTime: addMinutes( roundToNearestMinutes(new Date(), { nearestTo: 15 }), @@ -187,123 +171,79 @@ describe('New Appointment', () => { ).toISOString(), location: 'location', reason: 'reason', - type: 'type', + type: 'routine', } as Appointment - act(() => { - const appointmentDetailForm = wrapper.find(AppointmentDetailForm) - const onFieldChange = appointmentDetailForm.prop('onFieldChange') - onFieldChange('patient', expectedAppointment.patient) - }) - - wrapper.update() - - act(() => { - const appointmentDetailForm = wrapper.find(AppointmentDetailForm) - const onFieldChange = appointmentDetailForm.prop('onFieldChange') - onFieldChange('startDateTime', expectedAppointment.startDateTime) - }) - - wrapper.update() - - act(() => { - const appointmentDetailForm = wrapper.find(AppointmentDetailForm) - const onFieldChange = appointmentDetailForm.prop('onFieldChange') - onFieldChange('endDateTime', expectedAppointment.endDateTime) - }) - - wrapper.update() + userEvent.type( + screen.getByPlaceholderText(/scheduling\.appointment\.patient/i), + expectedAppointment.patient, + ) + userEvent.click( + await screen.findByText(`${expectedPatient.fullName} (${expectedPatient.code})`), + ) - act(() => { - const appointmentDetailForm = wrapper.find(AppointmentDetailForm) - const onFieldChange = appointmentDetailForm.prop('onFieldChange') - onFieldChange('location', expectedAppointment.location) + fireEvent.change(within(screen.getByTestId('startDateDateTimePicker')).getByRole('textbox'), { + target: { value: expectedAppointment.startDateTime }, }) - wrapper.update() - - act(() => { - const appointmentDetailForm = wrapper.find(AppointmentDetailForm) - const onFieldChange = appointmentDetailForm.prop('onFieldChange') - onFieldChange('reason', expectedAppointment.reason) + fireEvent.change(within(screen.getByTestId('endDateDateTimePicker')).getByRole('textbox'), { + target: { value: expectedAppointment.endDateTime }, }) - wrapper.update() + userEvent.type( + screen.getByRole('textbox', { name: /scheduling\.appointment\.location/i }), + expectedAppointment.location, + ) - act(() => { - const appointmentDetailForm = wrapper.find(AppointmentDetailForm) - const onFieldChange = appointmentDetailForm.prop('onFieldChange') - onFieldChange('type', expectedAppointment.type) - }) + userEvent.type( + screen.getByPlaceholderText('-- Choose --'), + `${expectedAppointment.type}{arrowdown}{enter}`, + ) - wrapper.update() + const reasonInput = screen.queryAllByRole('textbox', { hidden: false })[3] + userEvent.type(reasonInput, expectedAppointment.reason) - const saveButton = wrapper.find(Button).at(0) - expect(saveButton.text().trim()).toEqual('scheduling.appointments.createAppointment') - const onClick = saveButton.prop('onClick') as any + userEvent.click( + screen.getByRole('button', { + name: /scheduling.appointments.createAppointment/i, + }), + ) - await act(async () => { - await onClick() + await waitFor(() => { + expect(AppointmentRepository.save).toHaveBeenCalledWith({ + ...expectedAppointment, + patient: expectedPatient.id, + }) }) - - expect(AppointmentRepository.save).toHaveBeenCalledWith(expectedAppointment) - }) + }, 30000) it('should navigate to /appointments/:id when a new appointment is created', async () => { - jest.spyOn(components, 'Toast') - let wrapper: any - await act(async () => { - wrapper = await setup() - }) + const { history, expectedAppointment } = setup() - const expectedAppointment = { - patient: '123', - startDateTime: roundToNearestMinutes(new Date(), { nearestTo: 15 }).toISOString(), - endDateTime: addMinutes( - roundToNearestMinutes(new Date(), { nearestTo: 15 }), - 60, - ).toISOString(), - location: 'location', - reason: 'reason', - type: 'type', - } as Appointment + userEvent.type( + screen.getByPlaceholderText(/scheduling\.appointment\.patient/i), + `${expectedPatient.fullName}`, + ) + userEvent.click( + await screen.findByText(`${expectedPatient.fullName} (${expectedPatient.code})`), + ) - act(() => { - const appointmentDetailForm = wrapper.find(AppointmentDetailForm) - const onFieldChange = appointmentDetailForm.prop('onFieldChange') - onFieldChange('patient', expectedAppointment.patient) - }) - wrapper.update() - const saveButton = wrapper.find(Button).at(0) - expect(saveButton.text().trim()).toEqual('scheduling.appointments.createAppointment') - const onClick = saveButton.prop('onClick') as any + userEvent.click(screen.getByText(/scheduling.appointments.createAppointment/i)) - await act(async () => { - await onClick() + await waitFor(() => { + expect(history.location.pathname).toEqual(`/appointments/${expectedAppointment.id}`) + }) + await waitFor(() => { + expect(screen.getByText(`scheduling.appointment.successfullyCreated`)).toBeInTheDocument() }) - - expect(history.location.pathname).toEqual(`/appointments/${expectedNewAppointment.id}`) - expect(components.Toast).toHaveBeenCalledWith( - 'success', - 'states.success', - `scheduling.appointment.successfullyCreated`, - ) }) }) describe('on cancel click', () => { it('should navigate back to /appointments', async () => { - let wrapper: any - await act(async () => { - wrapper = await setup() - }) + const { history } = setup() - const cancelButton = wrapper.find(Button).at(1) - - act(() => { - const onClick = cancelButton.prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/actions\.cancel/i)) expect(history.location.pathname).toEqual('/appointments') }) diff --git a/src/__tests__/scheduling/appointments/view/ViewAppointment.test.tsx b/src/__tests__/scheduling/appointments/view/ViewAppointment.test.tsx index 4b425d6ba4..f5e7163994 100644 --- a/src/__tests__/scheduling/appointments/view/ViewAppointment.test.tsx +++ b/src/__tests__/scheduling/appointments/view/ViewAppointment.test.tsx @@ -1,16 +1,19 @@ -import * as components from '@hospitalrun/components' -import { mount, ReactWrapper } from 'enzyme' +import { Toaster } from '@hospitalrun/components' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import addMinutes from 'date-fns/addMinutes' +import format from 'date-fns/format' import { createMemoryHistory } from 'history' import React from 'react' -import { act } from 'react-dom/test-utils' +import { queryCache } from 'react-query' import { Provider } from 'react-redux' import { Router, Route } from 'react-router-dom' -import createMockStore, { MockStore } from 'redux-mock-store' +import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' -import * as ButtonBarProvider from '../../../../page-header/button-toolbar/ButtonBarProvider' +import { ButtonBarProvider } from '../../../../page-header/button-toolbar/ButtonBarProvider' +import ButtonToolbar from '../../../../page-header/button-toolbar/ButtonToolBar' import * as titleUtil from '../../../../page-header/title/TitleContext' -import AppointmentDetailForm from '../../../../scheduling/appointments/AppointmentDetailForm' import ViewAppointment from '../../../../scheduling/appointments/view/ViewAppointment' import AppointmentRepository from '../../../../shared/db/AppointmentRepository' import PatientRepository from '../../../../shared/db/PatientRepository' @@ -22,227 +25,184 @@ import { RootState } from '../../../../shared/store' const { TitleProvider } = titleUtil const mockStore = createMockStore<RootState, any>([thunk]) -const appointment = { - id: '123', - startDateTime: new Date().toISOString(), - endDateTime: new Date().toISOString(), - reason: 'reason', - location: 'location', - patient: '123', -} as Appointment - -const patient = { - id: '123', - fullName: 'full name', -} as Patient +const setup = (permissions = [Permissions.ReadAppointments], skipSpies = false) => { + const expectedAppointment = { + id: '123', + startDateTime: new Date().toISOString(), + endDateTime: addMinutes(new Date(), 60).toISOString(), + reason: 'reason', + location: 'location', + type: 'checkup', + patient: '123', + } as Appointment + const expectedPatient = { + id: '123', + fullName: 'full name', + } as Patient + + if (!skipSpies) { + jest.spyOn(AppointmentRepository, 'find').mockResolvedValue(expectedAppointment) + jest.spyOn(AppointmentRepository, 'delete').mockResolvedValue(expectedAppointment) + jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) + } -describe('View Appointment', () => { - let history: any - let store: MockStore - let setButtonToolBarSpy: any - let deleteAppointmentSpy: any - let findAppointmentSpy: any - - const setup = async (status = 'completed', permissions = [Permissions.ReadAppointments]) => { - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - if (status === 'completed') { - findAppointmentSpy = jest.spyOn(AppointmentRepository, 'find').mockResolvedValue(appointment) - deleteAppointmentSpy = jest - .spyOn(AppointmentRepository, 'delete') - .mockResolvedValue(appointment) - } - jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) - - history = createMemoryHistory() - history.push('/appointments/123') - - store = mockStore({ - user: { - permissions, - }, - appointment: { - appointment, - status, - patient, - }, - } as any) - - let wrapper: any - await act(async () => { - wrapper = await mount( - <Provider store={store}> - <Router history={history}> + const history = createMemoryHistory({ + initialEntries: [`/appointments/${expectedAppointment.id}`], + }) + const store = mockStore({ + user: { + permissions, + }, + } as any) + + return { + history, + expectedAppointment, + expectedPatient, + ...render( + <Provider store={store}> + <Router history={history}> + <ButtonBarProvider> + <ButtonToolbar /> <Route path="/appointments/:id"> <TitleProvider> <ViewAppointment /> </TitleProvider> </Route> - </Router> - </Provider>, - ) - }) - - wrapper.update() - return { wrapper: wrapper as ReactWrapper } + </ButtonBarProvider> + <Toaster draggable hideProgressBar /> + </Router> + </Provider>, + ), } +} +describe('View Appointment', () => { beforeEach(() => { + queryCache.clear() jest.resetAllMocks() - setButtonToolBarSpy = jest.fn() - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) - }) - - it('should have called the useUpdateTitle hook', async () => { - await setup() - - expect(titleUtil.useUpdateTitle).toHaveBeenCalled() }) it('should add a "Edit Appointment" button to the button tool bar if has WriteAppointment permissions', async () => { - await setup('loading', [Permissions.WriteAppointments, Permissions.ReadAppointments]) + setup([Permissions.WriteAppointments]) - const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] - expect((actualButtons[0] as any).props.children).toEqual('actions.edit') + await waitFor(() => { + expect(screen.getByRole('button', { name: /actions\.edit/i })).toBeInTheDocument() + }) }) it('should add a "Delete Appointment" button to the button tool bar if has DeleteAppointment permissions', async () => { - await setup('loading', [Permissions.DeleteAppointment, Permissions.ReadAppointments]) + setup([Permissions.DeleteAppointment]) - const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] - expect((actualButtons[0] as any).props.children).toEqual( - 'scheduling.appointments.deleteAppointment', - ) + await waitFor(() => { + expect( + screen.getByRole('button', { name: /scheduling\.appointments\.deleteAppointment/i }), + ).toBeInTheDocument() + }) }) it('button toolbar empty if has only ReadAppointments permission', async () => { - await setup('loading') + setup() + + expect( + await screen.findByPlaceholderText(/scheduling\.appointment\.patient/i), + ).toBeInTheDocument() - const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] - expect(actualButtons.length).toEqual(0) + expect(screen.queryAllByRole('button')).toHaveLength(0) }) it('should call getAppointment by id if id is present', async () => { - await setup() - expect(findAppointmentSpy).toHaveBeenCalledWith(appointment.id) - }) + const { expectedAppointment } = setup() - it('should render a loading spinner', async () => { - const { wrapper } = await setup('loading') - expect(wrapper.find(components.Spinner)).toHaveLength(1) + expect(AppointmentRepository.find).toHaveBeenCalledWith(expectedAppointment.id) }) - it('should render an AppointmentDetailForm with the correct data', async () => { - const { wrapper } = await setup() - wrapper.update() + // This relies on an implementation detial... Dunno how else to make it work + it('should render a loading spinner', () => { + // Force null as patient response so we get the "loading" condition + jest.spyOn(PatientRepository, 'find').mockResolvedValueOnce((null as unknown) as Patient) + + const { container } = setup([Permissions.ReadAppointments], true) - const appointmentDetailForm = wrapper.find(AppointmentDetailForm) - expect(appointmentDetailForm.prop('appointment')).toEqual(appointment) - expect(appointmentDetailForm.prop('isEditable')).toBeFalsy() + expect(container.querySelector(`[class^='css-']`)).toBeInTheDocument() }) - it('should render a modal for delete confirmation', async () => { - const { wrapper } = await setup() + it('should render an AppointmentDetailForm with the correct data', async () => { + const { expectedAppointment, expectedPatient } = setup() - const deleteAppointmentConfirmationModal = wrapper.find(components.Modal) - expect(deleteAppointmentConfirmationModal).toHaveLength(1) - expect(deleteAppointmentConfirmationModal.prop('closeButton')?.children).toEqual( - 'actions.delete', - ) - expect(deleteAppointmentConfirmationModal.prop('body')).toEqual( - 'scheduling.appointment.deleteConfirmationMessage', - ) - expect(deleteAppointmentConfirmationModal.prop('title')).toEqual('actions.confirmDelete') - }) + const patientInput = await screen.findByDisplayValue(expectedPatient.fullName as string) + expect(patientInput).toBeDisabled() - it('should render a delete appointment button in the button toolbar', async () => { - await setup('completed', [Permissions.ReadAppointments, Permissions.DeleteAppointment]) - const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] - expect((actualButtons[0] as any).props.children).toEqual( - 'scheduling.appointments.deleteAppointment', + const startDateInput = screen.getByDisplayValue( + format(new Date(expectedAppointment.startDateTime), 'MM/dd/yyyy h:mm a'), ) - }) + expect(startDateInput).toBeDisabled() - it('should pop up the modal when on delete appointment click', async () => { - const { wrapper } = await setup('completed', [ - Permissions.ReadAppointments, - Permissions.DeleteAppointment, - ]) + const endDateInput = screen.getByDisplayValue( + format(new Date(expectedAppointment.endDateTime), 'MM/dd/yyyy h:mm a'), + ) + expect(endDateInput).toBeDisabled() - const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] + const locationInput = screen.getByDisplayValue(expectedAppointment.location) + expect(locationInput).toBeDisabled() - act(() => { - const { onClick } = (actualButtons[0] as any).props - onClick({ preventDefault: jest.fn() }) - }) - wrapper.update() + // This is a weird one, because the type has a matched i18n description + const typeInput = screen.getByDisplayValue( + `scheduling.appointment.types.${expectedAppointment.type}`, + ) + expect(typeInput).toBeDisabled() - const deleteConfirmationModal = wrapper.find(components.Modal) - expect(deleteConfirmationModal.prop('show')).toEqual(true) + const reasonInput = screen.getByDisplayValue(expectedAppointment.reason) + expect(reasonInput).toBeDisabled() }) - it('should close the modal when the toggle button is clicked', async () => { - const { wrapper } = await setup('completed', [ + it('should delete the appointment after clicking the delete appointment button, and confirming in the delete confirmation modal', async () => { + const { expectedAppointment, history } = setup([ Permissions.ReadAppointments, Permissions.DeleteAppointment, ]) - const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] + userEvent.click( + screen.getByRole('button', { name: /scheduling\.appointments\.deleteAppointment/i }), + ) - act(() => { - const { onClick } = (actualButtons[0] as any).props - onClick({ preventDefault: jest.fn() }) + await waitFor(() => { + expect(screen.getByText(/actions.confirmDelete/i)).toBeInTheDocument() }) - wrapper.update() + expect( + screen.getByText(/scheduling\.appointment\.deleteConfirmationMessage/i), + ).toBeInTheDocument() - act(() => { - const deleteConfirmationModal = wrapper.find(components.Modal) - deleteConfirmationModal.prop('toggle')() + userEvent.click(screen.getByRole('button', { name: /actions\.delete/i })) + + await waitFor(() => { + expect(AppointmentRepository.delete).toHaveBeenCalledTimes(1) }) - wrapper.update() + expect(AppointmentRepository.delete).toHaveBeenCalledWith(expectedAppointment) - const deleteConfirmationModal = wrapper.find(components.Modal) - expect(deleteConfirmationModal.prop('show')).toEqual(false) + await waitFor(() => { + expect(history.location.pathname).toEqual('/appointments') + }) + await waitFor(() => { + expect(screen.getByText(/scheduling\.appointment\.successfullyDeleted/i)).toBeInTheDocument() + }) }) - it('should delete from appointment repository when modal confirmation button is clicked', async () => { - const { wrapper } = await setup('completed', [ - Permissions.ReadAppointments, - Permissions.DeleteAppointment, - ]) + it('should close the modal when the toggle button is clicked', async () => { + setup([Permissions.ReadAppointments, Permissions.DeleteAppointment]) - const deleteConfirmationModal = wrapper.find(components.Modal) + userEvent.click( + screen.getByRole('button', { name: /scheduling\.appointments\.deleteAppointment/i }), + ) - await act(async () => { - const closeButton = (await deleteConfirmationModal.prop('closeButton')) as any - closeButton.onClick() + await waitFor(() => { + expect(screen.getByText(/actions.confirmDelete/i)).toBeInTheDocument() }) - wrapper.update() - - expect(deleteAppointmentSpy).toHaveBeenCalledTimes(1) - expect(deleteAppointmentSpy).toHaveBeenCalledWith(appointment) - }) - it('should navigate to /appointments and display a message when delete is successful', async () => { - jest.spyOn(components, 'Toast') + userEvent.click(screen.getByRole('button', { name: /close/i })) - const { wrapper } = await setup('completed', [ - Permissions.ReadAppointments, - Permissions.DeleteAppointment, - ]) - - const deleteConfirmationModal = wrapper.find(components.Modal) - - await act(async () => { - const closeButton = (await deleteConfirmationModal.prop('closeButton')) as any - closeButton.onClick() + await waitFor(() => { + expect(screen.queryByText(/actions.confirmDelete/i)).not.toBeInTheDocument() }) - wrapper.update() - - expect(history.location.pathname).toEqual('/appointments') - expect(components.Toast).toHaveBeenCalledWith( - 'success', - 'states.success', - 'scheduling.appointment.successfullyDeleted', - ) }) }) diff --git a/src/__tests__/scheduling/hooks/useAppointment.test.tsx b/src/__tests__/scheduling/hooks/useAppointment.test.tsx index 93f555c7a4..4b2e5abcd8 100644 --- a/src/__tests__/scheduling/hooks/useAppointment.test.tsx +++ b/src/__tests__/scheduling/hooks/useAppointment.test.tsx @@ -1,9 +1,7 @@ -import { renderHook, act } from '@testing-library/react-hooks' - import useAppointment from '../../../scheduling/hooks/useAppointment' import AppointmentRepository from '../../../shared/db/AppointmentRepository' import Appointment from '../../../shared/model/Appointment' -import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util' +import executeQuery from '../../test-utils/use-query.util' describe('useAppointment', () => { it('should get an appointment by id', async () => { @@ -13,13 +11,7 @@ describe('useAppointment', () => { } as Appointment jest.spyOn(AppointmentRepository, 'find').mockResolvedValue(expectedAppointment) - let actualData: any - await act(async () => { - const renderHookResult = renderHook(() => useAppointment(expectedAppointmentId)) - const { result } = renderHookResult - await waitUntilQueryIsSuccessful(renderHookResult) - actualData = result.current.data - }) + const actualData = await executeQuery(() => useAppointment(expectedAppointmentId)) expect(AppointmentRepository.find).toHaveBeenCalledTimes(1) expect(AppointmentRepository.find).toBeCalledWith(expectedAppointmentId) diff --git a/src/__tests__/scheduling/hooks/useAppointments.test.tsx b/src/__tests__/scheduling/hooks/useAppointments.test.tsx index f5bcb51ce6..3c74216a14 100644 --- a/src/__tests__/scheduling/hooks/useAppointments.test.tsx +++ b/src/__tests__/scheduling/hooks/useAppointments.test.tsx @@ -1,9 +1,7 @@ -import { act, renderHook } from '@testing-library/react-hooks' - import useAppointments from '../../../scheduling/hooks/useAppointments' import AppointmentRepository from '../../../shared/db/AppointmentRepository' import Appointment from '../../../shared/model/Appointment' -import waitUntilQueryIsSuccessful from '../../test-utils/wait-for-query.util' +import executeQuery from '../../test-utils/use-query.util' describe('useAppointments', () => { it('should get an appointment by id', async () => { @@ -31,16 +29,9 @@ describe('useAppointments', () => { ] as Appointment[] jest.spyOn(AppointmentRepository, 'findAll').mockResolvedValue(expectedAppointments) - let actualData: any - await act(async () => { - await act(async () => { - const renderHookResult = renderHook(() => useAppointments()) - const { result } = renderHookResult - await waitUntilQueryIsSuccessful(renderHookResult) - actualData = result.current.data - }) - expect(AppointmentRepository.findAll).toHaveBeenCalledTimes(1) - expect(actualData).toEqual(expectedAppointments) - }) + const actualData = await executeQuery(() => useAppointments()) + + expect(AppointmentRepository.findAll).toHaveBeenCalledTimes(1) + expect(actualData).toEqual(expectedAppointments) }) }) diff --git a/src/__tests__/scheduling/hooks/useScheduleAppointment.test.tsx b/src/__tests__/scheduling/hooks/useScheduleAppointment.test.tsx index 72a36e9c2e..10e1220439 100644 --- a/src/__tests__/scheduling/hooks/useScheduleAppointment.test.tsx +++ b/src/__tests__/scheduling/hooks/useScheduleAppointment.test.tsx @@ -45,7 +45,7 @@ describe('useScheduleAppointment', () => { await executeMutation(() => { const result = useScheduleAppointment() return [result.mutate] - }, {}) + }, {} as Appointment) } catch (e) { expect(e).toEqual(expectedAppointmentError) expect(AppointmentRepository.save).not.toHaveBeenCalled() diff --git a/src/__tests__/scheduling/hooks/useUpdateAppointment.test.tsx b/src/__tests__/scheduling/hooks/useUpdateAppointment.test.tsx index 961f9dc418..bed1ac5158 100644 --- a/src/__tests__/scheduling/hooks/useUpdateAppointment.test.tsx +++ b/src/__tests__/scheduling/hooks/useUpdateAppointment.test.tsx @@ -1,5 +1,3 @@ -import { act } from '@testing-library/react-hooks' - import useUpdateAppointment from '../../../scheduling/hooks/useUpdateAppointment' import AppointmentRepository from '../../../shared/db/AppointmentRepository' import Appointment from '../../../shared/model/Appointment' @@ -16,17 +14,12 @@ describe('Use update appointment', () => { type: 'type', } as Appointment - jest.spyOn(AppointmentRepository, 'saveOrUpdate').mockResolvedValue(expectedAppointment) - it('should update appointment', async () => { - let actualData: any - - await act(async () => { - actualData = await executeMutation(() => { - const result = useUpdateAppointment(expectedAppointment) - return [result.mutate] - }, expectedAppointment) - }) + jest.spyOn(AppointmentRepository, 'saveOrUpdate').mockResolvedValue(expectedAppointment) + const actualData = await executeMutation(() => { + const result = useUpdateAppointment(expectedAppointment) + return [result.mutate] + }, expectedAppointment) expect(AppointmentRepository.saveOrUpdate).toHaveBeenCalledTimes(1) expect(AppointmentRepository.saveOrUpdate).toHaveBeenCalledWith(expectedAppointment) diff --git a/src/__tests__/settings/Settings.test.tsx b/src/__tests__/settings/Settings.test.tsx index 1d3b89de64..4afc7bf255 100644 --- a/src/__tests__/settings/Settings.test.tsx +++ b/src/__tests__/settings/Settings.test.tsx @@ -1,8 +1,10 @@ -import { mount } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' import { Router } from 'react-router-dom' +import { CombinedState } from 'redux' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' @@ -15,14 +17,10 @@ const mockStore = createMockStore<RootState, any>([thunk]) describe('Settings', () => { const setup = () => { - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - - const store = mockStore({ title: 'test' } as any) - + const store = mockStore({ title: 'test' } as CombinedState<any>) const history = createMemoryHistory() history.push('/settings') - - const wrapper = mount( + return render( <Provider store={store}> <Router history={history}> <TitleProvider> @@ -31,14 +29,27 @@ describe('Settings', () => { </Router> </Provider>, ) - - return wrapper } - - describe('layout', () => { - it('should call the useUpdateTitle hook', () => { - setup() - expect(titleUtil.useUpdateTitle).toHaveBeenCalled() + test('settings combo selection works', () => { + setup() + const langArr = [ + /Arabic/i, + /Chinese/i, + /English, American/i, + /French/i, + /German/i, + /Italian/i, + /Japanese/i, + /Portuguese/i, + /Russian/i, + /Spanish/i, + ] + + langArr.forEach((lang) => { + const combobox = screen.getByRole('combobox') + userEvent.click(combobox) + expect(screen.getByRole('option', { name: lang })).toBeInTheDocument() + userEvent.type(combobox, '{enter}') }) }) }) diff --git a/src/__tests__/shared/components/Sidebar.test.tsx b/src/__tests__/shared/components/Sidebar.test.tsx index 7602971847..83da00a671 100644 --- a/src/__tests__/shared/components/Sidebar.test.tsx +++ b/src/__tests__/shared/components/Sidebar.test.tsx @@ -1,6 +1,5 @@ -import { ListItem } from '@hospitalrun/components' -import { act } from '@testing-library/react' -import { mount, ReactWrapper } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' import React from 'react' import { Provider } from 'react-redux' @@ -16,59 +15,26 @@ const mockStore = createMockStore<RootState, any>([thunk]) describe('Sidebar', () => { let history = createMemoryHistory() - const allPermissions = [ - Permissions.ReadPatients, - Permissions.WritePatients, - Permissions.ReadAppointments, - Permissions.WriteAppointments, - Permissions.DeleteAppointment, - Permissions.AddAllergy, - Permissions.AddDiagnosis, - Permissions.RequestLab, - Permissions.CancelLab, - Permissions.CompleteLab, - Permissions.ViewLab, - Permissions.ViewLabs, - Permissions.RequestMedication, - Permissions.CompleteMedication, - Permissions.CancelMedication, - Permissions.ViewMedications, - Permissions.ViewMedication, - Permissions.ViewIncidents, - Permissions.ViewIncident, - Permissions.ViewIncidentWidgets, - Permissions.ReportIncident, - Permissions.ReadVisits, - Permissions.AddVisit, - Permissions.RequestImaging, - Permissions.ViewImagings, - ] + const allPermissions = Object.values(Permissions) const store = mockStore({ components: { sidebarCollapsed: false }, user: { permissions: allPermissions }, } as any) - const setup = (location: string) => { - history = createMemoryHistory() - history.push(location) - return mount( - <Router history={history}> - <Provider store={store}> - <Sidebar /> - </Provider> - </Router>, - ) - } - const setupNoPermissions = (location: string) => { + const setup = (location: string, permissions = true) => { history = createMemoryHistory() history.push(location) - return mount( + return render( <Router history={history}> <Provider - store={mockStore({ - components: { sidebarCollapsed: false }, - user: { permissions: [] }, - } as any)} + store={ + permissions + ? store + : mockStore({ + components: { sidebarCollapsed: false }, + user: { permissions: [] }, + } as any) + } > <Sidebar /> </Provider> @@ -76,35 +42,22 @@ describe('Sidebar', () => { ) } - const getIndex = (wrapper: ReactWrapper, label: string) => - wrapper.reduce((result, item, index) => (item.text().trim() === label ? index : result), -1) - describe('dashboard links', () => { it('should render the dashboard link', () => { - const wrapper = setup('/') + setup('/') - const listItems = wrapper.find(ListItem) - - expect(listItems.at(1).text().trim()).toEqual('dashboard.label') + expect(screen.getByText(/dashboard.label/i)).toBeInTheDocument() }) it('should be active when the current path is /', () => { - const wrapper = setup('/') - - const listItems = wrapper.find(ListItem) - - expect(listItems.at(1).prop('active')).toBeTruthy() + setup('/') + expect(screen.getByText(/dashboard\.label/i)).toHaveClass('active') }) it('should navigate to / when the dashboard link is clicked', () => { - const wrapper = setup('/patients') + setup('/patients') - const listItems = wrapper.find(ListItem) - - act(() => { - const onClick = listItems.at(1).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/dashboard.label/i)) expect(history.location.pathname).toEqual('/') }) @@ -112,224 +65,144 @@ describe('Sidebar', () => { describe('patients links', () => { it('should render the patients main link', () => { - const wrapper = setup('/') - - const listItems = wrapper.find(ListItem) + setup('/') - expect(listItems.at(2).text().trim()).toEqual('patients.label') + expect(screen.getByText(/patients.label/i)).toBeInTheDocument() }) it('should render the new_patient link', () => { - const wrapper = setup('/patients') - - const listItems = wrapper.find(ListItem) + setup('/patients') - expect(listItems.at(3).text().trim()).toEqual('patients.newPatient') + expect(screen.getByText(/patients.newPatient/i)).toBeInTheDocument() }) it('should not render the new_patient link when the user does not have write patient privileges', () => { - const wrapper = setupNoPermissions('/patients') + setup('/patients', false) - const listItems = wrapper.find(ListItem) - - listItems.forEach((_, i) => { - expect(listItems.at(i).text().trim()).not.toEqual('patients.newPatient') - }) + expect(screen.queryByText(/patients.newPatient/i)).not.toBeInTheDocument() }) it('should render the patients_list link', () => { - const wrapper = setup('/patients') - - const listItems = wrapper.find(ListItem) + setup('/patients') - expect(listItems.at(4).text().trim()).toEqual('patients.patientsList') + expect(screen.getByText(/patients.patientsList/i)).toBeInTheDocument() }) it('should not render the patients_list link when the user does not have read patient privileges', () => { - const wrapper = setupNoPermissions('/patients') - - const listItems = wrapper.find(ListItem) + setup('/patients', false) - listItems.forEach((_, i) => { - expect(listItems.at(i).text().trim()).not.toEqual('patients.patientsList') - }) + expect(screen.queryByText(/patients.patientsList/i)).not.toBeInTheDocument() }) it('main patients link should be active when the current path is /patients', () => { - const wrapper = setup('/patients') + setup('/patients') - const listItems = wrapper.find(ListItem) - - expect(listItems.at(2).prop('active')).toBeTruthy() + expect(screen.getByText(/patients\.label/i)).toHaveClass('active') }) it('should navigate to /patients when the patients main link is clicked', () => { - const wrapper = setup('/') - - const listItems = wrapper.find(ListItem) + setup('/') - act(() => { - const onClick = listItems.at(2).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/patients.label/i)) expect(history.location.pathname).toEqual('/patients') }) it('new patient should be active when the current path is /patients/new', () => { - const wrapper = setup('/patients/new') - - const listItems = wrapper.find(ListItem) + setup('/patients/new') - expect(listItems.at(3).prop('active')).toBeTruthy() + expect(screen.getByText(/patients\.newPatient/i)).toHaveClass('active') }) it('should navigate to /patients/new when the patients new link is clicked', () => { - const wrapper = setup('/patients') - - const listItems = wrapper.find(ListItem) - - act(() => { - const onClick = listItems.at(3).prop('onClick') as any - onClick() - }) + setup('/patients') + userEvent.click(screen.getByText(/patients.newPatient/i)) expect(history.location.pathname).toEqual('/patients/new') }) it('patients list link should be active when the current path is /patients', () => { - const wrapper = setup('/patients') + setup('/patients') - const listItems = wrapper.find(ListItem) - - expect(listItems.at(4).prop('active')).toBeTruthy() + expect(screen.getByText(/patients\.patientsList/i)).toHaveClass('active') }) it('should navigate to /patients when the patients list link is clicked', () => { - const wrapper = setup('/patients') - - const listItems = wrapper.find(ListItem) - - act(() => { - const onClick = listItems.at(4).prop('onClick') as any - onClick() - }) + setup('/patients') + userEvent.click(screen.getByText(/patients.patientsList/i)) expect(history.location.pathname).toEqual('/patients') }) }) describe('appointments link', () => { it('should render the scheduling link', () => { - const wrapper = setup('/appointments') + setup('/appointments') - const listItems = wrapper.find(ListItem) - const appointmentsIndex = getIndex(listItems, 'scheduling.label') - - expect(appointmentsIndex).not.toBe(-1) + expect(screen.getByText(/scheduling.label/i)).toBeInTheDocument() }) it('should render the new appointment link', () => { - const wrapper = setup('/appointments/new') - - const listItems = wrapper.find(ListItem) - const appointmentsIndex = getIndex(listItems, 'scheduling.appointments.new') + setup('/appointments/new') - expect(appointmentsIndex).not.toBe(-1) + expect(screen.getByText(/scheduling.appointments.new/i)).toBeInTheDocument() }) it('should not render the new appointment link when the user does not have write appointments privileges', () => { - const wrapper = setupNoPermissions('/appointments') - - const listItems = wrapper.find(ListItem) - const appointmentsIndex = getIndex(listItems, 'scheduling.appointments.new') + setup('/appointments', false) - expect(appointmentsIndex).toBe(-1) + expect(screen.queryByText(/scheduling.appointments.new/i)).not.toBeInTheDocument() }) it('should render the appointments schedule link', () => { - const wrapper = setup('/appointments') + setup('/appointments') - const listItems = wrapper.find(ListItem) - const appointmentsIndex = getIndex(listItems, 'scheduling.appointments.schedule') - - expect(appointmentsIndex).not.toBe(-1) + expect(screen.getByText(/scheduling.appointments.schedule/i)).toBeInTheDocument() }) it('should not render the appointments schedule link when the user does not have read appointments privileges', () => { - const wrapper = setupNoPermissions('/appointments') - - const listItems = wrapper.find(ListItem) - const appointmentsIndex = getIndex(listItems, 'scheduling.appointments.schedule') + setup('/appointments', false) - expect(appointmentsIndex).toBe(-1) + expect(screen.queryByText(/scheduling.appointments.schedule/i)).not.toBeInTheDocument() }) it('main scheduling link should be active when the current path is /appointments', () => { - const wrapper = setup('/appointments') - - const listItems = wrapper.find(ListItem) - const appointmentsIndex = getIndex(listItems, 'scheduling.label') + setup('/appointments') - expect(listItems.at(appointmentsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/scheduling\.label/i)).toHaveClass('active') }) it('should navigate to /appointments when the main scheduling link is clicked', () => { - const wrapper = setup('/') + setup('/') - const listItems = wrapper.find(ListItem) - const appointmentsIndex = getIndex(listItems, 'scheduling.label') - - act(() => { - const onClick = listItems.at(appointmentsIndex).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/scheduling.label/i)) expect(history.location.pathname).toEqual('/appointments') }) it('new appointment link should be active when the current path is /appointments/new', () => { - const wrapper = setup('/appointments/new') - - const listItems = wrapper.find(ListItem) - const appointmentsIndex = getIndex(listItems, 'scheduling.appointments.new') + setup('/appointments/new') - expect(listItems.at(appointmentsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/appointments\.new/i)).toHaveClass('active') }) it('should navigate to /appointments/new when the new appointment link is clicked', () => { - const wrapper = setup('/appointments') - - const listItems = wrapper.find(ListItem) - const appointmentsIndex = getIndex(listItems, 'scheduling.appointments.new') - - act(() => { - const onClick = listItems.at(appointmentsIndex).prop('onClick') as any - onClick() - }) + setup('/appointments') + userEvent.click(screen.getByText(/scheduling.appointments.new/i)) expect(history.location.pathname).toEqual('/appointments/new') }) it('appointments schedule link should be active when the current path is /appointments', () => { - const wrapper = setup('/appointments') - - const listItems = wrapper.find(ListItem) - const appointmentsIndex = getIndex(listItems, 'scheduling.label') + setup('/appointments') - expect(listItems.at(appointmentsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/scheduling\.appointments\.schedule/i)).toHaveClass('active') }) it('should navigate to /appointments when the appointments schedule link is clicked', () => { - const wrapper = setup('/appointments') + setup('/appointments') - const listItems = wrapper.find(ListItem) - const appointmentsIndex = getIndex(listItems, 'scheduling.label') - - act(() => { - const onClick = listItems.at(appointmentsIndex).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/scheduling.appointments.schedule/i)) expect(history.location.pathname).toEqual('/appointments') }) @@ -337,115 +210,73 @@ describe('Sidebar', () => { describe('labs links', () => { it('should render the main labs link', () => { - const wrapper = setup('/labs') - - const listItems = wrapper.find(ListItem) - const labsIndex = getIndex(listItems, 'labs.label') + setup('/labs') - expect(labsIndex).not.toBe(-1) + expect(screen.getByText(/labs.label/i)).toBeInTheDocument() }) it('should render the new labs request link', () => { - const wrapper = setup('/labs') - - const listItems = wrapper.find(ListItem) - const labsIndex = getIndex(listItems, 'labs.requests.new') + setup('/labs') - expect(labsIndex).not.toBe(-1) + expect(screen.getByText(/labs.requests.new/i)).toBeInTheDocument() }) it('should not render the new labs request link when user does not have request labs privileges', () => { - const wrapper = setupNoPermissions('/labs') + setup('/labs', false) - const listItems = wrapper.find(ListItem) - const labsIndex = getIndex(listItems, 'labs.requests.new') - - expect(labsIndex).toBe(-1) + expect(screen.queryByText(/labs.requests.new/i)).not.toBeInTheDocument() }) it('should render the labs list link', () => { - const wrapper = setup('/labs') - - const listItems = wrapper.find(ListItem) - const labsIndex = getIndex(listItems, 'labs.requests.label') + setup('/labs') - expect(labsIndex).not.toBe(-1) + expect(screen.getByText(/labs.requests.label/i)).toBeInTheDocument() }) it('should not render the labs list link when user does not have view labs privileges', () => { - const wrapper = setupNoPermissions('/labs') - - const listItems = wrapper.find(ListItem) - const labsIndex = getIndex(listItems, 'labs.requests.label') + setup('/labs', false) - expect(labsIndex).toBe(-1) + expect(screen.queryByText(/labs.requests.label/i)).not.toBeInTheDocument() }) it('main labs link should be active when the current path is /labs', () => { - const wrapper = setup('/labs') + setup('/labs') - const listItems = wrapper.find(ListItem) - const labsIndex = getIndex(listItems, 'labs.label') - - expect(listItems.at(labsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/labs\.label/i)).toHaveClass('active') }) it('should navigate to /labs when the main lab link is clicked', () => { - const wrapper = setup('/') - - const listItems = wrapper.find(ListItem) - const labsIndex = getIndex(listItems, 'labs.label') + setup('/') - act(() => { - const onClick = listItems.at(labsIndex).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/labs.label/i)) expect(history.location.pathname).toEqual('/labs') }) it('new lab request link should be active when the current path is /labs/new', () => { - const wrapper = setup('/labs/new') + setup('/labs/new') - const listItems = wrapper.find(ListItem) - const labsIndex = getIndex(listItems, 'labs.requests.new') - - expect(listItems.at(labsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/labs\.requests\.new/i)).toHaveClass('active') }) it('should navigate to /labs/new when the new labs link is clicked', () => { - const wrapper = setup('/labs') - - const listItems = wrapper.find(ListItem) - const labsIndex = getIndex(listItems, 'labs.requests.new') + setup('/labs') - act(() => { - const onClick = listItems.at(labsIndex).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/labs.requests.new/)) expect(history.location.pathname).toEqual('/labs/new') }) it('labs list link should be active when the current path is /labs', () => { - const wrapper = setup('/labs') - - const listItems = wrapper.find(ListItem) - const labsIndex = getIndex(listItems, 'labs.requests.label') + setup('/labs') - expect(listItems.at(labsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/labs\.requests\.label/i)).toHaveClass('active') }) it('should navigate to /labs when the labs list link is clicked', () => { - const wrapper = setup('/labs/new') + setup('/labs') - const listItems = wrapper.find(ListItem) - const labsIndex = getIndex(listItems, 'labs.requests.label') - - act(() => { - const onClick = listItems.at(labsIndex).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/labs.label/i)) expect(history.location.pathname).toEqual('/labs') }) @@ -453,166 +284,99 @@ describe('Sidebar', () => { describe('incident links', () => { it('should render the main incidents link', () => { - const wrapper = setup('/incidents') - - const listItems = wrapper.find(ListItem) - const incidentsIndex = getIndex(listItems, 'incidents.label') + setup('/incidents') - expect(incidentsIndex).not.toBe(-1) - }) - - it('should be the last one in the sidebar', () => { - const wrapper = setup('/incidents') - - const listItems = wrapper.find(ListItem) - const reportsLabel = listItems.length - 2 - - expect(listItems.at(reportsLabel).text().trim()).toBe('incidents.reports.label') - expect( - listItems - .at(reportsLabel - 1) - .text() - .trim(), - ).toBe('incidents.reports.new') - expect( - listItems - .at(reportsLabel - 2) - .text() - .trim(), - ).toBe('incidents.label') + expect(screen.getByText(/incidents.label/i)).toBeInTheDocument() }) it('should render the new incident report link', () => { - const wrapper = setup('/incidents') + setup('/incidents') - const listItems = wrapper.find(ListItem) - const incidentsIndex = getIndex(listItems, 'incidents.reports.new') + expect(screen.getByText(/incidents.reports.new/i)).toBeInTheDocument() + }) - expect(incidentsIndex).not.toBe(-1) + it('should be the last one in the sidebar', () => { + setup('/incidents') + expect(screen.getAllByText(/label/i)[5]).toHaveTextContent(/imagings.label/i) + expect(screen.getAllByText(/label/i)[6]).toHaveTextContent(/incidents.label/i) }) it('should not render the new incident report link when user does not have the report incidents privileges', () => { - const wrapper = setupNoPermissions('/incidents') - - const listItems = wrapper.find(ListItem) - const incidentsIndex = getIndex(listItems, 'incidents.reports.new') + setup('/incidents', false) - expect(incidentsIndex).toBe(-1) + expect(screen.queryByText(/incidents.reports.new/i)).not.toBeInTheDocument() }) it('should render the incidents list link', () => { - const wrapper = setup('/incidents') + setup('/incidents') - const listItems = wrapper.find(ListItem) - const incidentsIndex = getIndex(listItems, 'incidents.reports.label') - - expect(incidentsIndex).not.toBe(-1) + expect(screen.getByText(/incidents.reports.label/i)).toBeInTheDocument() }) it('should not render the incidents list link when user does not have the view incidents privileges', () => { - const wrapper = setupNoPermissions('/incidents') - - const listItems = wrapper.find(ListItem) - const incidentsIndex = getIndex(listItems, 'incidents.reports.label') + setup('/incidents', false) - expect(incidentsIndex).toBe(-1) + expect(screen.queryByText(/incidents.reports.label/i)).not.toBeInTheDocument() }) it('should render the incidents visualize link', () => { - const wrapper = setup('/incidents') + setup('/incidents') - const listItems = wrapper.find(ListItem) - expect(listItems.at(10).text().trim()).toEqual('incidents.visualize.label') + expect(screen.getByText(/incidents.visualize.label/i)).toBeInTheDocument() }) it('should not render the incidents visualize link when user does not have the view incident widgets privileges', () => { - const wrapper = setupNoPermissions('/incidents') - - const listItems = wrapper.find(ListItem) + setup('/incidents', false) - listItems.forEach((_, i) => { - expect(listItems.at(i).text().trim()).not.toEqual('incidents.visualize.label') - }) + expect(screen.queryByText(/incidents.visualize.label/i)).not.toBeInTheDocument() }) it('main incidents link should be active when the current path is /incidents', () => { - const wrapper = setup('/incidents') + setup('/incidents') - const listItems = wrapper.find(ListItem) - const incidentsIndex = getIndex(listItems, 'incidents.label') - - expect(listItems.at(incidentsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/incidents\.labe/i)).toHaveClass('active') }) it('should navigate to /incidents when the main incident link is clicked', () => { - const wrapper = setup('/') - - const listItems = wrapper.find(ListItem) - const incidentsIndex = getIndex(listItems, 'incidents.label') + setup('/') - act(() => { - const onClick = listItems.at(incidentsIndex).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/incidents.label/i)) expect(history.location.pathname).toEqual('/incidents') }) it('new incident report link should be active when the current path is /incidents/new', () => { - const wrapper = setup('/incidents/new') - - const listItems = wrapper.find(ListItem) - const incidentsIndex = getIndex(listItems, 'incidents.reports.new') + setup('/incidents/new') - expect(listItems.at(incidentsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/incidents\.reports\.new/i)).toHaveClass('active') }) - it('should navigate to /incidents/new when the new labs link is clicked', () => { - const wrapper = setup('/incidents') + it('should navigate to /incidents/new when the new incidents link is clicked', () => { + setup('/incidents') - const listItems = wrapper.find(ListItem) - const incidentsIndex = getIndex(listItems, 'incidents.reports.new') - - act(() => { - const onClick = listItems.at(incidentsIndex).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/incidents.reports.new/i)) expect(history.location.pathname).toEqual('/incidents/new') }) it('should navigate to /incidents/visualize when the incidents visualize link is clicked', () => { - const wrapper = setup('/incidents') - - const listItems = wrapper.find(ListItem) + setup('/incidents') - act(() => { - const onClick = listItems.at(10).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/incidents.visualize.label/i)) expect(history.location.pathname).toEqual('/incidents/visualize') }) it('incidents list link should be active when the current path is /incidents', () => { - const wrapper = setup('/incidents') - - const listItems = wrapper.find(ListItem) - const incidentsIndex = getIndex(listItems, 'incidents.reports.label') + setup('/incidents') - expect(listItems.at(incidentsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/incidents\.reports\.label/i)).toHaveClass('active') }) - it('should navigate to /incidents when the incidents list link is clicked', () => { - const wrapper = setup('/incidents/new') + it('should navigate to /incidents/new when the incidents list link is clicked', () => { + setup('/incidents/new') - const listItems = wrapper.find(ListItem) - const incidentsIndex = getIndex(listItems, 'incidents.reports.label') - - act(() => { - const onClick = listItems.at(incidentsIndex).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/incidents.reports.label/i)) expect(history.location.pathname).toEqual('/incidents') }) @@ -620,115 +384,73 @@ describe('Sidebar', () => { describe('imagings links', () => { it('should render the main imagings link', () => { - const wrapper = setup('/imaging') - - const listItems = wrapper.find(ListItem) - const imagingsIndex = getIndex(listItems, 'imagings.label') + setup('/imaging') - expect(imagingsIndex).not.toBe(-1) + expect(screen.getByText(/imagings.label/i)).toBeInTheDocument() }) it('should render the new imaging request link', () => { - const wrapper = setup('/imagings') + setup('/imagings') - const listItems = wrapper.find(ListItem) - const imagingsIndex = getIndex(listItems, 'imagings.requests.new') - - expect(imagingsIndex).not.toBe(-1) + expect(screen.getByText(/imagings.requests.new/i)).toBeInTheDocument() }) it('should not render the new imaging request link when user does not have the request imaging privileges', () => { - const wrapper = setupNoPermissions('/imagings') - - const listItems = wrapper.find(ListItem) - const imagingsIndex = getIndex(listItems, 'imagings.requests.new') + setup('/imagings', false) - expect(imagingsIndex).toBe(-1) + expect(screen.queryByText(/imagings.requests.new/i)).not.toBeInTheDocument() }) it('should render the imagings list link', () => { - const wrapper = setup('/imagings') - - const listItems = wrapper.find(ListItem) - const imagingsIndex = getIndex(listItems, 'imagings.requests.label') + setup('/imagings') - expect(imagingsIndex).not.toBe(-1) + expect(screen.getByText(/imagings.requests.label/i)).toBeInTheDocument() }) it('should not render the imagings list link when user does not have the view imagings privileges', () => { - const wrapper = setupNoPermissions('/imagings') + setup('/imagings', false) - const listItems = wrapper.find(ListItem) - const imagingsIndex = getIndex(listItems, 'imagings.requests.label') - - expect(imagingsIndex).toBe(-1) + expect(screen.queryByText(/imagings.requests.label/i)).not.toBeInTheDocument() }) it('main imagings link should be active when the current path is /imagings', () => { - const wrapper = setup('/imagings') - - const listItems = wrapper.find(ListItem) - const imagingsIndex = getIndex(listItems, 'imagings.label') + setup('/imagings') - expect(listItems.at(imagingsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/imagings\.label/i)).toHaveClass('active') }) it('should navigate to /imaging when the main imagings link is clicked', () => { - const wrapper = setup('/') - - const listItems = wrapper.find(ListItem) - const imagingsIndex = getIndex(listItems, 'imagings.label') + setup('/') - act(() => { - const onClick = listItems.at(imagingsIndex).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/imagings.label/i)) expect(history.location.pathname).toEqual('/imaging') }) it('new imaging request link should be active when the current path is /imagings/new', () => { - const wrapper = setup('/imagings/new') + setup('/imagings/new') - const listItems = wrapper.find(ListItem) - const imagingsIndex = getIndex(listItems, 'imagings.requests.new') - - expect(listItems.at(imagingsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/imagings\.requests\.new/i)).toHaveClass('active') }) it('should navigate to /imaging/new when the new imaging link is clicked', () => { - const wrapper = setup('/imagings') - - const listItems = wrapper.find(ListItem) - const imagingsIndex = getIndex(listItems, 'imagings.requests.new') + setup('/imagings') - act(() => { - const onClick = listItems.at(imagingsIndex).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/imagings.requests.new/i)) expect(history.location.pathname).toEqual('/imaging/new') }) it('imagings list link should be active when the current path is /imagings', () => { - const wrapper = setup('/imagings') - - const listItems = wrapper.find(ListItem) - const imagingsIndex = getIndex(listItems, 'imagings.label') + setup('/imagings') - expect(listItems.at(imagingsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/imagings\.requests\.label/i)).toHaveClass('active') }) it('should navigate to /imaging when the imagings list link is clicked', () => { - const wrapper = setup('/imagings/new') + setup('/imagings/new') - const listItems = wrapper.find(ListItem) - const imagingsIndex = getIndex(listItems, 'imagings.label') - - act(() => { - const onClick = listItems.at(imagingsIndex).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/imagings.label/i)) expect(history.location.pathname).toEqual('/imaging') }) @@ -736,115 +458,72 @@ describe('Sidebar', () => { describe('medications links', () => { it('should render the main medications link', () => { - const wrapper = setup('/medications') - - const listItems = wrapper.find(ListItem) - const medicationsIndex = getIndex(listItems, 'medications.label') + setup('/medications') - expect(medicationsIndex).not.toBe(-1) + expect(screen.getByText(/medications.label/i)).toBeInTheDocument() }) it('should render the new medications request link', () => { - const wrapper = setup('/medications') + setup('/medications') - const listItems = wrapper.find(ListItem) - const medicationsIndex = getIndex(listItems, 'medications.requests.new') - - expect(medicationsIndex).not.toBe(-1) + expect(screen.getByText(/medications.requests.new/i)).toBeInTheDocument() }) it('should not render the new medications request link when user does not have request medications privileges', () => { - const wrapper = setupNoPermissions('/medications') - - const listItems = wrapper.find(ListItem) - const medicationsIndex = getIndex(listItems, 'medications.requests.new') + setup('/medications', false) - expect(medicationsIndex).toBe(-1) + expect(screen.queryByText(/medications.requests.new/i)).not.toBeInTheDocument() }) it('should render the medications list link', () => { - const wrapper = setup('/medications') - - const listItems = wrapper.find(ListItem) - const medicationsIndex = getIndex(listItems, 'medications.requests.label') + setup('/medications') - expect(medicationsIndex).not.toBe(-1) + expect(screen.getByText(/medications.requests.label/i)).toBeInTheDocument() }) it('should not render the medications list link when user does not have view medications privileges', () => { - const wrapper = setupNoPermissions('/medications') + setup('/medications', false) - const listItems = wrapper.find(ListItem) - const medicationsIndex = getIndex(listItems, 'medications.requests.label') - - expect(medicationsIndex).toBe(-1) + expect(screen.queryByText(/medications.requests.label/i)).not.toBeInTheDocument() }) it('main medications link should be active when the current path is /medications', () => { - const wrapper = setup('/medications') - - const listItems = wrapper.find(ListItem) - const medicationsIndex = getIndex(listItems, 'medications.label') + setup('/medications') - expect(listItems.at(medicationsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/medications\.label/i)).toHaveClass('active') }) it('should navigate to /medications when the main lab link is clicked', () => { - const wrapper = setup('/') - - const listItems = wrapper.find(ListItem) - const medicationsIndex = getIndex(listItems, 'medications.label') + setup('/') - act(() => { - const onClick = listItems.at(medicationsIndex).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/medications.label/i)) expect(history.location.pathname).toEqual('/medications') }) it('new lab request link should be active when the current path is /medications/new', () => { - const wrapper = setup('/medications/new') + setup('/medications/new') - const listItems = wrapper.find(ListItem) - const medicationsIndex = getIndex(listItems, 'medications.requests.new') - - expect(listItems.at(medicationsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/medications\.requests\.new/i)).toHaveClass('active') }) it('should navigate to /medications/new when the new medications link is clicked', () => { - const wrapper = setup('/medications') - - const listItems = wrapper.find(ListItem) - const medicationsIndex = getIndex(listItems, 'medications.requests.new') - - act(() => { - const onClick = listItems.at(medicationsIndex).prop('onClick') as any - onClick() - }) + setup('/medications') + userEvent.click(screen.getByText(/medications.requests.new/i)) expect(history.location.pathname).toEqual('/medications/new') }) it('medications list link should be active when the current path is /medications', () => { - const wrapper = setup('/medications') + setup('/medications') - const listItems = wrapper.find(ListItem) - const medicationsIndex = getIndex(listItems, 'medications.requests.label') - - expect(listItems.at(medicationsIndex).prop('active')).toBeTruthy() + expect(screen.getByText(/medications\.requests\.label/i)).toHaveClass('active') }) it('should navigate to /medications when the medications list link is clicked', () => { - const wrapper = setup('/medications/new') - - const listItems = wrapper.find(ListItem) - const medicationsIndex = getIndex(listItems, 'medications.requests.label') + setup('/medications/new') - act(() => { - const onClick = listItems.at(medicationsIndex).prop('onClick') as any - onClick() - }) + userEvent.click(screen.getByText(/medications.requests.label/i)) expect(history.location.pathname).toEqual('/medications') }) diff --git a/src/__tests__/shared/components/input/DatePickerWithLabelFormGroup.test.tsx b/src/__tests__/shared/components/input/DatePickerWithLabelFormGroup.test.tsx index 0f50874ba5..75743149aa 100644 --- a/src/__tests__/shared/components/input/DatePickerWithLabelFormGroup.test.tsx +++ b/src/__tests__/shared/components/input/DatePickerWithLabelFormGroup.test.tsx @@ -1,104 +1,62 @@ -import { DateTimePicker, Label } from '@hospitalrun/components' -import { shallow } from 'enzyme' -import React, { ChangeEvent } from 'react' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import React from 'react' import DatePickerWithLabelFormGroup from '../../../../shared/components/input/DatePickerWithLabelFormGroup' describe('date picker with label form group', () => { describe('layout', () => { it('should render a label', () => { - const expectedName = 'test' - const wrapper = shallow( + const expectedName = 'stardate1111' + render( <DatePickerWithLabelFormGroup name={expectedName} - label="test" - value={new Date()} + label="stardate11111" + value={new Date('12/25/2020')} isEditable onChange={jest.fn()} />, ) - const label = wrapper.find(Label) - expect(label).toHaveLength(1) - expect(label.prop('htmlFor')).toEqual(`${expectedName}DatePicker`) - expect(label.prop('text')).toEqual(expectedName) + const name = screen.getByText(/stardate/i) + expect(name).toHaveAttribute('for', `${expectedName}DatePicker`) + expect(name).toHaveTextContent(expectedName) + expect(screen.getByRole('textbox')).toHaveDisplayValue(['12/25/2020']) }) - - it('should render and date time picker', () => { - const expectedName = 'test' - const expectedDate = new Date() - const wrapper = shallow( - <DatePickerWithLabelFormGroup - name={expectedName} - label="test" - value={new Date()} - isEditable - onChange={jest.fn()} - maxDate={expectedDate} - />, - ) - - const input = wrapper.find(DateTimePicker) - expect(input).toHaveLength(1) - expect(input.prop('maxDate')).toEqual(expectedDate) - }) - it('should render disabled is isDisable disabled is true', () => { - const expectedName = 'test' - const wrapper = shallow( + render( <DatePickerWithLabelFormGroup - name={expectedName} - label="test" + name="stardate22222" + label="stardate22222" value={new Date()} isEditable={false} onChange={jest.fn()} />, ) - - const input = wrapper.find(DateTimePicker) - expect(input).toHaveLength(1) - expect(input.prop('disabled')).toBeTruthy() - }) - - it('should render the proper value', () => { - const expectedName = 'test' - const expectedValue = new Date() - const wrapper = shallow( - <DatePickerWithLabelFormGroup - name={expectedName} - label="test" - value={expectedValue} - isEditable={false} - onChange={jest.fn()} - />, - ) - - const input = wrapper.find(DateTimePicker) - expect(input).toHaveLength(1) - expect(input.prop('selected')).toEqual(expectedValue) + expect(screen.getByRole('textbox')).toBeDisabled() }) }) describe('change handler', () => { it('should call the change handler on change', () => { - const expectedName = 'test' - const expectedValue = new Date() - const handler = jest.fn() - const wrapper = shallow( - <DatePickerWithLabelFormGroup - name={expectedName} - label="test" - value={expectedValue} - isEditable={false} - onChange={handler} - />, - ) - - const input = wrapper.find(DateTimePicker) - input.prop('onChange')(new Date(), { - target: { value: new Date().toISOString() }, - } as ChangeEvent<HTMLInputElement>) - expect(handler).toHaveBeenCalledTimes(1) + const TestComponent = () => { + const [value, setValue] = React.useState(new Date('01/01/2019')) + return ( + <DatePickerWithLabelFormGroup + name="stardate3333" + label="stardate3333" + value={value} + isEditable + onChange={setValue} + /> + ) + } + render(<TestComponent />) + const datepickerInput = screen.getByRole('textbox') + + expect(datepickerInput).toHaveDisplayValue(['01/01/2019']) + userEvent.type(datepickerInput, '{selectall}12/25/2021{enter}') + expect(datepickerInput).toHaveDisplayValue(['12/25/2021']) }) }) }) diff --git a/src/__tests__/shared/components/input/DateTimePickerWithLabelFormGroup.test.tsx b/src/__tests__/shared/components/input/DateTimePickerWithLabelFormGroup.test.tsx index 80b4e8ad42..7ddace4040 100644 --- a/src/__tests__/shared/components/input/DateTimePickerWithLabelFormGroup.test.tsx +++ b/src/__tests__/shared/components/input/DateTimePickerWithLabelFormGroup.test.tsx @@ -1,101 +1,73 @@ -import { DateTimePicker, Label } from '@hospitalrun/components' -import { shallow } from 'enzyme' -import React, { ChangeEvent } from 'react' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import React from 'react' import DateTimePickerWithLabelFormGroup from '../../../../shared/components/input/DateTimePickerWithLabelFormGroup' describe('date picker with label form group', () => { describe('layout', () => { it('should render a label', () => { - const expectedName = 'test' - const wrapper = shallow( + const expectedName = 'stardate11111' + render( <DateTimePickerWithLabelFormGroup name={expectedName} - label="test" + label="stardate11111" value={new Date()} isEditable onChange={jest.fn()} />, ) - const label = wrapper.find(Label) - expect(label).toHaveLength(1) - expect(label.prop('htmlFor')).toEqual(`${expectedName}DateTimePicker`) - expect(label.prop('text')).toEqual(expectedName) - }) - - it('should render and date time picker', () => { - const expectedName = 'test' - const wrapper = shallow( - <DateTimePickerWithLabelFormGroup - name={expectedName} - label="test" - value={new Date()} - isEditable - onChange={jest.fn()} - />, - ) - - const input = wrapper.find(DateTimePicker) - expect(input).toHaveLength(1) + const name = screen.getByText(/stardate/i) + expect(name).toHaveAttribute('for', `${expectedName}DateTimePicker`) + expect(name).toHaveTextContent(expectedName) }) it('should render disabled is isDisable disabled is true', () => { - const expectedName = 'test' - const wrapper = shallow( + render( <DateTimePickerWithLabelFormGroup - name={expectedName} - label="test" + name="stardate333333" + label="stardate333333" value={new Date()} isEditable={false} onChange={jest.fn()} />, ) - - const input = wrapper.find(DateTimePicker) - expect(input).toHaveLength(1) - expect(input.prop('disabled')).toBeTruthy() + expect(screen.getByRole('textbox')).toBeDisabled() }) it('should render the proper value', () => { - const expectedName = 'test' - const expectedValue = new Date() - const wrapper = shallow( + render( <DateTimePickerWithLabelFormGroup - name={expectedName} - label="test" - value={expectedValue} + name="stardate4444444" + label="stardate4444444" + value={new Date('12/25/2020 2:56 PM')} isEditable={false} onChange={jest.fn()} />, ) - - const input = wrapper.find(DateTimePicker) - expect(input).toHaveLength(1) - expect(input.prop('selected')).toEqual(expectedValue) + const datepickerInput = screen.getByRole('textbox') + expect(datepickerInput).toBeDisabled() + expect(datepickerInput).toHaveDisplayValue(/[12/25/2020 2:56 PM]/i) }) }) +}) - describe('change handler', () => { - it('should call the change handler on change', () => { - const expectedName = 'test' - const expectedValue = new Date() - const handler = jest.fn() - const wrapper = shallow( - <DateTimePickerWithLabelFormGroup - name={expectedName} - label="test" - value={expectedValue} - isEditable={false} - onChange={handler} - />, - ) +describe('change handler', () => { + it('should call the change handler on change', () => { + render( + <DateTimePickerWithLabelFormGroup + name="stardate55555555" + label="stardate55555555" + value={new Date('1/1/2021 2:56 PM')} + isEditable + onChange={jest.fn()} + />, + ) + const datepickerInput = screen.getByRole('textbox') - const input = wrapper.find(DateTimePicker) - input.prop('onChange')(new Date(), { - target: { value: new Date().toISOString() }, - } as ChangeEvent<HTMLInputElement>) - expect(handler).toHaveBeenCalledTimes(1) - }) + expect(datepickerInput).toHaveDisplayValue(/[01/01/2021 2:56 PM]/i) + userEvent.type(datepickerInput, '{selectall}12/25/2020 2:56 PM{enter}') + expect(datepickerInput).toHaveDisplayValue(/[12/25/2020 2:56 PM]/i) }) }) diff --git a/src/__tests__/shared/components/input/SelectWithLabelFormGroup.test.tsx b/src/__tests__/shared/components/input/SelectWithLabelFormGroup.test.tsx index 0d0e9dc903..63eacf5ded 100644 --- a/src/__tests__/shared/components/input/SelectWithLabelFormGroup.test.tsx +++ b/src/__tests__/shared/components/input/SelectWithLabelFormGroup.test.tsx @@ -1,14 +1,16 @@ -import { Label, Select } from '@hospitalrun/components' -import { shallow } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent, { specialChars } from '@testing-library/user-event' import React from 'react' import SelectWithLabelFormGroup from '../../../../shared/components/input/SelectWithLabelFormGroup' +const { arrowDown, enter } = specialChars + describe('select with label form group', () => { describe('layout', () => { it('should render a label', () => { const expectedName = 'test' - const wrapper = shallow( + render( <SelectWithLabelFormGroup options={[{ value: 'value1', label: 'label1' }]} name={expectedName} @@ -18,64 +20,58 @@ describe('select with label form group', () => { />, ) - const label = wrapper.find(Label) - expect(label).toHaveLength(1) - expect(label.prop('htmlFor')).toEqual(`${expectedName}Select`) - expect(label.prop('text')).toEqual(expectedName) + expect(screen.getByText(expectedName)).toHaveAttribute('for', `${expectedName}Select`) }) it('should render disabled is isDisable disabled is true', () => { - const expectedName = 'test' - const wrapper = shallow( + render( <SelectWithLabelFormGroup options={[{ value: 'value1', label: 'label1' }]} - name={expectedName} + name="test" label="test" isEditable={false} onChange={jest.fn()} />, ) - const select = wrapper.find(Select) - expect(select).toHaveLength(1) - expect(select.prop('disabled')).toBeTruthy() + expect(screen.getByRole('combobox')).toBeDisabled() }) it('should render the proper value', () => { - const expectedName = 'test' - const expectedDefaultSelected = [{ value: 'value', label: 'label' }] - const wrapper = shallow( + const expectedLabel = 'label' + render( <SelectWithLabelFormGroup - options={[{ value: 'value', label: 'label' }]} - name={expectedName} + options={[{ value: 'value', label: expectedLabel }]} + name="test" label="test" - defaultSelected={expectedDefaultSelected} - isEditable={false} + defaultSelected={[{ value: 'value', label: expectedLabel }]} + isEditable onChange={jest.fn()} />, ) - const select = wrapper.find(Select) - expect(select).toHaveLength(1) - expect(select.prop('defaultSelected')).toEqual(expectedDefaultSelected) + expect(screen.getByRole('combobox')).toHaveValue(expectedLabel) }) }) describe('change handler', () => { it('should call the change handler on change', () => { + const expectedLabel = 'label1' + const expectedValue = 'value1' const handler = jest.fn() - const wrapper = shallow( + render( <SelectWithLabelFormGroup - options={[{ value: 'value1', label: 'label1' }]} + options={[{ value: expectedValue, label: expectedLabel }]} name="name" label="test" - isEditable={false} + isEditable onChange={handler} />, ) - const select = wrapper.find(Select) - select.simulate('change') + userEvent.type(screen.getByRole('combobox'), `${expectedLabel}${arrowDown}${enter}`) + + expect(handler).toHaveBeenCalledWith([expectedValue]) expect(handler).toHaveBeenCalledTimes(1) }) }) diff --git a/src/__tests__/shared/components/input/TestInputWithLabelFormGroup.test.tsx b/src/__tests__/shared/components/input/TestInputWithLabelFormGroup.test.tsx index 9b448fab06..b19fc5be82 100644 --- a/src/__tests__/shared/components/input/TestInputWithLabelFormGroup.test.tsx +++ b/src/__tests__/shared/components/input/TestInputWithLabelFormGroup.test.tsx @@ -1,5 +1,5 @@ -import { Label, TextInput } from '@hospitalrun/components' -import { shallow } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import React from 'react' import TextInputWithLabelFormGroup from '../../../../shared/components/input/TextInputWithLabelFormGroup' @@ -8,92 +8,86 @@ describe('text input with label form group', () => { describe('layout', () => { it('should render a label', () => { const expectedName = 'test' - const wrapper = shallow( + const expectedLabel = 'label test' + render( <TextInputWithLabelFormGroup name={expectedName} - label="test" + label={expectedLabel} value="" isEditable onChange={jest.fn()} />, ) - const label = wrapper.find(Label) - expect(label).toHaveLength(1) - expect(label.prop('htmlFor')).toEqual(`${expectedName}TextInput`) - expect(label.prop('text')).toEqual(expectedName) + expect(screen.getByText(expectedLabel)).toHaveAttribute('for', `${expectedName}TextInput`) }) it('should render a text field', () => { - const expectedName = 'test' - const wrapper = shallow( + const expectedLabel = 'test' + render( <TextInputWithLabelFormGroup - name={expectedName} - label="test" + name="test" + label={expectedLabel} value="" isEditable onChange={jest.fn()} />, ) - const input = wrapper.find(TextInput) - expect(input).toHaveLength(1) + expect(screen.getByLabelText(expectedLabel)).toBeInTheDocument() }) it('should render disabled is isDisable disabled is true', () => { - const expectedName = 'test' - const wrapper = shallow( + const expectedLabel = 'test' + render( <TextInputWithLabelFormGroup - name={expectedName} - label="test" + name="test" + label={expectedLabel} value="" isEditable={false} onChange={jest.fn()} />, ) - const input = wrapper.find(TextInput) - expect(input).toHaveLength(1) - expect(input.prop('disabled')).toBeTruthy() + expect(screen.getByLabelText(expectedLabel)).toBeDisabled() }) it('should render the proper value', () => { - const expectedName = 'test' + const expectedLabel = 'test' const expectedValue = 'expected value' - const wrapper = shallow( + render( <TextInputWithLabelFormGroup - name={expectedName} - label="test" + name="test" + label={expectedLabel} value={expectedValue} isEditable={false} onChange={jest.fn()} />, ) - const input = wrapper.find(TextInput) - expect(input).toHaveLength(1) - expect(input.prop('value')).toEqual(expectedValue) + expect(screen.getByLabelText(expectedLabel)).toHaveValue(expectedValue) }) }) describe('change handler', () => { it('should call the change handler on change', () => { - const expectedName = 'test' + const expectedLabel = 'test' const expectedValue = 'expected value' const handler = jest.fn() - const wrapper = shallow( + render( <TextInputWithLabelFormGroup - name={expectedName} - label="test" - value={expectedValue} - isEditable={false} + name="test" + label={expectedLabel} + value="" + isEditable onChange={handler} />, ) - const input = wrapper.find(TextInput) - input.simulate('change') - expect(handler).toHaveBeenCalledTimes(1) + const input = screen.getByLabelText(expectedLabel) + userEvent.type(input, expectedValue) + expect(input).toHaveValue(expectedValue) + expect(handler).toHaveBeenCalledTimes(expectedValue.length) }) }) }) diff --git a/src/__tests__/shared/components/input/TextFieldWithLabelFormGroup.test.tsx b/src/__tests__/shared/components/input/TextFieldWithLabelFormGroup.test.tsx index 9980a076b6..013b8ef837 100644 --- a/src/__tests__/shared/components/input/TextFieldWithLabelFormGroup.test.tsx +++ b/src/__tests__/shared/components/input/TextFieldWithLabelFormGroup.test.tsx @@ -1,5 +1,5 @@ -import { Label, TextField } from '@hospitalrun/components' -import { shallow } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import React from 'react' import TextFieldWithLabelFormGroup from '../../../../shared/components/input/TextFieldWithLabelFormGroup' @@ -8,7 +8,8 @@ describe('text field with label form group', () => { describe('layout', () => { it('should render a label', () => { const expectedName = 'test' - const wrapper = shallow( + + render( <TextFieldWithLabelFormGroup name={expectedName} label="test" @@ -18,36 +19,28 @@ describe('text field with label form group', () => { />, ) - const label = wrapper.find(Label) - expect(label).toHaveLength(1) - expect(label.prop('htmlFor')).toEqual(`${expectedName}TextField`) - expect(label.prop('text')).toEqual(expectedName) + expect(screen.getByText(expectedName)).toHaveAttribute('for', `${expectedName}TextField`) }) it('should render label as required if isRequired is true', () => { - const expectedName = 'test' - const expectedRequired = true - const wrapper = shallow( + render( <TextFieldWithLabelFormGroup - name={expectedName} + name="test" label="test" value="" isEditable - isRequired={expectedRequired} + isRequired onChange={jest.fn()} />, ) - const label = wrapper.find(Label) - expect(label).toHaveLength(1) - expect(label.prop('isRequired')).toBeTruthy() + expect(screen.getByTitle(/required/i)).toBeInTheDocument() }) it('should render a text field', () => { - const expectedName = 'test' - const wrapper = shallow( + render( <TextFieldWithLabelFormGroup - name={expectedName} + name="test" label="test" value="" isEditable @@ -55,15 +48,13 @@ describe('text field with label form group', () => { />, ) - const input = wrapper.find(TextField) - expect(input).toHaveLength(1) + expect(screen.getByRole('textbox')).toBeInTheDocument() }) it('should render disabled is isDisable disabled is true', () => { - const expectedName = 'test' - const wrapper = shallow( + render( <TextFieldWithLabelFormGroup - name={expectedName} + name="test" label="test" value="" isEditable={false} @@ -71,48 +62,47 @@ describe('text field with label form group', () => { />, ) - const input = wrapper.find(TextField) - expect(input).toHaveLength(1) - expect(input.prop('disabled')).toBeTruthy() + expect(screen.getByRole('textbox')).toBeDisabled() }) it('should render the proper value', () => { - const expectedName = 'test' const expectedValue = 'expected value' - const wrapper = shallow( + + render( <TextFieldWithLabelFormGroup - name={expectedName} + data-testid="test" + name="test" label="test" value={expectedValue} - isEditable={false} + isEditable onChange={jest.fn()} />, ) - const input = wrapper.find(TextField) - expect(input).toHaveLength(1) - expect(input.prop('value')).toEqual(expectedValue) + expect(screen.getByRole('textbox')).toHaveValue(expectedValue) }) }) describe('change handler', () => { it('should call the change handler on change', () => { - const expectedName = 'test' const expectedValue = 'expected value' - const handler = jest.fn() - const wrapper = shallow( + let callBackValue = '' + + render( <TextFieldWithLabelFormGroup - name={expectedName} + name="test" label="test" - value={expectedValue} - isEditable={false} - onChange={handler} + value="" + isEditable + onChange={(e) => { + callBackValue += e.target.value + }} />, ) - const input = wrapper.find(TextField) - input.simulate('change') - expect(handler).toHaveBeenCalledTimes(1) + userEvent.type(screen.getByRole('textbox'), expectedValue) + + expect(callBackValue).toBe(expectedValue) }) }) }) diff --git a/src/__tests__/shared/components/navbar/Navbar.test.tsx b/src/__tests__/shared/components/navbar/Navbar.test.tsx index f91d76b4fe..0712ed3c45 100644 --- a/src/__tests__/shared/components/navbar/Navbar.test.tsx +++ b/src/__tests__/shared/components/navbar/Navbar.test.tsx @@ -1,15 +1,14 @@ -import { Navbar as HospitalRunNavbar } from '@hospitalrun/components' -import { mount } from 'enzyme' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { createMemoryHistory } from 'history' -import cloneDeep from 'lodash/cloneDeep' import React from 'react' -import { act } from 'react-dom/test-utils' import { Provider } from 'react-redux' import { Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import Navbar from '../../../../shared/components/navbar/Navbar' +import pageMap from '../../../../shared/components/navbar/pageMap' import Permissions from '../../../../shared/model/Permissions' import User from '../../../../shared/model/User' import { RootState } from '../../../../shared/store' @@ -17,22 +16,23 @@ import { RootState } from '../../../../shared/store' const mockStore = createMockStore<RootState, any>([thunk]) describe('Navbar', () => { - const history = createMemoryHistory() - const setup = (permissions: Permissions[], user?: User) => { + const history = createMemoryHistory() const store = mockStore({ title: '', user: { permissions, user }, } as any) - const wrapper = mount( - <Router history={history}> + return { + history, + ...render( <Provider store={store}> - <Navbar /> - </Provider> - </Router>, - ) - return wrapper + <Router history={history}> + <Navbar /> + </Router> + </Provider>, + ), + } } const userName = { @@ -40,168 +40,121 @@ describe('Navbar', () => { familyName: 'familyName', } as User - const allPermissions = [ - Permissions.ReadPatients, - Permissions.WritePatients, - Permissions.ReadAppointments, - Permissions.WriteAppointments, - Permissions.DeleteAppointment, - Permissions.AddAllergy, - Permissions.AddDiagnosis, - Permissions.RequestLab, - Permissions.CancelLab, - Permissions.CompleteLab, - Permissions.ViewLab, - Permissions.ViewLabs, - Permissions.RequestMedication, - Permissions.CancelMedication, - Permissions.CompleteMedication, - Permissions.ViewMedication, - Permissions.ViewMedications, - Permissions.ViewIncidents, - Permissions.ViewIncident, - Permissions.ReportIncident, - Permissions.AddVisit, - Permissions.ReadVisits, - Permissions.RequestImaging, - Permissions.ViewImagings, - ] + const allPermissions = Object.values(Permissions) describe('hamberger', () => { it('should render a hamberger link list', () => { - const wrapper = setup(allPermissions) - const hospitalRunNavbar = wrapper.find(HospitalRunNavbar) - const hamberger = hospitalRunNavbar.find('.nav-hamberger') - const { children } = hamberger.first().props() as any - - const [dashboardIcon, dashboardLabel] = children[0].props.children - const [newPatientIcon, newPatientLabel] = children[1].props.children - const [settingsIcon, settingsLabel] = children[children.length - 1].props.children - - expect(dashboardIcon.props.icon).toEqual('dashboard') - expect(dashboardLabel).toEqual('dashboard.label') - expect(newPatientIcon.props.icon).toEqual('patient-add') - expect(newPatientLabel).toEqual('patients.newPatient') - expect(settingsIcon.props.icon).toEqual('setting') - expect(settingsLabel).toEqual('settings.label') + setup(allPermissions) + + const navButton = screen.getByRole('button', { hidden: false }) + userEvent.click(navButton) + + // We want all the labels from the page mapping to be rendered when we have all permissions + const expectedLabels = Object.values(pageMap).map((pm) => pm.label) + + // Checks both order, and length - excluding buttons with no label + const renderedLabels = screen + .getAllByRole('button') + .map((b) => b.textContent) + .filter((s) => s) + expect(renderedLabels).toStrictEqual(expectedLabels) }) it('should not show an item if user does not have a permission', () => { // exclude labs, incidents, and imagings permissions - const wrapper = setup(cloneDeep(allPermissions).slice(0, 6)) - const hospitalRunNavbar = wrapper.find(HospitalRunNavbar) - const hamberger = hospitalRunNavbar.find('.nav-hamberger') - const { children } = hamberger.first().props() as any - - const labels = [ - 'labs.requests.new', - 'labs.requests.label', - 'incidents.reports.new', - 'incidents.reports.label', - 'medications.requests.new', - 'medications.requests.label', - 'imagings.requests.new', - 'imagings.requests.label', + // NOTE: "View Imagings" is based on the ReadPatients permission - not an Imagings permission + const excludedPermissions = [ + Permissions.ViewLab, + Permissions.ViewLabs, + Permissions.CancelLab, + Permissions.RequestLab, + Permissions.CompleteLab, + Permissions.ViewIncident, + Permissions.ViewIncidents, + Permissions.ViewIncidentWidgets, + Permissions.ReportIncident, + Permissions.ResolveIncident, + Permissions.ViewImagings, + Permissions.RequestImaging, ] + setup(allPermissions.filter((p) => !excludedPermissions.includes(p))) + const navButton = screen.getByRole('button', { hidden: false }) + userEvent.click(navButton) - children.forEach((option: any) => { - labels.forEach((label) => { - expect(option.props.children).not.toEqual(label) - }) - }) - }) - }) + const unexpectedLabels = Object.values(pageMap) + .filter((pm) => excludedPermissions.includes(pm.permission as Permissions)) + .map((pm) => pm.label) - it('should render a HospitalRun Navbar', () => { - const wrapper = setup(allPermissions) - const hospitalRunNavbar = wrapper.find(HospitalRunNavbar) + unexpectedLabels.forEach((label) => expect(screen.queryByText(label)).not.toBeInTheDocument()) + }) - expect(hospitalRunNavbar).toHaveLength(1) - }) + describe('header', () => { + it('should render a HospitalRun Navbar', () => { + setup(allPermissions) - describe('header', () => { - it('should render a HospitalRun Navbar with the navbar header', () => { - const wrapper = setup(allPermissions) - const header = wrapper.find('.nav-header') - const { children } = header.first().props() as any + expect(screen.getByText(/hospitalrun/i)).toBeInTheDocument() + expect(screen.getByRole('button', { hidden: false })).toBeInTheDocument() + }) - expect(children.props.children).toEqual('HospitalRun') - }) + it('should navigate to / when the header is clicked', () => { + const { history } = setup(allPermissions) - it('should navigate to / when the header is clicked', () => { - const wrapper = setup(allPermissions) - const header = wrapper.find('.nav-header') + history.location.pathname = '/enterprise-1701' + expect(history.location.pathname).not.toEqual('/') + userEvent.click(screen.getByText(/hospitalrun/i)) - act(() => { - const onClick = header.first().prop('onClick') as any - onClick() + expect(history.location.pathname).toEqual('/') }) - - expect(history.location.pathname).toEqual('/') }) - }) - - describe('add new', () => { - it('should show a shortcut if user has a permission', () => { - const wrapper = setup(allPermissions) - const hospitalRunNavbar = wrapper.find(HospitalRunNavbar) - const addNew = hospitalRunNavbar.find('.nav-add-new') - const { children } = addNew.first().props() as any - const [icon, label] = children[0].props.children + describe('add new', () => { + it('should show a shortcut if user has a permission', () => { + setup(allPermissions) - expect(icon.props.icon).toEqual('patient-add') - expect(label).toEqual('patients.newPatient') - }) + const navButton = screen.getByRole('button', { hidden: false }) + userEvent.click(navButton) + expect( + screen.getByRole('button', { + name: /patients\.newpatient/i, + }), + ).toBeInTheDocument() - it('should not show a shortcut if user does not have a permission', () => { - // exclude labs and incidents permissions - const wrapper = setup(cloneDeep(allPermissions).slice(0, 6)) - const hospitalRunNavbar = wrapper.find(HospitalRunNavbar) - const addNew = hospitalRunNavbar.find('.nav-add-new') - const { children } = addNew.first().props() as any - - children.forEach((option: any) => { - expect(option.props.children).not.toEqual('labs.requests.new') - expect(option.props.children).not.toEqual('incidents.requests.new') - expect(option.props.children).not.toEqual('imagings.requests.new') + // 0 & 1 index are dashboard fixed elements, 2 index is first menu label for user + expect(screen.queryAllByRole('button')[2]).toHaveTextContent(/patients\.newpatient/i) }) }) - }) - describe('account', () => { - it("should render a link with the user's identification", () => { - const expectedUserName = `user.login.currentlySignedInAs ${userName.givenName} ${userName.familyName}` + describe('account', () => { + it("should render a link with the user's identification", () => { + const { container } = setup(allPermissions, userName) + const navButton = container.querySelector('.nav-account')?.firstElementChild as Element + userEvent.click(navButton) - const wrapper = setup(allPermissions, userName) - const hospitalRunNavbar = wrapper.find(HospitalRunNavbar) - const accountLinkList = hospitalRunNavbar.find('.nav-account') - const { children } = accountLinkList.first().props() as any + expect( + screen.getByText(/user\.login\.currentlysignedinas givenname familyname/i), + ).toBeInTheDocument() + }) - expect(children[0].props.children).toEqual([undefined, expectedUserName]) - }) + it('should render a setting link list', () => { + setup(allPermissions) + const { container } = setup(allPermissions, userName) - it('should render a setting link list', () => { - const wrapper = setup(allPermissions) - const hospitalRunNavbar = wrapper.find(HospitalRunNavbar) - const accountLinkList = hospitalRunNavbar.find('.nav-account') - const { children } = accountLinkList.first().props() as any + const navButton = container.querySelector('.nav-account')?.firstElementChild as Element + userEvent.click(navButton) - expect(children[1].props.children).toEqual([undefined, 'settings.label']) - }) + expect(screen.getByText('settings.label')).toBeInTheDocument() + }) - it('should navigate to /settings when the list option is selected', () => { - const wrapper = setup(allPermissions) - const hospitalRunNavbar = wrapper.find(HospitalRunNavbar) - const accountLinkList = hospitalRunNavbar.find('.nav-account') - const { children } = accountLinkList.first().props() as any + it('should navigate to /settings when the list option is selected', () => { + setup(allPermissions) + const { history, container } = setup(allPermissions, userName) - act(() => { - children[0].props.onClick() - children[1].props.onClick() - }) + const navButton = container.querySelector('.nav-account')?.firstElementChild as Element + userEvent.click(navButton) + userEvent.click(screen.getByText('settings.label')) - expect(history.location.pathname).toEqual('/settings') + expect(history.location.pathname).toEqual('/settings') + }) }) }) }) diff --git a/src/__tests__/shared/components/network-status/NetworkStatusMessage.test.tsx b/src/__tests__/shared/components/network-status/NetworkStatusMessage.test.tsx index cf36ac4e1c..ea45cca483 100644 --- a/src/__tests__/shared/components/network-status/NetworkStatusMessage.test.tsx +++ b/src/__tests__/shared/components/network-status/NetworkStatusMessage.test.tsx @@ -1,5 +1,4 @@ -import { renderHook } from '@testing-library/react-hooks' -import { render, shallow } from 'enzyme' +import { render } from '@testing-library/react' import React from 'react' import { useTranslation } from '../../../../__mocks__/react-i18next' @@ -7,36 +6,41 @@ import { NetworkStatusMessage } from '../../../../shared/components/network-stat import { useNetworkStatus } from '../../../../shared/components/network-status/useNetworkStatus' jest.mock('../../../../shared/components/network-status/useNetworkStatus') -const useNetworkStatusMock = (useNetworkStatus as unknown) as jest.MockInstance< - ReturnType<typeof useNetworkStatus>, - any -> -const englishTranslationsMock = { - 'networkStatus.offline': 'you are working in offline mode', - 'networkStatus.online': 'you are back online', -} +describe('NetworkStatusMessage', () => { + const useNetworkStatusMock = (useNetworkStatus as unknown) as jest.MockInstance< + ReturnType<typeof useNetworkStatus>, + any + > -const { result } = renderHook(() => useTranslation() as any) -result.current.t = (key: keyof typeof englishTranslationsMock) => englishTranslationsMock[key] -const { t } = result.current + const englishTranslationsMock = { + 'networkStatus.offline': 'you are working in offline mode', + 'networkStatus.online': 'you are back online', + } + + const t = (key: keyof typeof englishTranslationsMock) => englishTranslationsMock[key] + + beforeEach(() => { + const mockTranslation = useTranslation() as any + mockTranslation.t = t + }) -describe('NetworkStatusMessage', () => { it('returns null if the app has always been online', () => { useNetworkStatusMock.mockReturnValue({ isOnline: true, wasOffline: false, }) - const wrapper = shallow(<NetworkStatusMessage />) - expect(wrapper.equals(null as any)).toBe(true) + const { container } = render(<NetworkStatusMessage />) + + expect(container).toBeEmptyDOMElement() }) it(`shows the message "${t('networkStatus.offline')}" if the app goes offline`, () => { useNetworkStatusMock.mockReturnValue({ isOnline: false, wasOffline: false, }) - const wrapper = render(<NetworkStatusMessage />) - expect(wrapper.text()).toContain(t('networkStatus.offline')) + const { container } = render(<NetworkStatusMessage />) + expect(container).toHaveTextContent(t('networkStatus.offline')) }) it(`shows the message "${t( 'networkStatus.online', @@ -45,7 +49,7 @@ describe('NetworkStatusMessage', () => { isOnline: true, wasOffline: true, }) - const wrapper = render(<NetworkStatusMessage />) - expect(wrapper.text()).toContain(t('networkStatus.online')) + const { container } = render(<NetworkStatusMessage />) + expect(container).toHaveTextContent(t('networkStatus.online')) }) }) diff --git a/src/__tests__/shared/hooks/useDebounce.test.ts b/src/__tests__/shared/hooks/useDebounce.test.ts index f4b2ab94b5..5dfecf1cad 100644 --- a/src/__tests__/shared/hooks/useDebounce.test.ts +++ b/src/__tests__/shared/hooks/useDebounce.test.ts @@ -3,9 +3,7 @@ import { renderHook, act } from '@testing-library/react-hooks' import useDebounce from '../../../shared/hooks/useDebounce' describe('useDebounce', () => { - beforeAll(() => jest.useFakeTimers()) - - afterAll(() => jest.useRealTimers()) + beforeEach(() => jest.useFakeTimers()) it('should set the next value after the input value has not changed for the specified amount of time', () => { const initialValue = 'initialValue' diff --git a/src/__tests__/shared/hooks/useTranslator.test.ts b/src/__tests__/shared/hooks/useTranslator.test.ts index 5da6b2ba6e..14cad9a161 100644 --- a/src/__tests__/shared/hooks/useTranslator.test.ts +++ b/src/__tests__/shared/hooks/useTranslator.test.ts @@ -11,12 +11,11 @@ describe('useTranslator', () => { }) it('should return useTranslation hook result if input value is NOT undefined', () => { - const { result: useTranslatorResult } = renderHook(() => useTranslator()) - const { result: useTranslationResult } = renderHook(() => useTranslation()) + const mockTranslation = useTranslation() as any + const expected = mockTranslation.t('patient.firstName') - const result = useTranslatorResult.current.t('patient.firstName') - const expected = useTranslationResult.current.t('patient.firstName') + const { result } = renderHook(() => useTranslator()) - expect(result).toBe(expected) + expect(result.current.t('patient.firstName')).toBe(expected) }) }) diff --git a/src/__tests__/shared/hooks/useUpdateEffect.test.ts b/src/__tests__/shared/hooks/useUpdateEffect.test.ts index f89ae78a63..13da638a72 100644 --- a/src/__tests__/shared/hooks/useUpdateEffect.test.ts +++ b/src/__tests__/shared/hooks/useUpdateEffect.test.ts @@ -3,13 +3,17 @@ import { renderHook } from '@testing-library/react-hooks' import useUpdateEffect from '../../../shared/hooks/useUpdateEffect' describe('useUpdateEffect', () => { - it('should call the function after udpate', () => { + it('should call the function after update', () => { const mockFn = jest.fn() let someVal = 'someVal' + const { rerender } = renderHook(() => useUpdateEffect(mockFn, [someVal])) + expect(mockFn).not.toHaveBeenCalled() + someVal = 'newVal' rerender() + expect(mockFn).toHaveBeenCalledTimes(1) }) }) diff --git a/src/__tests__/test-utils/console.utils.ts b/src/__tests__/test-utils/console.utils.ts new file mode 100644 index 0000000000..e46c7f9e89 --- /dev/null +++ b/src/__tests__/test-utils/console.utils.ts @@ -0,0 +1,5 @@ +export function expectOneConsoleError(expected: any) { + jest.spyOn(console, 'error').mockImplementationOnce((actual) => { + expect(actual).toEqual(expected) + }) +} diff --git a/src/__tests__/test-utils/use-mutation.util.ts b/src/__tests__/test-utils/use-mutation.util.ts index 8227ff8d39..8a47c2cf5a 100644 --- a/src/__tests__/test-utils/use-mutation.util.ts +++ b/src/__tests__/test-utils/use-mutation.util.ts @@ -1,19 +1,16 @@ import { act, renderHook } from '@testing-library/react-hooks' +import { MutateFunction } from 'react-query' -export default async function executeMutation(callback: any, ...input: any[]) { - let mutateToTest: any - await act(async () => { - const renderHookResult = renderHook(callback) - const { result, waitForNextUpdate } = renderHookResult - await waitForNextUpdate() - const [mutate] = result.current as any - mutateToTest = mutate - }) +export default async function executeMutation<TResult>( + callback: () => [MutateFunction<unknown, unknown, TResult>, ...any[]], + ...input: TResult[] +) { + const { result } = renderHook(callback) + const [mutate] = result.current let actualData: any await act(async () => { - const result = await mutateToTest(...input) - actualData = result + actualData = await mutate(...input) }) return actualData diff --git a/src/__tests__/test-utils/use-query.util.ts b/src/__tests__/test-utils/use-query.util.ts deleted file mode 100644 index 0776cd8ad1..0000000000 --- a/src/__tests__/test-utils/use-query.util.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { act, renderHook } from '@testing-library/react-hooks' - -import waitUntilQueryIsSuccessful from './wait-for-query.util' - -export default async function executeQuery(callback: any) { - let actualData: any - await act(async () => { - const renderHookResult = renderHook(callback) - const { result } = renderHookResult - await waitUntilQueryIsSuccessful(renderHookResult) - actualData = (result as any).current.data - }) - - return actualData -} diff --git a/src/__tests__/test-utils/use-query.util.tsx b/src/__tests__/test-utils/use-query.util.tsx new file mode 100644 index 0000000000..a73f59a7cb --- /dev/null +++ b/src/__tests__/test-utils/use-query.util.tsx @@ -0,0 +1,18 @@ +import { renderHook } from '@testing-library/react-hooks' +import React from 'react' +import { ReactQueryConfigProvider, QueryResult } from 'react-query' + +const reactQueryOverrides = { queries: { retry: false } } + +const wrapper: React.FC = ({ children }) => ( + <ReactQueryConfigProvider config={reactQueryOverrides}>{children}</ReactQueryConfigProvider> +) + +export default async function executeQuery<TResult>( + callback: () => QueryResult<TResult>, + waitCheck = (query: QueryResult<TResult>) => query.isSuccess, +) { + const { result, waitFor } = renderHook(callback, { wrapper }) + await waitFor(() => waitCheck(result.current), { timeout: 1000 }) + return result.current.data +} diff --git a/src/__tests__/test-utils/wait-for-query.util.ts b/src/__tests__/test-utils/wait-for-query.util.ts deleted file mode 100644 index 92d1bbd85e..0000000000 --- a/src/__tests__/test-utils/wait-for-query.util.ts +++ /dev/null @@ -1,7 +0,0 @@ -const isQuerySuccessful = (queryResult: any) => queryResult && queryResult.status === 'success' -const waitUntilQueryIsSuccessful = (renderHookResult: any) => - renderHookResult.waitFor(() => isQuerySuccessful(renderHookResult.result.current), { - timeout: 1000, - }) - -export default waitUntilQueryIsSuccessful diff --git a/src/imagings/hooks/useRequestImaging.tsx b/src/imagings/hooks/useRequestImaging.tsx index d5302eec00..c0e6055663 100644 --- a/src/imagings/hooks/useRequestImaging.tsx +++ b/src/imagings/hooks/useRequestImaging.tsx @@ -1,4 +1,4 @@ -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import { queryCache, useMutation } from 'react-query' import ImagingRepository from '../../shared/db/ImagingRepository' diff --git a/src/incidents/hooks/useReportIncident.tsx b/src/incidents/hooks/useReportIncident.tsx index 329e88f144..1421e4ff88 100644 --- a/src/incidents/hooks/useReportIncident.tsx +++ b/src/incidents/hooks/useReportIncident.tsx @@ -1,4 +1,4 @@ -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import { queryCache, useMutation } from 'react-query' import shortid from 'shortid' diff --git a/src/incidents/report/ReportIncident.tsx b/src/incidents/report/ReportIncident.tsx index 85a6a03c14..aa16b800c2 100644 --- a/src/incidents/report/ReportIncident.tsx +++ b/src/incidents/report/ReportIncident.tsx @@ -82,7 +82,7 @@ const ReportIncident = () => { } return ( - <form> + <form aria-label="Report Incident form"> <Row> <Column md={6}> <DateTimePickerWithLabelFormGroup diff --git a/src/incidents/view/ViewIncident.tsx b/src/incidents/view/ViewIncident.tsx index faa3a99146..7862f660c6 100644 --- a/src/incidents/view/ViewIncident.tsx +++ b/src/incidents/view/ViewIncident.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import { useSelector } from 'react-redux' import { useParams } from 'react-router-dom' @@ -13,7 +13,9 @@ const ViewIncident = () => { const { permissions } = useSelector((root: RootState) => root.user) const { t } = useTranslator() const updateTitle = useUpdateTitle() - updateTitle(t('incidents.reports.view')) + useEffect(() => { + updateTitle(t('incidents.reports.view')) + }) useAddBreadcrumbs([ { i18nKey: 'incidents.reports.view', diff --git a/src/labs/ViewLab.tsx b/src/labs/ViewLab.tsx index 01ab06b12e..547488457a 100644 --- a/src/labs/ViewLab.tsx +++ b/src/labs/ViewLab.tsx @@ -41,7 +41,9 @@ const ViewLab = () => { const [error, setError] = useState<LabError | undefined>(undefined) const updateTitle = useUpdateTitle() - updateTitle(getTitle(patient, labToView)) + useEffect(() => { + updateTitle(getTitle(patient, labToView)) + }) const breadcrumbs = [ { @@ -183,7 +185,7 @@ const ViewLab = () => { if (labToView.notes && labToView.notes[0] !== '') { return labToView.notes.map((note: string) => ( <Callout key={uuid()} data-test="note" color="info"> - <p>{note}</p> + <p data-testid="note">{note}</p> </Callout> )) } diff --git a/src/labs/hooks/useCompleteLab.ts b/src/labs/hooks/useCompleteLab.ts index 9d90406c30..bfb2d8c32f 100644 --- a/src/labs/hooks/useCompleteLab.ts +++ b/src/labs/hooks/useCompleteLab.ts @@ -1,4 +1,4 @@ -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import { useMutation, queryCache } from 'react-query' import LabRepository from '../../shared/db/LabRepository' diff --git a/src/labs/hooks/useRequestLab.ts b/src/labs/hooks/useRequestLab.ts index fcc8db33c7..ede8d40fe9 100644 --- a/src/labs/hooks/useRequestLab.ts +++ b/src/labs/hooks/useRequestLab.ts @@ -1,4 +1,4 @@ -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import { useMutation, queryCache } from 'react-query' import LabRepository from '../../shared/db/LabRepository' diff --git a/src/labs/utils/validate-lab.ts b/src/labs/utils/validate-lab.ts index 8c89bfd013..60aff42e93 100644 --- a/src/labs/utils/validate-lab.ts +++ b/src/labs/utils/validate-lab.ts @@ -1,4 +1,4 @@ -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import Lab from '../../shared/model/Lab' diff --git a/src/medications/ViewMedication.tsx b/src/medications/ViewMedication.tsx index d22f4daf69..bf8e3305a0 100644 --- a/src/medications/ViewMedication.tsx +++ b/src/medications/ViewMedication.tsx @@ -33,7 +33,9 @@ const ViewMedication = () => { const [isEditable, setIsEditable] = useState<boolean>(true) const updateTitle = useUpdateTitle() - updateTitle(getTitle(patient, medicationToView)) + useEffect(() => { + updateTitle(getTitle(patient, medicationToView)) + }) const breadcrumbs = [ { @@ -297,11 +299,12 @@ const ViewMedication = () => { <Row> <Column> <TextFieldWithLabelFormGroup + data-testid="notes" name="notes" label={t('medications.medication.notes')} value={medicationToView.notes} isEditable={isEditable} - onChange={onNotesChange} + onChange={(event) => onNotesChange(event)} /> </Column> </Row> diff --git a/src/medications/requests/NewMedicationRequest.tsx b/src/medications/requests/NewMedicationRequest.tsx index 44d3e07438..8171f8cf28 100644 --- a/src/medications/requests/NewMedicationRequest.tsx +++ b/src/medications/requests/NewMedicationRequest.tsx @@ -72,6 +72,7 @@ const NewMedicationRequest = () => { setNewMedicationRequest((previousNewMedicationRequest) => ({ ...previousNewMedicationRequest, patient: patient.id, + fullName: patient.fullName as string, })) } @@ -123,7 +124,7 @@ const NewMedicationRequest = () => { {status === 'error' && ( <Alert color="danger" title={t('states.error')} message={t(error.message || '')} /> )} - <form> + <form aria-label="Medication Request form"> <div className="form-group patient-typeahead"> <Label htmlFor="patientTypeahead" isRequired text={t('medications.medication.patient')} /> <Typeahead diff --git a/src/patients/ContactInfo.tsx b/src/patients/ContactInfo.tsx index 229b5f685b..11cf322ca9 100644 --- a/src/patients/ContactInfo.tsx +++ b/src/patients/ContactInfo.tsx @@ -128,7 +128,16 @@ const ContactInfo = (props: Props): ReactElement => { ) if (isEditable && data.length === 0) { - return <Spinner color="blue" loading size={20} type="SyncLoader" /> + return ( + <Spinner + aria-hidden="false" + aria-label="Loading" + color="blue" + loading + size={20} + type="SyncLoader" + /> + ) } return ( diff --git a/src/patients/allergies/ViewAllergy.tsx b/src/patients/allergies/ViewAllergy.tsx index 2ba0a66166..0cafce09e8 100644 --- a/src/patients/allergies/ViewAllergy.tsx +++ b/src/patients/allergies/ViewAllergy.tsx @@ -18,7 +18,7 @@ const ViewAllergy = () => { return ( <> <TextInputWithLabelFormGroup - name="name" + name="allergy" label={t('patient.allergies.allergyName')} isEditable={false} placeholder={t('patient.allergies.allergyName')} diff --git a/src/patients/care-goals/CareGoalForm.tsx b/src/patients/care-goals/CareGoalForm.tsx index 972c99d2ef..8741b8ffee 100644 --- a/src/patients/care-goals/CareGoalForm.tsx +++ b/src/patients/care-goals/CareGoalForm.tsx @@ -69,7 +69,7 @@ const CareGoalForm = (props: Props) => { } return ( - <form> + <form aria-label="care-goal-form"> {careGoalError?.message && <Alert color="danger" message={t(careGoalError.message)} />} <Row> <Column sm={12}> diff --git a/src/patients/care-plans/CarePlanForm.tsx b/src/patients/care-plans/CarePlanForm.tsx index 1e27aad0f8..d736c42a90 100644 --- a/src/patients/care-plans/CarePlanForm.tsx +++ b/src/patients/care-plans/CarePlanForm.tsx @@ -56,7 +56,7 @@ const CarePlanForm = (props: Props) => { const intentOptions: Option[] = Object.values(CarePlanIntent).map((v) => ({ label: v, value: v })) return ( - <form> + <form aria-label="form"> {carePlanError?.message && <Alert color="danger" message={t(carePlanError.message)} />} <Row> <Column sm={12}> diff --git a/src/patients/diagnoses/DiagnosisForm.tsx b/src/patients/diagnoses/DiagnosisForm.tsx index effc601cf2..e25c9a6e3b 100644 --- a/src/patients/diagnoses/DiagnosisForm.tsx +++ b/src/patients/diagnoses/DiagnosisForm.tsx @@ -60,7 +60,7 @@ const DiagnosisForm = (props: Props) => { })) return ( - <form> + <form aria-label="form"> {diagnosisError && ( <Alert color="danger" diff --git a/src/patients/edit/EditPatient.tsx b/src/patients/edit/EditPatient.tsx index 9c7296f791..4bf940ba91 100644 --- a/src/patients/edit/EditPatient.tsx +++ b/src/patients/edit/EditPatient.tsx @@ -26,11 +26,13 @@ const EditPatient = () => { const updateTitle = useUpdateTitle() - updateTitle( - `${t('patients.editPatient')}: ${getPatientFullName(givenPatient)} (${getPatientCode( - givenPatient, - )})`, - ) + useEffect(() => { + updateTitle( + `${t('patients.editPatient')}: ${getPatientFullName(givenPatient)} (${getPatientCode( + givenPatient, + )})`, + ) + }) const breadcrumbs = [ { i18nKey: 'patients.label', location: '/patients' }, diff --git a/src/patients/hooks/useAddAllergy.tsx b/src/patients/hooks/useAddAllergy.tsx index eb814a7435..4cf4824ed9 100644 --- a/src/patients/hooks/useAddAllergy.tsx +++ b/src/patients/hooks/useAddAllergy.tsx @@ -1,4 +1,4 @@ -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import { queryCache, useMutation } from 'react-query' import PatientRepository from '../../shared/db/PatientRepository' diff --git a/src/patients/hooks/useAddCareGoal.tsx b/src/patients/hooks/useAddCareGoal.tsx index f18bd2a7e9..190ce9ca0e 100644 --- a/src/patients/hooks/useAddCareGoal.tsx +++ b/src/patients/hooks/useAddCareGoal.tsx @@ -1,4 +1,4 @@ -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import { queryCache, useMutation } from 'react-query' import PatientRepository from '../../shared/db/PatientRepository' diff --git a/src/patients/hooks/useAddCarePlan.tsx b/src/patients/hooks/useAddCarePlan.tsx index 3a97e1dd3c..f6e5cb12b3 100644 --- a/src/patients/hooks/useAddCarePlan.tsx +++ b/src/patients/hooks/useAddCarePlan.tsx @@ -1,4 +1,4 @@ -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import { queryCache, useMutation } from 'react-query' import PatientRepository from '../../shared/db/PatientRepository' diff --git a/src/patients/hooks/useAddPatientDiagnosis.tsx b/src/patients/hooks/useAddPatientDiagnosis.tsx index 231b05cc96..df30fe36f7 100644 --- a/src/patients/hooks/useAddPatientDiagnosis.tsx +++ b/src/patients/hooks/useAddPatientDiagnosis.tsx @@ -1,4 +1,4 @@ -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import { queryCache, useMutation } from 'react-query' import PatientRepository from '../../shared/db/PatientRepository' diff --git a/src/patients/hooks/useAddPatientNote.tsx b/src/patients/hooks/useAddPatientNote.tsx index 01f301cc8b..a1a9af8867 100644 --- a/src/patients/hooks/useAddPatientNote.tsx +++ b/src/patients/hooks/useAddPatientNote.tsx @@ -1,4 +1,4 @@ -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import { queryCache, useMutation } from 'react-query' import PatientRepository from '../../shared/db/PatientRepository' diff --git a/src/patients/hooks/useAddPatientRelatedPerson.tsx b/src/patients/hooks/useAddPatientRelatedPerson.tsx index 577e0a196c..6e644b2db7 100644 --- a/src/patients/hooks/useAddPatientRelatedPerson.tsx +++ b/src/patients/hooks/useAddPatientRelatedPerson.tsx @@ -1,4 +1,4 @@ -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import { useMutation, queryCache } from 'react-query' import PatientRepository from '../../shared/db/PatientRepository' diff --git a/src/patients/hooks/useAddVisit.tsx b/src/patients/hooks/useAddVisit.tsx index 4fef90e21f..0d3334fde6 100644 --- a/src/patients/hooks/useAddVisit.tsx +++ b/src/patients/hooks/useAddVisit.tsx @@ -1,4 +1,4 @@ -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import { queryCache, useMutation } from 'react-query' import PatientRepository from '../../shared/db/PatientRepository' diff --git a/src/patients/notes/NotesList.tsx b/src/patients/notes/NotesList.tsx index cd62c0df3f..ce94592491 100644 --- a/src/patients/notes/NotesList.tsx +++ b/src/patients/notes/NotesList.tsx @@ -40,7 +40,9 @@ const NotesList = (props: Props) => { onClick={() => history.push(`/patients/${patientId}/notes/${note.id}`)} > <p className="ref__note-item-date">{new Date(note.date).toLocaleString()}</p> - <p className="ref__note-item-text">{note.text}</p> + <p role="listitem" className="ref__note-item-text"> + {note.text} + </p> </ListItem> ))} </List> diff --git a/src/patients/patient-slice.ts b/src/patients/patient-slice.ts index f083aaeaec..0524556172 100644 --- a/src/patients/patient-slice.ts +++ b/src/patients/patient-slice.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import PatientRepository from '../shared/db/PatientRepository' import Diagnosis from '../shared/model/Diagnosis' diff --git a/src/patients/view/ViewPatient.tsx b/src/patients/view/ViewPatient.tsx index 825b4bfb4c..00355a8af9 100644 --- a/src/patients/view/ViewPatient.tsx +++ b/src/patients/view/ViewPatient.tsx @@ -43,7 +43,9 @@ const ViewPatient = () => { const { data: patient, status } = usePatient(id) const updateTitle = useUpdateTitle() - updateTitle(t('patient.label')) + useEffect(() => { + updateTitle(t('patient.label')) + }, [updateTitle, t]) const breadcrumbs = [ { i18nKey: 'patients.label', location: '/patients' }, diff --git a/src/patients/visits/VisitForm.tsx b/src/patients/visits/VisitForm.tsx index ebc38b2424..24869b80f0 100644 --- a/src/patients/visits/VisitForm.tsx +++ b/src/patients/visits/VisitForm.tsx @@ -47,7 +47,7 @@ const VisitForm = (props: Props) => { Object.values(VisitStatus).map((v) => ({ label: v, value: v })) || [] return ( - <form> + <form aria-label="visit form"> {visitError?.message && <Alert color="danger" message={t(visitError.message)} />} <Row> <Column sm={6}> diff --git a/src/scheduling/appointments/edit/EditAppointment.tsx b/src/scheduling/appointments/edit/EditAppointment.tsx index 70d3bb7634..de93c79d94 100644 --- a/src/scheduling/appointments/edit/EditAppointment.tsx +++ b/src/scheduling/appointments/edit/EditAppointment.tsx @@ -1,5 +1,5 @@ import { Spinner, Button, Toast } from '@hospitalrun/components' -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import React, { useEffect, useState } from 'react' import { useHistory, useParams } from 'react-router-dom' @@ -18,7 +18,9 @@ const EditAppointment = () => { const { id } = useParams() const updateTitle = useUpdateTitle() - updateTitle(t('scheduling.appointments.editAppointment')) + useEffect(() => { + updateTitle(t('scheduling.appointments.editAppointment')) + }, [updateTitle, t]) const history = useHistory() const [newAppointment, setAppointment] = useState({} as Appointment) diff --git a/src/scheduling/appointments/new/NewAppointment.tsx b/src/scheduling/appointments/new/NewAppointment.tsx index d4475a41bf..82de8823a8 100644 --- a/src/scheduling/appointments/new/NewAppointment.tsx +++ b/src/scheduling/appointments/new/NewAppointment.tsx @@ -1,7 +1,7 @@ import { Button, Spinner, Toast } from '@hospitalrun/components' import addMinutes from 'date-fns/addMinutes' import roundToNearestMinutes from 'date-fns/roundToNearestMinutes' -import { isEmpty } from 'lodash' +import isEmpty from 'lodash/isEmpty' import React, { useEffect, useState } from 'react' import { useHistory, useLocation } from 'react-router-dom' @@ -102,7 +102,7 @@ const NewAppointment = () => { return ( <div> - <form> + <form aria-label="new appointment form"> <AppointmentDetailForm appointment={newAppointment as Appointment} patient={patient as Patient} diff --git a/src/scheduling/appointments/view/ViewAppointment.tsx b/src/scheduling/appointments/view/ViewAppointment.tsx index 094ce7bf06..df84cb2638 100644 --- a/src/scheduling/appointments/view/ViewAppointment.tsx +++ b/src/scheduling/appointments/view/ViewAppointment.tsx @@ -18,7 +18,13 @@ import { getAppointmentLabel } from '../util/scheduling-appointment.util' const ViewAppointment = () => { const { t } = useTranslator() const updateTitle = useUpdateTitle() - updateTitle(t('scheduling.appointments.viewAppointment')) + + useEffect(() => { + if (updateTitle) { + updateTitle(t('scheduling.appointments.viewAppointment')) + } + }, [updateTitle, t]) + const { id } = useParams() const history = useHistory() const [deleteMutate] = useDeleteAppointment() diff --git a/src/setupTests.js b/src/setupTests.js index 9e28209178..fb33a0a634 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -1,10 +1,26 @@ /* eslint-disable import/no-extraneous-dependencies */ -import Enzyme from 'enzyme' -import Adapter from 'enzyme-adapter-react-16' +import '@testing-library/jest-dom' +import { configure, act } from '@testing-library/react' import 'jest-canvas-mock' +import { queryCache } from 'react-query' import './__mocks__/i18next' import './__mocks__/matchMediaMock' import './__mocks__/react-i18next' -Enzyme.configure({ adapter: new Adapter() }) +// speeds up *ByRole queries a bit, but will need to specify { hidden: false } in some cases +configure({ defaultHidden: true }) + +jest.setTimeout(10000) + +afterEach(() => { + queryCache.clear() +}) + +afterEach(() => { + // eslint-disable-next-line no-underscore-dangle + if (setTimeout._isMockFunction) { + act(() => jest.runOnlyPendingTimers()) + jest.useRealTimers() + } +}) diff --git a/src/shared/components/input/DatePickerWithLabelFormGroup.tsx b/src/shared/components/input/DatePickerWithLabelFormGroup.tsx index a2b15d6b69..b75d0040cc 100644 --- a/src/shared/components/input/DatePickerWithLabelFormGroup.tsx +++ b/src/shared/components/input/DatePickerWithLabelFormGroup.tsx @@ -27,7 +27,7 @@ const DatePickerWithLabelFormGroup = (props: Props) => { } = props const id = `${name}DatePicker` return ( - <div className="form-group"> + <div className="form-group" data-testid={id}> <Label text={label} htmlFor={id} isRequired={isRequired} /> <DateTimePicker dateFormat="MM/dd/yyyy" diff --git a/src/shared/components/input/DateTimePickerWithLabelFormGroup.tsx b/src/shared/components/input/DateTimePickerWithLabelFormGroup.tsx index d511144408..d139fdc030 100644 --- a/src/shared/components/input/DateTimePickerWithLabelFormGroup.tsx +++ b/src/shared/components/input/DateTimePickerWithLabelFormGroup.tsx @@ -16,7 +16,7 @@ const DateTimePickerWithLabelFormGroup = (props: Props) => { const { onChange, label, name, isEditable, value, isRequired, feedback, isInvalid } = props const id = `${name}DateTimePicker` return ( - <div className="form-group"> + <div className="form-group" data-testid={id}> <Label text={label} isRequired={isRequired} htmlFor={id} /> <DateTimePicker dateFormat="MM/dd/yyyy h:mm aa" diff --git a/src/shared/components/input/LanguageSelector.tsx b/src/shared/components/input/LanguageSelector.tsx index c3b7d8ba18..46d5660717 100644 --- a/src/shared/components/input/LanguageSelector.tsx +++ b/src/shared/components/input/LanguageSelector.tsx @@ -1,4 +1,4 @@ -import { sortBy } from 'lodash' +import sortBy from 'lodash/sortBy' import React, { useState } from 'react' import i18n, { resources } from '../../config/i18n' diff --git a/src/shared/components/input/SelectWithLabelFormGroup.tsx b/src/shared/components/input/SelectWithLabelFormGroup.tsx index e48f249bcd..fb5fd11402 100644 --- a/src/shared/components/input/SelectWithLabelFormGroup.tsx +++ b/src/shared/components/input/SelectWithLabelFormGroup.tsx @@ -35,7 +35,7 @@ const SelectWithLabelFormGroup = (props: Props) => { } = props const id = `${name}Select` return ( - <div className="form-group"> + <div className="form-group" data-testid={id}> {label && <Label text={label} htmlFor={id} isRequired={isRequired} />} <Select id={id} diff --git a/src/shared/components/input/TextFieldWithLabelFormGroup.tsx b/src/shared/components/input/TextFieldWithLabelFormGroup.tsx index 857034706e..3af0b3195b 100644 --- a/src/shared/components/input/TextFieldWithLabelFormGroup.tsx +++ b/src/shared/components/input/TextFieldWithLabelFormGroup.tsx @@ -14,11 +14,12 @@ interface Props { const TextFieldWithLabelFormGroup = (props: Props) => { const { value, label, name, isEditable, isInvalid, isRequired, feedback, onChange } = props - const id = `${name}TextField` + const inputId = `${name}TextField` return ( <div className="form-group"> - {label && <Label text={label} htmlFor={id} isRequired={isRequired} />} + {label && <Label text={label} htmlFor={inputId} isRequired={isRequired} />} <TextField + id={inputId} rows={4} value={value} disabled={!isEditable} diff --git a/src/shared/config/pouchdb.ts b/src/shared/config/pouchdb.ts index e7eb77c722..c3fc9264e4 100644 --- a/src/shared/config/pouchdb.ts +++ b/src/shared/config/pouchdb.ts @@ -21,7 +21,12 @@ if (process.env.NODE_ENV === 'test') { serverDb = new PouchDB('hospitalrun', { skip_setup: true, adapter: 'memory' }) localDb = new PouchDB('local_hospitalrun', { skip_setup: true, adapter: 'memory' }) } else { + serverDb = new PouchDB(`${process.env.REACT_APP_HOSPITALRUN_API}/hospitalrun`, { + skip_setup: true, + }) + localDb = new PouchDB('local_hospitalrun', { skip_setup: true }) + localDb.sync(serverDb, { live: true, retry: true }) } export const schema = [ diff --git a/tsconfig.json b/tsconfig.json index 1d9146dac9..602b535185 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,8 @@ { "include": [ "src", - "types" + "types", + "./setupTests.js" ], "exclude": [ "node_modules",