Skip to content

Commit

Permalink
(test) Fix flaky behaviour in Programs and Conditions form tests (#1909)
Browse files Browse the repository at this point in the history
* (test) Fix flaky behaviour in Programs and Conditions form tests

* Try something else

* Fixup
  • Loading branch information
denniskigen authored Jul 12, 2024
1 parent eea68c6 commit ec7db79
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 148 deletions.
8 changes: 8 additions & 0 deletions __mocks__/programs.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export const mockEnrolledProgramsResponse = [
uuid: '64f950e6-1b07-4ac0-8e7e-f3e148f3463f',
name: 'HIV Care and Treatment',
allWorkflows: [],
concept: {
uuid: '70724784-438a-490e-a581-68b7d1f8f47f',
display: 'Human immunodeficiency virus (HIV) disease',
},
},
display: 'HIV Care and Treatment',
location: {
Expand All @@ -68,6 +72,10 @@ export const mockEnrolledInAllProgramsResponse = [
uuid: '64f950e6-1b07-4ac0-8e7e-f3e148f3463f',
name: 'HIV Care and Treatment',
allWorkflows: [],
concept: {
uuid: '70724784-438a-490e-a581-68b7d1f8f47f',
display: 'Human immunodeficiency virus (HIV) disease',
},
},
display: 'HIV Care and Treatment',
location: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,36 @@ const testProps = {
setTitle: jest.fn(),
};

const mockCreateCondition = createCondition as jest.Mock;
const mockUseConditionsSearch = useConditionsSearch as jest.Mock;
const mockShowSnackbar = showSnackbar as jest.Mock;
const mockCreateCondition = jest.mocked(createCondition);
const mockUseConditionsSearch = jest.mocked(useConditionsSearch);
const mockShowSnackbar = jest.mocked(showSnackbar);
const mockOpenmrsFetch = jest.mocked(openmrsFetch);

jest.mock('@openmrs/esm-framework', () => {
const originalModule = jest.requireActual('@openmrs/esm-framework');
jest.mock('@openmrs/esm-framework', () => ({
...jest.requireActual('@openmrs/esm-framework'),
showSnackbar: jest.fn(),
}));

return {
...originalModule,
showSnackbar: jest.fn(),
};
});
jest.mock('./conditions.resource', () => ({
...jest.requireActual('./conditions.resource'),
createCondition: jest.fn(),
editCondition: jest.fn(),
useConditionsSearch: jest.fn(),
}));

describe('Conditions form', () => {
beforeEach(() => {
jest.clearAllMocks();

jest.mock('./conditions.resource', () => {
const originalModule = jest.requireActual('./conditions.resource');
mockOpenmrsFetch.mockResolvedValue({ data: [] } as FetchResponse);

return {
...originalModule,
createCondition: jest.fn(),
editCondition: jest.fn(),
useConditionsSearch: jest.fn().mockImplementation(() => ({
conditions: [],
mockUseConditionsSearch.mockReturnValue({
searchResults: [],
error: null,
isSearching: false,
})),
};
});
});

describe('Conditions form', () => {
beforeEach(() => {
mockShowSnackbar.mockClear();
mockCreateCondition.mockResolvedValue({ status: 201, body: 'Condition created' } as unknown as FetchResponse);
});

it('renders the conditions form with all the relevant fields and values', () => {
Expand Down Expand Up @@ -123,13 +121,15 @@ describe('Conditions form', () => {
it('renders a success notification upon successfully recording a condition', async () => {
const user = userEvent.setup();

mockOpenmrsFetch.mockResolvedValue({ data: [] } as FetchResponse);
mockCreateCondition.mockResolvedValue({ status: 201, body: 'Condition created' });
mockUseConditionsSearch.mockReturnValue({
searchResults: searchedCondition,
error: null,
isSearching: false,
});
mockOpenmrsFetch.mockResolvedValue({
data: mockFhirConditionsResponse,
mutate: Promise.resolve(undefined),
} as unknown as FetchResponse);

renderConditionsForm();

Expand All @@ -146,17 +146,23 @@ describe('Conditions form', () => {
await user.type(onsetDateInput, '2020-05-05');
await user.click(submitButton);

// TODO: Figure out why the following assertions are flaky
// expect(mockShowSnackbar).toHaveBeenCalled();
// expect(mockShowSnackbar).toHaveBeenCalledWith({
// kind: 'success',
// subtitle: 'It is now visible on the Conditions page',
// title: 'Condition saved',
// });
expect(mockShowSnackbar).toHaveBeenCalled();
expect(mockShowSnackbar).toHaveBeenCalledWith({
kind: 'success',
subtitle: 'It is now visible on the Conditions page',
title: 'Condition saved',
});
});

it('renders an error notification if there was a problem recording a condition', async () => {
const user = userEvent.setup();

mockUseConditionsSearch.mockReturnValue({
searchResults: searchedCondition,
error: null,
isSearching: false,
});

renderConditionsForm();

const submitButton = screen.getByRole('button', { name: /save & close/i });
Expand All @@ -172,7 +178,7 @@ describe('Conditions form', () => {
},
};

mockCreateCondition.mockImplementation(() => Promise.reject(error));
mockCreateCondition.mockRejectedValue(error);
await user.type(conditionSearchInput, 'Headache');
await user.click(screen.getByRole('menuitem', { name: /Headache/i }));
await user.type(onsetDateInput, '2020-05-05');
Expand All @@ -185,13 +191,15 @@ describe('Conditions form', () => {
it('validates the form against the provided zod schema before submitting it', async () => {
const user = userEvent.setup();

mockOpenmrsFetch.mockResolvedValue({ data: [] } as FetchResponse);
mockCreateCondition.mockResolvedValue({ status: 201, body: 'Condition created' });
mockUseConditionsSearch.mockReturnValue({
searchResults: searchedCondition,
error: null,
isSearching: false,
});
mockOpenmrsFetch.mockResolvedValue({
data: mockFhirConditionsResponse,
mutate: Promise.resolve(undefined),
} as unknown as FetchResponse);

renderConditionsForm();

Expand All @@ -215,13 +223,12 @@ describe('Conditions form', () => {
expect(screen.queryByText(/a condition is required/i)).not.toBeInTheDocument();
expect(screen.queryByText(/a clinical status is required/i)).not.toBeInTheDocument();

// TODO: Figure out why the following assertions are flaky
// expect(mockShowSnackbar).toHaveBeenCalled();
// expect(mockShowSnackbar).toHaveBeenCalledWith({
// kind: 'success',
// subtitle: 'It is now visible on the Conditions page',
// title: 'Condition saved',
// });
expect(mockShowSnackbar).toHaveBeenCalled();
expect(mockShowSnackbar).toHaveBeenCalledWith({
kind: 'success',
subtitle: 'It is now visible on the Conditions page',
title: 'Condition saved',
});
});

it('launching the form with an existing condition prepopulates the form with the condition details', async () => {
Expand All @@ -243,7 +250,7 @@ describe('Conditions form', () => {
mockOpenmrsFetch.mockResolvedValue({ data: mockFhirConditionsResponse } as FetchResponse);
renderConditionsForm();

expect(screen.queryByRole('searchbox', { name: /Enter condition/i })).not.toBeInTheDocument();
expect(screen.queryByRole('searchbox', { name: /enter condition/i })).not.toBeInTheDocument();

const inactiveStatusInput = screen.getByLabelText(/inactive/i);
const submitButton = screen.getByRole('button', { name: /save & close/i });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ const ConditionsWidget: React.FC<ConditionsWidgetProps> = ({

try {
await createCondition(payload);
mutate();
await mutate();

showSnackbar({
kind: 'success',
Expand Down Expand Up @@ -153,7 +153,7 @@ const ConditionsWidget: React.FC<ConditionsWidgetProps> = ({

try {
await updateCondition(conditionToEdit?.id, payload);
mutate();
await mutate();

showSnackbar({
kind: 'success',
Expand Down
2 changes: 0 additions & 2 deletions packages/esm-patient-programs-app/src/dashboard.meta.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export const moduleName = '@openmrs/esm-patient-programs-app';

export const dashboardMeta = {
slot: 'patient-chart-programs-dashboard-slot',
columns: 1,
Expand Down
113 changes: 48 additions & 65 deletions packages/esm-patient-programs-app/src/programs/programs-form.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import React from 'react';
import userEvent from '@testing-library/user-event';
import { render, screen } from '@testing-library/react';
import { openmrsFetch, showSnackbar } from '@openmrs/esm-framework';
import { mockPatient } from 'tools';
import { type FetchResponse, showSnackbar } from '@openmrs/esm-framework';
import { mockCareProgramsResponse, mockEnrolledProgramsResponse, mockLocationsResponse } from '__mocks__';
import { createProgramEnrollment, updateProgramEnrollment } from './programs.resource';
import { mockPatient } from 'tools';
import {
createProgramEnrollment,
updateProgramEnrollment,
useAvailablePrograms,
useEnrollments,
} from './programs.resource';
import ProgramsForm from './programs-form.workspace';

const mockCreateProgramEnrollment = createProgramEnrollment as jest.Mock;
const mockUpdateProgramEnrollment = updateProgramEnrollment as jest.Mock;
const mockOpenmrsFetch = openmrsFetch as jest.Mock;
const mockShowSnackbar = showSnackbar as jest.Mock;
const mockUseAvailablePrograms = jest.mocked(useAvailablePrograms);
const mockUseEnrollments = jest.mocked(useEnrollments);
const mockCreateProgramEnrollment = jest.mocked(createProgramEnrollment);
const mockUpdateProgramEnrollment = jest.mocked(updateProgramEnrollment);
const mockShowSnackbar = jest.mocked(showSnackbar);

const mockCloseWorkspace = jest.fn();
const mockCloseWorkspaceWithSavedChanges = jest.fn();
const mockPromptBeforeClosing = jest.fn();

jest.mock('@openmrs/esm-framework', () => ({
...jest.requireActual('@openmrs/esm-framework'),
Expand All @@ -20,20 +29,36 @@ jest.mock('@openmrs/esm-framework', () => ({
}));

jest.mock('./programs.resource', () => ({
...jest.requireActual('./programs.resource'),
createProgramEnrollment: jest.fn(),
updateProgramEnrollment: jest.fn(),
useEnrollments: jest.fn().mockReturnValue({
data: mockEnrolledProgramsResponse,
isLoading: false,
isError: false,
mutateEnrollments: jest.fn().mockResolvedValue(undefined),
}),
useAvailablePrograms: jest.fn(),
useEnrollments: jest.fn(),
}));

describe('ProgramsForm', () => {
beforeEach(() => {
jest.clearAllMocks();

mockUseAvailablePrograms.mockReturnValue({
data: mockCareProgramsResponse,
eligiblePrograms: [],
error: null,
isLoading: false,
});

mockUseEnrollments.mockReturnValue({
data: mockEnrolledProgramsResponse,
error: null,
isLoading: false,
isValidating: false,
activeEnrollments: [],
mutateEnrollments: jest.fn(),
});

mockCreateProgramEnrollment.mockResolvedValue({
status: 201,
statusText: 'Created',
} as unknown as FetchResponse);
});

it('renders a success toast notification upon successfully recording a program enrollment', async () => {
Expand All @@ -42,10 +67,6 @@ describe('ProgramsForm', () => {
const inpatientWardUuid = 'b1a8b05e-3542-4037-bbd3-998ee9c40574';
const oncologyScreeningProgramUuid = '11b129ca-a5e7-4025-84bf-b92a173e20de';

mockOpenmrsFetch.mockReturnValueOnce({ data: { results: mockCareProgramsResponse } });
mockOpenmrsFetch.mockReturnValueOnce({ data: { results: mockEnrolledProgramsResponse } });
mockCreateProgramEnrollment.mockResolvedValueOnce({ status: 201, statusText: 'Created' });

renderProgramsForm();

const programNameInput = screen.getByRole('combobox', { name: /program name/i });
Expand All @@ -54,12 +75,11 @@ describe('ProgramsForm', () => {
const enrollButton = screen.getByRole('button', { name: /save and close/i });

await user.click(enrollButton);
expect(screen.getByText(/programrequired/i)).toBeInTheDocument();
expect(screen.getByText(/program is required/i)).toBeInTheDocument();

await user.type(enrollmentDateInput, '2020-05-05');
await user.selectOptions(programNameInput, [oncologyScreeningProgramUuid]);
await user.selectOptions(enrollmentLocationInput, [inpatientWardUuid]);

expect(screen.getByRole('option', { name: /Inpatient Ward/i })).toBeInTheDocument();

await user.click(enrollButton);
Expand Down Expand Up @@ -92,7 +112,10 @@ describe('ProgramsForm', () => {
const enrollButton = screen.getByRole('button', { name: /save and close/i });
const completionDateInput = screen.getByRole('textbox', { name: /date completed/i });

mockUpdateProgramEnrollment.mockResolvedValueOnce({ status: 200, statusText: 'OK' });
mockUpdateProgramEnrollment.mockResolvedValue({
status: 200,
statusText: 'OK',
} as unknown as FetchResponse);

await user.type(completionDateInput, '05/05/2020');
await user.tab();
Expand All @@ -102,8 +125,8 @@ describe('ProgramsForm', () => {
expect(mockUpdateProgramEnrollment).toHaveBeenCalledWith(
mockEnrolledProgramsResponse[0].uuid,
expect.objectContaining({
dateEnrolled: '2020-01-16T00:00:00+00:00',
dateCompleted: '2020-05-05T00:00:00+00:00',
dateCompleted: expect.stringMatching(/^2020-05-05/),
dateEnrolled: expect.stringMatching(/^2020-01-16/),
location: mockEnrolledProgramsResponse[0].location.uuid,
patient: mockPatient.id,
program: mockEnrolledProgramsResponse[0].program.uuid,
Expand All @@ -119,54 +142,14 @@ describe('ProgramsForm', () => {
}),
);
});

it('renders an error notification if there was a problem recording a program enrollment', async () => {
const user = userEvent.setup();

const inpatientWardUuid = 'b1a8b05e-3542-4037-bbd3-998ee9c40574';
const oncologyScreeningProgramUuid = '11b129ca-a5e7-4025-84bf-b92a173e20de';

const error = {
message: 'Internal Server Error',
response: {
status: 500,
statusText: 'Internal Server Error',
},
};

mockOpenmrsFetch.mockReturnValue({ data: { results: mockCareProgramsResponse } });
mockOpenmrsFetch.mockReturnValue({ data: { results: mockEnrolledProgramsResponse } });
mockCreateProgramEnrollment.mockRejectedValueOnce(error);

renderProgramsForm();

const programNameInput = screen.getByRole('combobox', { name: /program name/i });
const enrollmentDateInput = screen.getByRole('textbox', { name: /date enrolled/i });
const enrollmentLocationInput = screen.getByRole('combobox', { name: /enrollment location/i });
const enrollButton = screen.getByRole('button', { name: /save and close/i });

await user.type(enrollmentDateInput, '2020-05-05');
await user.selectOptions(programNameInput, [oncologyScreeningProgramUuid]);
await user.selectOptions(enrollmentLocationInput, [inpatientWardUuid]);

expect(enrollButton).toBeEnabled();

await user.click(enrollButton);

expect(mockShowSnackbar).toHaveBeenCalledWith({
subtitle: 'An unknown error occurred',
kind: 'error',
title: 'Error saving program enrollment',
});
});
});

function renderProgramsForm(programEnrollmentUuidToEdit?: string) {
const testProps = {
closeWorkspace: jest.fn(),
closeWorkspace: mockCloseWorkspace,
closeWorkspaceWithSavedChanges: mockCloseWorkspaceWithSavedChanges,
patientUuid: mockPatient.id,
promptBeforeClosing: jest.fn(),
promptBeforeClosing: mockPromptBeforeClosing,
setTitle: jest.fn(),
};

Expand Down
Loading

0 comments on commit ec7db79

Please sign in to comment.