Skip to content

Commit

Permalink
(test) Add some missing test cases and improve typing in test files (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
denniskigen authored Dec 16, 2024
1 parent 7269bbf commit ccb94fb
Show file tree
Hide file tree
Showing 47 changed files with 943 additions and 385 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,75 @@
import React from 'react';
import '@testing-library/jest-dom';
import userEvent from '@testing-library/user-event';
import { type AppProps } from 'single-spa';
import { render, screen } from '@testing-library/react';
import Root from './devtools.component';
import { render } from '@testing-library/react';

describe(`<Root />`, () => {
it(`renders without dying`, () => {
render(<Root />);
jest.mock('./import-map.component', () => ({
__esModule: true,
default: () => <div role="dialog">Mock Import Map</div>,
importMapOverridden: false,
}));

const defaultProps: AppProps = {
name: '@openmrs/esm-devtools-app-page-0',
singleSpa: {},
mountParcel: jest.fn(),
};

describe('DevTools', () => {
beforeEach(() => {
localStorage.clear();
delete window.spaEnv;
jest.resetModules();
});

describe('Root component', () => {
it('should not render DevTools in production without the devtools localStorage flag', () => {
window.spaEnv = 'production';

const { container } = render(<Root {...defaultProps} />);
expect(container).toBeEmptyDOMElement();
});

it('should render DevTools in development environments', () => {
window.spaEnv = 'development';

render(<Root {...defaultProps} />);

expect(screen.getByRole('button', { name: '{···}' })).toBeInTheDocument();
});

it('should render DevTools when the devtools localStorage flag is set', () => {
localStorage.setItem('openmrs:devtools', 'true');

render(<Root {...defaultProps} />);

expect(screen.getByRole('button', { name: '{···}' })).toBeInTheDocument();
});
});

describe('DevTools component', () => {
const user = userEvent.setup();

beforeEach(() => {
window.spaEnv = 'development';
});

it('should toggle DevToolsPopup when clicking trigger button', async () => {
render(<Root {...defaultProps} />);

const triggerButton = screen.getByRole('button', { name: '{···}' });
// Initially, popup should not be present
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();

// Click to open
await user.click(triggerButton);
expect(screen.getByRole('dialog')).toBeInTheDocument();

// Click to close
await user.click(triggerButton);
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import React, { useState } from 'react';
import classNames from 'classnames';
import { type AppProps } from 'single-spa';
import { importMapOverridden } from './import-map.component';
import DevToolsPopup from './devtools-popup.component';
import styles from './devtools.styles.css';

export default function Root(props) {
export default function Root(props: AppProps) {
return window.spaEnv === 'development' || Boolean(localStorage.getItem('openmrs:devtools')) ? (
<DevTools {...props} />
) : null;
}

function DevTools() {
function DevTools(props: AppProps) {
const [devToolsOpen, setDevToolsOpen] = useState(false);
const [isOverridden, setIsOverridden] = useState(importMapOverridden);

return (
<>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ export default function ImportMap(props: ImportMapProps) {
}
}, [props]);

return (
<div className={styles.importMap}>
<ImportMapList ref={importMapListRef} />
</div>
);
return <div className={styles.importMap}>{<ImportMapList ref={importMapListRef} />}</div>;
}

export function importMapOverridden(): boolean {
Expand Down
4 changes: 2 additions & 2 deletions packages/apps/esm-help-menu-app/src/root.component.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react';
import { render } from '@testing-library/react';
import Root from './root.component';

describe(`<Root />`, () => {
it(`renders without dying`, () => {
describe('Root', () => {
it('renders without dying', () => {
render(<Root />);
});
});
1 change: 1 addition & 0 deletions packages/apps/esm-help-menu-app/src/root.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import HelpMenu from './help-menu/help.component';
const Root: React.FC = () => {
return <HelpMenu />;
};

export default Root;
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { implementerToolsConfigStore, temporaryConfigStore, Type } from '@openmr
import { Configuration } from './configuration.component';
import { useConceptLookup, useGetConceptByUuid } from './interactive-editor/value-editors/concept-search.resource';

const mockUseConceptLookup = useConceptLookup as jest.Mock;
const mockUseGetConceptByUuid = useGetConceptByUuid as jest.Mock;
const mockUseConceptLookup = jest.mocked(useConceptLookup);
const mockUseGetConceptByUuid = jest.mocked(useGetConceptByUuid);

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

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

mockUseGetConceptByUuid.mockImplementation(() => ({
concept: { name: { display: 'Fedora' } },
error: null,
concept: {
name: { display: 'Fedora' },
display: 'Fedora',
answers: [],
mappings: [],
uuid: '61523693-72e2-456d-8c64-8c5293febeb6',
},
error: undefined,
isLoadingConcept: false,
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react';
import { render } from '@testing-library/react';
import Root from './implementer-tools.component';

describe(`<Root />`, () => {
it(`renders without dying`, () => {
describe('ImplementerTools', () => {
it('renders without dying', () => {
render(<Root />);
});
});
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import React from 'react';
import userEvent from '@testing-library/user-event';
import { render, screen } from '@testing-library/react';
import { navigate, useSession } from '@openmrs/esm-framework';
import { navigate, type Session, useSession } from '@openmrs/esm-framework';
import ChangeLocationLink from './change-location-link.extension';

const navigateMock = navigate as jest.Mock;
const useSessionMock = useSession as jest.Mock;
const mockNavigate = jest.mocked(navigate);
const mockUseSession = jest.mocked(useSession);

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

describe('<ChangeLocationLink/>', () => {
describe('ChangeLocationLink', () => {
beforeEach(() => {
useSessionMock.mockReturnValue({
mockUseSession.mockReturnValue({
sessionLocation: {
display: 'Waffle House',
},
});
} as Session);
});

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

await user.click(changeLocationButton);

expect(navigateMock).toHaveBeenCalledWith({
expect(mockNavigate).toHaveBeenCalledWith({
to: '${openmrsSpaBase}/login/location?returnToUrl=/openmrs/spa/home&update=true',
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import ChangePasswordLink from './change-password-link.extension';

const mockShowModal = jest.mocked(showModal);

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

render(<ChangePasswordLink />);
Expand Down
30 changes: 15 additions & 15 deletions packages/apps/esm-login-app/src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,18 @@ export const configSchema = {
_type: Type.String,
_required: true,
_description: 'The source URL of the logo image',
_validations: [validators.isUrl]
_validations: [validators.isUrl],
},
alt: {
_type: Type.String,
_required: true,
_description: 'The alternative text for the logo image'
}
}
_description: 'The alternative text for the logo image',
},
},
},
_default: [],
_description: 'An array of logos to be displayed in the footer next to the OpenMRS logo.',
}
},
},
showPasswordOnSeparateScreen: {
_type: Type.Boolean,
Expand All @@ -100,29 +100,29 @@ export const configSchema = {
};

export interface ConfigSchema {
provider: {
loginUrl: string;
logoutUrl: string;
type: string;
};
chooseLocation: {
enabled: boolean;
locationsPerRequest: number;
numberToShow: number;
useLoginLocationTag: boolean;
};
footer: {
additionalLogos: Array<{
alt: string;
src: string;
}>;
};
links: {
loginSuccess: string;
};
logo: {
alt: string;
src: string;
};
footer: {
additionalLogos: Array<{
src: string;
alt: string;
}>;
provider: {
loginUrl: string;
logoutUrl: string;
type: string;
};
showPasswordOnSeparateScreen: boolean;
}
Loading

0 comments on commit ccb94fb

Please sign in to comment.