Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) O3-4040: Add delete ability to program enrollments #2133

Open
wants to merge 31 commits into
base: main
Choose a base branch
from

Conversation

PiusKariuki
Copy link
Contributor

@PiusKariuki PiusKariuki commented Dec 5, 2024

Requirements

  • This PR has a title that briefly describes the work done including the ticket number. If there is a ticket, make sure your PR title includes a conventional commit label. See existing PR titles for inspiration.
  • My work conforms to the OpenMRS 3.0 Styleguide and design documentation.
  • My work includes tests or is validated by existing tests.

Summary

This PR adds DELETE ability to program enrollments

Screenshots

Screenshot from 2024-12-05 13-49-19
Screenshot from 2024-12-05 13-49-26
Screenshot from 2024-12-05 13-49-38

Related Issue

https://openmrs.atlassian.net/browse/O3-4040

Other

@PiusKariuki
Copy link
Contributor Author

@vasharma05 @denniskigen can you please review this for me

@brandones brandones changed the title (feat) Add delete ability to program enrollments#4040 (feat) O3-4040: Add delete ability to program enrollments Dec 15, 2024
Copy link
Contributor

@brandones brandones left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, @PiusKariuki ! The main thing is to clean up the user-facing text. We don't want to scare users by making them think they might be deleting a whole program, when they are in fact just deleting the patient's enrollment.

@PiusKariuki
Copy link
Contributor Author

@brandones I have made the necessary updates requested.

Copy link
Contributor

@brandones brandones left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @PiusKariuki ! Excellent work.

In the future, if you could work on figuring out how to add commits to your work in a way that makes it easy for reviewers to see what changed since last review, that would be really helpful. Right now it looks like you made like 14 new commits, which would be inconvenient to view one-by-one. Maybe check out git rebase --interactive if you haven't.

Just a suggestion, nice work otherwise!

@brandones
Copy link
Contributor

Oops, looks like the E2E test failure is related to this work. Please fix the E2E test.

Copy link
Collaborator

@NethmiRodrigo NethmiRodrigo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @PiusKariuki! Left a few nitpicks about the tests

}));

const mockMutateEnrollments = jest.fn();
(useEnrollments as jest.Mock).mockImplementation(() => ({ mutateEnrollments: mockMutateEnrollments }));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see the correct way to mock functions where we prefer to use jest.mocked where possible - https://o3-docs.openmrs.org/docs/frontend-modules/unit-and-integration-testing#annotating-mocks-with-type-information

Comment on lines +27 to +29
beforeEach(() => {
jest.clearAllMocks();
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to explicitly do this, since we have this specified by default in the jest.config

Suggested change
beforeEach(() => {
jest.clearAllMocks();
});

Comment on lines +11 to +14
jest.mock('@openmrs/esm-framework', () => ({
showSnackbar: jest.fn(),
getCoreTranslation: jest.fn((key, defaultText) => defaultText),
}));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

});

it('handles delete action successfully', async () => {
(deleteProgramEnrollment as jest.Mock).mockResolvedValue({ ok: true });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto


it('handles delete action error', async () => {
const errorMessage = 'failed to delete';
(deleteProgramEnrollment as jest.Mock).mockRejectedValue(new Error(errorMessage));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too

Comment on lines +7 to +14
jest.mock('@openmrs/esm-framework', () => ({
showModal: jest.fn(),
useLayoutType: jest.fn(() => 'desktop'),
}));

jest.mock('@openmrs/esm-patient-common-lib', () => ({
launchPatientWorkspace: jest.fn(),
}));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here for this test case about mocking patterns

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine for @openmrs/esm-patient-common-lib, @NethmiRodrigo.

Copy link
Member

@denniskigen denniskigen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start, @PiusKariuki. I've left some feedback.

Comment on lines 15 to 17
jest.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key, defaultText) => defaultText }),
}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to mock react-i18next, it's already mocked here using this mock.

