From 0aba4d1ddc31410ba01b188015a2d9ab59cbdfc6 Mon Sep 17 00:00:00 2001 From: AJAL ODORA JONATHAN <43242517+ODORA0@users.noreply.github.com> Date: Fri, 18 Oct 2024 13:05:56 +0300 Subject: [PATCH 1/2] Add tests for creating a new clinical view --- jest.config.js | 17 +- package.json | 2 +- .../add-columns-modal.component.test.tsx | 183 ++++++++++++++++++ .../add-columns-modal.component.tsx | 27 +-- .../add-package-modal.component.test.tsx | 114 +++++++++++ .../add-submenu-modal.component.test.tsx | 108 +++++++++++ ...nfigure-dashboard-modal.component.test.tsx | 121 ++++++++++++ .../configure-dashboard-modal.component.tsx | 2 +- ...ete-config-detail-modal.component.test.tsx | 96 +++++++++ .../edit-tab-definition-modal.test.tsx | 56 ++++++ .../interactive-builder.component.test.tsx | 99 ++++++++++ yarn.lock | 10 +- 12 files changed, 807 insertions(+), 28 deletions(-) create mode 100644 src/components/interactive-builder/add-columns-modal.component.test.tsx create mode 100644 src/components/interactive-builder/add-package-modal.component.test.tsx create mode 100644 src/components/interactive-builder/add-submenu-modal.component.test.tsx create mode 100644 src/components/interactive-builder/configure-dashboard-modal.component.test.tsx create mode 100644 src/components/interactive-builder/delete-config-detail-modal.component.test.tsx create mode 100644 src/components/interactive-builder/edit-tab-definition-modal.test.tsx create mode 100644 src/components/interactive-builder/interactive-builder.component.test.tsx diff --git a/jest.config.js b/jest.config.js index ceaca2f..c472496 100644 --- a/jest.config.js +++ b/jest.config.js @@ -16,14 +16,15 @@ module.exports = { '^.+\\.tsx?$': ['@swc/jest'], }, transformIgnorePatterns: ['/node_modules/(?!@openmrs)'], - moduleNameMapper: { - '@openmrs/esm-framework': '@openmrs/esm-framework/mock', - '@openmrs/esm-utils': '@openmrs/esm-framework/mock', - '\\.(s?css)$': 'identity-obj-proxy', - '^lodash-es/(.*)$': 'lodash/$1', - 'lodash-es': 'lodash', - '^dexie$': require.resolve('dexie'), - }, +moduleNameMapper: { + '@openmrs/esm-framework': '@openmrs/esm-framework/mock', + '@openmrs/esm-utils': '@openmrs/esm-framework/mock', + '\\.(s?css)$': 'identity-obj-proxy', + '^lodash-es/(.*)$': 'lodash/$1', + 'lodash-es': 'lodash', + '^dexie$': require.resolve('dexie'), + '^@testing-library/jest-dom/extend-expect$': '@testing-library/jest-dom', // <-- Add this line +}, setupFilesAfterEnv: ['/src/setup-tests.ts'], testPathIgnorePatterns: [path.resolve(__dirname, 'e2e')], testEnvironment: 'jsdom', diff --git a/package.json b/package.json index 5b4f803..9489c2c 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "@swc/core": "^1.5.7", "@swc/jest": "^0.2.36", "@testing-library/dom": "^9.3.4", - "@testing-library/jest-dom": "^6.4.8", + "@testing-library/jest-dom": "^6.6.1", "@testing-library/react": "^14.3.1", "@testing-library/user-event": "^14.5.2", "@types/jest": "^29.5.12", diff --git a/src/components/interactive-builder/add-columns-modal.component.test.tsx b/src/components/interactive-builder/add-columns-modal.component.test.tsx new file mode 100644 index 0000000..2df749e --- /dev/null +++ b/src/components/interactive-builder/add-columns-modal.component.test.tsx @@ -0,0 +1,183 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import userEvent from '@testing-library/user-event'; +import { useTranslation } from 'react-i18next'; +import ConfigureDashboardModal from './add-columns-modal.component'; +import { showSnackbar } from '@openmrs/esm-framework'; +import { DefinitionTypes } from '../../types'; + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key) => key, + }), +})); + +jest.mock('@openmrs/esm-framework', () => ({ + isDesktop: jest.fn(() => true), + showSnackbar: jest.fn(), + useLayoutType: jest.fn(() => 'desktop'), +})); + +jest.mock('../../hooks/useForm', () => ({ + useForms: jest.fn(() => ({ + isLoadingForm: false, + forms: [{ display: 'Form 1' }], + formsError: null, + })), +})); + +jest.mock('../../hooks/useFormConcepts', () => ({ + useFormConcepts: jest.fn(() => ({ + isLoadingFormConcepts: false, + formConcepts: [{ concept: 'concept1', label: 'Concept 1' }], + formConceptsError: null, + mutate: jest.fn(), + })), +})); + +jest.mock('../../hooks/useEncounter', () => ({ + useEncounterTypes: jest.fn(() => ({ + isLoading: false, + encounterTypes: [{ uuid: 'encounter1', display: 'Encounter 1' }], + encounterTypesError: null, + })), +})); + +jest.mock('../../helpers', () => ({ + generateNodeId: jest.fn(() => 'generated-id'), +})); + +const mockProps = { + schema: { + '@openmrs/esm-patient-chart-app': { + extensionSlots: { + 'patient-chart-dashboard-slot': { + add: [], + configure: { + slot1: { + title: 'Slot 1 Title', + slotName: 'slot1', + tabDefinitions: [ + { + id: 'tab1', + tabName: 'Tab 1', + headerTitle: 'Tab 1 Header', + displayText: 'Display Text for Tab 1', + encounterType: 'Encounter 1', + hasFilter: false, + columns: [], + launchOptions: { displayText: 'Launch Tab 1' }, + formList: [], + }, + ], + }, + }, + }, + slot1: { + add: [], + configure: { + slot1: { + title: 'Slot 1 Title', + slotName: 'slot1', + tabDefinitions: [ + { + id: 'tab1', + tabName: 'Tab 1', + headerTitle: 'Tab 1 Header', + displayText: 'Display Text for Tab 1', + encounterType: 'Encounter 1', + hasFilter: false, + columns: [], + launchOptions: { displayText: 'Launch Tab 1' }, + formList: [], + }, + ], + }, + }, + }, + }, + }, + }, + onSchemaChange: jest.fn(), + closeModal: jest.fn(), + slotDetails: { + slot: 'slot1', + title: 'Dashboard Slot 1', + path: '/dashboard/slot1', + }, + tabDefinition: { + id: 'tab1', + tabName: 'Tab 1', + headerTitle: 'Tab 1 Header', + displayText: 'Display Text for Tab 1', + encounterType: 'Encounter 1', + columns: [], + launchOptions: { displayText: 'Launch Tab 1' }, + formList: [], + }, + definitionType: DefinitionTypes.TAB_DEFINITION, // Use the enum value +}; + +describe('ConfigureDashboardModal', () => { + const user = userEvent.setup(); // Initialize userEvent + + it('renders without crashing', () => { + render(); + expect(screen.getByText('createNewSubMenu')).toBeInTheDocument(); + }); + + it('handles form input changes', async () => { + render(); + + await user.type(screen.getByLabelText('columnTitle'), 'New Column'); + await user.selectOptions(screen.getByLabelText('selectConcept'), 'concept1'); + await user.selectOptions(screen.getByLabelText('selectEncounterType'), 'Encounter 1'); + + expect(screen.getByLabelText('columnTitle')).toHaveValue('New Column'); + expect(screen.getByLabelText('selectConcept')).toHaveValue('concept1'); + expect(screen.getByLabelText('selectEncounterType')).toHaveValue('Encounter 1'); + }); + + it('calls updateSchema and closeModal on create column', async () => { + render(); + + await user.type(screen.getByLabelText('columnTitle'), 'New Column'); + await user.selectOptions(screen.getByLabelText('selectConcept'), 'concept1'); + await user.selectOptions(screen.getByLabelText('selectEncounterType'), 'Encounter 1'); + + await user.click(screen.getByText('createSubMenu')); + + expect(mockProps.onSchemaChange).toHaveBeenCalled(); + expect(mockProps.closeModal).toHaveBeenCalled(); + expect(showSnackbar).toHaveBeenCalledWith({ + title: 'success', + kind: 'success', + isLowContrast: true, + subtitle: 'tabColumnsCreated', + }); + }); + + it('shows error snackbar on schema update failure', async () => { + const errorProps = { + ...mockProps, + onSchemaChange: () => { + throw new Error('Test Error'); + }, + }; + + render(); + + await user.type(screen.getByLabelText('columnTitle'), 'New Column'); + await user.selectOptions(screen.getByLabelText('selectConcept'), 'concept1'); + await user.selectOptions(screen.getByLabelText('selectEncounterType'), 'Encounter 1'); + + await user.click(screen.getByText('createSubMenu')); + + expect(showSnackbar).toHaveBeenCalledWith({ + title: 'errorCreatingTabColumns', + kind: 'error', + subtitle: 'Test Error', + }); + }); +}); diff --git a/src/components/interactive-builder/add-columns-modal.component.tsx b/src/components/interactive-builder/add-columns-modal.component.tsx index 5eb5833..8f42003 100644 --- a/src/components/interactive-builder/add-columns-modal.component.tsx +++ b/src/components/interactive-builder/add-columns-modal.component.tsx @@ -160,10 +160,10 @@ const ConfigureDashboardModal: React.FC = ({ setEncounterType(event.target.value); }} > - {!encounterType && } + {!encounterType && } {encounterTypes.length === 0 || (encounterTypesError && ( - + ))} {encounterTypes?.length > 0 && encounterTypes.map((encounterType) => ( @@ -193,9 +193,11 @@ const ConfigureDashboardModal: React.FC = ({ setColumnConcept(event.target.value); }} > - {!columnConcept && } + {!columnConcept && } {formConcepts.length === 0 || - (formConceptsError && )} + (formConceptsError && ( + + ))} {formConcepts?.length > 0 && formConcepts.map((concept) => ( @@ -216,21 +218,20 @@ const ConfigureDashboardModal: React.FC = ({ name="isDate" orientation="horizontal" legendText={t('isDate', 'Is date')} - className={styles.label} - defaultSelected={isColumnDate} - onChange={(event) => setIsColumnDate(event.toString())} + defaultSelected={isColumnDate ? 'true' : 'false'} // Cast boolean to string + onChange={(value) => setIsColumnDate(value === 'true')} // Convert string back to boolean > @@ -239,20 +240,20 @@ const ConfigureDashboardModal: React.FC = ({ orientation="horizontal" legendText={t('isLink', 'Is link')} className={styles.label} - defaultSelected={isColumnLink} - onChange={(event) => setIsColumnLink(event.toString())} + defaultSelected={isColumnLink ? 'true' : 'false'} // Cast boolean to string + onChange={(value) => setIsColumnLink(value === 'true')} // Convert string back to boolean > diff --git a/src/components/interactive-builder/add-package-modal.component.test.tsx b/src/components/interactive-builder/add-package-modal.component.test.tsx new file mode 100644 index 0000000..ba9018f --- /dev/null +++ b/src/components/interactive-builder/add-package-modal.component.test.tsx @@ -0,0 +1,114 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import { useTranslation } from 'react-i18next'; +import { showSnackbar } from '@openmrs/esm-framework'; +import PackageModal from './add-package-modal.component'; +import type { Schema } from '../../types'; + +jest.mock('react-i18next', () => ({ + useTranslation: jest.fn(), +})); + +jest.mock('@openmrs/esm-framework', () => ({ + showSnackbar: jest.fn(), +})); + +jest.mock('../../helpers', () => ({ + isValidSlotName: jest.fn(), + toCamelCase: jest.fn(), +})); + +const mockUseTranslation = useTranslation as jest.Mock; +const mockShowSnackbar = showSnackbar as jest.Mock; +const mockIsValidSlotName = require('../../helpers').isValidSlotName as jest.Mock; +const mockToCamelCase = require('../../helpers').toCamelCase as jest.Mock; + +describe('PackageModal', () => { + const closeModal = jest.fn(); + const onSchemaChange = jest.fn(); + const schema: Schema = { + '@openmrs/esm-patient-chart-app': { + extensionSlots: { + 'patient-chart-dashboard-slot': { + add: [], + configure: {}, + }, + }, + }, + }; + + beforeEach(() => { + mockUseTranslation.mockReturnValue({ t: (key: string) => key }); + mockIsValidSlotName.mockReturnValue(true); + mockToCamelCase.mockReturnValue('camelCaseTitle'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('renders the modal with initial state', () => { + render(); + + expect(screen.getByLabelText('enterClinicalViewTitle')).toBeInTheDocument(); + expect(screen.getByLabelText('enterSlotName')).toBeInTheDocument(); + expect(screen.getByText('cancel')).toBeInTheDocument(); + expect(screen.getByText('save')).toBeInTheDocument(); + }); + + it('updates key when title changes', () => { + render(); + + fireEvent.change(screen.getByLabelText('enterClinicalViewTitle'), { target: { value: 'New Title' } }); + + expect(mockToCamelCase).toHaveBeenCalledWith('New Title'); + }); + + it('shows error when slot name is invalid', () => { + mockIsValidSlotName.mockReturnValue(false); + + render(); + + fireEvent.change(screen.getByLabelText('enterSlotName'), { target: { value: 'Invalid Slot Name' } }); + + expect(screen.getByText('invalidSlotName')).toBeInTheDocument(); + }); + + it('calls updatePackages and closeModal on save', () => { + render(); + + fireEvent.change(screen.getByLabelText('enterClinicalViewTitle'), { target: { value: 'New Title' } }); + fireEvent.change(screen.getByLabelText('enterSlotName'), { target: { value: 'valid-slot-name' } }); + + fireEvent.click(screen.getByText('save')); + + expect(onSchemaChange).toHaveBeenCalled(); + expect(closeModal).toHaveBeenCalled(); + expect(mockShowSnackbar).toHaveBeenCalledWith({ + title: 'success', + kind: 'success', + isLowContrast: true, + subtitle: 'packageCreated', + }); + }); + + it('shows error snackbar on updatePackages error', () => { + onSchemaChange.mockImplementation(() => { + throw new Error('Test error'); + }); + + render(); + + fireEvent.change(screen.getByLabelText('enterClinicalViewTitle'), { target: { value: 'New Title' } }); + fireEvent.change(screen.getByLabelText('enterSlotName'), { target: { value: 'valid-slot-name' } }); + + fireEvent.click(screen.getByText('save')); + + expect(mockShowSnackbar).toHaveBeenCalledWith({ + title: 'errorCreatingPackage', + kind: 'error', + subtitle: 'Test error', + }); + }); +}); diff --git a/src/components/interactive-builder/add-submenu-modal.component.test.tsx b/src/components/interactive-builder/add-submenu-modal.component.test.tsx new file mode 100644 index 0000000..37f13c6 --- /dev/null +++ b/src/components/interactive-builder/add-submenu-modal.component.test.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import { useTranslation } from 'react-i18next'; +import { showSnackbar } from '@openmrs/esm-framework'; +import NewSubMenuModal from './add-submenu-modal.component'; +import { type Schema } from '../../types'; + +jest.mock('react-i18next', () => ({ + useTranslation: jest.fn(), +})); + +jest.mock('@openmrs/esm-framework', () => ({ + showSnackbar: jest.fn(), +})); + +const mockUseTranslation = useTranslation as jest.Mock; +const mockShowSnackbar = showSnackbar as jest.Mock; + +describe('NewSubMenuModal', () => { + const schema: Schema = { + '@openmrs/esm-patient-chart-app': { + extensionSlots: { + 'patient-chart-dashboard-slot': { + add: [], + configure: { + 'nav-group#example': { + slotName: 'example-slot', + title: 'Example Title', // Add the missing title property here + }, + }, + }, + }, + }, + }; + + const onSchemaChange = jest.fn(); + const closeModal = jest.fn(); + + beforeEach(() => { + mockUseTranslation.mockReturnValue({ + t: (key: string) => key, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('renders the modal with input fields', () => { + render(); + + expect(screen.getByLabelText('programIdentifier')).toBeInTheDocument(); + expect(screen.getByLabelText('menuName')).toBeInTheDocument(); + }); + + it('displays an error snackbar if slot name is not found', async () => { + const schemaWithoutSlot = { + '@openmrs/esm-patient-chart-app': { + extensionSlots: { + 'patient-chart-dashboard-slot': { + add: [], + configure: {}, + }, // Add 'patient-chart-dashboard-slot' here, even if it's empty + }, + }, + }; + + render(); + + await waitFor(() => { + expect(mockShowSnackbar).toHaveBeenCalledWith({ + title: 'errorFindingSlotName', + kind: 'error', + subtitle: 'slotNameNotFound', + }); + }); + }); + + it('updates schema and shows success snackbar on valid input', async () => { + render(); + + fireEvent.change(screen.getByLabelText('programIdentifier'), { target: { value: 'Hiv Care and Treatment' } }); + fireEvent.change(screen.getByLabelText('menuName'), { target: { value: 'Patient Summary' } }); + + fireEvent.click(screen.getByText('createSubMenu')); + + await waitFor(() => { + expect(onSchemaChange).toHaveBeenCalled(); + expect(mockShowSnackbar).toHaveBeenCalledWith({ + title: 'success', + kind: 'success', + isLowContrast: true, + subtitle: 'submenuCreated', + }); + expect(closeModal).toHaveBeenCalled(); + }); + }); + + it('disables create button if inputs are empty', () => { + render(); + + // Find the button by its role instead of using getByText + const createButton = screen.getByRole('button', { name: 'createSubMenu' }); + + expect(createButton).toBeDisabled(); // This should now work as you're targeting the actual button + }); +}); diff --git a/src/components/interactive-builder/configure-dashboard-modal.component.test.tsx b/src/components/interactive-builder/configure-dashboard-modal.component.test.tsx new file mode 100644 index 0000000..1d074d6 --- /dev/null +++ b/src/components/interactive-builder/configure-dashboard-modal.component.test.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import userEvent from '@testing-library/user-event'; +import ConfigureDashboardModal from './configure-dashboard-modal.component'; +import { showSnackbar } from '@openmrs/esm-framework'; + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +jest.mock('@openmrs/esm-framework', () => ({ + showSnackbar: jest.fn(), +})); + +const mockSchema = { + '@openmrs/esm-patient-chart-app': { + extensionSlots: { + 'patient-chart-dashboard-slot': { + add: [], + configure: {}, + }, + }, + }, +}; + +const mockOnSchemaChange = jest.fn(); +const mockCloseModal = jest.fn(); + +describe('ConfigureDashboardModal', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders the modal with initial state', () => { + render( + , + ); + + expect(screen.getByLabelText('slotName')).toHaveValue('test-slot'); + expect(screen.getByLabelText('selectWidget')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /createSubMenu/i })).toBeDisabled(); + }); + + it('enables the create button when a widget is selected', async () => { + render( + , + ); + + const selectWidget = screen.getByLabelText('selectWidget'); + await userEvent.selectOptions(selectWidget, 'encounter-list-table-tabs'); + + expect(screen.getByText('createSubMenu')).toBeEnabled(); + }); + + it('calls onSchemaChange and closeModal when create button is clicked', async () => { + render( + , + ); + + await userEvent.selectOptions(screen.getByLabelText('selectWidget'), 'encounter-list-table-tabs'); + await userEvent.type(screen.getByLabelText('tabName'), 'Test Tab'); + await userEvent.type(screen.getByLabelText('displayTitle'), 'Test Display Title'); + + await userEvent.click(screen.getByText('createSubMenu')); + + expect(mockOnSchemaChange).toHaveBeenCalled(); + expect(mockCloseModal).toHaveBeenCalled(); + expect(showSnackbar).toHaveBeenCalledWith({ + title: 'success', + kind: 'success', + isLowContrast: true, + subtitle: 'submenuCreated', + }); + }); + + it('shows error snackbar when updateSchema throws an error', async () => { + const faultySchema = { + ...mockSchema, + '@openmrs/esm-patient-chart-app': null, + }; + + render( + , + ); + + await userEvent.selectOptions(screen.getByLabelText('selectWidget'), 'encounter-list-table-tabs'); + await userEvent.type(screen.getByLabelText('tabName'), 'Test Tab'); + await userEvent.type(screen.getByLabelText('displayTitle'), 'Test Display Title'); + + await userEvent.click(screen.getByText('createSubMenu')); + + expect(showSnackbar).toHaveBeenCalledWith({ + title: 'errorCreatingSubmenu', + kind: 'error', + subtitle: expect.any(String), + }); + }); +}); diff --git a/src/components/interactive-builder/configure-dashboard-modal.component.tsx b/src/components/interactive-builder/configure-dashboard-modal.component.tsx index ee321cc..a6486b8 100644 --- a/src/components/interactive-builder/configure-dashboard-modal.component.tsx +++ b/src/components/interactive-builder/configure-dashboard-modal.component.tsx @@ -135,7 +135,7 @@ const ConfigureDashboardModal: React.FC = ({ setSelectedWidget(event.target.value); }} > - {!selectedWidget && } + {!selectedWidget && } {availableWidgets.length === 0 && } {availableWidgets?.length > 0 && availableWidgets.map((widget) => ( diff --git a/src/components/interactive-builder/delete-config-detail-modal.component.test.tsx b/src/components/interactive-builder/delete-config-detail-modal.component.test.tsx new file mode 100644 index 0000000..caa08cb --- /dev/null +++ b/src/components/interactive-builder/delete-config-detail-modal.component.test.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import DeleteConfigDetailModal from './delete-config-detail-modal.component'; +import { showSnackbar } from '@openmrs/esm-framework'; +import { useTranslation } from 'react-i18next'; +import { WidgetTypes } from '../../types'; + +jest.mock('@openmrs/esm-framework', () => ({ + showSnackbar: jest.fn(), +})); + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})); + +const mockCloseModal = jest.fn(); +const mockOnSchemaChange = jest.fn(); + +const mockProps = { + closeModal: mockCloseModal, + schema: { + '@openmrs/esm-patient-chart-app': { + extensionSlots: { + 'patient-chart-dashboard-slot': { + configure: { + configKey1: { + title: 'Sample Title', // Required property + slotName: 'sample-slot', // Required property + isExpanded: true, // Optional + tabDefinitions: [ + { + id: 'tab1', // Add id property here + tabName: 'tab1', + headerTitle: 'Header 1', + displayText: 'Tab 1', + encounterType: 'encounter1', + columns: [], + launchOptions: { displayText: 'Launch' }, + formList: [], + }, + ], + }, + }, + }, + slot1: { + configure: { + configKey1: { + title: 'Another Title', // Add required title here + slotName: 'slot1', // Add required slotName here + widgetType1: [{ tabName: 'tab1' }], + }, + }, + }, + }, + }, + }, + onSchemaChange: mockOnSchemaChange, + slotDetails: { + title: 'Sample Slot Title', // Required by DashboardConfig + path: 'sample/slot/path', // Required by DashboardConfig + slot: 'slot1', // Slot identifier + }, + tabDefinition: { + id: 'tab1', // Add required id + tabName: 'tab1', + headerTitle: 'Header 1', + displayText: 'Tab 1', // Add required displayText + encounterType: 'encounter1', // Add required encounterType + columns: [], // Add required columns + launchOptions: { displayText: 'Launch' }, // Add required launchOptions + formList: [], // Add required formList + }, + configurationKey: 'configKey1', + widgetType: WidgetTypes.ENCOUNTER_LIST_TABLE_TABS, // Updated to match WidgetTypes +}; + +describe('DeleteConfigDetailModal', () => { + it('renders the modal with correct text', () => { + render(); + + expect(screen.getByText('deleteConfigDetailsConfirmationText')).toBeInTheDocument(); + expect(screen.getByText('menuSlot : slot1')).toBeInTheDocument(); + expect(screen.getByText('tabName : tab1')).toBeInTheDocument(); + expect(screen.getByText('headerTitle : Header 1')).toBeInTheDocument(); + }); + + it('calls closeModal when cancel button is clicked', () => { + render(); + + fireEvent.click(screen.getByText('cancel')); + expect(mockCloseModal).toHaveBeenCalled(); + }); +}); diff --git a/src/components/interactive-builder/edit-tab-definition-modal.test.tsx b/src/components/interactive-builder/edit-tab-definition-modal.test.tsx new file mode 100644 index 0000000..ddccb8f --- /dev/null +++ b/src/components/interactive-builder/edit-tab-definition-modal.test.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import { I18nextProvider } from 'react-i18next'; +import i18n from 'i18next'; +import EditTabDefinitionModal from './edit-tab-definition-modal'; + +const renderWithI18n = (ui) => { + return render({ui}); +}; + +describe('EditTabDefinitionModal', () => { + const mockOnSave = jest.fn(); + const mockOnCancel = jest.fn(); + const tabDefinition = { tabName: 'Initial Tab', headerTitle: 'Initial Header' }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders with initial values', () => { + const { getByLabelText } = renderWithI18n( + , + ); + + expect(getByLabelText('Tab Name')).toHaveValue('Initial Tab'); + expect(getByLabelText('Header Title')).toHaveValue('Initial Header'); + }); + + it('calls onSave with updated values', () => { + const { getByLabelText, getByText } = renderWithI18n( + , + ); + + fireEvent.change(getByLabelText('Tab Name'), { target: { value: 'Updated Tab' } }); + fireEvent.change(getByLabelText('Header Title'), { target: { value: 'Updated Header' } }); + + fireEvent.click(getByText('Save')); + + expect(mockOnSave).toHaveBeenCalledWith({ + ...tabDefinition, + tabName: 'Updated Tab', + headerTitle: 'Updated Header', + }); + }); + + it('calls onCancel when cancel button is clicked', () => { + const { getByText } = renderWithI18n( + , + ); + + fireEvent.click(getByText('Cancel')); + + expect(mockOnCancel).toHaveBeenCalled(); + }); +}); diff --git a/src/components/interactive-builder/interactive-builder.component.test.tsx b/src/components/interactive-builder/interactive-builder.component.test.tsx new file mode 100644 index 0000000..31fb98b --- /dev/null +++ b/src/components/interactive-builder/interactive-builder.component.test.tsx @@ -0,0 +1,99 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import { showModal, showSnackbar } from '@openmrs/esm-framework'; +import InteractiveBuilder from './interactive-builder.component'; +import { v4 as uuidv4 } from 'uuid'; +import { type Schema } from '../../types'; + +jest.mock('@openmrs/esm-framework', () => ({ + showModal: jest.fn(), + showSnackbar: jest.fn(), +})); + +jest.mock('uuid', () => ({ + v4: jest.fn(), +})); + +describe('InteractiveBuilder', () => { + const mockOnSchemaChange = jest.fn(); + const mockSchema: Schema = { + id: uuidv4(), + '@openmrs/esm-patient-chart-app': { + extensionSlots: { + 'patient-chart-dashboard-slot': { + add: ['navGroup1'], // This ensures that submenus are added + configure: { + navGroup1: { + title: 'Sample Clinical View', + slotName: 'sample-slot', + tabDefinitions: [ + { + id: 'tab1', + tabName: 'Tab 1', + headerTitle: 'Header 1', + displayText: 'Tab 1 Display Text', + encounterType: 'encounter-type-1', + columns: [ + { + id: 'col1', + title: 'Column 1', + concept: 'Concept 1', + isDate: false, + isLink: false, + }, + ], + launchOptions: { displayText: 'Launch' }, + formList: [], + }, + ], + }, + }, + }, + }, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders interactive builder with initial state and start button', () => { + render(); + + // Check that the explainer text and start button are rendered + expect(screen.getByText(/interactive builder/i)).toBeInTheDocument(); + expect(screen.getByText(/start building/i)).toBeInTheDocument(); + }); + + it('opens the "Add Clinical View" modal when start button is clicked', () => { + render(); + + // Simulate click on start button + fireEvent.click(screen.getByText(/start building/i)); + + // Expect showModal to be called with correct arguments + expect(showModal).toHaveBeenCalledWith( + 'new-package-modal', + expect.objectContaining({ + onSchemaChange: mockOnSchemaChange, + }), + ); + }); + + it('opens the "Add Submenu" modal when "Add Submenu" button is clicked', () => { + render(); + + // Simulate click on "Add Submenu" button + fireEvent.click(screen.getByText(/add clinical view submenu/i)); + + // Expect showModal to be called with correct arguments + expect(showModal).toHaveBeenCalledWith( + 'new-menu-modal', + expect.objectContaining({ + schema: mockSchema, + onSchemaChange: mockOnSchemaChange, + }), + ); + }); +}); diff --git a/yarn.lock b/yarn.lock index 05c07eb..cb8cbbc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3127,7 +3127,7 @@ __metadata: "@swc/core": "npm:^1.5.7" "@swc/jest": "npm:^0.2.36" "@testing-library/dom": "npm:^9.3.4" - "@testing-library/jest-dom": "npm:^6.4.8" + "@testing-library/jest-dom": "npm:^6.6.1" "@testing-library/react": "npm:^14.3.1" "@testing-library/user-event": "npm:^14.5.2" "@types/jest": "npm:^29.5.12" @@ -5722,9 +5722,9 @@ __metadata: languageName: node linkType: hard -"@testing-library/jest-dom@npm:^6.4.8": - version: 6.5.0 - resolution: "@testing-library/jest-dom@npm:6.5.0" +"@testing-library/jest-dom@npm:^6.6.1": + version: 6.6.1 + resolution: "@testing-library/jest-dom@npm:6.6.1" dependencies: "@adobe/css-tools": "npm:^4.4.0" aria-query: "npm:^5.0.0" @@ -5733,7 +5733,7 @@ __metadata: dom-accessibility-api: "npm:^0.6.3" lodash: "npm:^4.17.21" redent: "npm:^3.0.0" - checksum: 10/3d2080888af5fd7306f57448beb5a23f55d965e265b5e53394fffc112dfb0678d616a5274ff0200c46c7618f293520f86fc8562eecd8bdbc0dbb3294d63ec431 + checksum: 10/006357d7547cc50624564a1b0e9986d0f0252365a6e5ac1c7c989032159c47226adfbd9a9ac17eef236738e9597541d344176176b5c3ed0e06e8f4b33387e135 languageName: node linkType: hard From e2f16783638cd8bc431a10742a675fd399fe77c0 Mon Sep 17 00:00:00 2001 From: AJAL ODORA JONATHAN <43242517+ODORA0@users.noreply.github.com> Date: Fri, 18 Oct 2024 13:43:37 +0300 Subject: [PATCH 2/2] Remove comments and change from fireEvent to userEvent --- jest.config.js | 2 +- .../add-columns-modal.component.test.tsx | 4 +- .../add-columns-modal.component.tsx | 18 ++++---- .../add-package-modal.component.test.tsx | 31 ++++++++------ .../add-submenu-modal.component.test.tsx | 18 ++++---- ...ete-config-detail-modal.component.test.tsx | 42 +++++++++---------- .../edit-tab-definition-modal.test.tsx | 22 ++++++---- .../interactive-builder.component.test.tsx | 20 ++++----- 8 files changed, 82 insertions(+), 75 deletions(-) diff --git a/jest.config.js b/jest.config.js index c472496..6b7f3be 100644 --- a/jest.config.js +++ b/jest.config.js @@ -23,7 +23,7 @@ moduleNameMapper: { '^lodash-es/(.*)$': 'lodash/$1', 'lodash-es': 'lodash', '^dexie$': require.resolve('dexie'), - '^@testing-library/jest-dom/extend-expect$': '@testing-library/jest-dom', // <-- Add this line + '^@testing-library/jest-dom/extend-expect$': '@testing-library/jest-dom', }, setupFilesAfterEnv: ['/src/setup-tests.ts'], testPathIgnorePatterns: [path.resolve(__dirname, 'e2e')], diff --git a/src/components/interactive-builder/add-columns-modal.component.test.tsx b/src/components/interactive-builder/add-columns-modal.component.test.tsx index 2df749e..fef61c3 100644 --- a/src/components/interactive-builder/add-columns-modal.component.test.tsx +++ b/src/components/interactive-builder/add-columns-modal.component.test.tsx @@ -116,11 +116,11 @@ const mockProps = { launchOptions: { displayText: 'Launch Tab 1' }, formList: [], }, - definitionType: DefinitionTypes.TAB_DEFINITION, // Use the enum value + definitionType: DefinitionTypes.TAB_DEFINITION, }; describe('ConfigureDashboardModal', () => { - const user = userEvent.setup(); // Initialize userEvent + const user = userEvent.setup(); it('renders without crashing', () => { render(); diff --git a/src/components/interactive-builder/add-columns-modal.component.tsx b/src/components/interactive-builder/add-columns-modal.component.tsx index 8f42003..ed61092 100644 --- a/src/components/interactive-builder/add-columns-modal.component.tsx +++ b/src/components/interactive-builder/add-columns-modal.component.tsx @@ -92,8 +92,6 @@ const ConfigureDashboardModal: React.FC = ({ }, }; onSchemaChange(updatedSchema); - - // Show success notification setColumnTitle(''); setColumnConcept(''); setIsColumnDate(false); @@ -218,20 +216,20 @@ const ConfigureDashboardModal: React.FC = ({ name="isDate" orientation="horizontal" legendText={t('isDate', 'Is date')} - defaultSelected={isColumnDate ? 'true' : 'false'} // Cast boolean to string - onChange={(value) => setIsColumnDate(value === 'true')} // Convert string back to boolean + defaultSelected={isColumnDate ? 'true' : 'false'} + onChange={(value) => setIsColumnDate(value === 'true')} > @@ -240,20 +238,20 @@ const ConfigureDashboardModal: React.FC = ({ orientation="horizontal" legendText={t('isLink', 'Is link')} className={styles.label} - defaultSelected={isColumnLink ? 'true' : 'false'} // Cast boolean to string - onChange={(value) => setIsColumnLink(value === 'true')} // Convert string back to boolean + defaultSelected={isColumnLink ? 'true' : 'false'} + onChange={(value) => setIsColumnLink(value === 'true')} > diff --git a/src/components/interactive-builder/add-package-modal.component.test.tsx b/src/components/interactive-builder/add-package-modal.component.test.tsx index ba9018f..d013aed 100644 --- a/src/components/interactive-builder/add-package-modal.component.test.tsx +++ b/src/components/interactive-builder/add-package-modal.component.test.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import { render, fireEvent, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { useTranslation } from 'react-i18next'; import { showSnackbar } from '@openmrs/esm-framework'; import PackageModal from './add-package-modal.component'; import type { Schema } from '../../types'; +import userEvent from '@testing-library/user-event'; jest.mock('react-i18next', () => ({ useTranslation: jest.fn(), @@ -57,31 +58,34 @@ describe('PackageModal', () => { expect(screen.getByText('save')).toBeInTheDocument(); }); - it('updates key when title changes', () => { + it('updates key when title changes', async () => { + const user = userEvent.setup(); render(); - fireEvent.change(screen.getByLabelText('enterClinicalViewTitle'), { target: { value: 'New Title' } }); + await user.type(screen.getByLabelText('enterClinicalViewTitle'), 'New Title'); expect(mockToCamelCase).toHaveBeenCalledWith('New Title'); }); - it('shows error when slot name is invalid', () => { + it('shows error when slot name is invalid', async () => { mockIsValidSlotName.mockReturnValue(false); + const user = userEvent.setup(); render(); - fireEvent.change(screen.getByLabelText('enterSlotName'), { target: { value: 'Invalid Slot Name' } }); + await user.type(screen.getByLabelText('enterSlotName'), 'Invalid Slot Name'); expect(screen.getByText('invalidSlotName')).toBeInTheDocument(); }); - it('calls updatePackages and closeModal on save', () => { + it('calls updatePackages and closeModal on save', async () => { + const user = userEvent.setup(); render(); - fireEvent.change(screen.getByLabelText('enterClinicalViewTitle'), { target: { value: 'New Title' } }); - fireEvent.change(screen.getByLabelText('enterSlotName'), { target: { value: 'valid-slot-name' } }); + await user.type(screen.getByLabelText('enterClinicalViewTitle'), 'New Title'); + await user.type(screen.getByLabelText('enterSlotName'), 'valid-slot-name'); - fireEvent.click(screen.getByText('save')); + await user.click(screen.getByText('save')); expect(onSchemaChange).toHaveBeenCalled(); expect(closeModal).toHaveBeenCalled(); @@ -93,17 +97,18 @@ describe('PackageModal', () => { }); }); - it('shows error snackbar on updatePackages error', () => { + it('shows error snackbar on updatePackages error', async () => { + const user = userEvent.setup(); onSchemaChange.mockImplementation(() => { throw new Error('Test error'); }); render(); - fireEvent.change(screen.getByLabelText('enterClinicalViewTitle'), { target: { value: 'New Title' } }); - fireEvent.change(screen.getByLabelText('enterSlotName'), { target: { value: 'valid-slot-name' } }); + await user.type(screen.getByLabelText('enterClinicalViewTitle'), 'New Title'); + await user.type(screen.getByLabelText('enterSlotName'), 'valid-slot-name'); - fireEvent.click(screen.getByText('save')); + await user.click(screen.getByText('save')); expect(mockShowSnackbar).toHaveBeenCalledWith({ title: 'errorCreatingPackage', diff --git a/src/components/interactive-builder/add-submenu-modal.component.test.tsx b/src/components/interactive-builder/add-submenu-modal.component.test.tsx index 37f13c6..173ed4d 100644 --- a/src/components/interactive-builder/add-submenu-modal.component.test.tsx +++ b/src/components/interactive-builder/add-submenu-modal.component.test.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { useTranslation } from 'react-i18next'; import { showSnackbar } from '@openmrs/esm-framework'; import NewSubMenuModal from './add-submenu-modal.component'; import { type Schema } from '../../types'; +import userEvent from '@testing-library/user-event'; jest.mock('react-i18next', () => ({ useTranslation: jest.fn(), @@ -26,7 +27,7 @@ describe('NewSubMenuModal', () => { configure: { 'nav-group#example': { slotName: 'example-slot', - title: 'Example Title', // Add the missing title property here + title: 'Example Title', }, }, }, @@ -61,7 +62,7 @@ describe('NewSubMenuModal', () => { 'patient-chart-dashboard-slot': { add: [], configure: {}, - }, // Add 'patient-chart-dashboard-slot' here, even if it's empty + }, }, }, }; @@ -78,12 +79,12 @@ describe('NewSubMenuModal', () => { }); it('updates schema and shows success snackbar on valid input', async () => { + const user = userEvent.setup(); render(); - fireEvent.change(screen.getByLabelText('programIdentifier'), { target: { value: 'Hiv Care and Treatment' } }); - fireEvent.change(screen.getByLabelText('menuName'), { target: { value: 'Patient Summary' } }); - - fireEvent.click(screen.getByText('createSubMenu')); + await user.type(screen.getByLabelText('programIdentifier'), 'Hiv Care and Treatment'); + await user.type(screen.getByLabelText('menuName'), 'Patient Summary'); + await user.click(screen.getByText('createSubMenu')); await waitFor(() => { expect(onSchemaChange).toHaveBeenCalled(); @@ -100,9 +101,8 @@ describe('NewSubMenuModal', () => { it('disables create button if inputs are empty', () => { render(); - // Find the button by its role instead of using getByText const createButton = screen.getByRole('button', { name: 'createSubMenu' }); - expect(createButton).toBeDisabled(); // This should now work as you're targeting the actual button + expect(createButton).toBeDisabled(); }); }); diff --git a/src/components/interactive-builder/delete-config-detail-modal.component.test.tsx b/src/components/interactive-builder/delete-config-detail-modal.component.test.tsx index caa08cb..8d05af4 100644 --- a/src/components/interactive-builder/delete-config-detail-modal.component.test.tsx +++ b/src/components/interactive-builder/delete-config-detail-modal.component.test.tsx @@ -1,10 +1,9 @@ import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import DeleteConfigDetailModal from './delete-config-detail-modal.component'; -import { showSnackbar } from '@openmrs/esm-framework'; -import { useTranslation } from 'react-i18next'; import { WidgetTypes } from '../../types'; +import userEvent from '@testing-library/user-event'; jest.mock('@openmrs/esm-framework', () => ({ showSnackbar: jest.fn(), @@ -27,12 +26,12 @@ const mockProps = { 'patient-chart-dashboard-slot': { configure: { configKey1: { - title: 'Sample Title', // Required property - slotName: 'sample-slot', // Required property - isExpanded: true, // Optional + title: 'Sample Title', + slotName: 'sample-slot', + isExpanded: true, tabDefinitions: [ { - id: 'tab1', // Add id property here + id: 'tab1', tabName: 'tab1', headerTitle: 'Header 1', displayText: 'Tab 1', @@ -48,8 +47,8 @@ const mockProps = { slot1: { configure: { configKey1: { - title: 'Another Title', // Add required title here - slotName: 'slot1', // Add required slotName here + title: 'Another Title', + slotName: 'slot1', widgetType1: [{ tabName: 'tab1' }], }, }, @@ -59,22 +58,22 @@ const mockProps = { }, onSchemaChange: mockOnSchemaChange, slotDetails: { - title: 'Sample Slot Title', // Required by DashboardConfig - path: 'sample/slot/path', // Required by DashboardConfig - slot: 'slot1', // Slot identifier + title: 'Sample Slot Title', + path: 'sample/slot/path', + slot: 'slot1', }, tabDefinition: { - id: 'tab1', // Add required id + id: 'tab1', tabName: 'tab1', headerTitle: 'Header 1', - displayText: 'Tab 1', // Add required displayText - encounterType: 'encounter1', // Add required encounterType - columns: [], // Add required columns - launchOptions: { displayText: 'Launch' }, // Add required launchOptions - formList: [], // Add required formList + displayText: 'Tab 1', + encounterType: 'encounter1', + columns: [], + launchOptions: { displayText: 'Launch' }, + formList: [], }, configurationKey: 'configKey1', - widgetType: WidgetTypes.ENCOUNTER_LIST_TABLE_TABS, // Updated to match WidgetTypes + widgetType: WidgetTypes.ENCOUNTER_LIST_TABLE_TABS, }; describe('DeleteConfigDetailModal', () => { @@ -87,10 +86,11 @@ describe('DeleteConfigDetailModal', () => { expect(screen.getByText('headerTitle : Header 1')).toBeInTheDocument(); }); - it('calls closeModal when cancel button is clicked', () => { + it('calls closeModal when cancel button is clicked', async () => { + const user = userEvent.setup(); render(); - fireEvent.click(screen.getByText('cancel')); + await user.click(screen.getByText('cancel')); expect(mockCloseModal).toHaveBeenCalled(); }); }); diff --git a/src/components/interactive-builder/edit-tab-definition-modal.test.tsx b/src/components/interactive-builder/edit-tab-definition-modal.test.tsx index ddccb8f..2ca112b 100644 --- a/src/components/interactive-builder/edit-tab-definition-modal.test.tsx +++ b/src/components/interactive-builder/edit-tab-definition-modal.test.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import { render, fireEvent } from '@testing-library/react'; +import { render } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { I18nextProvider } from 'react-i18next'; import i18n from 'i18next'; import EditTabDefinitionModal from './edit-tab-definition-modal'; +import userEvent from '@testing-library/user-event'; -const renderWithI18n = (ui) => { +const renderWithI18n = (ui: React.ReactElement) => { return render({ui}); }; @@ -27,15 +28,19 @@ describe('EditTabDefinitionModal', () => { expect(getByLabelText('Header Title')).toHaveValue('Initial Header'); }); - it('calls onSave with updated values', () => { + it('calls onSave with updated values', async () => { + const user = userEvent.setup(); const { getByLabelText, getByText } = renderWithI18n( , ); - fireEvent.change(getByLabelText('Tab Name'), { target: { value: 'Updated Tab' } }); - fireEvent.change(getByLabelText('Header Title'), { target: { value: 'Updated Header' } }); + await user.clear(getByLabelText('Tab Name')); + await user.type(getByLabelText('Tab Name'), 'Updated Tab'); - fireEvent.click(getByText('Save')); + await user.clear(getByLabelText('Header Title')); + await user.type(getByLabelText('Header Title'), 'Updated Header'); + + await user.click(getByText('Save')); expect(mockOnSave).toHaveBeenCalledWith({ ...tabDefinition, @@ -44,12 +49,13 @@ describe('EditTabDefinitionModal', () => { }); }); - it('calls onCancel when cancel button is clicked', () => { + it('calls onCancel when cancel button is clicked', async () => { + const user = userEvent.setup(); const { getByText } = renderWithI18n( , ); - fireEvent.click(getByText('Cancel')); + await user.click(getByText('Cancel')); expect(mockOnCancel).toHaveBeenCalled(); }); diff --git a/src/components/interactive-builder/interactive-builder.component.test.tsx b/src/components/interactive-builder/interactive-builder.component.test.tsx index 31fb98b..f1f1821 100644 --- a/src/components/interactive-builder/interactive-builder.component.test.tsx +++ b/src/components/interactive-builder/interactive-builder.component.test.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { showModal, showSnackbar } from '@openmrs/esm-framework'; import InteractiveBuilder from './interactive-builder.component'; import { v4 as uuidv4 } from 'uuid'; import { type Schema } from '../../types'; +import userEvent from '@testing-library/user-event'; jest.mock('@openmrs/esm-framework', () => ({ showModal: jest.fn(), @@ -22,7 +23,7 @@ describe('InteractiveBuilder', () => { '@openmrs/esm-patient-chart-app': { extensionSlots: { 'patient-chart-dashboard-slot': { - add: ['navGroup1'], // This ensures that submenus are added + add: ['navGroup1'], configure: { navGroup1: { title: 'Sample Clinical View', @@ -61,18 +62,16 @@ describe('InteractiveBuilder', () => { it('renders interactive builder with initial state and start button', () => { render(); - // Check that the explainer text and start button are rendered expect(screen.getByText(/interactive builder/i)).toBeInTheDocument(); expect(screen.getByText(/start building/i)).toBeInTheDocument(); }); - it('opens the "Add Clinical View" modal when start button is clicked', () => { + it('opens the "Add Clinical View" modal when start button is clicked', async () => { + const user = userEvent.setup(); render(); - // Simulate click on start button - fireEvent.click(screen.getByText(/start building/i)); + await user.click(screen.getByText(/start building/i)); - // Expect showModal to be called with correct arguments expect(showModal).toHaveBeenCalledWith( 'new-package-modal', expect.objectContaining({ @@ -81,13 +80,12 @@ describe('InteractiveBuilder', () => { ); }); - it('opens the "Add Submenu" modal when "Add Submenu" button is clicked', () => { + it('opens the "Add Submenu" modal when "Add Submenu" button is clicked', async () => { + const user = userEvent.setup(); render(); - // Simulate click on "Add Submenu" button - fireEvent.click(screen.getByText(/add clinical view submenu/i)); + await user.click(screen.getByText(/add clinical view submenu/i)); - // Expect showModal to be called with correct arguments expect(showModal).toHaveBeenCalledWith( 'new-menu-modal', expect.objectContaining({