-
Notifications
You must be signed in to change notification settings - Fork 246
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
base: main
Are you sure you want to change the base?
Changes from 29 commits
cbbff83
b81e251
80c62ed
14e044a
2ede494
a66f460
a6684fa
576c2ad
ac33454
8ddee66
4ee37bb
a683be1
470f200
5ea6b91
f4b550f
3236b0d
7ab780f
de252d4
9d444b6
49e52d3
847261c
4352fe8
17a7079
7840ee8
9869444
421d61f
f871f46
cc0cfb0
6762df0
db768d8
7223a13
04842e4
dfe5356
05f8b3d
1f17b78
3a64344
a0bc18a
bdae7c6
b1a153b
17c07ea
70f7a04
f1498e9
fb93d08
9dd408f
0cacc23
06413b5
c7b7962
3b7127e
1654142
6f98879
92d545c
26d2a49
1876c76
53bbbe2
6da388d
529b542
4dd1699
fbc226a
5262ad8
4990847
f9db4ff
bd588b9
4363f81
cf60eeb
902454b
be6cfb9
d71d7f3
72826b1
7dad9e1
218b024
e7db186
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,100 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import React from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { render, screen, fireEvent } from '@testing-library/react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { deleteProgramEnrollment, useEnrollments } from './programs.resource'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import DeleteProgramModal from './delete-program.modal'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { showSnackbar, getCoreTranslation } from '@openmrs/esm-framework'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
jest.mock('./programs.resource', () => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
deleteProgramEnrollment: jest.fn(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
useEnrollments: jest.fn(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
})); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
jest.mock('@openmrs/esm-framework', () => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
showSnackbar: jest.fn(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
getCoreTranslation: jest.fn((key, defaultText) => defaultText), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
})); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please see how to extend framework mocks - https://o3-docs.openmrs.org/docs/frontend-modules/unit-and-integration-testing#you-should-not-need-to-use-partial-mocks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And please update the mocks |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
jest.mock('react-i18next', () => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
useTranslation: () => ({ t: (key, defaultText) => defaultText }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
})); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const mockMutateEnrollments = jest.fn(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(useEnrollments as jest.Mock).mockImplementation(() => ({ mutateEnrollments: mockMutateEnrollments })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
describe('DeleteProgramModal', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const closeDeleteModalMock = jest.fn(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const programEnrollmentId = '123'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const patientUuid = 'abc'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
beforeEach(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
jest.clearAllMocks(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also please note this. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
denniskigen marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
it('renders modal with delete confirmation text ', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
it('Calls closeDeleteModal when cancel button is clicked', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
render( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<DeleteProgramModal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
closeDeleteModal={closeDeleteModalMock} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
programEnrollmentId={programEnrollmentId} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
patientUuid={patientUuid} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fireEvent.click(screen.getByText('Cancel')); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(closeDeleteModalMock).toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
it('handles delete action successfully', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(deleteProgramEnrollment as jest.Mock).mockResolvedValue({ ok: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
render( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<DeleteProgramModal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
closeDeleteModal={closeDeleteModalMock} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
programEnrollmentId={programEnrollmentId} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
patientUuid={patientUuid} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fireEvent.click(screen.getByText('Confirm')); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(screen.getByText('Deleting...')).toBeInTheDocument(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await screen.findByText('Confirm'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(deleteProgramEnrollment).toHaveBeenCalledWith(programEnrollmentId, expect.any(AbortController)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(mockMutateEnrollments).toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(closeDeleteModalMock).toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(showSnackbar).toHaveBeenCalledWith({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
isLowContrast: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
kind: 'success', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
title: 'Program Enrollment Deleted', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
it('handles delete action error', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const errorMessage = 'failed to delete'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(deleteProgramEnrollment as jest.Mock).mockRejectedValue(new Error(errorMessage)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here too |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
render( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<DeleteProgramModal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
closeDeleteModal={closeDeleteModalMock} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
programEnrollmentId={programEnrollmentId} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
patientUuid={patientUuid} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fireEvent.click(screen.getByText('Confirm')); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await screen.findByText('Confirm'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(showSnackbar).toHaveBeenCalledWith({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
isLowContrast: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
kind: 'error', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
title: 'Error deleting program enrollment', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
subtitle: errorMessage, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,67 @@ | ||||||||||||||||||||||||||||||||||||
import React, { useCallback, useState } from 'react'; | ||||||||||||||||||||||||||||||||||||
import { Button, InlineLoading, ModalBody, ModalFooter, ModalHeader } from '@carbon/react'; | ||||||||||||||||||||||||||||||||||||
import { useTranslation } from 'react-i18next'; | ||||||||||||||||||||||||||||||||||||
import { deleteProgramEnrollment, useEnrollments } from './programs.resource'; | ||||||||||||||||||||||||||||||||||||
import { showSnackbar, getCoreTranslation } from '@openmrs/esm-framework'; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
interface DeleteProgramProps { | ||||||||||||||||||||||||||||||||||||
closeDeleteModal: () => void; | ||||||||||||||||||||||||||||||||||||
programEnrollmentId: string; | ||||||||||||||||||||||||||||||||||||
patientUuid: string; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
const DeleteProgramModal: React.FC<DeleteProgramProps> = ({ closeDeleteModal, programEnrollmentId, patientUuid }) => { | ||||||||||||||||||||||||||||||||||||
const { t } = useTranslation(); | ||||||||||||||||||||||||||||||||||||
const [isDeleting, setIsDeleting] = useState(false); | ||||||||||||||||||||||||||||||||||||
const { mutateEnrollments } = useEnrollments(patientUuid); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
const handleDelete = useCallback(async () => { | ||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||
denniskigen marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||
setIsDeleting(true); | ||||||||||||||||||||||||||||||||||||
const response = await deleteProgramEnrollment(programEnrollmentId, new AbortController()); | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||
if (response.ok) { | ||||||||||||||||||||||||||||||||||||
mutateEnrollments(); | ||||||||||||||||||||||||||||||||||||
closeDeleteModal(); | ||||||||||||||||||||||||||||||||||||
showSnackbar({ | ||||||||||||||||||||||||||||||||||||
isLowContrast: true, | ||||||||||||||||||||||||||||||||||||
kind: 'success', | ||||||||||||||||||||||||||||||||||||
title: t('programDeleted', 'Program Enrollment Deleted'), | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||||
showSnackbar({ | ||||||||||||||||||||||||||||||||||||
isLowContrast: false, | ||||||||||||||||||||||||||||||||||||
kind: 'error', | ||||||||||||||||||||||||||||||||||||
title: t('errorDeletingProgram', 'Error deleting program enrollment'), | ||||||||||||||||||||||||||||||||||||
subtitle: error?.message, | ||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||
} finally { | ||||||||||||||||||||||||||||||||||||
setIsDeleting(false); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
}, [closeDeleteModal, programEnrollmentId, t, mutateEnrollments]); | ||||||||||||||||||||||||||||||||||||
denniskigen marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||
<div> | ||||||||||||||||||||||||||||||||||||
<ModalHeader | ||||||||||||||||||||||||||||||||||||
closeModal={closeDeleteModal} | ||||||||||||||||||||||||||||||||||||
title={t('deletePatientProgramEnrollment', 'Delete Program Enrollment')} | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sentence case is preferable to title case for body text. |
||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||
<ModalBody> | ||||||||||||||||||||||||||||||||||||
<p>{t('deleteModalConfirmationText', 'Are you sure you want to delete this program enrollment?')}</p> | ||||||||||||||||||||||||||||||||||||
</ModalBody> | ||||||||||||||||||||||||||||||||||||
<ModalFooter> | ||||||||||||||||||||||||||||||||||||
<Button kind="secondary" onClick={closeDeleteModal}> | ||||||||||||||||||||||||||||||||||||
{getCoreTranslation('cancel', 'Cancel')} | ||||||||||||||||||||||||||||||||||||
</Button> | ||||||||||||||||||||||||||||||||||||
<Button kind="danger" onClick={handleDelete} disabled={isDeleting}> | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
And the styles for that would be: @use '@carbon/layout';
@use '@carbon/type';
.deleteButton {
:global(.cds--inline-loading) {
min-height: layout.$spacing-05;
}
:global(.cds--inline-loading__text) {
@include type.type-style('body-01');
}
} |
||||||||||||||||||||||||||||||||||||
{isDeleting ? ( | ||||||||||||||||||||||||||||||||||||
<InlineLoading description={t('deleting', 'Deleting') + '...'} /> | ||||||||||||||||||||||||||||||||||||
) : ( | ||||||||||||||||||||||||||||||||||||
<span>{getCoreTranslation('confirm', 'Confirm')}</span> | ||||||||||||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||||||||||||
</Button> | ||||||||||||||||||||||||||||||||||||
</ModalFooter> | ||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
export default DeleteProgramModal; |
PiusKariuki marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,70 @@ | ||||||||||||||
import { showModal } from '@openmrs/esm-framework'; | ||||||||||||||
import { ProgramsActionsMenu } from './programs-actions-menu.component'; | ||||||||||||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'; | ||||||||||||||
import React from 'react'; | ||||||||||||||
import { launchPatientWorkspace } from '@openmrs/esm-patient-common-lib'; | ||||||||||||||
|
||||||||||||||
jest.mock('@openmrs/esm-framework', () => ({ | ||||||||||||||
showModal: jest.fn(), | ||||||||||||||
useLayoutType: jest.fn(() => 'desktop'), | ||||||||||||||
})); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
And then further below you could do this inside a beforeEach(() => {
mockUseLayoutType.mockReturnValue('small-desktop'); // or 'large-desktop' or 'tablet'
}) To override the |
||||||||||||||
|
||||||||||||||
jest.mock('@openmrs/esm-patient-common-lib', () => ({ | ||||||||||||||
launchPatientWorkspace: jest.fn(), | ||||||||||||||
})); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here for this test case about mocking patterns There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine for |
||||||||||||||
|
||||||||||||||
jest.mock('react-i18next', () => ({ | ||||||||||||||
useTranslation: () => ({ t: (key: string, defaultText: string) => defaultText }), | ||||||||||||||
})); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||||||||||||||
|
||||||||||||||
describe('ProgramActionsMenu', () => { | ||||||||||||||
const patientUuid = 'abc'; | ||||||||||||||
const programEnrollmentId = '123'; | ||||||||||||||
|
||||||||||||||
it('renders OverflowMenu with edit and delete actions', async () => { | ||||||||||||||
PiusKariuki marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
render(<ProgramsActionsMenu patientUuid={patientUuid} programEnrollmentId={programEnrollmentId} />); | ||||||||||||||
|
||||||||||||||
const overFlowButton = screen.getByRole('button'); | ||||||||||||||
fireEvent.click(overFlowButton); | ||||||||||||||
|
||||||||||||||
await waitFor(() => { | ||||||||||||||
expect(screen.getByText('Edit')).toBeInTheDocument(); | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
await waitFor(() => { | ||||||||||||||
expect(screen.getByText('Delete')).toBeInTheDocument(); | ||||||||||||||
}); | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
it('launches edit program form when edit button is clicked', () => { | ||||||||||||||
render(<ProgramsActionsMenu patientUuid={patientUuid} programEnrollmentId={programEnrollmentId} />); | ||||||||||||||
|
||||||||||||||
fireEvent.click(screen.getByRole('button')); | ||||||||||||||
fireEvent.click(screen.getByText('Edit')); | ||||||||||||||
|
||||||||||||||
expect(launchPatientWorkspace).toHaveBeenCalledWith('programs-form-workspace', { programEnrollmentId }); | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
it('launches delete program dialog when delete option is clicked', async () => { | ||||||||||||||
const disposeMock = jest.fn(); | ||||||||||||||
(showModal as jest.Mock).mockReturnValue(disposeMock); | ||||||||||||||
|
||||||||||||||
render(<ProgramsActionsMenu patientUuid={patientUuid} programEnrollmentId={programEnrollmentId} />); | ||||||||||||||
|
||||||||||||||
fireEvent.click(screen.getByRole('button')); | ||||||||||||||
fireEvent.click(screen.getByText('Delete')); | ||||||||||||||
|
||||||||||||||
expect(showModal).toHaveBeenCalledWith('program-delete-confirmation-modal', { | ||||||||||||||
closeDeleteModal: expect.any(Function), | ||||||||||||||
patientUuid, | ||||||||||||||
programEnrollmentId, | ||||||||||||||
}); | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Simulation for creating the dispose function | ||||||||||||||
*/ | ||||||||||||||
const { closeDeleteModal } = (showModal as jest.Mock).mock.calls[0][1]; | ||||||||||||||
closeDeleteModal(); | ||||||||||||||
expect(disposeMock).toHaveBeenCalled(); | ||||||||||||||
}); | ||||||||||||||
}); |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,42 @@ | ||||||||||||||||||||||||||||||||||
import { useTranslation } from 'react-i18next'; | ||||||||||||||||||||||||||||||||||
import { showModal, useLayoutType } from '@openmrs/esm-framework'; | ||||||||||||||||||||||||||||||||||
import { Layer, OverflowMenu, OverflowMenuItem } from '@carbon/react'; | ||||||||||||||||||||||||||||||||||
import React, { useCallback } from 'react'; | ||||||||||||||||||||||||||||||||||
import { launchPatientWorkspace } from '@openmrs/esm-patient-common-lib'; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
interface ProgramActionsProps { | ||||||||||||||||||||||||||||||||||
patientUuid: string; | ||||||||||||||||||||||||||||||||||
programEnrollmentId: string; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
export const ProgramsActionsMenu = ({ patientUuid, programEnrollmentId }: ProgramActionsProps) => { | ||||||||||||||||||||||||||||||||||
const { t } = useTranslation(); | ||||||||||||||||||||||||||||||||||
const isTablet = useLayoutType() === 'tablet'; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const launchEditProgramsForm = useCallback( | ||||||||||||||||||||||||||||||||||
() => launchPatientWorkspace('programs-form-workspace', { programEnrollmentId }), | ||||||||||||||||||||||||||||||||||
[programEnrollmentId], | ||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const launchDeleteProgramDialog = () => { | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can wrap this in a |
||||||||||||||||||||||||||||||||||
const dispose = showModal('program-delete-confirmation-modal', { | ||||||||||||||||||||||||||||||||||
closeDeleteModal: () => dispose(), | ||||||||||||||||||||||||||||||||||
programEnrollmentId, | ||||||||||||||||||||||||||||||||||
patientUuid, | ||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||
<Layer> | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
And then the associated styles would look something like this: .layer {
height: 100%;
:global(.cds--overflow-menu) {
min-height: unset;
}
}
.menuItem {
max-width: none;
} This will make your UI visually consistent with other flows like the Conditions edit experience. |
||||||||||||||||||||||||||||||||||
<OverflowMenu | ||||||||||||||||||||||||||||||||||
name={t('editOrDeleteProgram', 'Edit or delete program')} | ||||||||||||||||||||||||||||||||||
aria-label={t('editOrDeleteProgram', 'Edit or delete program')} | ||||||||||||||||||||||||||||||||||
size={isTablet ? 'lg' : 'sm'} | ||||||||||||||||||||||||||||||||||
flipped | ||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
The |
||||||||||||||||||||||||||||||||||
<OverflowMenuItem id="editProgram" onClick={launchEditProgramsForm} itemText={t('edit', 'Edit')} /> | ||||||||||||||||||||||||||||||||||
<OverflowMenuItem id="deleteProgam" onClick={launchDeleteProgramDialog} itemText={t('delete', 'Delete')} /> | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||
</OverflowMenu> | ||||||||||||||||||||||||||||||||||
</Layer> | ||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||
}; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,6 +1,6 @@ | ||||||
import React, { type ComponentProps, useCallback, useMemo } from 'react'; | ||||||
import classNames from 'classnames'; | ||||||
import { type TFunction, useTranslation } from 'react-i18next'; | ||||||
import { useTranslation } from 'react-i18next'; | ||||||
import { | ||||||
Button, | ||||||
DataTable, | ||||||
|
@@ -19,7 +19,6 @@ import { | |||||
import { | ||||||
AddIcon, | ||||||
type ConfigObject, | ||||||
EditIcon, | ||||||
formatDate, | ||||||
formatDatetime, | ||||||
useConfig, | ||||||
|
@@ -29,16 +28,12 @@ import { | |||||
import { CardHeader, EmptyState, ErrorState, launchPatientWorkspace } from '@openmrs/esm-patient-common-lib'; | ||||||
import { findLastState, usePrograms } from './programs.resource'; | ||||||
import styles from './programs-detailed-summary.scss'; | ||||||
import { ProgramsActionsMenu } from './programs-actions-menu.component'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the correct name for this component and file should be |
||||||
|
||||||
interface ProgramsDetailedSummaryProps { | ||||||
patientUuid: string; | ||||||
} | ||||||
|
||||||
interface ProgramEditButtonProps { | ||||||
programEnrollmentId: string; | ||||||
t: TFunction; | ||||||
} | ||||||
|
||||||
const ProgramsDetailedSummary: React.FC<ProgramsDetailedSummaryProps> = ({ patientUuid }) => { | ||||||
const { t } = useTranslation(); | ||||||
const { hideAddProgramButton, showProgramStatusField } = useConfig<ConfigObject>(); | ||||||
|
@@ -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"> | ||||||
PiusKariuki marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
<ProgramEditButton programEnrollmentId={enrollments[i]?.uuid} t={t} /> | ||||||
<TableCell> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But it is here to ensure the OverflowMenu takes up the appropriate amount of space.
Suggested change
|
||||||
<ProgramsActionsMenu patientUuid={patientUuid} programEnrollmentId={enrollments[i]?.uuid} /> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should add this to the |
||||||
</TableCell> | ||||||
</TableRow> | ||||||
))} | ||||||
|
@@ -176,25 +171,4 @@ const ProgramsDetailedSummary: React.FC<ProgramsDetailedSummaryProps> = ({ patie | |||||
return <EmptyState displayText={displayText} headerTitle={headerTitle} launchForm={launchProgramsForm} />; | ||||||
}; | ||||||
|
||||||
function ProgramEditButton({ programEnrollmentId, t }: ProgramEditButtonProps) { | ||||||
const isTablet = useLayoutType() === 'tablet'; | ||||||
const launchEditProgramsForm = useCallback( | ||||||
() => launchPatientWorkspace('programs-form-workspace', { programEnrollmentId }), | ||||||
[programEnrollmentId], | ||||||
); | ||||||
|
||||||
return ( | ||||||
<Button | ||||||
aria-label="edit program" | ||||||
kind="ghost" | ||||||
renderIcon={(props: ComponentProps<typeof EditIcon>) => <EditIcon size={16} {...props} />} | ||||||
iconDescription={t('editProgram', 'Edit Program')} | ||||||
onClick={launchEditProgramsForm} | ||||||
hasIconOnly | ||||||
tooltipPosition="left" | ||||||
size={isTablet ? 'lg' : 'sm'} | ||||||
/> | ||||||
); | ||||||
} | ||||||
|
||||||
export default ProgramsDetailedSummary; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer userEvent over fireEvent.