Skip to content

Commit ccb94fb

Browse files
authored
(test) Add some missing test cases and improve typing in test files (#1239)
This PR improves test coverage and standardizes test descriptions across Core. Key changes include: - Adding coverage for missing test cases in the Devtools, Help Menu, and Implementer Tools test suites among others. - Standardizing test case descriptions across all modules. - Improving TypeScript types in test files. - Using `jest.mocked()` instead of direct type assertions for mocks as suggested in our [mocking patterns](https://o3-docs.openmrs.org/docs/frontend-modules/unit-and-integration-testing#mocking-patterns) guide. - Adding proper type annotations for mock data and responses - Cleaning up redundant type casts and improving mock implementations - Maintaining consistent naming conventions for mock variables (mock* prefix) - Adding missing type information to test utilities and helpers
1 parent 7269bbf commit ccb94fb

File tree

47 files changed

+943
-385
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+943
-385
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,75 @@
11
import React from 'react';
2+
import '@testing-library/jest-dom';
3+
import userEvent from '@testing-library/user-event';
4+
import { type AppProps } from 'single-spa';
5+
import { render, screen } from '@testing-library/react';
26
import Root from './devtools.component';
3-
import { render } from '@testing-library/react';
47

5-
describe(`<Root />`, () => {
6-
it(`renders without dying`, () => {
7-
render(<Root />);
8+
jest.mock('./import-map.component', () => ({
9+
__esModule: true,
10+
default: () => <div role="dialog">Mock Import Map</div>,
11+
importMapOverridden: false,
12+
}));
13+
14+
const defaultProps: AppProps = {
15+
name: '@openmrs/esm-devtools-app-page-0',
16+
singleSpa: {},
17+
mountParcel: jest.fn(),
18+
};
19+
20+
describe('DevTools', () => {
21+
beforeEach(() => {
22+
localStorage.clear();
23+
delete window.spaEnv;
24+
jest.resetModules();
25+
});
26+
27+
describe('Root component', () => {
28+
it('should not render DevTools in production without the devtools localStorage flag', () => {
29+
window.spaEnv = 'production';
30+
31+
const { container } = render(<Root {...defaultProps} />);
32+
expect(container).toBeEmptyDOMElement();
33+
});
34+
35+
it('should render DevTools in development environments', () => {
36+
window.spaEnv = 'development';
37+
38+
render(<Root {...defaultProps} />);
39+
40+
expect(screen.getByRole('button', { name: '{···}' })).toBeInTheDocument();
41+
});
42+
43+
it('should render DevTools when the devtools localStorage flag is set', () => {
44+
localStorage.setItem('openmrs:devtools', 'true');
45+
46+
render(<Root {...defaultProps} />);
47+
48+
expect(screen.getByRole('button', { name: '{···}' })).toBeInTheDocument();
49+
});
50+
});
51+
52+
describe('DevTools component', () => {
53+
const user = userEvent.setup();
54+
55+
beforeEach(() => {
56+
window.spaEnv = 'development';
57+
});
58+
59+
it('should toggle DevToolsPopup when clicking trigger button', async () => {
60+
render(<Root {...defaultProps} />);
61+
62+
const triggerButton = screen.getByRole('button', { name: '{···}' });
63+
// Initially, popup should not be present
64+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
65+
66+
// Click to open
67+
await user.click(triggerButton);
68+
expect(screen.getByRole('dialog')).toBeInTheDocument();
69+
70+
// Click to close
71+
await user.click(triggerButton);
72+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
73+
});
874
});
975
});

packages/apps/esm-devtools-app/src/devtools/devtools.component.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import React, { useState } from 'react';
22
import classNames from 'classnames';
3+
import { type AppProps } from 'single-spa';
34
import { importMapOverridden } from './import-map.component';
45
import DevToolsPopup from './devtools-popup.component';
56
import styles from './devtools.styles.css';
67

7-
export default function Root(props) {
8+
export default function Root(props: AppProps) {
89
return window.spaEnv === 'development' || Boolean(localStorage.getItem('openmrs:devtools')) ? (
910
<DevTools {...props} />
1011
) : null;
1112
}
1213

13-
function DevTools() {
14+
function DevTools(props: AppProps) {
1415
const [devToolsOpen, setDevToolsOpen] = useState(false);
1516
const [isOverridden, setIsOverridden] = useState(importMapOverridden);
17+
1618
return (
1719
<>
1820
<div

packages/apps/esm-devtools-app/src/devtools/import-map.component.tsx

+1-5
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,7 @@ export default function ImportMap(props: ImportMapProps) {
1414
}
1515
}, [props]);
1616

17-
return (
18-
<div className={styles.importMap}>
19-
<ImportMapList ref={importMapListRef} />
20-
</div>
21-
);
17+
return <div className={styles.importMap}>{<ImportMapList ref={importMapListRef} />}</div>;
2218
}
2319

2420
export function importMapOverridden(): boolean {

packages/apps/esm-help-menu-app/src/root.component.test.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import React from 'react';
22
import { render } from '@testing-library/react';
33
import Root from './root.component';
44

5-
describe(`<Root />`, () => {
6-
it(`renders without dying`, () => {
5+
describe('Root', () => {
6+
it('renders without dying', () => {
77
render(<Root />);
88
});
99
});

packages/apps/esm-help-menu-app/src/root.component.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ import HelpMenu from './help-menu/help.component';
44
const Root: React.FC = () => {
55
return <HelpMenu />;
66
};
7+
78
export default Root;

packages/apps/esm-implementer-tools-app/src/configuration/configuration.test.tsx

+12-6
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { implementerToolsConfigStore, temporaryConfigStore, Type } from '@openmr
66
import { Configuration } from './configuration.component';
77
import { useConceptLookup, useGetConceptByUuid } from './interactive-editor/value-editors/concept-search.resource';
88

9-
const mockUseConceptLookup = useConceptLookup as jest.Mock;
10-
const mockUseGetConceptByUuid = useGetConceptByUuid as jest.Mock;
9+
const mockUseConceptLookup = jest.mocked(useConceptLookup);
10+
const mockUseGetConceptByUuid = jest.mocked(useGetConceptByUuid);
1111

1212
jest.mock('./interactive-editor/value-editors/concept-search.resource', () => ({
1313
useConceptLookup: jest.fn().mockImplementation(() => ({
@@ -144,14 +144,20 @@ describe('Configuration', () => {
144144
const user = userEvent.setup();
145145

146146
mockUseConceptLookup.mockImplementation(() => ({
147-
concepts: [{ uuid: '61523693-72e2-456d-8c64-8c5293febeb6', display: 'Fedora' }],
148-
error: null,
147+
concepts: [{ uuid: '61523693-72e2-456d-8c64-8c5293febeb6', display: 'Fedora', answers: [], mappings: [] }],
148+
error: undefined,
149149
isSearchingConcepts: false,
150150
}));
151151

152152
mockUseGetConceptByUuid.mockImplementation(() => ({
153-
concept: { name: { display: 'Fedora' } },
154-
error: null,
153+
concept: {
154+
name: { display: 'Fedora' },
155+
display: 'Fedora',
156+
answers: [],
157+
mappings: [],
158+
uuid: '61523693-72e2-456d-8c64-8c5293febeb6',
159+
},
160+
error: undefined,
155161
isLoadingConcept: false,
156162
}));
157163

packages/apps/esm-implementer-tools-app/src/implementer-tools.test.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import React from 'react';
22
import { render } from '@testing-library/react';
33
import Root from './implementer-tools.component';
44

5-
describe(`<Root />`, () => {
6-
it(`renders without dying`, () => {
5+
describe('ImplementerTools', () => {
6+
it('renders without dying', () => {
77
render(<Root />);
88
});
99
});

packages/apps/esm-login-app/src/change-location-link/change-location-link.test.tsx

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import React from 'react';
22
import userEvent from '@testing-library/user-event';
33
import { render, screen } from '@testing-library/react';
4-
import { navigate, useSession } from '@openmrs/esm-framework';
4+
import { navigate, type Session, useSession } from '@openmrs/esm-framework';
55
import ChangeLocationLink from './change-location-link.extension';
66

7-
const navigateMock = navigate as jest.Mock;
8-
const useSessionMock = useSession as jest.Mock;
7+
const mockNavigate = jest.mocked(navigate);
8+
const mockUseSession = jest.mocked(useSession);
99

1010
delete window.location;
1111
window.location = new URL('https://dev3.openmrs.org/openmrs/spa/home') as unknown as Location;
1212

13-
describe('<ChangeLocationLink/>', () => {
13+
describe('ChangeLocationLink', () => {
1414
beforeEach(() => {
15-
useSessionMock.mockReturnValue({
15+
mockUseSession.mockReturnValue({
1616
sessionLocation: {
1717
display: 'Waffle House',
1818
},
19-
});
19+
} as Session);
2020
});
2121

2222
it('should display the `Change location` link', async () => {
@@ -29,7 +29,7 @@ describe('<ChangeLocationLink/>', () => {
2929

3030
await user.click(changeLocationButton);
3131

32-
expect(navigateMock).toHaveBeenCalledWith({
32+
expect(mockNavigate).toHaveBeenCalledWith({
3333
to: '${openmrsSpaBase}/login/location?returnToUrl=/openmrs/spa/home&update=true',
3434
});
3535
});

packages/apps/esm-login-app/src/change-password/change-password-link.test.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import ChangePasswordLink from './change-password-link.extension';
66

77
const mockShowModal = jest.mocked(showModal);
88

9-
describe('<ChangePasswordLink/>', () => {
10-
it('should display the `Change password` link', async () => {
9+
describe('ChangePasswordLink', () => {
10+
it('should launch the change password modal', async () => {
1111
const user = userEvent.setup();
1212

1313
render(<ChangePasswordLink />);

packages/apps/esm-login-app/src/config-schema.ts

+15-15
Original file line numberDiff line numberDiff line change
@@ -78,18 +78,18 @@ export const configSchema = {
7878
_type: Type.String,
7979
_required: true,
8080
_description: 'The source URL of the logo image',
81-
_validations: [validators.isUrl]
81+
_validations: [validators.isUrl],
8282
},
8383
alt: {
8484
_type: Type.String,
8585
_required: true,
86-
_description: 'The alternative text for the logo image'
87-
}
88-
}
86+
_description: 'The alternative text for the logo image',
87+
},
88+
},
8989
},
9090
_default: [],
9191
_description: 'An array of logos to be displayed in the footer next to the OpenMRS logo.',
92-
}
92+
},
9393
},
9494
showPasswordOnSeparateScreen: {
9595
_type: Type.Boolean,
@@ -100,29 +100,29 @@ export const configSchema = {
100100
};
101101

102102
export interface ConfigSchema {
103-
provider: {
104-
loginUrl: string;
105-
logoutUrl: string;
106-
type: string;
107-
};
108103
chooseLocation: {
109104
enabled: boolean;
110105
locationsPerRequest: number;
111106
numberToShow: number;
112107
useLoginLocationTag: boolean;
113108
};
109+
footer: {
110+
additionalLogos: Array<{
111+
alt: string;
112+
src: string;
113+
}>;
114+
};
114115
links: {
115116
loginSuccess: string;
116117
};
117118
logo: {
118119
alt: string;
119120
src: string;
120121
};
121-
footer: {
122-
additionalLogos: Array<{
123-
src: string;
124-
alt: string;
125-
}>;
122+
provider: {
123+
loginUrl: string;
124+
logoutUrl: string;
125+
type: string;
126126
};
127127
showPasswordOnSeparateScreen: boolean;
128128
}

0 commit comments

Comments
 (0)