From 34b4a426c04fc799cdf5515606e3eb7dcc0baad6 Mon Sep 17 00:00:00 2001 From: Dennis Kigen Date: Fri, 13 Dec 2024 22:38:34 +0300 Subject: [PATCH] (chore) Standardize Jest setup and improve testing practices (#1236) This PR makes the following changes to our testing setup: - Adds eslint plugins for Jest-DOM, Playwright and Testing Library and updates the eslint config to use them - Refactors tests to use modern Testing Library best practices as enforced by the eslint plugins - Adds an option to Jest to clear mocks between tests for consistency and to avoid manual cleanup For tests I couldn't get to work with the eslint plugins, I've added an ESLint ignore comment to the top of the file. The plan is to revisit those and refactor them in a separate PR. --- .eslintrc | 26 ++- package.json | 3 + packages/apps/esm-devtools-app/jest.config.js | 1 + .../apps/esm-help-menu-app/jest.config.js | 1 + .../esm-implementer-tools-app/jest.config.js | 1 + .../src/configuration/configuration.test.tsx | 2 +- .../global-implementer-tools-button.test.tsx | 3 +- packages/apps/esm-login-app/jest.config.js | 1 + .../change-location-link.test.tsx | 7 +- .../location-picker-view.component.tsx | 2 +- .../location-picker/location-picker.test.tsx | 2 +- .../redirect-logout/redirect-logout.test.tsx | 1 - .../apps/esm-offline-tools-app/jest.config.js | 1 + .../esm-primary-navigation-app/jest.config.js | 1 + .../src/components/logo/logo.test.tsx | 4 +- packages/framework/esm-api/jest.config.js | 1 + .../current-patient.test.ts | 1 - packages/framework/esm-config/jest.config.js | 1 + packages/framework/esm-context/jest.config.js | 1 + .../esm-dynamic-loading/jest.config.js | 1 + .../esm-error-handling/jest.config.js | 1 + .../esm-expression-evaluator/jest.config.js | 1 + .../framework/esm-extensions/jest.config.js | 1 + .../esm-feature-flags/jest.config.js | 1 + .../framework/esm-framework/jest.config.js | 1 + .../extension-config.test.tsx | 9 +- packages/framework/esm-offline/jest.config.js | 1 + .../framework/esm-react-utils/jest.config.js | 1 + .../src/ConfigurableLink.test.tsx | 4 +- .../esm-react-utils/src/extensions.test.tsx | 67 +++++--- .../src/openmrsComponentDecorator.test.tsx | 45 +++-- .../src/useAbortController.test.tsx | 4 +- .../esm-react-utils/src/useConfig.test.tsx | 30 ++-- .../src/useOnClickOutside.test.tsx | 8 +- .../src/useOpenmrsFetchAll.test.tsx | 4 +- .../src/useOpenmrsInfinite.test.tsx | 4 +- .../src/useOpenmrsPagination.test.tsx | 4 +- packages/framework/esm-routes/jest.config.js | 7 +- packages/framework/esm-state/jest.config.js | 1 + .../framework/esm-styleguide/jest.config.js | 1 + .../custom-overflow-menu.test.tsx | 4 +- .../location-picker.component.tsx | 12 +- .../location-picker/location-picker.test.tsx | 14 +- .../src/page-header/page-header.test.tsx | 1 + ...atient-banner-patient-identifiers.test.tsx | 2 +- .../src/snackbars/snackbar.test.tsx | 2 - .../action-menu-button.test.tsx | 4 +- .../container/workspace-container.test.tsx | 28 ++-- packages/framework/esm-utils/jest.config.js | 1 + yarn.lock | 156 +++++++++++++++++- 50 files changed, 340 insertions(+), 140 deletions(-) diff --git a/.eslintrc b/.eslintrc index 4834a944b..bb3f1db6e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,14 +1,24 @@ { "env": { - "node": true, - "browser": true + "node": true }, - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:jest-dom/recommended"], + "overrides": [ + { + "files": ["**/*.test.tsx"], + "extends": ["plugin:testing-library/react"] + }, + { + "files": ["e2e/**/*.spec.ts"], + "extends": ["plugin:playwright/recommended"], + "rules": { + "testing-library/prefer-screen-queries": "off" + } + } + ], "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint", "import", "react-hooks"], "rules": { - "import/no-duplicates": "error", - "react-hooks/rules-of-hooks": "error", // Disabling these rules for now just to keep the diff small. I'll enable them in a future PR that fixes lint issues. "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-types": "off", @@ -23,7 +33,7 @@ "fixStyle": "inline-type-imports" } ], - "prefer-const": "off", + "import/no-duplicates": "error", "no-console": ["error", { "allow": ["warn", "error"] }], "no-unsafe-optional-chaining": "off", "no-explicit-any": "off", @@ -55,6 +65,8 @@ } ] } - ] + ], + "prefer-const": "off", + "react-hooks/rules-of-hooks": "error" } } diff --git a/package.json b/package.json index 6c17af9da..c33526e08 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,10 @@ "dotenv": "^16.0.3", "eslint": "^8.55.0", "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jest-dom": "^5.5.0", + "eslint-plugin-playwright": "^2.1.0", "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-testing-library": "^7.1.1", "fake-indexeddb": "^4.0.2", "fork-ts-checker-webpack-plugin": "^7.2.13", "husky": "^8.0.3", diff --git a/packages/apps/esm-devtools-app/jest.config.js b/packages/apps/esm-devtools-app/jest.config.js index d2c66915b..a38303cea 100644 --- a/packages/apps/esm-devtools-app/jest.config.js +++ b/packages/apps/esm-devtools-app/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '\\.(m?j|t)sx?$': ['@swc/jest'], }, diff --git a/packages/apps/esm-help-menu-app/jest.config.js b/packages/apps/esm-help-menu-app/jest.config.js index b8e9945c8..4f218ce8e 100644 --- a/packages/apps/esm-help-menu-app/jest.config.js +++ b/packages/apps/esm-help-menu-app/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/packages/apps/esm-implementer-tools-app/jest.config.js b/packages/apps/esm-implementer-tools-app/jest.config.js index 4ecbc9268..4e1a85c86 100644 --- a/packages/apps/esm-implementer-tools-app/jest.config.js +++ b/packages/apps/esm-implementer-tools-app/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/packages/apps/esm-implementer-tools-app/src/configuration/configuration.test.tsx b/packages/apps/esm-implementer-tools-app/src/configuration/configuration.test.tsx index b17a51352..97ccaf9ed 100644 --- a/packages/apps/esm-implementer-tools-app/src/configuration/configuration.test.tsx +++ b/packages/apps/esm-implementer-tools-app/src/configuration/configuration.test.tsx @@ -1,5 +1,5 @@ +/* eslint-disable */ import React from 'react'; - import { render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { implementerToolsConfigStore, temporaryConfigStore, Type } from '@openmrs/esm-framework/src/internal'; diff --git a/packages/apps/esm-implementer-tools-app/src/global-implementer-tools-button.test.tsx b/packages/apps/esm-implementer-tools-app/src/global-implementer-tools-button.test.tsx index 96b73b943..d4079ab94 100644 --- a/packages/apps/esm-implementer-tools-app/src/global-implementer-tools-button.test.tsx +++ b/packages/apps/esm-implementer-tools-app/src/global-implementer-tools-button.test.tsx @@ -1,10 +1,9 @@ -import { cleanup, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import React from 'react'; import '@testing-library/jest-dom'; import GlobalImplementerToolsButton from './global-implementer-tools.component'; describe('Testing the global implementer tools button', () => { - afterEach(cleanup); it('should render global Implementer tools', () => { render(); const button = screen.getByTestId('globalImplementerToolsButton'); diff --git a/packages/apps/esm-login-app/jest.config.js b/packages/apps/esm-login-app/jest.config.js index 63686b803..f9b96de50 100644 --- a/packages/apps/esm-login-app/jest.config.js +++ b/packages/apps/esm-login-app/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/packages/apps/esm-login-app/src/change-location-link/change-location-link.test.tsx b/packages/apps/esm-login-app/src/change-location-link/change-location-link.test.tsx index 45a196c49..737f93bbe 100644 --- a/packages/apps/esm-login-app/src/change-location-link/change-location-link.test.tsx +++ b/packages/apps/esm-login-app/src/change-location-link/change-location-link.test.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import ChangeLocationLink from './change-location-link.extension'; -import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; import { navigate, useSession } from '@openmrs/esm-framework'; +import ChangeLocationLink from './change-location-link.extension'; const navigateMock = navigate as jest.Mock; const useSessionMock = useSession as jest.Mock; @@ -17,10 +17,11 @@ describe('', () => { display: 'Waffle House', }, }); - render(); }); it('should display the `Change location` link', async () => { + render(); + const user = userEvent.setup(); const changeLocationButton = await screen.findByRole('button', { name: /Change/i, diff --git a/packages/apps/esm-login-app/src/location-picker/location-picker-view.component.tsx b/packages/apps/esm-login-app/src/location-picker/location-picker-view.component.tsx index d82aa9868..a53ca7631 100644 --- a/packages/apps/esm-login-app/src/location-picker/location-picker-view.component.tsx +++ b/packages/apps/esm-login-app/src/location-picker/location-picker-view.component.tsx @@ -12,9 +12,9 @@ import { getCoreTranslation, } from '@openmrs/esm-framework'; import type { LoginReferrer } from '../login/login.component'; -import styles from './location-picker.scss'; import { useDefaultLocation, useLocationCount } from './location-picker.resource'; import type { ConfigSchema } from '../config-schema'; +import styles from './location-picker.scss'; interface LocationPickerProps { hideWelcomeMessage?: boolean; diff --git a/packages/apps/esm-login-app/src/location-picker/location-picker.test.tsx b/packages/apps/esm-login-app/src/location-picker/location-picker.test.tsx index 6ab9faf22..2aac877dc 100644 --- a/packages/apps/esm-login-app/src/location-picker/location-picker.test.tsx +++ b/packages/apps/esm-login-app/src/location-picker/location-picker.test.tsx @@ -1,3 +1,4 @@ +/* eslint-disable */ import { act, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { @@ -45,7 +46,6 @@ mockedUseSession.mockReturnValue({ describe('LocationPickerView', () => { beforeEach(() => { - jest.clearAllMocks(); mockedOpenmrsFetch.mockImplementation((url) => { if (url === `/ws/fhir2/R4/Location?_id=${fistLocation.uuid}`) { return validatingLocationSuccessResponse; diff --git a/packages/apps/esm-login-app/src/redirect-logout/redirect-logout.test.tsx b/packages/apps/esm-login-app/src/redirect-logout/redirect-logout.test.tsx index 9a28d9d0b..9315099ba 100644 --- a/packages/apps/esm-login-app/src/redirect-logout/redirect-logout.test.tsx +++ b/packages/apps/esm-login-app/src/redirect-logout/redirect-logout.test.tsx @@ -27,7 +27,6 @@ Object.defineProperty(document, 'documentElement', { describe('Testing Logout', () => { beforeEach(() => { - jest.clearAllMocks(); (useConnectivity as jest.Mock).mockReturnValue(true); (openmrsFetch as jest.Mock).mockResolvedValue({}); (useSession as jest.Mock).mockReturnValue({ diff --git a/packages/apps/esm-offline-tools-app/jest.config.js b/packages/apps/esm-offline-tools-app/jest.config.js index 82eaa0682..ce3137f98 100644 --- a/packages/apps/esm-offline-tools-app/jest.config.js +++ b/packages/apps/esm-offline-tools-app/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/packages/apps/esm-primary-navigation-app/jest.config.js b/packages/apps/esm-primary-navigation-app/jest.config.js index ef2c0b2ad..9f8667aa9 100644 --- a/packages/apps/esm-primary-navigation-app/jest.config.js +++ b/packages/apps/esm-primary-navigation-app/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '\\.(m?j|t)sx?$': ['@swc/jest'], }, diff --git a/packages/apps/esm-primary-navigation-app/src/components/logo/logo.test.tsx b/packages/apps/esm-primary-navigation-app/src/components/logo/logo.test.tsx index c49ec5a0a..e143dfe52 100644 --- a/packages/apps/esm-primary-navigation-app/src/components/logo/logo.test.tsx +++ b/packages/apps/esm-primary-navigation-app/src/components/logo/logo.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useConfig } from '@openmrs/esm-framework'; -import { cleanup, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import Logo from './logo.component'; const mockUseConfig = useConfig as jest.Mock; @@ -11,8 +11,6 @@ jest.mock('@openmrs/esm-framework', () => ({ })); describe('', () => { - afterEach(cleanup); - it('should display OpenMRS logo', () => { const mockConfig = { logo: { src: null, alt: null, name: null } }; mockUseConfig.mockReturnValue(mockConfig); diff --git a/packages/framework/esm-api/jest.config.js b/packages/framework/esm-api/jest.config.js index 7d6f83ed0..2b881b54f 100644 --- a/packages/framework/esm-api/jest.config.js +++ b/packages/framework/esm-api/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/packages/framework/esm-api/src/shared-api-objects/current-patient.test.ts b/packages/framework/esm-api/src/shared-api-objects/current-patient.test.ts index 27fbd1c8d..8b86604ae 100644 --- a/packages/framework/esm-api/src/shared-api-objects/current-patient.test.ts +++ b/packages/framework/esm-api/src/shared-api-objects/current-patient.test.ts @@ -17,7 +17,6 @@ jest.mock('@openmrs/esm-offline', () => ({ describe('fetchPatientData', () => { beforeEach(() => { - jest.clearAllMocks(); mockGetSynchronizationItems.mockResolvedValue([]); }); diff --git a/packages/framework/esm-config/jest.config.js b/packages/framework/esm-config/jest.config.js index e2d20e983..bdae11246 100644 --- a/packages/framework/esm-config/jest.config.js +++ b/packages/framework/esm-config/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/packages/framework/esm-context/jest.config.js b/packages/framework/esm-context/jest.config.js index 52484b6fd..364ee793e 100644 --- a/packages/framework/esm-context/jest.config.js +++ b/packages/framework/esm-context/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/packages/framework/esm-dynamic-loading/jest.config.js b/packages/framework/esm-dynamic-loading/jest.config.js index 52484b6fd..364ee793e 100644 --- a/packages/framework/esm-dynamic-loading/jest.config.js +++ b/packages/framework/esm-dynamic-loading/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/packages/framework/esm-error-handling/jest.config.js b/packages/framework/esm-error-handling/jest.config.js index 6c2ea075c..ad945e9af 100644 --- a/packages/framework/esm-error-handling/jest.config.js +++ b/packages/framework/esm-error-handling/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/packages/framework/esm-expression-evaluator/jest.config.js b/packages/framework/esm-expression-evaluator/jest.config.js index 52484b6fd..364ee793e 100644 --- a/packages/framework/esm-expression-evaluator/jest.config.js +++ b/packages/framework/esm-expression-evaluator/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/packages/framework/esm-extensions/jest.config.js b/packages/framework/esm-extensions/jest.config.js index 40f9c4048..59cc19b80 100644 --- a/packages/framework/esm-extensions/jest.config.js +++ b/packages/framework/esm-extensions/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.(j|t)sx?$': ['@swc/jest'], }, diff --git a/packages/framework/esm-feature-flags/jest.config.js b/packages/framework/esm-feature-flags/jest.config.js index 52484b6fd..364ee793e 100644 --- a/packages/framework/esm-feature-flags/jest.config.js +++ b/packages/framework/esm-feature-flags/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/packages/framework/esm-framework/jest.config.js b/packages/framework/esm-framework/jest.config.js index c11f1de5d..666d484bc 100644 --- a/packages/framework/esm-framework/jest.config.js +++ b/packages/framework/esm-framework/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.(j|t)sx?$': ['@swc/jest'], }, diff --git a/packages/framework/esm-framework/src/integration-tests/extension-config.test.tsx b/packages/framework/esm-framework/src/integration-tests/extension-config.test.tsx index 9c010aafa..9f14e7aa1 100644 --- a/packages/framework/esm-framework/src/integration-tests/extension-config.test.tsx +++ b/packages/framework/esm-framework/src/integration-tests/extension-config.test.tsx @@ -1,3 +1,4 @@ +/* eslint-disable */ import React from 'react'; import { act, render, screen, waitFor } from '@testing-library/react'; import { type Person } from '@openmrs/esm-api'; @@ -71,7 +72,7 @@ describe('Interaction between configuration and extension systems', () => { render(); - screen.findByText('Betty'); + await screen.findByText('Betty'); const slot = screen.getByTestId('slot'); const extensions = slot.childNodes; @@ -201,7 +202,7 @@ describe('Interaction between configuration and extension systems', () => { render(); - await waitFor(() => expect(screen.getByText('Pearl')).toBeInTheDocument()); + await screen.findByText('Pearl'); act(() => { temporaryConfigStore.setState({ @@ -236,7 +237,7 @@ describe('Interaction between configuration and extension systems', () => { await act(async () => await promise); render(); - await waitFor(() => expect(screen.getByText(/Mr. Slate/)).toBeInTheDocument()); + await screen.findByText(/Mr. Slate/); expect(screen.getByTestId('slot')).toHaveTextContent(/green/); act(() => { @@ -428,7 +429,7 @@ describe('Interaction between configuration and extension systems', () => { render(); - await waitFor(() => expect(screen.getByTestId(/slot/)).toBeInTheDocument()); + await screen.findByTestId(/slot/); expect(screen.getByTestId('slot').firstChild).toHaveAttribute('data-extension-id', 'Schmoo'); }); diff --git a/packages/framework/esm-offline/jest.config.js b/packages/framework/esm-offline/jest.config.js index b1320037d..7b4db33a9 100644 --- a/packages/framework/esm-offline/jest.config.js +++ b/packages/framework/esm-offline/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/packages/framework/esm-react-utils/jest.config.js b/packages/framework/esm-react-utils/jest.config.js index a633605ef..52e58b2c8 100644 --- a/packages/framework/esm-react-utils/jest.config.js +++ b/packages/framework/esm-react-utils/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.(j|t)sx?$': ['@swc/jest'], }, diff --git a/packages/framework/esm-react-utils/src/ConfigurableLink.test.tsx b/packages/framework/esm-react-utils/src/ConfigurableLink.test.tsx index b323174d1..790c3f48a 100644 --- a/packages/framework/esm-react-utils/src/ConfigurableLink.test.tsx +++ b/packages/framework/esm-react-utils/src/ConfigurableLink.test.tsx @@ -22,8 +22,10 @@ describe(`ConfigurableLink`, () => { , ); const link = screen.getByRole('link', { name: /spa home/i }); - expect(link).toBeTruthy(); + expect(link).toBeInTheDocument(); + // eslint-disable-next-line testing-library/no-node-access expect(link.closest('a')).toHaveClass('fancy-link'); + // eslint-disable-next-line testing-library/no-node-access expect(link.closest('a')).toHaveAttribute('href', '/openmrs/spa/home'); }); diff --git a/packages/framework/esm-react-utils/src/extensions.test.tsx b/packages/framework/esm-react-utils/src/extensions.test.tsx index 10a80693f..29f58c558 100644 --- a/packages/framework/esm-react-utils/src/extensions.test.tsx +++ b/packages/framework/esm-react-utils/src/extensions.test.tsx @@ -1,9 +1,11 @@ -import React, { useCallback, useReducer } from 'react'; +/* eslint-disable */ +import React, { useReducer } from 'react'; import '@testing-library/jest-dom'; +import userEvent from '@testing-library/user-event'; import { act, render, screen, waitFor, within } from '@testing-library/react'; +import { registerFeatureFlag, setFeatureFlag } from '@openmrs/esm-feature-flags'; import { attach, - type ConnectedExtension, getExtensionNameFromId, registerExtension, updateInternalExtensionStore, @@ -14,11 +16,8 @@ import { ExtensionSlot, openmrsComponentDecorator, useExtensionSlotMeta, - type ExtensionData, useRenderableExtensions, } from '.'; -import userEvent from '@testing-library/user-event'; -import { registerFeatureFlag, setFeatureFlag } from '@openmrs/esm-feature-flags'; // For some reason in the test context `isEqual` always returns true // when using the import substitution in jest.config.js. Here's a custom @@ -30,16 +29,16 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { updateInternalExtensionStore(() => ({ slots: {}, extensions: {} })); }); - afterEach(() => { - jest.clearAllMocks(); - }); - test('Extension receives state changes passed through (not using )', async () => { + const user = userEvent.setup(); + function EnglishExtension({ suffix }) { return
English{suffix}
; } + registerSimpleExtension('English', 'esm-languages-app', EnglishExtension); attach('Box', 'English'); + const App = openmrsComponentDecorator({ moduleName: 'esm-languages-app', featureName: 'Languages', @@ -53,20 +52,25 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { ); }); + render(); - await waitFor(() => expect(screen.getByText(/English/)).toBeInTheDocument()); + expect(await screen.findByText(/English/)).toBeInTheDocument(); expect(screen.getByText(/English/)).toHaveTextContent('English!'); - userEvent.click(screen.getByText('Toggle suffix')); - await waitFor(() => expect(screen.getByText(/English/)).toHaveTextContent('English?')); + await user.click(screen.getByText('Toggle suffix')); + expect(screen.getByText(/English/)).toHaveTextContent('English?'); }); test('Extension receives state changes (using )', async () => { + const user = userEvent.setup(); + function HaitianCreoleExtension({ suffix }) { return
Haitian Creole{suffix}
; } + registerSimpleExtension('Haitian', 'esm-languages-app', HaitianCreoleExtension); attach('Box', 'Haitian'); + const App = openmrsComponentDecorator({ moduleName: 'esm-languages-app', featureName: 'Languages', @@ -84,21 +88,24 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { ); }); + render(); - await waitFor(() => expect(screen.getByText(/Haitian/)).toBeInTheDocument()); + expect(await screen.findByText(/Haitian/)).toBeInTheDocument(); expect(screen.getByText(/Haitian/)).toHaveTextContent('Haitian Creole!'); - userEvent.click(screen.getByText('Toggle suffix')); - await waitFor(() => expect(screen.getByText(/Haitian/)).toHaveTextContent('Haitian Creole?')); + await user.click(screen.getByText('Toggle suffix')); + expect(screen.getByText(/Haitian/)).toHaveTextContent('Haitian Creole?'); }); test('ExtensionSlot throws error if both state and children provided', () => { const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); + const App = () => ( ); + expect(() => render()).toThrow(); expect(consoleError).toHaveBeenNthCalledWith( 1, @@ -116,6 +123,7 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { code: 'es', }); attach('Box', 'Spanish'); + const App = openmrsComponentDecorator({ moduleName: 'esm-languages-app', featureName: 'Languages', @@ -135,21 +143,26 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { ); }); + render(); - await waitFor(() => expect(screen.getByRole('heading')).toBeInTheDocument()); + expect(await screen.findByRole('heading')).toBeInTheDocument(); expect(screen.getByRole('heading')).toHaveTextContent('es'); - await waitFor(() => expect(screen.getByText('Spanish')).toBeInTheDocument()); + expect(screen.getByText('Spanish')).toBeInTheDocument(); }); test('Both meta and state can be used at the same time', async () => { + const user = userEvent.setup(); function SwahiliExtension({ suffix }) { return
Swahili{suffix}
; } + registerSimpleExtension('Swahili', 'esm-languages-app', SwahiliExtension, { code: 'sw', }); + attach('Box', 'Swahili'); + const App = openmrsComponentDecorator({ moduleName: 'esm-languages-app', featureName: 'Languages', @@ -171,12 +184,13 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { ); }); + render(); - await waitFor(() => expect(screen.getByRole('heading')).toBeInTheDocument()); + expect(await screen.findByRole('heading')).toBeInTheDocument(); expect(screen.getByRole('heading')).toHaveTextContent('sw'); await waitFor(() => expect(screen.getByText(/Swahili/)).toHaveTextContent('Swahili!')); - userEvent.click(screen.getByText('Toggle suffix')); + await user.click(screen.getByText('Toggle suffix')); await waitFor(() => expect(screen.getByText(/Swahili/)).toHaveTextContent('Swahili?')); }); @@ -187,8 +201,10 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { registerSimpleExtension('Hindi', 'esm-languages-app', undefined, { code: 'hi', }); + attach('Box', 'Urdu'); attach('Box', 'Hindi'); + const App = openmrsComponentDecorator({ moduleName: 'esm-languages-app', featureName: 'Languages', @@ -207,9 +223,10 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { ); }); + render(); - await waitFor(() => expect(screen.getByTestId('Urdu')).toBeInTheDocument()); + expect(await screen.findByTestId('Urdu')).toBeInTheDocument(); expect(within(screen.getByTestId('Urdu')).getByRole('heading')).toHaveTextContent('urd'); expect(within(screen.getByTestId('Hindi')).getByRole('heading')).toHaveTextContent('hi'); }); @@ -219,18 +236,22 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { registerSimpleExtension('Turkish', 'esm-languages-app', undefined, undefined, 'turkic'); registerSimpleExtension('Turkmeni', 'esm-languages-app', undefined, undefined, 'turkic'); registerSimpleExtension('Kurmanji', 'esm-languages-app', undefined, undefined, 'kurdish'); + attach('Box', 'Arabic'); attach('Box', 'Turkish'); attach('Box', 'Turkmeni'); attach('Box', 'Kurmanji'); + registerFeatureFlag('turkic', '', ''); registerFeatureFlag('kurdish', '', ''); setFeatureFlag('turkic', true); + const App = openmrsComponentDecorator({ moduleName: 'esm-languages-app', featureName: 'Languages', disableTranslations: true, })(() => ); + render(); await waitFor(() => expect(screen.getByText(/Turkmeni/)).toBeInTheDocument()); @@ -255,13 +276,13 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { featureName: 'Languages', disableTranslations: true, })(() => { - const extensions = useRenderableExtensions('Box'); - const Ext = extensions[0]; + const utils = useRenderableExtensions('Box'); + const Ext = utils[0]; return ; }); render(); - await waitFor(() => expect(screen.getByText('Spanish hola')).toBeInTheDocument()); + expect(await screen.findByText('Spanish hola')).toBeInTheDocument(); }); }); diff --git a/packages/framework/esm-react-utils/src/openmrsComponentDecorator.test.tsx b/packages/framework/esm-react-utils/src/openmrsComponentDecorator.test.tsx index 46b999dee..7e6c54cec 100644 --- a/packages/framework/esm-react-utils/src/openmrsComponentDecorator.test.tsx +++ b/packages/framework/esm-react-utils/src/openmrsComponentDecorator.test.tsx @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { render, screen, waitFor} from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { openmrsComponentDecorator } from './openmrsComponentDecorator'; import { ComponentContext } from './ComponentContext'; @@ -10,11 +10,11 @@ describe('openmrs-component-decorator', () => { moduleName: 'test', }; - it('renders a component', () => { + it('renders a component', async () => { const DecoratedComp = openmrsComponentDecorator(opts)(CompThatWorks); render(); - screen.findByText('The button'); + expect(await screen.findByText('The button')).toBeInTheDocument(); }); it('catches any errors in the component tree and renders a ui explaining something bad happened', () => { @@ -36,21 +36,21 @@ describe('openmrs-component-decorator', () => { render(); }); - it("rendering a unsafe component in strict mode should log error in console",()=>{ + it('rendering a unsafe component in strict mode should log error in console', () => { const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); - const UnsafeDecoratedCompnent = openmrsComponentDecorator(opts)(UnsafeComponent); - render(); - expect(consoleError.mock.calls[0][0]).toContain('Warning: Using UNSAFE_componentWillMount'); - consoleError.mockRestore(); - }) + const UnsafeDecoratedCompnent = openmrsComponentDecorator(opts)(UnsafeComponent); + render(); + expect(consoleError.mock.calls[0][0]).toContain('Warning: Using UNSAFE_componentWillMount'); + consoleError.mockRestore(); + }); - it("rendering an unsafe component without strict mode should not log an error in console",()=>{ - const spy = jest.spyOn(console, "error"); - const unsafeComponentOptions=Object.assign(opts,{strictMode:false}) - const UnsafeDecoratedCompnent = openmrsComponentDecorator(unsafeComponentOptions)(UnsafeComponent); - render(); - expect(spy).not.toHaveBeenCalled(); - }) + it('rendering an unsafe component without strict mode should not log an error in console', () => { + const spy = jest.spyOn(console, 'error'); + const unsafeComponentOptions = Object.assign(opts, { strictMode: false }); + const UnsafeDecoratedCompnent = openmrsComponentDecorator(unsafeComponentOptions)(UnsafeComponent); + render(); + expect(spy).not.toHaveBeenCalled(); + }); }); function CompThatWorks() { @@ -66,13 +66,10 @@ function CompWithConfig() { return
{moduleName}
; } +class UnsafeComponent extends Component { + UNSAFE_componentWillMount() {} -class UnsafeComponent extends Component { - - UNSAFE_componentWillMount() { - } - - render() { - return

This is Unsafe Component

; - } + render() { + return

This is Unsafe Component

; + } } diff --git a/packages/framework/esm-react-utils/src/useAbortController.test.tsx b/packages/framework/esm-react-utils/src/useAbortController.test.tsx index 5674395d2..ffc9349e4 100644 --- a/packages/framework/esm-react-utils/src/useAbortController.test.tsx +++ b/packages/framework/esm-react-utils/src/useAbortController.test.tsx @@ -1,10 +1,8 @@ -import { renderHook, cleanup } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import '@testing-library/jest-dom'; import useAbortController from './useAbortController'; describe('useAbortController', () => { - afterEach(cleanup); - it('returns an AbortController', () => { const { result } = renderHook(() => useAbortController()); expect(result.current).not.toBeNull(); diff --git a/packages/framework/esm-react-utils/src/useConfig.test.tsx b/packages/framework/esm-react-utils/src/useConfig.test.tsx index a3da88877..65ac0cbec 100644 --- a/packages/framework/esm-react-utils/src/useConfig.test.tsx +++ b/packages/framework/esm-react-utils/src/useConfig.test.tsx @@ -1,13 +1,15 @@ +/* eslint-disable -- Test file uses React Testing Library patterns that conflict + with current ESLint rules. TODO: Investigate updating test patterns or ESLint config */ import React from 'react'; -import { render, cleanup, screen, waitFor, act } from '@testing-library/react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import { + configInternalStore, defineConfigSchema, - temporaryConfigStore, provide, - configInternalStore, + temporaryConfigStore, type ConfigInternalStore, } from '@openmrs/esm-config'; -import type { MockedStore } from '../__mocks__/openmrs-esm-state.mock'; +import { type MockedStore } from '@openmrs/esm-state/mock'; import { useConfig } from './useConfig'; import { ComponentContext } from './ComponentContext'; @@ -41,7 +43,7 @@ describe(`useConfig in root context`, () => { render( Suspense!}> - + , @@ -65,7 +67,7 @@ describe(`useConfig in root context`, () => { render( Suspense!}> - + , @@ -73,11 +75,9 @@ describe(`useConfig in root context`, () => { await waitFor(() => expect(screen.findByText('foo thing')).toBeTruthy()); - await cleanup(); - render( Suspense!}> - + , @@ -95,7 +95,7 @@ describe(`useConfig in root context`, () => { render( Suspense!}> - + , @@ -109,13 +109,12 @@ describe(`useConfig in root context`, () => { }), ); - await waitFor(() => expect(screen.findByText('A new thing')).toBeTruthy()); + await screen.findByText('A new thing'); }); }); describe(`useConfig in an extension`, () => { afterEach(clearConfig); - afterEach(cleanup); it(`can return extension config as a react hook`, async () => { defineConfigSchema('ext-module', { @@ -129,6 +128,7 @@ describe(`useConfig in an extension`, () => { { { { { { { { const ref = useOnClickOutside(handler); return
{children}
; }; - const ref = render(); + const view = render(); // act - await user.click(ref.container); + await user.click(view.container); // verify expect(handler).toHaveBeenCalledTimes(1); @@ -52,11 +52,11 @@ describe('useOnClickOutside', () => { const ref = useOnClickOutside(handler); return
{children}
; }; - const ref = render(); + const view = render(); const spy = jest.spyOn(window, 'removeEventListener'); // act - ref.unmount(); + view.unmount(); // verify expect(spy).toHaveBeenCalledWith('mousedown', expect.any(Function)); diff --git a/packages/framework/esm-react-utils/src/useOpenmrsFetchAll.test.tsx b/packages/framework/esm-react-utils/src/useOpenmrsFetchAll.test.tsx index f7b073243..053a32902 100644 --- a/packages/framework/esm-react-utils/src/useOpenmrsFetchAll.test.tsx +++ b/packages/framework/esm-react-utils/src/useOpenmrsFetchAll.test.tsx @@ -1,4 +1,4 @@ -import { renderHook, cleanup, waitFor, act } from '@testing-library/react'; +import { renderHook, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import { useOpenmrsFetchAll } from './useOpenmrsFetchAll'; import { type OpenMRSPaginatedResponse } from './useOpenmrsPagination'; @@ -27,8 +27,6 @@ export async function getTestData(url: string, totalCount: number): Promise { - afterEach(cleanup); - it('should render all rows on if number of rows < pageSize', async () => { const expectedRowCount = 17; const { result } = renderHook(() => diff --git a/packages/framework/esm-react-utils/src/useOpenmrsInfinite.test.tsx b/packages/framework/esm-react-utils/src/useOpenmrsInfinite.test.tsx index ca29e509e..e089af7f8 100644 --- a/packages/framework/esm-react-utils/src/useOpenmrsInfinite.test.tsx +++ b/packages/framework/esm-react-utils/src/useOpenmrsInfinite.test.tsx @@ -1,11 +1,9 @@ import '@testing-library/jest-dom'; -import { act, cleanup, renderHook, waitFor } from '@testing-library/react'; +import { act, renderHook, waitFor } from '@testing-library/react'; import { useOpenmrsInfinite } from './useOpenmrsInfinite'; import { getIntArray, getTestData } from './useOpenmrsPagination.test'; describe('useOpenmrsInfinite', () => { - afterEach(cleanup); - it('should load all rows with 1 fetch if number of rows < pageSize', async () => { const pageSize = 20; const expectedRowCount = 17; diff --git a/packages/framework/esm-react-utils/src/useOpenmrsPagination.test.tsx b/packages/framework/esm-react-utils/src/useOpenmrsPagination.test.tsx index 3603af51c..a1bd9d207 100644 --- a/packages/framework/esm-react-utils/src/useOpenmrsPagination.test.tsx +++ b/packages/framework/esm-react-utils/src/useOpenmrsPagination.test.tsx @@ -1,4 +1,4 @@ -import { renderHook, cleanup, waitFor, act } from '@testing-library/react'; +import { renderHook, waitFor, act } from '@testing-library/react'; import '@testing-library/jest-dom'; import { useOpenmrsPagination, type OpenMRSPaginatedResponse } from './useOpenmrsPagination'; @@ -26,8 +26,6 @@ export async function getTestData(url: string, totalCount: number): Promise { - afterEach(cleanup); - it('should not fetch anything if url is null', async () => { const { result } = renderHook(() => useOpenmrsPagination(null as any, 50, { diff --git a/packages/framework/esm-routes/jest.config.js b/packages/framework/esm-routes/jest.config.js index c62eabc99..2b4dc5458 100644 --- a/packages/framework/esm-routes/jest.config.js +++ b/packages/framework/esm-routes/jest.config.js @@ -1,9 +1,10 @@ module.exports = { + clearMocks: true, transform: { - "^.+\\.(m?j|t)sx?$": ["@swc/jest"], + '^.+\\.(m?j|t)sx?$': ['@swc/jest'], }, - testEnvironment: "jsdom", + testEnvironment: 'jsdom', testEnvironmentOptions: { - url: "http://localhost/", + url: 'http://localhost/', }, }; diff --git a/packages/framework/esm-state/jest.config.js b/packages/framework/esm-state/jest.config.js index 52484b6fd..364ee793e 100644 --- a/packages/framework/esm-state/jest.config.js +++ b/packages/framework/esm-state/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/packages/framework/esm-styleguide/jest.config.js b/packages/framework/esm-styleguide/jest.config.js index 2b6450625..8cd5f3fdc 100644 --- a/packages/framework/esm-styleguide/jest.config.js +++ b/packages/framework/esm-styleguide/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '\\.[jt]sx?$': '@swc/jest', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': diff --git a/packages/framework/esm-styleguide/src/custom-overflow-menu/custom-overflow-menu.test.tsx b/packages/framework/esm-styleguide/src/custom-overflow-menu/custom-overflow-menu.test.tsx index e083865f1..c12b3395e 100644 --- a/packages/framework/esm-styleguide/src/custom-overflow-menu/custom-overflow-menu.test.tsx +++ b/packages/framework/esm-styleguide/src/custom-overflow-menu/custom-overflow-menu.test.tsx @@ -22,9 +22,9 @@ describe('CustomOverflowMenuComponent', () => { const triggerButton = screen.getByRole('button', { name: /menu/i }); await user.click(triggerButton); - expect(triggerButton.getAttribute('aria-expanded')).toBe('true'); + expect(triggerButton).toHaveAttribute('aria-expanded', 'true'); await user.click(triggerButton); - expect(triggerButton.getAttribute('aria-expanded')).toBe('false'); + expect(triggerButton).toHaveAttribute('aria-expanded', 'false'); }); }); diff --git a/packages/framework/esm-styleguide/src/location-picker/location-picker.component.tsx b/packages/framework/esm-styleguide/src/location-picker/location-picker.component.tsx index ef1a704f8..f3a2baeb5 100644 --- a/packages/framework/esm-styleguide/src/location-picker/location-picker.component.tsx +++ b/packages/framework/esm-styleguide/src/location-picker/location-picker.component.tsx @@ -1,8 +1,8 @@ -import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; +import React, { useState, useRef, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { InlineLoading, Search, RadioButton, RadioButtonGroup, RadioButtonSkeleton } from '@carbon/react'; -import styles from './location-picker.module.scss'; +import { InlineLoading, RadioButton, RadioButtonGroup, RadioButtonSkeleton, Search } from '@carbon/react'; import { useLocationByUuid, useLocations } from './location-picker.resource'; +import styles from './location-picker.module.scss'; interface LocationPickerProps { selectedLocationUuid?: string; @@ -92,12 +92,12 @@ export const LocationPicker: React.FC = ({
{locations?.length > 0 ? ( { onChange(ev.toString()); }} + orientation="vertical" + valueSelected={selectedLocationUuid} > {locations.map((entry, i) => ( { }); expect( - screen.queryByRole('searchbox', { + screen.getByRole('searchbox', { name: /search for a location/i, }), ).toBeInTheDocument(); - expect(screen.queryByRole('searchbox', { name: /search for a location/i })).toBeInTheDocument(); + expect(screen.getByRole('searchbox', { name: /search for a location/i })).toBeInTheDocument(); const locations = screen.getAllByRole('radio'); expect(locations.length).toBe(4); const expectedLocations = [/community outreach/, /inpatient ward/, /mobile clinic/, /outpatient clinic/]; expectedLocations.forEach((row) => - expect(screen.queryByRole('radio', { name: new RegExp(row, 'i') })).toBeInTheDocument(), + expect(screen.getByRole('radio', { name: new RegExp(row, 'i') })).toBeInTheDocument(), ); }); @@ -94,7 +94,7 @@ describe('LocationPicker', () => { render(); }); const inpatientWardOption = screen.getByRole('radio', { name: /inpatient ward/i }); - expect(inpatientWardOption).toHaveProperty('checked', true); + expect(inpatientWardOption).toBeChecked(); }); it('loads the default location on top of the list', async () => { @@ -109,7 +109,7 @@ describe('LocationPicker', () => { const expectedLocations = [/community outreach/, /inpatient ward/, /mobile clinic/, /outpatient clinic/]; expectedLocations.forEach((location) => - expect(screen.queryByRole('radio', { name: new RegExp(location, 'i') })).toBeInTheDocument(), + expect(screen.getByRole('radio', { name: new RegExp(location, 'i') })).toBeInTheDocument(), ); }); @@ -132,7 +132,7 @@ describe('LocationPicker', () => { const searchInput = screen.getByRole('searchbox', { name: /search for a location/i }); await user.type(searchInput, 'search_for_no_location'); - expect(screen.queryByText(/no results to display/i)).toBeInTheDocument(); + expect(screen.getByText(/no results to display/i)).toBeInTheDocument(); const locations = screen.queryAllByRole('radio'); expect(locations.length).toBe(0); }); diff --git a/packages/framework/esm-styleguide/src/page-header/page-header.test.tsx b/packages/framework/esm-styleguide/src/page-header/page-header.test.tsx index e2cf3e76f..8035f9380 100644 --- a/packages/framework/esm-styleguide/src/page-header/page-header.test.tsx +++ b/packages/framework/esm-styleguide/src/page-header/page-header.test.tsx @@ -49,6 +49,7 @@ describe('PageHeaderContent', () => { ); await screen.findByText(/test title/i); + // eslint-disable-next-line testing-library/no-node-access expect(container.firstChild).toHaveClass('custom-class'); }); diff --git a/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.test.tsx b/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.test.tsx index 87d2195f3..2261d2fce 100644 --- a/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.test.tsx +++ b/packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.test.tsx @@ -68,7 +68,7 @@ describe('PatientBannerPatientIdentifiers', () => { render(); - expect(screen.queryByText(/openmrs id/i)).toBeInTheDocument(); + expect(screen.getByText(/openmrs id/i)).toBeInTheDocument(); expect(screen.queryByText(/national id/i)).not.toBeInTheDocument(); }); }); diff --git a/packages/framework/esm-styleguide/src/snackbars/snackbar.test.tsx b/packages/framework/esm-styleguide/src/snackbars/snackbar.test.tsx index b05755956..8f51ce514 100644 --- a/packages/framework/esm-styleguide/src/snackbars/snackbar.test.tsx +++ b/packages/framework/esm-styleguide/src/snackbars/snackbar.test.tsx @@ -7,8 +7,6 @@ jest.useFakeTimers(); const mockedCloseSnackbar = jest.fn(); describe('Snackbar component', () => { - afterEach(() => jest.clearAllMocks()); - it('renders a snackbar notification', () => { renderSnackbar(); diff --git a/packages/framework/esm-styleguide/src/workspaces/action-menu-button/action-menu-button.test.tsx b/packages/framework/esm-styleguide/src/workspaces/action-menu-button/action-menu-button.test.tsx index dc03a6a8f..a29294568 100644 --- a/packages/framework/esm-styleguide/src/workspaces/action-menu-button/action-menu-button.test.tsx +++ b/packages/framework/esm-styleguide/src/workspaces/action-menu-button/action-menu-button.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { screen, render, cleanup } from '@testing-library/react'; +import { screen, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { useLayoutType } from '@openmrs/esm-react-utils'; import { Pen } from '@carbon/react/icons'; @@ -32,8 +32,6 @@ jest.mock('../workspaces', () => { }); describe('ActionMenuButton', () => { - beforeEach(cleanup); - it('should display tablet view', async () => { const user = userEvent.setup(); diff --git a/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.test.tsx b/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.test.tsx index 8c7dfa8c7..581a14108 100644 --- a/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.test.tsx +++ b/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.test.tsx @@ -78,14 +78,11 @@ describe('WorkspaceContainer in window mode', () => { ); const header = screen.getByRole('banner'); expect(within(header).getByText('COVID Admission')).toBeInTheDocument(); - const workspaces = renderHook(() => useWorkspaces()); - act(() => workspaces.result.current.workspaces[0].setTitle('COVID Discharge')); + const utils = renderHook(() => useWorkspaces()); + act(() => utils.result.current.workspaces[0].setTitle('COVID Discharge')); expect(within(header).getByText('COVID Discharge')).toBeInTheDocument(); act(() => - workspaces.result.current.workspaces[0].setTitle( - 'Space Ghost', -
Space Ghost
, - ), + utils.result.current.workspaces[0].setTitle('Space Ghost',
Space Ghost
), ); expect(within(header).getByTestId('patient-name')).toBeInTheDocument(); }); @@ -100,8 +97,8 @@ describe('WorkspaceContainer in window mode', () => { expect(within(header).getByText('COVID Admission')).toBeInTheDocument(); act(() => launchWorkspace('clinical-form', { workspaceTitle: 'COVID Discharge' })); expect(within(header).getByText('COVID Discharge')).toBeInTheDocument(); - const workspaces = renderHook(() => useWorkspaces()); - act(() => workspaces.result.current.workspaces[0].setTitle('Fancy Special Title')); + const utils = renderHook(() => useWorkspaces()); + act(() => utils.result.current.workspaces[0].setTitle('Fancy Special Title')); expect(within(header).getByText('Fancy Special Title')).toBeInTheDocument(); act(() => launchWorkspace('clinical-form', { workspaceTitle: 'COVID Admission Again' })); expect(within(header).getByText('Fancy Special Title')).toBeInTheDocument(); @@ -109,13 +106,13 @@ describe('WorkspaceContainer in window mode', () => { test('should reopen hidden workspace window when user relaunches the same workspace window', async () => { const user = userEvent.setup(); - const workspaces = renderHook(() => useWorkspaces()); + const utils = renderHook(() => useWorkspaces()); mockedIsDesktop.mockReturnValue(true); - expect(workspaces.result.current.workspaces.length).toBe(0); + expect(utils.result.current.workspaces.length).toBe(0); renderWorkspaceWindow(); act(() => launchWorkspace('clinical-form', { workspaceTitle: 'POC Triage' })); - expect(workspaces.result.current.workspaces.length).toBe(1); + expect(utils.result.current.workspaces.length).toBe(1); const header = screen.getByRole('banner'); expect(within(header).getByText('POC Triage')).toBeInTheDocument(); expectToBeVisible(screen.getByRole('complementary')); @@ -130,7 +127,7 @@ describe('WorkspaceContainer in window mode', () => { expectToBeVisible(await screen.findByRole('complementary')); expect(screen.queryByRole('complementary')).not.toHaveClass('hiddenRelative'); expect(screen.queryByRole('complementary')).not.toHaveClass('hiddenFixed'); - expect(workspaces.result.current.workspaces.length).toBe(1); + expect(utils.result.current.workspaces.length).toBe(1); input = screen.getByRole('textbox'); expect(input).toHaveValue("what's good"); }); @@ -143,14 +140,17 @@ describe('WorkspaceContainer in window mode', () => { act(() => launchWorkspace('clinical-form')); const header = screen.getByRole('banner'); expect(within(header).getByText('clinicalForm')).toBeInTheDocument(); + // eslint-disable-next-line testing-library/no-node-access expect(screen.getByRole('complementary').firstChild).not.toHaveClass('maximizedWindow'); const maximizeButton = await screen.findByRole('button', { name: 'Maximize' }); await user.click(maximizeButton); + // eslint-disable-next-line testing-library/no-node-access expect(screen.getByRole('complementary').firstChild).toHaveClass('maximizedWindow'); const minimizeButton = await screen.findByRole('button', { name: 'Minimize' }); await user.click(minimizeButton); + // eslint-disable-next-line testing-library/no-node-access expect(screen.getByRole('complementary').firstChild).not.toHaveClass('maximizedWindow'); }); @@ -203,13 +203,13 @@ describe('WorkspaceContainer in overlay mode', () => { renderWorkspaceOverlay(); act(() => launchWorkspace('patient-search', { workspaceTitle: 'Make an appointment' })); - expect(screen.queryByRole('complementary')).toBeInTheDocument(); + expect(screen.getByRole('complementary')).toBeInTheDocument(); expectToBeVisible(screen.getByRole('complementary')); expect(screen.getByText('Make an appointment')).toBeInTheDocument(); const closeButton = screen.getByRole('button', { name: 'Close' }); await user.click(closeButton); - expect(screen.queryByRole('complementary')).toHaveClass('hiddenRelative'); + expect(screen.getByRole('complementary')).toHaveClass('hiddenRelative'); }); }); diff --git a/packages/framework/esm-utils/jest.config.js b/packages/framework/esm-utils/jest.config.js index 52484b6fd..364ee793e 100644 --- a/packages/framework/esm-utils/jest.config.js +++ b/packages/framework/esm-utils/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + clearMocks: true, transform: { '^.+\\.tsx?$': ['@swc/jest'], }, diff --git a/yarn.lock b/yarn.lock index 699c0b17e..c453f2ef6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1288,6 +1288,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.16.3": + version: 7.26.0 + resolution: "@babel/runtime@npm:7.26.0" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 10/9f4ea1c1d566c497c052d505587554e782e021e6ccd302c2ad7ae8291c8e16e3f19d4a7726fb64469e057779ea2081c28b7dbefec6d813a22f08a35712c0f699 + languageName: node + linkType: hard + "@babel/template@npm:^7.18.10, @babel/template@npm:^7.22.15, @babel/template@npm:^7.3.3": version: 7.24.0 resolution: "@babel/template@npm:7.24.0" @@ -2956,7 +2965,10 @@ __metadata: dotenv: "npm:^16.0.3" eslint: "npm:^8.55.0" eslint-plugin-import: "npm:^2.31.0" + eslint-plugin-jest-dom: "npm:^5.5.0" + eslint-plugin-playwright: "npm:^2.1.0" eslint-plugin-react-hooks: "npm:^4.6.2" + eslint-plugin-testing-library: "npm:^7.1.1" fake-indexeddb: "npm:^4.0.2" fork-ts-checker-webpack-plugin: "npm:^7.2.13" husky: "npm:^8.0.3" @@ -6064,6 +6076,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:8.18.0, @typescript-eslint/scope-manager@npm:^8.15.0": + version: 8.18.0 + resolution: "@typescript-eslint/scope-manager@npm:8.18.0" + dependencies: + "@typescript-eslint/types": "npm:8.18.0" + "@typescript-eslint/visitor-keys": "npm:8.18.0" + checksum: 10/869fd569a1f98cd284001062cca501e25ef7079c761242926d3b35454da64e398391ddb9d686adb34bf7bee6446491617b52c54ba54db07ee637ad4ef024d262 + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:6.15.0": version: 6.15.0 resolution: "@typescript-eslint/type-utils@npm:6.15.0" @@ -6088,6 +6110,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:8.18.0": + version: 8.18.0 + resolution: "@typescript-eslint/types@npm:8.18.0" + checksum: 10/6c6473c169671ca946df7c1e0e424e5296dd44d89833d5c82a0ec0fdb2c668c62f8de31c85b18754d332198f18340cf2b6f13d3b13d02770ee9d1a93a099f069 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:6.15.0": version: 6.15.0 resolution: "@typescript-eslint/typescript-estree@npm:6.15.0" @@ -6106,6 +6135,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:8.18.0": + version: 8.18.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.18.0" + dependencies: + "@typescript-eslint/types": "npm:8.18.0" + "@typescript-eslint/visitor-keys": "npm:8.18.0" + debug: "npm:^4.3.4" + fast-glob: "npm:^3.3.2" + is-glob: "npm:^4.0.3" + minimatch: "npm:^9.0.4" + semver: "npm:^7.6.0" + ts-api-utils: "npm:^1.3.0" + peerDependencies: + typescript: ">=4.8.4 <5.8.0" + checksum: 10/8ffd54a58dcc2c1b33f55c29193656fde772946d9dea87e06084a242dad3098049ecff9758e215c9f27ed358c5c7dabcae96cf19bc824098e075500725faf2e1 + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:6.15.0": version: 6.15.0 resolution: "@typescript-eslint/utils@npm:6.15.0" @@ -6123,6 +6170,21 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:^8.15.0": + version: 8.18.0 + resolution: "@typescript-eslint/utils@npm:8.18.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.4.0" + "@typescript-eslint/scope-manager": "npm:8.18.0" + "@typescript-eslint/types": "npm:8.18.0" + "@typescript-eslint/typescript-estree": "npm:8.18.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.8.0" + checksum: 10/ced2775200a4d88f9c1808f2f9a4dc43505939c4bcd5b60ca2e74bf291d6f6993789ce9d56f373c39476080a9f430e969258ee8111d0a7a9ea85da399151d27e + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:6.15.0": version: 6.15.0 resolution: "@typescript-eslint/visitor-keys@npm:6.15.0" @@ -6133,6 +6195,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:8.18.0": + version: 8.18.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.18.0" + dependencies: + "@typescript-eslint/types": "npm:8.18.0" + eslint-visitor-keys: "npm:^4.2.0" + checksum: 10/6b2e1e471097ddd903dcb125ba8ff42bf4262fc4f408ca3afacf4161cff6f06b7ab4a6a7dd273e34b61a676f89a00535de7497c77d9001a10512ba3fe7d91971 + languageName: node + linkType: hard + "@ungap/structured-clone@npm:^1.2.0": version: 1.2.0 resolution: "@ungap/structured-clone@npm:1.2.0" @@ -9991,6 +10063,33 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-jest-dom@npm:^5.5.0": + version: 5.5.0 + resolution: "eslint-plugin-jest-dom@npm:5.5.0" + dependencies: + "@babel/runtime": "npm:^7.16.3" + requireindex: "npm:^1.2.0" + peerDependencies: + "@testing-library/dom": ^8.0.0 || ^9.0.0 || ^10.0.0 + eslint: ^6.8.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + peerDependenciesMeta: + "@testing-library/dom": + optional: true + checksum: 10/73aaaa6117abbe3b197bc6b1e45839aaa9c2b4c86e7efbc4ff29f03318ec7f019a8e32652c06c61f06fdb22fb296c068a802a268e7b88aa6b71d3477d949b2c6 + languageName: node + linkType: hard + +"eslint-plugin-playwright@npm:^2.1.0": + version: 2.1.0 + resolution: "eslint-plugin-playwright@npm:2.1.0" + dependencies: + globals: "npm:^13.23.0" + peerDependencies: + eslint: ">=8.40.0" + checksum: 10/5c36202a56760203bf3738b03fbd1fddce520f09772b998d2cd7631636f5dec1c4dda724af94ccafb462ffadd113bb8a940ef6991661095cab4997cf19863000 + languageName: node + linkType: hard + "eslint-plugin-react-hooks@npm:^4.6.2": version: 4.6.2 resolution: "eslint-plugin-react-hooks@npm:4.6.2" @@ -10000,6 +10099,18 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-testing-library@npm:^7.1.1": + version: 7.1.1 + resolution: "eslint-plugin-testing-library@npm:7.1.1" + dependencies: + "@typescript-eslint/scope-manager": "npm:^8.15.0" + "@typescript-eslint/utils": "npm:^8.15.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + checksum: 10/48a7a7f93afd16f9cf9cccaf7a1e7ba2e2ea9072d598558ce758d396c7a4d6a71e49b4ec654feef67350141f4f2737d7460c07dbfaed4eb60a09d1c7ceb11558 + languageName: node + linkType: hard + "eslint-scope@npm:5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" @@ -10027,6 +10138,13 @@ __metadata: languageName: node linkType: hard +"eslint-visitor-keys@npm:^4.2.0": + version: 4.2.0 + resolution: "eslint-visitor-keys@npm:4.2.0" + checksum: 10/9651b3356b01760e586b4c631c5268c0e1a85236e3292bf754f0472f465bf9a856c0ddc261fceace155334118c0151778effafbab981413dbf9288349343fa25 + languageName: node + linkType: hard + "eslint@npm:^8.55.0": version: 8.56.0 resolution: "eslint@npm:8.56.0" @@ -10318,7 +10436,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9": +"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" dependencies: @@ -11058,7 +11176,7 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.19.0": +"globals@npm:^13.19.0, globals@npm:^13.23.0": version: 13.24.0 resolution: "globals@npm:13.24.0" dependencies: @@ -13997,6 +14115,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^9.0.4": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10/dd6a8927b063aca6d910b119e1f2df6d2ce7d36eab91de83167dd136bb85e1ebff97b0d3de1cb08bd1f7e018ca170b4962479fefab5b2a69e2ae12cb2edc8348 + languageName: node + linkType: hard + "minimist@npm:^1.2.0": version: 1.2.8 resolution: "minimist@npm:1.2.8" @@ -16814,6 +16941,13 @@ __metadata: languageName: node linkType: hard +"requireindex@npm:^1.2.0": + version: 1.2.0 + resolution: "requireindex@npm:1.2.0" + checksum: 10/266d1cb31f6cbc4b6cf2e898f5bbc45581f7919bcf61bba5c45d0adb69b722b9ff5a13727be3350cde4520d7cd37f39df45d58a29854baaa4552cd6b05ae4a1a + languageName: node + linkType: hard + "requires-port@npm:^1.0.0": version: 1.0.0 resolution: "requires-port@npm:1.0.0" @@ -17306,6 +17440,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.6.0": + version: 7.6.3 + resolution: "semver@npm:7.6.3" + bin: + semver: bin/semver.js + checksum: 10/36b1fbe1a2b6f873559cd57b238f1094a053dbfd997ceeb8757d79d1d2089c56d1321b9f1069ce263dc64cfa922fa1d2ad566b39426fe1ac6c723c1487589e10 + languageName: node + linkType: hard + "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -18599,6 +18742,15 @@ __metadata: languageName: node linkType: hard +"ts-api-utils@npm:^1.3.0": + version: 1.4.3 + resolution: "ts-api-utils@npm:1.4.3" + peerDependencies: + typescript: ">=4.2.0" + checksum: 10/713c51e7392323305bd4867422ba130fbf70873ef6edbf80ea6d7e9c8f41eeeb13e40e8e7fe7cd321d74e4864777329797077268c9f570464303a1723f1eed39 + languageName: node + linkType: hard + "ts-toolbelt@npm:^6.3.3": version: 6.15.5 resolution: "ts-toolbelt@npm:6.15.5"