describe('DeleteProgramModal', () => {
const closeDeleteModalMock = jest.fn();
const programEnrollmentId = '123';
const patientUuid = 'abc';
Copy link
Member

@denniskigen denniskigen Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add this import to the top:

import { mockPatient } from 'tools';

So that we can do:

Suggested change
const patientUuid = 'abc';
const patientUuid = mockPatient.id

beforeEach(() => {
jest.clearAllMocks();
});

Copy link
Member

@denniskigen denniskigen Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good pattern for setting up the test is doing something like:

const mockDeleteProgramEnrollment = jest.mocked(deleteProgramEnrollment);
const mockShowSnackbar = jest.mocked(showSnackbar);
const mockMutateEnrollments = jest.fn(() => Promise.resolve(undefined));
const mockUseEnrollments = jest.mocked(useEnrollments);
const mockCloseDeleteModal = jest.fn();
	
const testProps = {
  closeDeleteModal: mockCloseDeleteModal,
  patientUuid: mockPatient.id,
  programEnrollmentId: '9b1deb4d-5b9f-4fec-a6ea-6b0c0f3a3d0f',
};

const renderDeleteProgramModal = () => {
  render(
    <DeleteProgramModal {...testProps} />,
  );
};

So that in the tests we can just call:

it('renders something', () => {
  renderDeleteProgramModal();
  // assertions
})

Comment on lines +32 to +42
render(
<DeleteProgramModal
closeDeleteModal={closeDeleteModalMock}
programEnrollmentId={programEnrollmentId}
patientUuid={patientUuid}
/>,
);
expect(screen.getByText('Delete Program Enrollment')).toBeInTheDocument();
expect(screen.getByText('Are you sure you want to delete this program enrollment?')).toBeInTheDocument();
expect(screen.getByText('Cancel')).toBeInTheDocument();
expect(screen.getByText('Confirm')).toBeInTheDocument();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer using a11y roles wherever possible to make your tests more robust:

Suggested change
render(
<DeleteProgramModal
closeDeleteModal={closeDeleteModalMock}
programEnrollmentId={programEnrollmentId}
patientUuid={patientUuid}
/>,
);
expect(screen.getByText('Delete Program Enrollment')).toBeInTheDocument();
expect(screen.getByText('Are you sure you want to delete this program enrollment?')).toBeInTheDocument();
expect(screen.getByText('Cancel')).toBeInTheDocument();
expect(screen.getByText('Confirm')).toBeInTheDocument();
renderDeleteProgramModal();
expect(screen.getByRole('heading', { name: /delete program enrollment/i })).toBeInTheDocument();
expect(screen.getByText(/are you sure you want to delete this program enrollment?/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /confirm/i })).toBeInTheDocument();

@@ -0,0 +1,100 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer userEvent over fireEvent.

<div>
<ModalHeader
closeModal={closeDeleteModal}
title={t('deletePatientProgramEnrollment', 'Delete Program Enrollment')}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
title={t('deletePatientProgramEnrollment', 'Delete Program Enrollment')}
title={t('deletePatientProgramEnrollment', 'Delete program enrollment')}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentence case is preferable to title case for body text.

Comment on lines +16 to +18
jest.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string, defaultText: string) => defaultText }),
}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, we shouldn't mock this. A lot of the tips from the programs modal test can be applied here too.

Comment on lines +7 to +14
jest.mock('@openmrs/esm-framework', () => ({
showModal: jest.fn(),
useLayoutType: jest.fn(() => 'desktop'),
}));

jest.mock('@openmrs/esm-patient-common-lib', () => ({
launchPatientWorkspace: jest.fn(),
}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine for @openmrs/esm-patient-common-lib, @NethmiRodrigo.

Comment on lines +7 to +10
jest.mock('@openmrs/esm-framework', () => ({
showModal: jest.fn(),
useLayoutType: jest.fn(() => 'desktop'),
}));
Copy link
Member

@denniskigen denniskigen Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
jest.mock('@openmrs/esm-framework', () => ({
showModal: jest.fn(),
useLayoutType: jest.fn(() => 'desktop'),
}));
const mockShowModal = jest.mocked(showModal);
const mockUseLayoutType = jest.mocked(useLayoutType);

And then further below you could do this inside a beforeEach block:

beforeEach(() => {
  mockUseLayoutType.mockReturnValue('small-desktop'); // or 'large-desktop' or 'tablet' 
})

To override the useLayoutType implementation to suit your test.

@@ -160,8 +155,8 @@ const ProgramsDetailedSummary: React.FC<ProgramsDetailedSummaryProps> = ({ patie
{row.cells.map((cell) => (
<TableCell key={cell.id}>{cell.value?.content ?? cell.value}</TableCell>
))}
<TableCell className="cds--table-column-menu">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we still need this className...

@denniskigen
Copy link
Member

The programs e2e test is failing because this button doesn't exist after moving it to the ProgramsActionsMenu component. You'll likely need to mimic this setup.

@PiusKariuki
Copy link
Contributor Author

Thank you @denniskigen and @NethmiRodrigo . I am working on fixing these tests right now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants