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

Deleting meeting series or one instance #281

Merged
5 changes: 5 additions & 0 deletions .changeset/nervous-crabs-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@nordeck/matrix-meetings-widget': minor
---

Support deleting a single occurrence of a recurring meeting.
8 changes: 6 additions & 2 deletions e2e/src/pages/meetingCardPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,17 @@ export class MeetingCardPage {
return new MoreSettingsMenuPage(this.widget.getByRole('menu'));
}

async deleteMeeting() {
async deleteMeeting(buttonLabel: string = 'Delete') {
const menu = await this.openMoreSettingsMenu();
await menu.deleteMenuItem.click();
await this.widget
.getByRole('dialog', { name: 'Delete meeting' })
.getByRole('button', { name: 'Delete' })
.getByRole('button', { name: buttonLabel })
.click();

await this.widget
.getByRole('dialog', { name: 'Delete meeting' })
.waitFor({ state: 'hidden' });
}

async editMeeting(): Promise<ScheduleMeetingWidgetPage> {
Expand Down
37 changes: 35 additions & 2 deletions e2e/src/recurringMeetings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,47 @@ test.describe('Recurring Meetings', () => {
await expect(meeting.meetingTimeRangeText).toHaveText(
'10:30 AM – 11:30 AM. Recurrence: Every day for 5 times',
);
await meeting.deleteMeeting();
await meeting.deleteMeeting('Delete series');

await expect(
aliceMeetingsWidgetPage.getMeeting('My Meeting').card,
).toHaveCount(0);
});

// TODO: Delete single meeting
test('should delete a single recurring meeting', async ({
aliceMeetingsWidgetPage,
}) => {
await aliceMeetingsWidgetPage.setDateFilter([2040, 10, 1], [2040, 10, 9]);

const aliceScheduleMeetingWidgetPage =
await aliceMeetingsWidgetPage.scheduleMeeting();

await aliceScheduleMeetingWidgetPage.titleTextbox.fill('My Meeting');
await aliceScheduleMeetingWidgetPage.descriptionTextbox.fill(
'My Description',
);
await aliceScheduleMeetingWidgetPage.setStart([2040, 10, 3], '10:30 AM');
await aliceScheduleMeetingWidgetPage.selectRecurrence('daily');
await aliceScheduleMeetingWidgetPage.setEndAfterMeetingCount(5);
await aliceScheduleMeetingWidgetPage.submit();

const meeting = aliceMeetingsWidgetPage.getMeeting(
'My Meeting',
'10/03/2040',
);
await expect(meeting.meetingTimeRangeText).toHaveText(
'10:30 AM – 11:30 AM. Recurrence: Every day for 5 times',
);
await meeting.deleteMeeting('Delete meeting');

await expect(
aliceMeetingsWidgetPage.getMeeting('My Meeting', '10/03/2040').card,
).toHaveCount(0);
maheichyk marked this conversation as resolved.
Show resolved Hide resolved

await expect(
aliceMeetingsWidgetPage.getMeeting('My Meeting').card,
).toHaveCount(4);
});

// TODO: Delete starting from
});
8 changes: 3 additions & 5 deletions matrix-meetings-widget/public/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,6 @@
},
"meetingCard": {
"actions": "Aktionen",
"deleteConfirmButton": "Löschen",
"deleteConfirmHeader": "Besprechung löschen",
"deleteConfirmMessage": "Du bist dabei die Besprechung „{{title}}“ am {{startTime, datetime}} und alles darin zu löschen. Bist du sicher?",
"deleteFailed": "Bitte versuche es erneut.",
"deleteFailedTitle": "Fehler beim Löschen der Besprechung",
"deleteInOpenXchangeMenu": "Besprechung in Open-Xchange löschen",
"deleteMenu": "Besprechung löschen",
"durationSubheader_default": "{{startDate, datetime}} – {{endDate, datetime}}",
Expand Down Expand Up @@ -160,7 +155,10 @@
"deleteFailed": "Bitte versuche es erneut.",
"deleteFailedTitle": "Fehler beim Löschen der Besprechung",
"deleteInOpenXchangeMenu": "Besprechung in Open-Xchange löschen",
"deleteMeetingConfirmButton": "Besprechung löschen",
"deleteMenu": "Löschen",
"deleteSeriesConfirmButton": "Besprechungsserie löschen",
"deleteSeriesConfirmMessage": "Du bist dabei die Besprechung oder die Besprechungsserie „{{title}}“ am {{startTime, datetime}} und alles darin zu löschen. Bist du sicher?",
"editInOpenXchangeMenu": "Besprechung in Open-Xchange bearbeiten",
"editMenu": "Bearbeiten",
"join": "Beitreten",
Expand Down
8 changes: 3 additions & 5 deletions matrix-meetings-widget/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,6 @@
},
"meetingCard": {
"actions": "Actions",
"deleteConfirmButton": "Delete",
"deleteConfirmHeader": "Delete meeting",
"deleteConfirmMessage": "Are you sure you want to delete the meeting “{{title}}” on {{startTime, datetime}} and every content related to it?",
"deleteFailed": "Please try again.",
"deleteFailedTitle": "Failed to delete the meeting",
"deleteInOpenXchangeMenu": "Delete meeting in Open-Xchange",
"deleteMenu": "Delete meeting",
"durationSubheader_default": "{{startDate, datetime}} – {{endDate, datetime}}",
Expand Down Expand Up @@ -160,7 +155,10 @@
"deleteFailed": "Please try again.",
"deleteFailedTitle": "Failed to delete the meeting",
"deleteInOpenXchangeMenu": "Delete meeting in Open-Xchange",
"deleteMeetingConfirmButton": "Delete meeting",
"deleteMenu": "Delete",
"deleteSeriesConfirmButton": "Delete series",
"deleteSeriesConfirmMessage": "Are you sure you want to delete the meeting or the meeting series “{{title}}” on {{startTime, datetime}} and every content related to it?",
"editInOpenXchangeMenu": "Edit meeting in Open-Xchange",
"editMenu": "Edit",
"join": "Join",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { render, screen, within } from '@testing-library/react';
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { axe } from 'jest-axe';
import { ConfirmDeleteDialog } from './ConfirmDeleteDialog';
Expand Down Expand Up @@ -140,4 +140,77 @@ describe('<ConfirmDeleteDialog/>', () => {
expect(onConfirm).not.toBeCalled();
expect(onCancel).toBeCalledTimes(1);
});

it('should render additional buttons', async () => {
render(
<ConfirmDeleteDialog
confirmTitle="Confirm"
description="The description of the modal"
onCancel={onCancel}
onConfirm={onConfirm}
open
title="Confirm the deletion"
additionalButtons={<button>Example</button>}
/>,
);

const deleteModal = screen.getByRole('dialog', {
name: 'Confirm the deletion',
});

expect(
within(deleteModal).getByRole('button', { name: 'Example' }),
).toBeInTheDocument();
});

it('should call onEnter every time the dialog is displayed', async () => {
const onEnter = jest.fn();

const { rerender } = render(
<ConfirmDeleteDialog
open
confirmTitle="Confirm"
description="The description of the modal"
onCancel={onCancel}
onConfirm={onConfirm}
onEnter={onEnter}
title="Confirm the deletion"
/>,
);

expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(onEnter).toBeCalledTimes(1);

rerender(
<ConfirmDeleteDialog
open={false}
confirmTitle="Confirm"
description="The description of the modal"
onCancel={onCancel}
onConfirm={onConfirm}
onEnter={onEnter}
title="Confirm the deletion"
/>,
);

await waitFor(() => {
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
expect(onEnter).toBeCalledTimes(1);

rerender(
<ConfirmDeleteDialog
open
confirmTitle="Confirm"
description="The description of the modal"
onCancel={onCancel}
onConfirm={onConfirm}
onEnter={onEnter}
title="Confirm the deletion"
/>,
);

expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(onEnter).toBeCalledTimes(2);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import {
DialogTitle,
} from '@mui/material';
import { unstable_useId as useId } from '@mui/utils';
import React, { DispatchWithoutAction, PropsWithChildren } from 'react';
import React, {
DispatchWithoutAction,
PropsWithChildren,
ReactElement,
} from 'react';
import { useTranslation } from 'react-i18next';

type ConfirmDeleteDialogProps = PropsWithChildren<{
Expand All @@ -33,8 +37,10 @@ type ConfirmDeleteDialogProps = PropsWithChildren<{
description: string;
confirmTitle: string;
loading?: boolean;
additionalButtons?: ReactElement;
onCancel: DispatchWithoutAction;
onConfirm: DispatchWithoutAction;
onEnter?: DispatchWithoutAction;
}>;

export function ConfirmDeleteDialog({
Expand All @@ -43,8 +49,10 @@ export function ConfirmDeleteDialog({
description,
confirmTitle,
loading,
additionalButtons,
onCancel,
onConfirm,
onEnter,
children,
}: ConfirmDeleteDialogProps) {
const { t } = useTranslation();
Expand All @@ -58,6 +66,7 @@ export function ConfirmDeleteDialog({
aria-labelledby={dialogTitleId}
onClose={onCancel}
open={open}
onTransitionEnter={onEnter}
>
<DialogTitle id={dialogTitleId}>{title}</DialogTitle>

Expand All @@ -75,6 +84,9 @@ export function ConfirmDeleteDialog({
<Button autoFocus onClick={onCancel} variant="outlined">
{t('cancel', 'Cancel')}
</Button>

{additionalButtons}

<LoadingButton
color="error"
loading={loading}
Expand Down
Loading
Loading