diff --git a/CHANGELOG.md b/CHANGELOG.md index 9780d62c86..d22279cdb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,102 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [2.0.0-alpha.5](https://github.com/HospitalRun/hospitalrun-frontend/compare/v2.0.0-alpha.4...v2.0.0-alpha.5) (2020-09-28) + + +### Features + +* Added smooth scroll to index.css ([#2408](https://github.com/HospitalRun/hospitalrun-frontend/issues/2408)) ([a7f771f](https://github.com/HospitalRun/hospitalrun-frontend/commit/a7f771f693f124ecadc5f7f5eaf257ccf367563c)) +* **added tests:** incidentsDownloadCSV ([dc3c93e](https://github.com/HospitalRun/hospitalrun-frontend/commit/dc3c93eeecac464ba0e3fdc9111d1ed32ac566b2)), closes [#2292](https://github.com/HospitalRun/hospitalrun-frontend/issues/2292) +* **caregoal:** add care goals to patient ([#2360](https://github.com/HospitalRun/hospitalrun-frontend/issues/2360)) ([8eb8f6f](https://github.com/HospitalRun/hospitalrun-frontend/commit/8eb8f6f0910d8259f15c1878249e630ff3a8be18)) +* **couchdb:** added Windows alternatives to the couchdb helper scripts ([f0a1cb8](https://github.com/HospitalRun/hospitalrun-frontend/commit/f0a1cb8a0749ba9255ee188c358bd097be804fff)) +* **diagnosis:** adds new diagnosis fields ([#2276](https://github.com/HospitalRun/hospitalrun-frontend/issues/2276)) ([2532435](https://github.com/HospitalRun/hospitalrun-frontend/commit/2532435feea8066588694a965aa15fc942b2ef19)) +* **diagnosis:** link diagnosis with visit ([#2321](https://github.com/HospitalRun/hospitalrun-frontend/issues/2321)) ([e558c27](https://github.com/HospitalRun/hospitalrun-frontend/commit/e558c2778effa743411f647b3ac5ab83074b9ad1)) +* **download csv of incident table:** uses json2csv. filters data ([6575a74](https://github.com/HospitalRun/hospitalrun-frontend/commit/6575a7403bcc920b56ae6903232cf3ee8d09952b)), closes [#2292](https://github.com/HospitalRun/hospitalrun-frontend/issues/2292) +* **imaging:** link image with visit ([#2309](https://github.com/HospitalRun/hospitalrun-frontend/issues/2309)) ([c0ee742](https://github.com/HospitalRun/hospitalrun-frontend/commit/c0ee7428bf582af8dfd763a9f7b6c264c9da9bcf)) +* **incidentscsv:** fixes ([a5f60b2](https://github.com/HospitalRun/hospitalrun-frontend/commit/a5f60b228ceccc7030b36c4498ec1c227897092a)), closes [#2292](https://github.com/HospitalRun/hospitalrun-frontend/issues/2292) +* **incidentscsv:** made functions more abstract ([1fb36c9](https://github.com/HospitalRun/hospitalrun-frontend/commit/1fb36c97f16760ba8867e61d3413f34b3611a7be)), closes [#2292](https://github.com/HospitalRun/hospitalrun-frontend/issues/2292) +* **incidentscsvtest:** newline bug in tests ([d577970](https://github.com/HospitalRun/hospitalrun-frontend/commit/d577970aa13c5afb440ec3ae885efceee9c58739)), closes [#2292](https://github.com/HospitalRun/hospitalrun-frontend/issues/2292) +* **labs:** support list of notes for lab requests ([#2325](https://github.com/HospitalRun/hospitalrun-frontend/issues/2325)) ([019a5f8](https://github.com/HospitalRun/hospitalrun-frontend/commit/019a5f8491c07f27218476e70652010a6f7e27ae)) +* add new useTranslator ([#2197](https://github.com/HospitalRun/hospitalrun-frontend/issues/2197)) ([d756cb6](https://github.com/HospitalRun/hospitalrun-frontend/commit/d756cb68c34eb6aaff020edf69430d4fd3e8ad59)) +* added an entry in pageMap for incident infographic ([e23c913](https://github.com/HospitalRun/hospitalrun-frontend/commit/e23c913ab0a6f68aaa86e74142a25c5448878694)) +* Added blood type info to patient ([#2190](https://github.com/HospitalRun/hospitalrun-frontend/issues/2190)) ([01e8343](https://github.com/HospitalRun/hospitalrun-frontend/commit/01e834301e318553c3c49768c5af5f5c72a40d0b)) +* added label for visualize and fixed title ([e06fbc3](https://github.com/HospitalRun/hospitalrun-frontend/commit/e06fbc3e1746ea36d6de1f3d4837cc92f2c40bdb)) +* added ListItem component to incidents in Sidebar for visualize tab ([d73a3e7](https://github.com/HospitalRun/hospitalrun-frontend/commit/d73a3e7109a5923eccebc79f768fc09958e2746c)) +* added permission for viewing widgets to Permissions model ([68d6297](https://github.com/HospitalRun/hospitalrun-frontend/commit/68d6297735b13783212a1c3787d8b3a00b8aa446)) +* added route for visualize component and performed sanity check ([9dc7855](https://github.com/HospitalRun/hospitalrun-frontend/commit/9dc78558affd1419801a91e060d0b412b4c09072)) +* added test cases for visualize route ([c6f17b3](https://github.com/HospitalRun/hospitalrun-frontend/commit/c6f17b3b1b98b80aae7eb805fb2f2571c2b850b9)) +* implemented use of hook to retrieve reported incidents ([8d04353](https://github.com/HospitalRun/hospitalrun-frontend/commit/8d04353bfe0ef3a89d6d93e8dfedcbd928376a8e)) +* imported LineGraph component and rendered it with dumby data ([7cc66eb](https://github.com/HospitalRun/hospitalrun-frontend/commit/7cc66ebe530e2449e6ae6e733a600d5b6881d2a7)) +* incidents per month are now managed in state ([f2bc985](https://github.com/HospitalRun/hospitalrun-frontend/commit/f2bc985fd90878d9b084598693afd817e77f855c)) +* linegraph component now renders dynamic data stored in state ([56ae1b8](https://github.com/HospitalRun/hospitalrun-frontend/commit/56ae1b844fb881bf1c29cc826cb2a80099cde545)) +* partial implementation of useEffect hook ([29a603e](https://github.com/HospitalRun/hospitalrun-frontend/commit/29a603e0db5df1a94aece929a2314cc812bf6dca)) +* use of Array.fill in monthlyIncidents useState ([bb4b9fb](https://github.com/HospitalRun/hospitalrun-frontend/commit/bb4b9fb682b0d1bac90094acbf32795708bcf97e)) +* **allergies:** ability to click on allergy and view an allergy ([#2224](https://github.com/HospitalRun/hospitalrun-frontend/issues/2224)) ([071508c](https://github.com/HospitalRun/hospitalrun-frontend/commit/071508c358befcf16973af41fcfc4bceba3a8feb)) +* **appointments:** provide more information on listed appointments ([a7b8441](https://github.com/HospitalRun/hospitalrun-frontend/commit/a7b844183b1db9d9e6daf1e144170dc5d3dd6921)) +* **incidents:** add ability to resolve incidents ([#2222](https://github.com/HospitalRun/hospitalrun-frontend/issues/2222)) ([5e95a9c](https://github.com/HospitalRun/hospitalrun-frontend/commit/5e95a9ccf15d094174be817ce2d18f70cb13a485)) +* **incidents, scheduling, other:** add russian locale ([7fa152b](https://github.com/HospitalRun/hospitalrun-frontend/commit/7fa152bd2367884941e615f02073c0e1f66a4f56)) +* **labs:** labs tab should use Table component ([88a7c08](https://github.com/HospitalRun/hospitalrun-frontend/commit/88a7c0863c0a7752b99683e864d461209fe16b5d)) +* **navbar:** add icons to 'quick add' icon in navbar ([#2220](https://github.com/HospitalRun/hospitalrun-frontend/issues/2220)) ([7e0aba7](https://github.com/HospitalRun/hospitalrun-frontend/commit/7e0aba7d1a3fe14db48f55e6250cfbca75ed7ad8)) +* **navbar:** display currently logged in user name ([5a4bfa5](https://github.com/HospitalRun/hospitalrun-frontend/commit/5a4bfa54af9d30d9b6b26416f06a5e3325699551)) +* **Navbar:** remove search from navbar ([#2237](https://github.com/HospitalRun/hospitalrun-frontend/issues/2237)) ([0c85e24](https://github.com/HospitalRun/hospitalrun-frontend/commit/0c85e2450acad2dbf7addb0b657a037e2cf397b7)) +* **new:** warn about potential duplicate patient ([#2187](https://github.com/HospitalRun/hospitalrun-frontend/issues/2187)) ([#2240](https://github.com/HospitalRun/hospitalrun-frontend/issues/2240)) ([f49831c](https://github.com/HospitalRun/hospitalrun-frontend/commit/f49831cd11b67a8f28071a05dbc832891c5b4bf5)) +* **new scripts and contributing.md:** added scripts to init and cleanup couchdb ([a331d62](https://github.com/HospitalRun/hospitalrun-frontend/commit/a331d621b274e795ae26f5471a669b076c7a8b06)) +* triggers a re-render on useEffect when data loads ([699be8c](https://github.com/HospitalRun/hospitalrun-frontend/commit/699be8c436122de63972db3bd4d9e90ebbfc06aa)) +* updated useEffect dependency and implemented map ([2b72f7e](https://github.com/HospitalRun/hospitalrun-frontend/commit/2b72f7ee5775256ebad5d1c72c72125b60f68498)) +* **imaging:** create basic imaging module ([#2250](https://github.com/HospitalRun/hospitalrun-frontend/issues/2250)) ([f256129](https://github.com/HospitalRun/hospitalrun-frontend/commit/f25612928d4cf09d054c7605373c8be13e4f4fce)) +* **loading:** add shared loading component ([24c45bc](https://github.com/HospitalRun/hospitalrun-frontend/commit/24c45bc4919e4c0c088f38f744a0bd03014b7d3a)) +* **medications:** implement basic medication module ([#2249](https://github.com/HospitalRun/hospitalrun-frontend/issues/2249)) ([ab1bd5c](https://github.com/HospitalRun/hospitalrun-frontend/commit/ab1bd5c4238fc118518f573af8bb418fce2a95af)) +* **patient:** put "Unknow" next to date of brith field ([#2217](https://github.com/HospitalRun/hospitalrun-frontend/issues/2217)) ([ce16424](https://github.com/HospitalRun/hospitalrun-frontend/commit/ce1642406e0015ce7c51a182d9db6011ad4aefc3)) +* **patient, actions:** add russian locale ([a03a63e](https://github.com/HospitalRun/hospitalrun-frontend/commit/a03a63eb30db7b3387a308934d14dfaab3b72fd2)) +* **patients:** add no patients exist ([#2235](https://github.com/HospitalRun/hospitalrun-frontend/issues/2235)) ([886163a](https://github.com/HospitalRun/hospitalrun-frontend/commit/886163aafe30de2c66d41e0491600f00749ca682)) +* **patients:** add visit functionality ([#2251](https://github.com/HospitalRun/hospitalrun-frontend/issues/2251)) ([f11e702](https://github.com/HospitalRun/hospitalrun-frontend/commit/f11e702d5440ef50d7e2dc635cf71a06cfc12d11)) +* **viewlab.tsx:** added success toast for updated/completed labs ([5d08ccf](https://github.com/HospitalRun/hospitalrun-frontend/commit/5d08ccf84b48e503b8a907c829c80b2ad72349f3)) + + +### Bug Fixes + +* add successfullycompleted resolves [#2295](https://github.com/HospitalRun/hospitalrun-frontend/issues/2295) ([2fc4ca4](https://github.com/HospitalRun/hospitalrun-frontend/commit/2fc4ca4bd8f285f8dd60c1459040f051132e4ea8)) +* fix typo successfully alphabet text ([40ab00f](https://github.com/HospitalRun/hospitalrun-frontend/commit/40ab00feb2b8584a090e607d98793de56a31fa47)) +* update text for oncomplete lab ([8c60adb](https://github.com/HospitalRun/hospitalrun-frontend/commit/8c60adb08e5b3ff4f1fc7cd38ff73a74fff76417)) +* **added translation keys #2258:** added missing translation keys PR [#2258](https://github.com/HospitalRun/hospitalrun-frontend/issues/2258) ([6339e78](https://github.com/HospitalRun/hospitalrun-frontend/commit/6339e78285b01fdf7c8481aaf98b55471e5111b2)) +* **allergies:** fixes allergies tab not refreshing data after allergy has been added ([0e29639](https://github.com/HospitalRun/hospitalrun-frontend/commit/0e296398ec12ca0c74621a5f5b476cb5465e6583)) +* **care plan:** fix view button label in list ([#2376](https://github.com/HospitalRun/hospitalrun-frontend/issues/2376)) ([6156f38](https://github.com/HospitalRun/hospitalrun-frontend/commit/6156f38f8ea3e8bbf8bb9496f210d8815ee4a78e)) +* **care plans:** fix undefined care plans behavior ([e43c9c7](https://github.com/HospitalRun/hospitalrun-frontend/commit/e43c9c7aa13cf1a76986945323379fa909e55e80)) +* **couchdb-init.sh and couchdb-cleanup.sh:** removed files which where commited by accident ([42c8cf7](https://github.com/HospitalRun/hospitalrun-frontend/commit/42c8cf76ee15d7e1c93200abfa8a585146d67a0e)) +* **eslint rule and subsequent changes to the codebase:** eslint rule no-console added ([6d9ca06](https://github.com/HospitalRun/hospitalrun-frontend/commit/6d9ca06443de91b5137a2495941b83ecefbd0979)), closes [#2307](https://github.com/HospitalRun/hospitalrun-frontend/issues/2307) +* **incidents:** fix find all incidents ([aabd33e](https://github.com/HospitalRun/hospitalrun-frontend/commit/aabd33e2651246ac8eaadc1c6b260b1f3b6e7591)) +* **incidents:** fix search incidents query not working ([0818c11](https://github.com/HospitalRun/hospitalrun-frontend/commit/0818c115d08c8edb58f0b10b0b4df7d4a1ab2626)) +* **incidentscvstests:** fixed timezone test bug ([02294e6](https://github.com/HospitalRun/hospitalrun-frontend/commit/02294e6b7c0c21173dede054fb23fdf4d47a3c96)), closes [#2292](https://github.com/HospitalRun/hospitalrun-frontend/issues/2292) +* **issue-2306:** removed additional Report Incident nav link from quick create menu ([c971fb2](https://github.com/HospitalRun/hospitalrun-frontend/commit/c971fb2d1357d82f9e9cb5a0b2abe22dd945bf48)) +* **labs:** fix successfully updated lab message ([7f65174](https://github.com/HospitalRun/hospitalrun-frontend/commit/7f651742be76050b46a3360864aec4f516a44694)) +* **login:** fixes broken login ([d72c4e9](https://github.com/HospitalRun/hospitalrun-frontend/commit/d72c4e99cf032eb37e354cdb15abef0a694c38a9)) +* **login:** improved login validation ([028daff](https://github.com/HospitalRun/hospitalrun-frontend/commit/028daff0648d7758984b04960f24e8bf20002ea4)) +* **navigation:** align quick menu with sidebar and mobile version ([0187c34](https://github.com/HospitalRun/hospitalrun-frontend/commit/0187c3406306540b5da3f574a6abe984dcd84754)) +* **patient:** add DOB to add related person search ([c1ffd82](https://github.com/HospitalRun/hospitalrun-frontend/commit/c1ffd82f64eb44400d82815102b2efdbca19b433)) +* **patient:** successfully updated message should show actual text ([7ca79bc](https://github.com/HospitalRun/hospitalrun-frontend/commit/7ca79bcda2d40ecd95fc8ed6ceffefac85249725)) +* **patients:** fix infinite loop in patients search ([08fff6f](https://github.com/HospitalRun/hospitalrun-frontend/commit/08fff6f9e345368c166adb578b19e2a1b801f3b7)) +* **patients:** fixes related person search crashing if no DoB ([babc7e3](https://github.com/HospitalRun/hospitalrun-frontend/commit/babc7e3e817ecb20b5a2cf7b3afde99182740b4b)) +* **shared:** couchdb auth popup local.ini httpd section update ([937f78e](https://github.com/HospitalRun/hospitalrun-frontend/commit/937f78ef1d1a4e0ffb79582f27b3e60603f76249)), closes [#2256](https://github.com/HospitalRun/hospitalrun-frontend/issues/2256) +* **shared:** couchdb auth popup nginx.conf update ([eca339a](https://github.com/HospitalRun/hospitalrun-frontend/commit/eca339a72d089cafbc348ec431c2d36a327299c5)) +* **shared:** couchdb auth popup pr lgtm merge issue ([6cf6739](https://github.com/HospitalRun/hospitalrun-frontend/commit/6cf6739e9c8fd44db3f293cc1b3e0145c68ef30a)) +* **shared:** pouchdb auth - add skip_setup flag ([a8d34e1](https://github.com/HospitalRun/hospitalrun-frontend/commit/a8d34e14ab89447bd2753248038907c89801f327)), closes [#2256](https://github.com/HospitalRun/hospitalrun-frontend/issues/2256) +* **shared:** update couchdb auth local.ini httpd section ([d939bb2](https://github.com/HospitalRun/hospitalrun-frontend/commit/d939bb2a69adb58262696f62dcf2c0ee453c6815)) +* **sidebar:** test improvements ([7b75fe0](https://github.com/HospitalRun/hospitalrun-frontend/commit/7b75fe01395ec44e3e95ea5866e78178313272e8)) +* react-hooks/exhaustive-deps warnings do not appear when running ([6434fd0](https://github.com/HospitalRun/hospitalrun-frontend/commit/6434fd07784b4cddc886c5759b34dbbdab273006)) +* **typo:** fixed typo and used locale ([fd2ec69](https://github.com/HospitalRun/hospitalrun-frontend/commit/fd2ec6961f51dc7259b2c8872e60461a4eb1ae38)), closes [#2292](https://github.com/HospitalRun/hospitalrun-frontend/issues/2292) +* correct incident icon ([#2252](https://github.com/HospitalRun/hospitalrun-frontend/issues/2252)) ([c156f25](https://github.com/HospitalRun/hospitalrun-frontend/commit/c156f25aa975c9ade32e814777f7f055612c6b3a)) +* fix find all + sort request behavior ([922d767](https://github.com/HospitalRun/hospitalrun-frontend/commit/922d7675bc9c7f51cacd100be0becbd5ee6087e1)) +* remove console.log from tests output ([fe3a71a](https://github.com/HospitalRun/hospitalrun-frontend/commit/fe3a71a0d1169071117adaf529aa5ff6a0a891ef)) +* search should only pull back relevant document types ([ab6f3cc](https://github.com/HospitalRun/hospitalrun-frontend/commit/ab6f3cc9590e9399220890a939e6e569032d1185)) +* **use effect line to newlabrequest.tsx:** added use effect ([9b34904](https://github.com/HospitalRun/hospitalrun-frontend/commit/9b34904252c8ccbc479abbdecd6f3f0ba40bcbd3)) +* prevent app route always changes to root after refresh ([24b73bc](https://github.com/HospitalRun/hospitalrun-frontend/commit/24b73bcad48ea1eed7d9ac7b30d81491f6d8a1d6)) +* remove duplicate prop ([faea7ba](https://github.com/HospitalRun/hospitalrun-frontend/commit/faea7bae3e00d3c471802a875bcce7e719f9b277)) +* reset lab state when navigation from lab form ([#2268](https://github.com/HospitalRun/hospitalrun-frontend/issues/2268)) ([aa8e2ed](https://github.com/HospitalRun/hospitalrun-frontend/commit/aa8e2ed5f1c49d37fff0e6e8f928814e23cd359d)) +* undefined care plan behavior ([279d649](https://github.com/HospitalRun/hospitalrun-frontend/commit/279d64914ca0fab541ade4f64d9569f27000ddae)) +* update hardcoded text with i18n locales ([#2203](https://github.com/HospitalRun/hospitalrun-frontend/issues/2203)) ([573df57](https://github.com/HospitalRun/hospitalrun-frontend/commit/573df570bc0c8effd45fe232002a47367dcb8076)) +* weird spacing issue in quick add icon ([14ef415](https://github.com/HospitalRun/hospitalrun-frontend/commit/14ef415334354bd61475ead9469b8579dde8bf6c)) + ## [2.0.0-alpha.4](https://github.com/HospitalRun/hospitalrun-frontend/compare/v2.0.0-alpha.3...v2.0.0-alpha.4) (2020-06-26) diff --git a/package.json b/package.json index 83ae8ec69c..ad70a1361e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hospitalrun/frontend", - "version": "2.0.0-alpha.4", + "version": "2.0.0-alpha.5", "description": "React frontend for HospitalRun", "private": false, "license": "MIT", @@ -91,7 +91,7 @@ "enzyme-adapter-react-16": "~1.15.2", "eslint": "~6.8.0", "eslint-config-airbnb": "~18.2.0", - "eslint-config-prettier": "~6.11.0", + "eslint-config-prettier": "~6.12.0", "eslint-import-resolver-typescript": "~2.3.0", "eslint-plugin-import": "~2.22.0", "eslint-plugin-jest": "~24.0.0", diff --git a/src/__tests__/labs/Labs.test.tsx b/src/__tests__/labs/Labs.test.tsx index e8254db294..19077a05a5 100644 --- a/src/__tests__/labs/Labs.test.tsx +++ b/src/__tests__/labs/Labs.test.tsx @@ -1,3 +1,4 @@ +import { act } from '@testing-library/react' import { mount, ReactWrapper } from 'enzyme' import React from 'react' import { Provider } from 'react-redux' @@ -28,46 +29,39 @@ describe('Labs', () => { jest .spyOn(PatientRepository, 'find') .mockResolvedValue({ id: '12345', fullName: 'test test' } as Patient) - const setup = (route: string, permissions: Permissions[] = []) => { + + const setup = async (initialEntry: string, permissions: Permissions[] = []) => { const store = mockStore({ user: { permissions }, - breadcrumbs: { breadcrumbs: [] }, - components: { sidebarCollapsed: false }, - lab: { - lab: ({ - id: '1234', - patientId: 'patientId', - requestedOn: new Date().toISOString(), - } as unknown) as Lab, - patient: { id: 'patientId', fullName: 'some name' }, - error: {}, - }, } as any) - const wrapper = mount( - - - - - - - , - ) + let wrapper: any + await act(async () => { + wrapper = await mount( + + + + + + + , + ) + }) + wrapper.update() return { wrapper: wrapper as ReactWrapper } } describe('routing', () => { describe('/labs/new', () => { - it('should render the new lab request screen when /labs/new is accessed', () => { - const permissions: Permissions[] = [Permissions.RequestLab] - const { wrapper } = setup('/labs/new', permissions) + 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) }) - it('should not navigate to /labs/new if the user does not have RequestLab permissions', () => { - const { wrapper } = setup('/labs/new') + 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) }) @@ -75,15 +69,14 @@ describe('Labs', () => { describe('/labs/:id', () => { it('should render the view lab screen when /labs/:id is accessed', async () => { - const permissions: Permissions[] = [Permissions.ViewLab] - const { wrapper } = setup('/labs/1234', permissions) + const { wrapper } = await setup('/labs/1234', [Permissions.ViewLab]) expect(wrapper.find(ViewLab)).toHaveLength(1) }) }) it('should not navigate to /labs/:id if the user does not have ViewLab permissions', async () => { - const { wrapper } = setup('/labs/1234') + const { wrapper } = await setup('/labs/1234') expect(wrapper.find(ViewLab)).toHaveLength(0) }) diff --git a/src/__tests__/labs/ViewLab.test.tsx b/src/__tests__/labs/ViewLab.test.tsx index baa9019ac5..42b9b1be3b 100644 --- a/src/__tests__/labs/ViewLab.test.tsx +++ b/src/__tests__/labs/ViewLab.test.tsx @@ -9,6 +9,8 @@ import { Router, Route } from 'react-router-dom' import createMockStore from 'redux-mock-store' 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 * as titleUtil from '../../page-header/title/TitleContext' @@ -38,15 +40,16 @@ describe('View Lab', () => { let setButtonToolBarSpy: any let labRepositorySaveSpy: any const expectedDate = new Date() + 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) - jest.spyOn(LabRepository, 'find').mockResolvedValue(lab) 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}`) @@ -80,7 +83,7 @@ describe('View Lab', () => { }) wrapper.find(ViewLab).props().updateTitle = jest.fn() wrapper.update() - return wrapper + return { wrapper: wrapper as ReactWrapper } } describe('title', () => { @@ -94,8 +97,9 @@ 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 { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const forPatientDiv = wrapper.find('.for-patient') + expect(forPatientDiv.find('h4').text().trim()).toEqual('labs.lab.for') expect(forPatientDiv.find('h5').text().trim()).toEqual(mockPatient.fullName) @@ -103,7 +107,7 @@ describe('View Lab', () => { 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 { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const labTypeDiv = wrapper.find('.lab-type') expect(labTypeDiv.find('h4').text().trim()).toEqual('labs.lab.type') @@ -112,7 +116,7 @@ describe('View Lab', () => { 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 { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const requestedOnDiv = wrapper.find('.requested-on') expect(requestedOnDiv.find('h4').text().trim()).toEqual('labs.lab.requestedOn') @@ -123,7 +127,7 @@ describe('View Lab', () => { 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 { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const completedOnDiv = wrapper.find('.completed-on') expect(completedOnDiv).toHaveLength(0) @@ -131,7 +135,7 @@ describe('View Lab', () => { 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 { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const completedOnDiv = wrapper.find('.canceled-on') expect(completedOnDiv).toHaveLength(0) @@ -142,7 +146,7 @@ describe('View Lab', () => { ...mockLab, result: 'expected results', } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) @@ -154,9 +158,9 @@ describe('View Lab', () => { 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 { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) - const notes = wrapper.find('[data-test="note"]') + 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, @@ -164,13 +168,22 @@ describe('View Lab', () => { ) expect(pastNotesIndex).not.toBe(-1) - expect(notes.length).toBe(1) + expect(notes).toHaveLength(1) + }) + + 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') + + expect(notes).toHaveLength(0) }) 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 { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const notesTextField = wrapper.find(TextFieldWithLabelFormGroup).at(1) @@ -180,8 +193,17 @@ describe('View Lab', () => { it('should display errors', async () => { const expectedLab = { ...mockLab, status: 'requested' } as Lab - const expectedError = { message: 'some message', result: 'some result feedback' } - const wrapper = await setup(expectedLab, [Permissions.ViewLab], expectedError) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab, Permissions.CompleteLab]) + + const expectedError = { message: 'some message', result: 'some result feedback' } as LabError + 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) @@ -195,7 +217,7 @@ describe('View Lab', () => { 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 { 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') @@ -205,7 +227,7 @@ describe('View Lab', () => { }) 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, [ + const { wrapper } = await setup(mockLab, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, @@ -223,7 +245,7 @@ describe('View Lab', () => { 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 { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const labStatusDiv = wrapper.find('.lab-status') const badge = labStatusDiv.find(Badge) @@ -239,7 +261,7 @@ describe('View Lab', () => { status: 'canceled', canceledOn: '2020-03-30T04:45:20.102Z', } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const canceledOnDiv = wrapper.find('.canceled-on') expect(canceledOnDiv.find('h4').text().trim()).toEqual('labs.lab.canceledOn') @@ -252,7 +274,7 @@ describe('View Lab', () => { 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, [ + const { wrapper } = await setup(expectedLab, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, @@ -264,7 +286,7 @@ describe('View Lab', () => { 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]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const updateButton = wrapper.find(Button) expect(updateButton).toHaveLength(0) @@ -273,7 +295,7 @@ describe('View Lab', () => { 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 { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const textsField = wrapper.find(TextFieldWithLabelFormGroup) const notesTextField = wrapper.find('notesTextField') @@ -287,7 +309,7 @@ describe('View Lab', () => { 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 { 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') @@ -302,7 +324,7 @@ describe('View Lab', () => { status: 'completed', completedOn: '2020-03-30T04:44:20.102Z', } as Lab - const wrapper = await setup(expectedLab, [Permissions.ViewLab]) + const { wrapper } = await setup(expectedLab, [Permissions.ViewLab]) const completedOnDiv = wrapper.find('.completed-on') expect(completedOnDiv.find('h4').text().trim()).toEqual('labs.lab.completedOn') @@ -315,7 +337,7 @@ describe('View Lab', () => { 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, [ + const { wrapper } = await setup(expectedLab, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, @@ -328,7 +350,7 @@ describe('View Lab', () => { 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, [ + const { wrapper } = await setup(expectedLab, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, @@ -345,26 +367,26 @@ describe('View Lab', () => { describe('on update', () => { it('should update the lab with the new information', async () => { - const wrapper = await setup(mockLab, [Permissions.ViewLab]) + const { wrapper } = await setup(mockLab, [Permissions.ViewLab]) const expectedResult = 'expected result' const newNotes = 'expected notes' const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) act(() => { - const onChange = resultTextField.prop('onChange') + const onChange = resultTextField.prop('onChange') as any onChange({ currentTarget: { value: expectedResult } }) }) wrapper.update() const notesTextField = wrapper.find(TextFieldWithLabelFormGroup).at(1) act(() => { - const onChange = notesTextField.prop('onChange') + 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') + const onClick = updateButton.prop('onClick') as any onClick() }) @@ -380,7 +402,7 @@ describe('View Lab', () => { 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 { wrapper } = await setup(mockLab, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, @@ -389,14 +411,14 @@ describe('View Lab', () => { const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) await act(async () => { - const onChange = resultTextField.prop('onChange') + 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') + const onClick = completeButton.prop('onClick') as any await onClick() }) wrapper.update() @@ -416,7 +438,7 @@ describe('View Lab', () => { 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, [ + const { wrapper } = await setup(mockLab, [ Permissions.ViewLab, Permissions.CompleteLab, Permissions.CancelLab, @@ -425,14 +447,14 @@ describe('View Lab', () => { const resultTextField = wrapper.find(TextFieldWithLabelFormGroup).at(0) await act(async () => { - const onChange = resultTextField.prop('onChange') + const onChange = resultTextField.prop('onChange') as any await onChange({ currentTarget: { value: expectedResult } }) }) wrapper.update() const cancelButton = wrapper.find(Button).at(2) await act(async () => { - const onClick = cancelButton.prop('onClick') + const onClick = cancelButton.prop('onClick') as any await onClick() }) wrapper.update() diff --git a/src/__tests__/labs/ViewLabs.test.tsx b/src/__tests__/labs/ViewLabs.test.tsx index 9a14691b4b..483f111d52 100644 --- a/src/__tests__/labs/ViewLabs.test.tsx +++ b/src/__tests__/labs/ViewLabs.test.tsx @@ -1,4 +1,4 @@ -import { Select, Table } from '@hospitalrun/components' +import { Select, Table, TextInput } from '@hospitalrun/components' import { act } from '@testing-library/react' import { mount, ReactWrapper } from 'enzyme' import { createMemoryHistory } from 'history' @@ -8,7 +8,6 @@ import { Router } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' -import * as labsSlice from '../../labs/labs-slice' import ViewLabs from '../../labs/ViewLabs' import * as ButtonBarProvider from '../../page-header/button-toolbar/ButtonBarProvider' import * as titleUtil from '../../page-header/title/TitleContext' @@ -19,65 +18,63 @@ import { RootState } from '../../shared/store' const mockStore = createMockStore([thunk]) -let history: any -const expectedLab = { - code: 'L-1234', - id: '1234', - type: 'lab type', - patient: 'patientId', - status: 'requested', - requestedOn: new Date().toISOString(), -} as Lab - -const setup = (permissions: Permissions[] = [Permissions.ViewLabs, Permissions.RequestLab]) => { - const store = mockStore({ - user: { permissions }, - labs: { labs: [expectedLab] }, - } as any) - history = createMemoryHistory() - - jest.spyOn(titleUtil, 'useUpdateTitle').mockImplementation(() => jest.fn()) - jest.spyOn(LabRepository, 'findAll').mockResolvedValue([expectedLab]) - - const wrapper = mount( - - - - - - - , - ) - - wrapper.find(ViewLabs).props().updateTitle = jest.fn() - wrapper.update() - return { wrapper: wrapper as ReactWrapper } -} - -describe('title', () => { - it('should have called the useUpdateTitle hook', async () => { - setup() - expect(titleUtil.useUpdateTitle).toHaveBeenCalled() +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( + + + + + + + + + , + ) + }) + + wrapper.find(ViewLabs).props().updateTitle = jest.fn() + wrapper.update() + return { wrapper: wrapper as ReactWrapper } + } + + describe('title', () => { + it('should have called the useUpdateTitle hook', async () => { + await setup() + expect(titleUtil.useUpdateTitle).toHaveBeenCalled() + }) }) -}) -describe('View Labs', () => { describe('button bar', () => { + beforeEach(() => { + setButtonToolBarSpy.mockReset() + }) + it('should display button to add new lab request', async () => { - const setButtonToolBarSpy = jest.fn() - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) - jest.spyOn(LabRepository, 'findAll').mockResolvedValue([]) - setup() + await 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') }) it('should not display button to add new lab request if the user does not have permissions', async () => { - const setButtonToolBarSpy = jest.fn() - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy) - jest.spyOn(LabRepository, 'findAll').mockResolvedValue([]) - setup([]) + await setup([Permissions.ViewLabs]) const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] expect(actualButtons).toEqual([]) @@ -85,8 +82,20 @@ describe('View Labs', () => { }) describe('table', () => { - it('should render a table with data', () => { - const { wrapper } = setup() + 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 @@ -104,8 +113,8 @@ describe('View Labs', () => { expect(table.prop('data')).toEqual([expectedLab]) }) - it('should navigate to the lab when the view button is clicked', () => { - const { wrapper } = setup() + 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) act(() => { @@ -117,55 +126,59 @@ describe('View Labs', () => { }) describe('dropdown', () => { - it('should search for labs when dropdown changes', () => { - const searchLabsSpy = jest.spyOn(labsSlice, 'searchLabs') - const { wrapper } = setup() + const searchLabsSpy = jest.spyOn(LabRepository, 'search') + beforeEach(() => { searchLabsSpy.mockClear() + }) - act(() => { + 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 - onChange({ - target: { - value: 'requested', - }, - preventDefault: jest.fn(), - }) + await onChange([expectedStatus]) }) - wrapper.update() expect(searchLabsSpy).toHaveBeenCalledTimes(1) + expect(searchLabsSpy).toHaveBeenCalledWith( + expect.objectContaining({ status: expectedStatus }), + ) }) }) -}) - -describe('search functionality', () => { - beforeEach(() => jest.useFakeTimers()) - - afterEach(() => jest.useRealTimers()) - it('should search for labs after the search text has not changed for 500 milliseconds', async () => { - const searchLabsSpy = jest.spyOn(labsSlice, 'searchLabs') + describe('search functionality', () => { + const searchLabsSpy = jest.spyOn(LabRepository, 'search') - searchLabsSpy.mockClear() + beforeEach(() => { + searchLabsSpy.mockClear() + }) - beforeEach(async () => { - const { wrapper } = setup() + it('should search for labs after the search text has not changed for 500 milliseconds', async () => { + jest.useFakeTimers() + const { wrapper } = await setup([Permissions.ViewLabs]) - searchLabsSpy.mockClear() + const expectedSearchText = 'search text' act(() => { - const onChange = wrapper.find(Select).prop('onChange') as any + const onChange = wrapper.find(TextInput).prop('onChange') as any onChange({ target: { - value: 'requested', + value: expectedSearchText, }, preventDefault: jest.fn(), }) }) - wrapper.update() + act(() => { + jest.advanceTimersByTime(500) + }) + expect(searchLabsSpy).toHaveBeenCalledTimes(1) + expect(searchLabsSpy).toHaveBeenCalledWith( + expect.objectContaining({ text: expectedSearchText }), + ) }) }) }) diff --git a/src/__tests__/labs/hooks/useCancelLab.test.ts b/src/__tests__/labs/hooks/useCancelLab.test.ts new file mode 100644 index 0000000000..e73e9750b6 --- /dev/null +++ b/src/__tests__/labs/hooks/useCancelLab.test.ts @@ -0,0 +1,33 @@ +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' +import executeMutation from '../../test-utils/use-mutation.util' + +describe('Use Cancel Lab', () => { + const expectedCanceledOnDate = new Date() + const lab = { + id: 'id lab', + status: 'requested', + } as Lab + const expectedCanceledLab = { + ...lab, + status: 'canceled', + 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) + }) + + expect(LabRepository.saveOrUpdate).toHaveBeenCalledTimes(1) + expect(LabRepository.saveOrUpdate).toHaveBeenCalledWith(lab) + expect(actualData).toEqual(expectedCanceledLab) + }) +}) diff --git a/src/__tests__/labs/hooks/useCompleteLab.test.ts b/src/__tests__/labs/hooks/useCompleteLab.test.ts new file mode 100644 index 0000000000..b275422f04 --- /dev/null +++ b/src/__tests__/labs/hooks/useCompleteLab.test.ts @@ -0,0 +1,58 @@ +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 executeMutation from '../../test-utils/use-mutation.util' + +describe('Use Complete lab', () => { + const expectedCompletedOnDate = new Date() + const lab = { + type: 'test', + result: 'some result', + } as Lab + const expectedCompletedLab = { + ...lab, + completedOn: expectedCompletedOnDate.toISOString(), + status: 'completed', + } as Lab + + Date.now = jest.fn(() => expectedCompletedOnDate.valueOf()) + jest.spyOn(LabRepository, 'saveOrUpdate').mockResolvedValue(expectedCompletedLab) + + beforeEach(() => { + jest.clearAllMocks() + }) + + it('should save lab as complete', async () => { + let actualData: any + await act(async () => { + actualData = await executeMutation(() => useCompleteLab(), lab) + }) + + expect(LabRepository.saveOrUpdate).toHaveBeenCalledTimes(1) + expect(LabRepository.saveOrUpdate).toHaveBeenCalledWith(expectedCompletedLab) + expect(actualData).toEqual(expectedCompletedLab) + }) + + it('should throw errors', async () => { + expect.hasAssertions() + + const expectedLabError = { + result: 'some result error message', + } as LabError + + 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() + } + }) + }) +}) diff --git a/src/__tests__/labs/hooks/useLab.test.ts b/src/__tests__/labs/hooks/useLab.test.ts new file mode 100644 index 0000000000..cd98fa268d --- /dev/null +++ b/src/__tests__/labs/hooks/useLab.test.ts @@ -0,0 +1,29 @@ +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' + +describe('Use lab', () => { + const expectedLabId = 'lab id' + const expectedLab = { + 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 + }) + + expect(LabRepository.find).toHaveBeenCalledTimes(1) + expect(LabRepository.find).toHaveBeenCalledWith(expectedLabId) + expect(actualData).toEqual(expectedLab) + }) +}) diff --git a/src/__tests__/labs/hooks/useLabsSearch.test.ts b/src/__tests__/labs/hooks/useLabsSearch.test.ts new file mode 100644 index 0000000000..c0bff21528 --- /dev/null +++ b/src/__tests__/labs/hooks/useLabsSearch.test.ts @@ -0,0 +1,65 @@ +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' + +describe('Use Labs Search', () => { + const expectedLabs = [ + { + id: 'lab id', + }, + ] as Lab[] + + const labRepositoryFindAllSpy = jest + .spyOn(LabRepository, 'findAll') + .mockResolvedValue(expectedLabs) + const labRepositorySearchSpy = jest.spyOn(LabRepository, 'search').mockResolvedValue(expectedLabs) + + beforeEach(() => { + labRepositoryFindAllSpy.mockClear() + }) + + it('should return all labs', async () => { + const expectedLabsSearchRequest = { + text: '', + 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 + }) + + expect(labRepositoryFindAllSpy).toHaveBeenCalledTimes(1) + expect(labRepositorySearchSpy).not.toHaveBeenCalled() + expect(actualData).toEqual(expectedLabs) + }) + + it('should search for labs', async () => { + const expectedLabsSearchRequest = { + text: 'search text', + 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 + }) + + expect(labRepositoryFindAllSpy).not.toHaveBeenCalled() + expect(labRepositorySearchSpy).toHaveBeenCalledTimes(1) + expect(labRepositorySearchSpy).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 new file mode 100644 index 0000000000..fc83aa805c --- /dev/null +++ b/src/__tests__/labs/hooks/useRequestLab.test.ts @@ -0,0 +1,59 @@ +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 executeMutation from '../../test-utils/use-mutation.util' + +describe('Use Request lab', () => { + const expectedRequestedOnDate = new Date() + const lab = { + type: 'test', + patient: '123', + } as Lab + const expectedRequestedLab = { + ...lab, + requestedOn: expectedRequestedOnDate.toISOString(), + } as Lab + + Date.now = jest.fn(() => expectedRequestedOnDate.valueOf()) + jest.spyOn(LabRepository, 'save').mockResolvedValue(expectedRequestedLab) + + beforeEach(() => { + jest.clearAllMocks() + }) + + it('should save new request lab', async () => { + let actualData: any + await act(async () => { + actualData = await executeMutation(() => useRequestLab(), lab) + }) + + expect(LabRepository.save).toHaveBeenCalledTimes(1) + expect(LabRepository.save).toHaveBeenCalledWith(lab) + expect(actualData).toEqual(expectedRequestedLab) + }) + + it('should return errors', async () => { + expect.hasAssertions() + + const expectedError = { + message: 'error message', + patient: 'error patient', + type: 'error type', + } as LabError + + 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() + } + }) + }) +}) diff --git a/src/__tests__/labs/hooks/useUpdateLab.test.ts b/src/__tests__/labs/hooks/useUpdateLab.test.ts new file mode 100644 index 0000000000..6ab9840284 --- /dev/null +++ b/src/__tests__/labs/hooks/useUpdateLab.test.ts @@ -0,0 +1,27 @@ +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' +import executeMutation from '../../test-utils/use-mutation.util' + +describe('Use update lab', () => { + const expectedLab = { + type: 'some type', + 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) + }) + + expect(LabRepository.saveOrUpdate).toHaveBeenCalledTimes(1) + expect(LabRepository.saveOrUpdate).toHaveBeenCalledWith(expectedLab) + expect(actualData).toEqual(expectedLab) + }) +}) diff --git a/src/__tests__/labs/lab-slice.test.ts b/src/__tests__/labs/lab-slice.test.ts deleted file mode 100644 index 45449b3aea..0000000000 --- a/src/__tests__/labs/lab-slice.test.ts +++ /dev/null @@ -1,406 +0,0 @@ -import createMockStore from 'redux-mock-store' -import thunk from 'redux-thunk' - -import labSlice, { - requestLab, - fetchLabStart, - fetchLabSuccess, - updateLabStart, - updateLabSuccess, - requestLabStart, - requestLabSuccess, - completeLabStart, - completeLabSuccess, - cancelLabStart, - cancelLabSuccess, - fetchLab, - cancelLab, - completeLab, - completeLabError, - requestLabError, - updateLab, -} from '../../labs/lab-slice' -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 { RootState } from '../../shared/store' - -const mockStore = createMockStore([thunk]) - -describe('lab slice', () => { - describe('reducers', () => { - describe('fetchLabStart', () => { - it('should set status to loading', async () => { - const labStore = labSlice(undefined, fetchLabStart()) - - expect(labStore.status).toEqual('loading') - }) - }) - - describe('fetchLabSuccess', () => { - it('should set the lab, patient, and status to success', () => { - const expectedLab = { id: 'labId' } as Lab - const expectedPatient = { id: 'patient' } as Patient - - const labStore = labSlice( - undefined, - fetchLabSuccess({ lab: expectedLab, patient: expectedPatient }), - ) - - expect(labStore.status).toEqual('completed') - expect(labStore.lab).toEqual(expectedLab) - expect(labStore.patient).toEqual(expectedPatient) - }) - }) - - describe('updateLabStart', () => { - it('should set status to loading', async () => { - const labStore = labSlice(undefined, updateLabStart()) - - expect(labStore.status).toEqual('loading') - }) - }) - - describe('updateLabSuccess', () => { - it('should set the lab and status to success', () => { - const expectedLab = { id: 'labId' } as Lab - - const labStore = labSlice(undefined, updateLabSuccess(expectedLab)) - - expect(labStore.status).toEqual('completed') - expect(labStore.lab).toEqual(expectedLab) - }) - }) - - describe('requestLabStart', () => { - it('should set status to loading', async () => { - const labStore = labSlice(undefined, requestLabStart()) - - expect(labStore.status).toEqual('loading') - }) - }) - - describe('requestLabSuccess', () => { - it('should set the lab and status to success', () => { - const expectedLab = { id: 'labId' } as Lab - - const labStore = labSlice(undefined, requestLabSuccess(expectedLab)) - - expect(labStore.status).toEqual('completed') - expect(labStore.lab).toEqual(expectedLab) - }) - }) - - describe('requestLabError', () => { - const expectedError = { message: 'some message', result: 'some result error' } - - const labStore = labSlice(undefined, requestLabError(expectedError)) - - expect(labStore.status).toEqual('error') - expect(labStore.error).toEqual(expectedError) - }) - - describe('completeLabStart', () => { - it('should set status to loading', async () => { - const labStore = labSlice(undefined, completeLabStart()) - - expect(labStore.status).toEqual('loading') - }) - }) - - describe('completeLabSuccess', () => { - it('should set the lab and status to success', () => { - const expectedLab = { id: 'labId' } as Lab - - const labStore = labSlice(undefined, completeLabSuccess(expectedLab)) - - expect(labStore.status).toEqual('completed') - expect(labStore.lab).toEqual(expectedLab) - }) - }) - - describe('completeLabError', () => { - const expectedError = { message: 'some message', result: 'some result error' } - - const labStore = labSlice(undefined, completeLabError(expectedError)) - - expect(labStore.status).toEqual('error') - expect(labStore.error).toEqual(expectedError) - }) - - describe('cancelLabStart', () => { - it('should set status to loading', async () => { - const labStore = labSlice(undefined, cancelLabStart()) - - expect(labStore.status).toEqual('loading') - }) - }) - - describe('cancelLabSuccess', () => { - it('should set the lab and status to success', () => { - const expectedLab = { id: 'labId' } as Lab - - const labStore = labSlice(undefined, cancelLabSuccess(expectedLab)) - - expect(labStore.status).toEqual('completed') - expect(labStore.lab).toEqual(expectedLab) - }) - }) - }) - - describe('fetch lab', () => { - let patientRepositorySpy: any - let labRepositoryFindSpy: any - - const mockLab = { - id: 'labId', - patient: 'patient', - } as Lab - - const mockPatient = { - id: 'patient', - } as Patient - - beforeEach(() => { - patientRepositorySpy = jest.spyOn(PatientRepository, 'find').mockResolvedValue(mockPatient) - labRepositoryFindSpy = jest.spyOn(LabRepository, 'find').mockResolvedValue(mockLab) - }) - - it('should fetch the lab and patient', async () => { - const store = mockStore() - - await store.dispatch(fetchLab(mockLab.id)) - const actions = store.getActions() - - expect(actions[0]).toEqual(fetchLabStart()) - expect(labRepositoryFindSpy).toHaveBeenCalledWith(mockLab.id) - expect(patientRepositorySpy).toHaveBeenCalledWith(mockLab.patient) - expect(actions[1]).toEqual(fetchLabSuccess({ lab: mockLab, patient: mockPatient })) - }) - }) - - describe('cancel lab', () => { - const mockLab = { - id: 'labId', - patient: 'patient', - } as Lab - let labRepositorySaveOrUpdateSpy: any - - beforeEach(() => { - Date.now = jest.fn().mockReturnValue(new Date().valueOf()) - labRepositorySaveOrUpdateSpy = jest - .spyOn(LabRepository, 'saveOrUpdate') - .mockResolvedValue(mockLab) - }) - - it('should cancel a lab', async () => { - const expectedCanceledLab = { - ...mockLab, - canceledOn: new Date(Date.now()).toISOString(), - status: 'canceled', - } as Lab - - const store = mockStore() - - await store.dispatch(cancelLab(mockLab)) - const actions = store.getActions() - - expect(actions[0]).toEqual(cancelLabStart()) - expect(labRepositorySaveOrUpdateSpy).toHaveBeenCalledWith(expectedCanceledLab) - expect(actions[1]).toEqual(cancelLabSuccess(expectedCanceledLab)) - }) - - it('should call on success callback if provided', async () => { - const expectedCanceledLab = { - ...mockLab, - canceledOn: new Date(Date.now()).toISOString(), - status: 'canceled', - } as Lab - - const store = mockStore() - const onSuccessSpy = jest.fn() - await store.dispatch(cancelLab(mockLab, onSuccessSpy)) - - expect(onSuccessSpy).toHaveBeenCalledWith(expectedCanceledLab) - }) - }) - - describe('complete lab', () => { - const mockLab = { - id: 'labId', - patient: 'patient', - result: 'lab result', - } as Lab - let labRepositorySaveOrUpdateSpy: any - - beforeEach(() => { - Date.now = jest.fn().mockReturnValue(new Date().valueOf()) - labRepositorySaveOrUpdateSpy = jest - .spyOn(LabRepository, 'saveOrUpdate') - .mockResolvedValue(mockLab) - }) - - it('should complete a lab', async () => { - const expectedCompletedLab = { - ...mockLab, - completedOn: new Date(Date.now()).toISOString(), - status: 'completed', - result: 'lab result', - } as Lab - - const store = mockStore() - - await store.dispatch(completeLab(mockLab)) - const actions = store.getActions() - - expect(actions[0]).toEqual(completeLabStart()) - expect(labRepositorySaveOrUpdateSpy).toHaveBeenCalledWith(expectedCompletedLab) - expect(actions[1]).toEqual(completeLabSuccess(expectedCompletedLab)) - }) - - it('should call on success callback if provided', async () => { - const expectedCompletedLab = { - ...mockLab, - completedOn: new Date(Date.now()).toISOString(), - status: 'completed', - } as Lab - - const store = mockStore() - const onSuccessSpy = jest.fn() - await store.dispatch(completeLab(mockLab, onSuccessSpy)) - - expect(onSuccessSpy).toHaveBeenCalledWith(expectedCompletedLab) - }) - - it('should validate that the lab can be completed', async () => { - const store = mockStore() - const onSuccessSpy = jest.fn() - await store.dispatch(completeLab({ id: 'labId' } as Lab, onSuccessSpy)) - const actions = store.getActions() - - expect(actions[1]).toEqual( - completeLabError({ - result: 'labs.requests.error.resultRequiredToComplete', - message: 'labs.requests.error.unableToComplete', - }), - ) - expect(onSuccessSpy).not.toHaveBeenCalled() - }) - }) - - describe('request lab', () => { - const mockLab = { - id: 'labId', - type: 'labType', - patient: 'patient', - } as Lab - let labRepositorySaveSpy: any - - beforeEach(() => { - jest.restoreAllMocks() - Date.now = jest.fn().mockReturnValue(new Date().valueOf()) - labRepositorySaveSpy = jest.spyOn(LabRepository, 'save').mockResolvedValue(mockLab) - }) - - it('should request a new lab', async () => { - const store = mockStore({ - user: { - user: { - id: 'fake id', - }, - }, - } as any) - - const expectedRequestedLab = { - ...mockLab, - requestedOn: new Date(Date.now()).toISOString(), - status: 'requested', - requestedBy: store.getState().user.user.id, - } as Lab - - await store.dispatch(requestLab(mockLab)) - - const actions = store.getActions() - - expect(actions[0]).toEqual(requestLabStart()) - expect(labRepositorySaveSpy).toHaveBeenCalledWith(expectedRequestedLab) - expect(actions[1]).toEqual(requestLabSuccess(expectedRequestedLab)) - }) - - it('should execute the onSuccess callback if provided', async () => { - const store = mockStore({ - user: { - user: { - id: 'fake id', - }, - }, - } as any) - const onSuccessSpy = jest.fn() - - await store.dispatch(requestLab(mockLab, onSuccessSpy)) - - expect(onSuccessSpy).toHaveBeenCalledWith(mockLab) - }) - - it('should validate that the lab can be requested', async () => { - const store = mockStore() - const onSuccessSpy = jest.fn() - await store.dispatch(requestLab({} as Lab, onSuccessSpy)) - - const actions = store.getActions() - - expect(actions[0]).toEqual(requestLabStart()) - expect(actions[1]).toEqual( - requestLabError({ - patient: 'labs.requests.error.patientRequired', - type: 'labs.requests.error.typeRequired', - message: 'labs.requests.error.unableToRequest', - }), - ) - expect(labRepositorySaveSpy).not.toHaveBeenCalled() - expect(onSuccessSpy).not.toHaveBeenCalled() - }) - }) - - describe('update lab', () => { - const mockLab = { - id: 'labId', - patient: 'patient', - result: 'lab result', - } as Lab - let labRepositorySaveOrUpdateSpy: any - - const expectedUpdatedLab = { - ...mockLab, - type: 'some other type', - } - - beforeEach(() => { - Date.now = jest.fn().mockReturnValue(new Date().valueOf()) - labRepositorySaveOrUpdateSpy = jest - .spyOn(LabRepository, 'saveOrUpdate') - .mockResolvedValue(expectedUpdatedLab) - }) - - it('should update the lab', async () => { - const store = mockStore() - - await store.dispatch(updateLab(expectedUpdatedLab)) - const actions = store.getActions() - - expect(actions[0]).toEqual(updateLabStart()) - expect(labRepositorySaveOrUpdateSpy).toHaveBeenCalledWith(expectedUpdatedLab) - expect(actions[1]).toEqual(updateLabSuccess(expectedUpdatedLab)) - }) - - it('should call the onSuccess callback if successful', async () => { - const store = mockStore() - const onSuccessSpy = jest.fn() - - await store.dispatch(updateLab(expectedUpdatedLab, onSuccessSpy)) - - expect(onSuccessSpy).toHaveBeenCalledWith(expectedUpdatedLab) - }) - }) -}) diff --git a/src/__tests__/labs/labs.slice.test.ts b/src/__tests__/labs/labs.slice.test.ts deleted file mode 100644 index 5e05eb15fb..0000000000 --- a/src/__tests__/labs/labs.slice.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { AnyAction } from 'redux' -import { mocked } from 'ts-jest/utils' - -import labs, { fetchLabsStart, fetchLabsSuccess, searchLabs } from '../../labs/labs-slice' -import LabRepository from '../../shared/db/LabRepository' -import SortRequest from '../../shared/db/SortRequest' -import Lab from '../../shared/model/Lab' - -interface SearchContainer { - text: string - status: 'requested' | 'completed' | 'canceled' | 'all' - defaultSortRequest: SortRequest -} - -const defaultSortRequest: SortRequest = { - sorts: [ - { - field: 'requestedOn', - direction: 'desc', - }, - ], -} - -const expectedSearchObject: SearchContainer = { - text: 'search string', - status: 'all', - defaultSortRequest, -} - -describe('labs slice', () => { - beforeEach(() => { - jest.resetAllMocks() - }) - - describe('labs reducer', () => { - it('should create the proper intial state with empty labs array', () => { - const labsStore = labs(undefined, {} as AnyAction) - expect(labsStore.isLoading).toBeFalsy() - expect(labsStore.labs).toHaveLength(0) - expect(labsStore.statusFilter).toEqual('all') - }) - - it('it should handle the FETCH_LABS_SUCCESS action', () => { - const expectedLabs = [{ id: '1234' }] - const labsStore = labs(undefined, { - type: fetchLabsSuccess.type, - payload: expectedLabs, - }) - - expect(labsStore.isLoading).toBeFalsy() - expect(labsStore.labs).toEqual(expectedLabs) - }) - }) - - describe('searchLabs', () => { - it('should dispatch the FETCH_LABS_START action', async () => { - const dispatch = jest.fn() - const getState = jest.fn() - - await searchLabs('search string', 'all')(dispatch, getState, null) - - expect(dispatch).toHaveBeenCalledWith({ type: fetchLabsStart.type }) - }) - - it('should call the LabRepository search method with the correct search criteria', async () => { - const dispatch = jest.fn() - const getState = jest.fn() - jest.spyOn(LabRepository, 'search') - - await searchLabs(expectedSearchObject.text, expectedSearchObject.status)( - dispatch, - getState, - null, - ) - - expect(LabRepository.search).toHaveBeenCalledWith(expectedSearchObject) - }) - - it('should call the LabRepository findAll method if there is no string text and status is set to all', async () => { - const dispatch = jest.fn() - const getState = jest.fn() - jest.spyOn(LabRepository, 'findAll') - - await searchLabs('', expectedSearchObject.status)(dispatch, getState, null) - - expect(LabRepository.findAll).toHaveBeenCalledTimes(1) - }) - - it('should dispatch the FETCH_LABS_SUCCESS action', async () => { - const dispatch = jest.fn() - const getState = jest.fn() - - const expectedLabs = [ - { - type: 'text', - }, - ] as Lab[] - - const mockedLabRepository = mocked(LabRepository, true) - mockedLabRepository.search.mockResolvedValue(expectedLabs) - - await searchLabs(expectedSearchObject.text, expectedSearchObject.status)( - dispatch, - getState, - null, - ) - - expect(dispatch).toHaveBeenLastCalledWith({ - type: fetchLabsSuccess.type, - payload: expectedLabs, - }) - }) - }) - - describe('sort Request', () => { - it('should have called findAll with sort request in searchLabs method', async () => { - const dispatch = jest.fn() - const getState = jest.fn() - jest.spyOn(LabRepository, 'findAll') - - await searchLabs('', expectedSearchObject.status)(dispatch, getState, null) - - expect(LabRepository.findAll).toHaveBeenCalledWith(expectedSearchObject.defaultSortRequest) - }) - - it('should include sorts in the search criteria', async () => { - const dispatch = jest.fn() - const getState = jest.fn() - jest.spyOn(LabRepository, 'search') - - await searchLabs(expectedSearchObject.text, expectedSearchObject.status)( - dispatch, - getState, - null, - ) - - expect(LabRepository.search).toHaveBeenCalledWith(expectedSearchObject) - }) - }) -}) diff --git a/src/__tests__/labs/requests/NewLabRequest.test.tsx b/src/__tests__/labs/requests/NewLabRequest.test.tsx index a9175944b3..f97dd730fe 100644 --- a/src/__tests__/labs/requests/NewLabRequest.test.tsx +++ b/src/__tests__/labs/requests/NewLabRequest.test.tsx @@ -9,6 +9,8 @@ import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' 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 TextFieldWithLabelFormGroup from '../../../shared/components/input/TextFieldWithLabelFormGroup' import TextInputWithLabelFormGroup from '../../../shared/components/input/TextInputWithLabelFormGroup' @@ -19,26 +21,31 @@ import Patient from '../../../shared/model/Patient' import { RootState } from '../../../shared/store' const mockStore = createMockStore([thunk]) - describe('New Lab Request', () => { - const setup = async (store = mockStore({ lab: { status: 'loading', error: {} } } as any)) => { - const history = createMemoryHistory() + 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()) - const wrapper: ReactWrapper = await mount( - - - - - - - , - ) + let wrapper: any + await act(async () => { + wrapper = await mount( + + + + + + + , + ) + }) wrapper.find(NewLabRequest).props().updateTitle = jest.fn() wrapper.update() - return { wrapper } + return { wrapper: wrapper as ReactWrapper } } describe('form layout', () => { @@ -98,16 +105,26 @@ describe('New Lab Request', () => { }) }) - describe('errors', async () => { + describe('errors', () => { const error = { message: 'some message', patient: 'some patient message', type: 'some type error', - } - const store = mockStore({ lab: { status: 'error', error } } as any) - const { wrapper } = await setup(store) + } 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) @@ -123,12 +140,9 @@ describe('New Lab Request', () => { }) }) - describe('on cancel', async () => { - const history = createMemoryHistory() - - const { wrapper } = await setup() - - it('should navigate back to /labs', () => { + describe('on cancel', () => { + it('should navigate back to /labs', async () => { + const { wrapper } = await setup() const cancelButton = wrapper.find(Button).at(1) act(() => { @@ -140,8 +154,7 @@ describe('New Lab Request', () => { }) }) - describe('on save', async () => { - const history = createMemoryHistory() + describe('on save', () => { let labRepositorySaveSpy: any const expectedDate = new Date() const expectedNotes = 'expected notes' @@ -157,7 +170,7 @@ describe('New Lab Request', () => { lab: { status: 'loading', error: {} }, user: { user: { id: 'fake id' } }, } as any) - const { wrapper } = await setup(store) + beforeEach(() => { jest.resetAllMocks() Date.now = jest.fn(() => expectedDate.valueOf()) @@ -169,6 +182,8 @@ describe('New Lab Request', () => { }) 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') diff --git a/src/__tests__/labs/utils/validate-lab.test.ts b/src/__tests__/labs/utils/validate-lab.test.ts new file mode 100644 index 0000000000..7d52f05c67 --- /dev/null +++ b/src/__tests__/labs/utils/validate-lab.test.ts @@ -0,0 +1,60 @@ +import { LabError, validateLabRequest, validateLabComplete } from '../../../labs/utils/validate-lab' +import Lab from '../../../shared/model/Lab' + +describe('lab validator', () => { + describe('validate request', () => { + it('should not return error when fields are filled', () => { + const lab = { + patient: 'some patient', + type: 'type test', + } as Lab + + const expectedError = {} as LabError + + const actualError = validateLabRequest(lab) + + expect(actualError).toEqual(expectedError) + }) + + it('should return error when fields are missing', () => { + const lab = {} as Lab + + const expectedError = { + patient: 'labs.requests.error.patientRequired', + type: 'labs.requests.error.typeRequired', + message: 'labs.requests.error.unableToRequest', + } as LabError + + const actualError = validateLabRequest(lab) + + expect(actualError).toEqual(expectedError) + }) + }) + + describe('validate completed', () => { + it('should not return error when result is filled', () => { + const lab = { + result: 'some result', + } as Lab + + const expectedError = {} as LabError + + const actualError = validateLabComplete(lab) + + expect(actualError).toEqual(expectedError) + }) + + it('should return error when result is missing', () => { + const lab = {} as Lab + + const expectedError = { + result: 'labs.requests.error.resultRequiredToComplete', + message: 'labs.requests.error.unableToComplete', + } as LabError + + const actualError = validateLabComplete(lab) + + expect(actualError).toEqual(expectedError) + }) + }) +}) diff --git a/src/__tests__/patients/Patients.test.tsx b/src/__tests__/patients/Patients.test.tsx index d153419d28..4067fc00a3 100644 --- a/src/__tests__/patients/Patients.test.tsx +++ b/src/__tests__/patients/Patients.test.tsx @@ -12,6 +12,7 @@ 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' @@ -27,8 +28,8 @@ describe('/patients/new', () => { const store = mockStore({ title: 'test', user: { user: { id: '123' }, permissions: [Permissions.WritePatients] }, - patient: {}, breadcrumbs: { breadcrumbs: [] }, + patient: {}, components: { sidebarCollapsed: false }, } as any) @@ -46,6 +47,8 @@ describe('/patients/new', () => { ) }) + wrapper.update() + expect(wrapper.find(NewPatient)).toHaveLength(1) expect(store.getActions()).toContainEqual( @@ -91,6 +94,9 @@ describe('/patients/edit/:id', () => { } as Patient jest.spyOn(PatientRepository, 'find').mockResolvedValue(patient) + jest + .spyOn(patientNameUtil, 'getPatientFullName') + .mockReturnValue(`${patient.prefix} ${patient.givenName} ${patient.familyName}`) const store = mockStore({ title: 'test', @@ -115,14 +121,14 @@ describe('/patients/edit/:id', () => { expect(wrapper.find(EditPatient)).toHaveLength(1) - expect(store.getActions()).toContainEqual( - addBreadcrumbs([ + 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', () => { diff --git a/src/__tests__/patients/care-goals/CareGoalTab.test.tsx b/src/__tests__/patients/care-goals/CareGoalTab.test.tsx index 57b0d42877..2ce1f2e2ae 100644 --- a/src/__tests__/patients/care-goals/CareGoalTab.test.tsx +++ b/src/__tests__/patients/care-goals/CareGoalTab.test.tsx @@ -3,7 +3,7 @@ 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 { Router, Route } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' @@ -32,7 +32,9 @@ describe('Care Goals Tab', () => { wrapper = await mount( - + + + , ) diff --git a/src/__tests__/patients/care-plans/CarePlanTab.test.tsx b/src/__tests__/patients/care-plans/CarePlanTab.test.tsx index 693541168d..62b0a30260 100644 --- a/src/__tests__/patients/care-plans/CarePlanTab.test.tsx +++ b/src/__tests__/patients/care-plans/CarePlanTab.test.tsx @@ -4,7 +4,7 @@ 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 { Router, Route } from 'react-router-dom' import createMockStore from 'redux-mock-store' import thunk from 'redux-thunk' @@ -33,7 +33,9 @@ describe('Care Plan Tab', () => { wrapper = await mount( - + + + , ) diff --git a/src/__tests__/patients/edit/EditPatient.test.tsx b/src/__tests__/patients/edit/EditPatient.test.tsx index a49d95261c..d7d12f0b0a 100644 --- a/src/__tests__/patients/edit/EditPatient.test.tsx +++ b/src/__tests__/patients/edit/EditPatient.test.tsx @@ -11,7 +11,6 @@ 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 * as patientSlice from '../../../patients/patient-slice' import PatientRepository from '../../../shared/db/PatientRepository' import Patient from '../../../shared/model/Patient' import { RootState } from '../../../shared/store' @@ -63,7 +62,9 @@ describe('Edit Patient', () => { ) wrapper.find(EditPatient).props().updateTitle = jest.fn() + wrapper.update() + return wrapper } @@ -87,14 +88,12 @@ describe('Edit Patient', () => { expect(wrapper.find(GeneralInformation)).toHaveLength(1) }) - it('should dispatch fetchPatient when component loads', async () => { + it('should load a Patient when component loads', async () => { await act(async () => { await setup() }) expect(PatientRepository.find).toHaveBeenCalledWith(patient.id) - expect(store.getActions()).toContainEqual(patientSlice.fetchPatientStart()) - expect(store.getActions()).toContainEqual(patientSlice.fetchPatientSuccess(patient)) }) it('should dispatch updatePatient when save button is clicked', async () => { @@ -114,8 +113,6 @@ describe('Edit Patient', () => { }) expect(PatientRepository.saveOrUpdate).toHaveBeenCalledWith(patient) - expect(store.getActions()).toContainEqual(patientSlice.updatePatientStart()) - expect(store.getActions()).toContainEqual(patientSlice.updatePatientSuccess(patient)) }) it('should navigate to /patients/:id when cancel is clicked', async () => { diff --git a/src/__tests__/patients/patient-slice.test.ts b/src/__tests__/patients/patient-slice.test.ts index fa68233f53..c99e5df924 100644 --- a/src/__tests__/patients/patient-slice.test.ts +++ b/src/__tests__/patients/patient-slice.test.ts @@ -10,9 +10,6 @@ import patient, { createPatientError, createPatientStart, createPatientSuccess, - fetchPatient, - fetchPatientStart, - fetchPatientSuccess, updatePatient, updatePatientError, updatePatientStart, @@ -38,34 +35,6 @@ describe('patients slice', () => { expect(patientStore.patient).toEqual({}) }) - it('should handle the FETCH_PATIENT_START action', () => { - const patientStore = patient(undefined, { - type: fetchPatientStart.type, - }) - - expect(patientStore.status).toEqual('loading') - }) - - it('should handle the FETCH_PATIENT_SUCCESS actions', () => { - const expectedPatient = { - id: '123', - rev: '123', - sex: 'male', - dateOfBirth: new Date().toISOString(), - givenName: 'test', - familyName: 'test', - } - const patientStore = patient(undefined, { - type: fetchPatientSuccess.type, - payload: { - ...expectedPatient, - }, - }) - - expect(patientStore.status).toEqual('completed') - expect(patientStore.patient).toEqual(expectedPatient) - }) - it('should handle the CREATE_PATIENT_START action', () => { const patientsStore = patient(undefined, { type: createPatientStart.type, @@ -321,42 +290,6 @@ describe('patients slice', () => { ) }) }) - - describe('fetch patient', () => { - it('should dispatch the FETCH_PATIENT_START action', async () => { - const store = mockStore() - const expectedPatientId = 'sliceId6' - const expectedPatient = { id: expectedPatientId, givenName: 'some name' } as Patient - jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) - - await store.dispatch(fetchPatient(expectedPatientId)) - - expect(store.getActions()[0]).toEqual(fetchPatientStart()) - }) - - it('should call the PatientRepository find method with the correct patient id', async () => { - const store = mockStore() - const expectedPatientId = 'sliceId6' - const expectedPatient = { id: expectedPatientId, givenName: 'some name' } as Patient - jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) - - await store.dispatch(fetchPatient(expectedPatientId)) - - expect(PatientRepository.find).toHaveBeenCalledWith(expectedPatientId) - }) - - it('should dispatch the FETCH_PATIENT_SUCCESS action with the correct data', async () => { - const store = mockStore() - const expectedPatientId = 'sliceId6' - const expectedPatient = { id: expectedPatientId, givenName: 'some name' } as Patient - jest.spyOn(PatientRepository, 'find').mockResolvedValue(expectedPatient) - - await store.dispatch(fetchPatient(expectedPatientId)) - - expect(store.getActions()[1]).toEqual(fetchPatientSuccess(expectedPatient)) - }) - }) - describe('update patient', () => { it('should dispatch the UPDATE_PATIENT_START action', async () => { const store = mockStore() diff --git a/src/__tests__/patients/util/patient-name-util.test.ts b/src/__tests__/patients/util/patient-util.test.ts similarity index 66% rename from src/__tests__/patients/util/patient-name-util.test.ts rename to src/__tests__/patients/util/patient-util.test.ts index f0fd700634..03455e5a11 100644 --- a/src/__tests__/patients/util/patient-name-util.test.ts +++ b/src/__tests__/patients/util/patient-util.test.ts @@ -1,7 +1,11 @@ -import { getPatientFullName, getPatientName } from '../../../patients/util/patient-name-util' +import { + getPatientCode, + getPatientFullName, + getPatientName, +} from '../../../patients/util/patient-util' import Patient from '../../../shared/model/Patient' -describe('patient name util', () => { +describe('patient util', () => { describe('getPatientName', () => { it('should build the patients name when three different type of names are passed in', () => { const expectedGiven = 'given' @@ -56,10 +60,32 @@ describe('patient name util', () => { suffix: 'suffix', } as Patient - const expectedFullName = `${patient.givenName} ${patient.familyName} ${patient.suffix}` + it('should return the patients name given a patient', () => { + const expectedFullName = `${patient.givenName} ${patient.familyName} ${patient.suffix}` - const actualFullName = getPatientFullName(patient) + const actualFullName = getPatientFullName(patient) - expect(actualFullName).toEqual(expectedFullName) + expect(actualFullName).toEqual(expectedFullName) + }) + + it('should return a empty string given undefined', () => { + expect(getPatientFullName(undefined)).toEqual('') + }) + }) + + describe('getPatientCode', () => { + const patient = { + code: 'code', + } as Patient + + it('should return the patients code given a patient', () => { + const actualFullName = getPatientCode(patient) + + expect(actualFullName).toEqual(patient.code) + }) + + it('should return a empty string given undefined', () => { + expect(getPatientCode(undefined)).toEqual('') + }) }) }) diff --git a/src/__tests__/patients/view/ViewPatient.test.tsx b/src/__tests__/patients/view/ViewPatient.test.tsx index 80713efce7..b4ca4c142d 100644 --- a/src/__tests__/patients/view/ViewPatient.test.tsx +++ b/src/__tests__/patients/view/ViewPatient.test.tsx @@ -18,7 +18,6 @@ import Diagnoses from '../../../patients/diagnoses/Diagnoses' import GeneralInformation from '../../../patients/GeneralInformation' import Labs from '../../../patients/labs/Labs' import NotesTab from '../../../patients/notes/NoteTab' -import * as patientSlice from '../../../patients/patient-slice' import RelatedPersonTab from '../../../patients/related-persons/RelatedPersonTab' import ViewPatient from '../../../patients/view/ViewPatient' import PatientRepository from '../../../shared/db/PatientRepository' @@ -49,8 +48,11 @@ describe('ViewPatient', () => { 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') jest.spyOn(PatientRepository, 'getLabs').mockResolvedValue([]) @@ -93,8 +95,6 @@ describe('ViewPatient', () => { await setup() expect(PatientRepository.find).toHaveBeenCalledWith(patient.id) - expect(store.getActions()).toContainEqual(patientSlice.fetchPatientStart()) - expect(store.getActions()).toContainEqual(patientSlice.fetchPatientSuccess(patient)) }) it('should have called useUpdateTitle hook', async () => { @@ -104,10 +104,6 @@ describe('ViewPatient', () => { }) it('should add a "Edit Patient" button to the button tool bar if has WritePatients permissions', async () => { - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter') - const setButtonToolBarSpy = jest.fn() - mocked(ButtonBarProvider).useButtonToolbarSetter.mockReturnValue(setButtonToolBarSpy) - await setup([Permissions.WritePatients]) const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] @@ -115,10 +111,6 @@ describe('ViewPatient', () => { }) it('button toolbar empty if only has ReadPatients permission', async () => { - jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter') - const setButtonToolBarSpy = jest.fn() - mocked(ButtonBarProvider).useButtonToolbarSetter.mockReturnValue(setButtonToolBarSpy) - await setup() const actualButtons: React.ReactNode[] = setButtonToolBarSpy.mock.calls[0][0] diff --git a/src/labs/ViewLab.tsx b/src/labs/ViewLab.tsx index d253c4cb21..c9c5f05f31 100644 --- a/src/labs/ViewLab.tsx +++ b/src/labs/ViewLab.tsx @@ -1,11 +1,12 @@ import { Row, Column, Badge, Button, Alert, Toast, Callout, Label } from '@hospitalrun/components' import format from 'date-fns/format' import React, { useEffect, useState } from 'react' -import { useSelector, useDispatch } from 'react-redux' +import { useSelector } from 'react-redux' import { useParams, useHistory } from 'react-router-dom' import useAddBreadcrumbs from '../page-header/breadcrumbs/useAddBreadcrumbs' import { useUpdateTitle } from '../page-header/title/TitleContext' +import usePatient from '../patients/hooks/usePatient' import TextFieldWithLabelFormGroup from '../shared/components/input/TextFieldWithLabelFormGroup' import useTranslator from '../shared/hooks/useTranslator' import Lab from '../shared/model/Lab' @@ -13,7 +14,11 @@ import Patient from '../shared/model/Patient' import Permissions from '../shared/model/Permissions' import { RootState } from '../shared/store' import { uuid } from '../shared/util/uuid' -import { cancelLab, completeLab, updateLab, fetchLab } from './lab-slice' +import useCancelLab from './hooks/useCancelLab' +import useCompleteLab from './hooks/useCompleteLab' +import useLab from './hooks/useLab' +import useUpdateLab from './hooks/useUpdateLab' +import { LabError } from './utils/validate-lab' const getTitle = (patient: Patient | undefined, lab: Lab | undefined) => patient && lab ? `${lab.type} for ${patient.fullName}(${lab.code})` : '' @@ -22,14 +27,19 @@ const ViewLab = () => { const { id } = useParams() const { t } = useTranslator() const history = useHistory() - const dispatch = useDispatch() const { permissions } = useSelector((state: RootState) => state.user) - const { lab, patient, status, error } = useSelector((state: RootState) => state.lab) const [labToView, setLabToView] = useState() const [newNotes, setNewNotes] = useState() const [isEditable, setIsEditable] = useState(true) + const { data: lab } = useLab(id) + const { data: patient } = usePatient(lab?.patient) + const [updateLab] = useUpdateLab() + const [completeLab] = useCompleteLab() + const [cancelLab] = useCancelLab() + const [error, setError] = useState(undefined) + const updateTitle = useUpdateTitle() updateTitle(getTitle(patient, labToView)) @@ -41,12 +51,6 @@ const ViewLab = () => { ] useAddBreadcrumbs(breadcrumbs) - useEffect(() => { - if (id) { - dispatch(fetchLab(id)) - } - }, [id, dispatch]) - useEffect(() => { if (lab) { setLabToView({ ...lab }) @@ -66,45 +70,46 @@ const ViewLab = () => { } const onUpdate = async () => { - const onSuccess = (update: Lab) => { - history.push(`/labs/${update.id}`) - Toast( - 'success', - t('states.success'), - `${t('labs.successfullyUpdated')} ${update.type} for ${patient?.fullName}`, - ) - } if (labToView) { const newLab = labToView as Lab + if (newNotes) { newLab.notes = newLab.notes ? [...newLab.notes, newNotes] : [newNotes] setNewNotes('') } - dispatch(updateLab(newLab, onSuccess)) - } - } - const onComplete = async () => { - const onSuccess = (complete: Lab) => { - history.push(`/labs/${complete.id}`) + + const updatedLab = await updateLab(newLab) + history.push(`/labs/${updatedLab?.id}`) Toast( 'success', t('states.success'), - `${t('labs.successfullyCompleted')} ${complete.type} for ${patient?.fullName} `, + `${t('labs.successfullyUpdated')} ${updatedLab?.type} for ${patient?.fullName}`, ) } + setError(undefined) + } - if (labToView) { - dispatch(completeLab(labToView, onSuccess)) + const onComplete = async () => { + try { + if (labToView) { + const completedLab = await completeLab(labToView) + history.push(`/labs/${completedLab?.id}`) + Toast( + 'success', + t('states.success'), + `${t('labs.successfullyCompleted')} ${completedLab?.type} for ${patient?.fullName} `, + ) + } + setError(undefined) + } catch (e) { + setError(e) } } const onCancel = async () => { - const onSuccess = () => { - history.push('/labs') - } - if (labToView) { - dispatch(cancelLab(labToView, onSuccess)) + cancelLab(labToView) + history.push('/labs') } } @@ -188,7 +193,7 @@ const ViewLab = () => { return ( <> - {status === 'error' && ( + {error && ( )} @@ -227,8 +232,8 @@ const ViewLab = () => { label={t('labs.lab.result')} value={labToView.result} isEditable={isEditable} - isInvalid={!!error.result} - feedback={t(error.result as string)} + isInvalid={!!error?.result} + feedback={t(error?.result as string)} onChange={onResultChange} />