From 18c87857de1b7883e7067a973e3c5ddfed3a77ea Mon Sep 17 00:00:00 2001 From: Dohyung Ahn Date: Fri, 12 Aug 2022 15:48:13 +0900 Subject: [PATCH 1/2] fix: prevent opening form popup through detail popup when it is disabled --- .../popup/eventDetailPopup.spec.tsx | 59 +++++++++++++++++-- .../src/components/popup/eventDetailPopup.tsx | 33 +++++++---- 2 files changed, 75 insertions(+), 17 deletions(-) diff --git a/apps/calendar/src/components/popup/eventDetailPopup.spec.tsx b/apps/calendar/src/components/popup/eventDetailPopup.spec.tsx index 46836999a..7667024bd 100644 --- a/apps/calendar/src/components/popup/eventDetailPopup.spec.tsx +++ b/apps/calendar/src/components/popup/eventDetailPopup.spec.tsx @@ -5,11 +5,12 @@ import { initCalendarStore, StoreProvider, useDispatch } from '@src/contexts/cal import { EventBusProvider } from '@src/contexts/eventBus'; import { FloatingLayerProvider } from '@src/contexts/floatingLayer'; import EventModel from '@src/model/eventModel'; -import { render, screen } from '@src/test/utils'; +import { render, screen, userEvent } from '@src/test/utils'; import TZDate from '@src/time/date'; import { EventBusImpl } from '@src/utils/eventBus'; import type { PropsWithChildren } from '@t/components/common'; +import type { Options } from '@t/options'; describe('event detail popup', () => { const mockCalendarId = 'calendarId'; @@ -48,7 +49,7 @@ describe('event detail popup', () => { return {children}; }; - beforeEach(() => { + function setup(options: Options = {}) { const eventBus = new EventBusImpl(); const store = initCalendarStore({ calendars: [ @@ -57,9 +58,14 @@ describe('event detail popup', () => { name: mockCalendarName, }, ], + ...options, }); - render( + // Spy should be set before rendering + const showFormPopupSpy = jest.fn(); + store.getState().dispatch.popup.showFormPopup = showFormPopupSpy; + + const renderResult = render( @@ -68,9 +74,17 @@ describe('event detail popup', () => { ); - }); + + return { + eventBus, + store, + renderResult, + showFormPopupSpy, + }; + } it('should display location when `event.location` is exists', () => { + setup(); const { location } = event; const locationText = screen.getByText(location).textContent; @@ -78,6 +92,7 @@ describe('event detail popup', () => { }); it('should display recurrence rule when `event.recurrenceRule` is exists', () => { + setup(); const { recurrenceRule } = event; const recurrenceRuleText = screen.getByText(recurrenceRule).textContent; @@ -85,6 +100,7 @@ describe('event detail popup', () => { }); it('should display attendees when `event.attendees` is exists', () => { + setup(); const { attendees } = event; const text = attendees.join(', '); const attendeesText = screen.getByText(text).textContent; @@ -93,6 +109,7 @@ describe('event detail popup', () => { }); it('should display state when `event.state` is exists', () => { + setup(); const { state } = event; const stateText = screen.getByText(state).textContent; @@ -100,12 +117,14 @@ describe('event detail popup', () => { }); it('should display calendar name when `event.calendarId` and corresponding calendar is exists', () => { + setup(); const calendarName = screen.getByText(mockCalendarName); expect(calendarName).toBeInTheDocument(); }); it('should display body when `event.body` is exists', () => { + setup(); const { body } = event; const bodyText = screen.getByText(body).textContent; @@ -113,10 +132,42 @@ describe('event detail popup', () => { }); it('should display edit and delete buttons when event is not read only', () => { + setup(); const editButton = screen.getByText('Edit'); const deleteButton = screen.getByText('Delete'); expect(editButton).not.toBeNull(); expect(deleteButton).not.toBeNull(); }); + + it('should open the form popup when edit button is clicked, while the `useFormPopup` option is enabled', async () => { + // Given + const { showFormPopupSpy } = setup({ useFormPopup: true }); + const user = userEvent.setup(); + const editButton = screen.getByText('Edit'); + + // When + await user.click(editButton); + + // Then + expect(showFormPopupSpy).toHaveBeenCalledWith(expect.objectContaining({ event })); + }); + + it('should only fire the `beforeUpdateEvent` event when edit button is clicked, while the `useFormPopup` option is disabled', async () => { + // Given + const { eventBus } = setup({ useFormPopup: false }); + const mockEventHandler = jest.fn(); + eventBus.on('beforeUpdateEvent', mockEventHandler); + + const user = userEvent.setup(); + const editButton = screen.getByText('Edit'); + + // When + await user.click(editButton); + + // Then + expect(mockEventHandler).toHaveBeenCalledWith( + expect.objectContaining({ event: event.toEventObject() }) + ); + }); }); diff --git a/apps/calendar/src/components/popup/eventDetailPopup.tsx b/apps/calendar/src/components/popup/eventDetailPopup.tsx index abf67e211..34633e3e1 100644 --- a/apps/calendar/src/components/popup/eventDetailPopup.tsx +++ b/apps/calendar/src/components/popup/eventDetailPopup.tsx @@ -13,6 +13,7 @@ import { useLayoutContainer } from '@src/contexts/layoutContainer'; import { cls } from '@src/helpers/css'; import { isLeftOutOfLayout, isTopOutOfLayout } from '@src/helpers/popup'; import { useCalendarColor } from '@src/hooks/calendar/useCalendarColor'; +import { optionsSelector } from '@src/selectors'; import { eventDetailPopupParamSelector } from '@src/selectors/popup'; import TZDate from '@src/time/date'; import { isNil } from '@src/utils/type'; @@ -66,6 +67,7 @@ function calculatePopupArrowPosition(eventRect: Rect, layoutRect: Rect, popupRec } export function EventDetailPopup() { + const { useFormPopup } = useStore(optionsSelector); const popupParams = useStore(eventDetailPopupParamSelector); const { event, eventRect } = popupParams ?? {}; @@ -128,19 +130,24 @@ export function EventDetailPopup() { left: eventRect.left + eventRect.width / 2, }; - const onClickEditButton = () => - showFormPopup({ - isCreationPopup: false, - event, - title, - location, - start, - end, - isAllday, - isPrivate, - eventState: state, - popupArrowPointPosition, - }); + const onClickEditButton = () => { + if (useFormPopup) { + showFormPopup({ + isCreationPopup: false, + event, + title, + location, + start, + end, + isAllday, + isPrivate, + eventState: state, + popupArrowPointPosition, + }); + } else { + eventBus.fire('beforeUpdateEvent', { event: event.toEventObject(), changes: {} }); + } + }; const onClickDeleteButton = () => { eventBus.fire('beforeDeleteEvent', event.toEventObject()); From 5ea042b52b942d42acedc0295f46c1f6bdb0b9e5 Mon Sep 17 00:00:00 2001 From: Dohyung Ahn Date: Fri, 12 Aug 2022 15:55:59 +0900 Subject: [PATCH 2/2] docs: update description of `beforeUpdateEvent` instance event --- docs/en/apis/calendar.md | 3 ++- docs/ko/apis/calendar.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/en/apis/calendar.md b/docs/en/apis/calendar.md index e2628eec2..629b2d336 100644 --- a/docs/en/apis/calendar.md +++ b/docs/en/apis/calendar.md @@ -809,8 +809,9 @@ The `event` property is information of the existing event, and the `changes` pro In the following cases, `beforeUpdateEvent` is executed. -- `When useFormPopup` and `useDetailPopup` of the calendar instance options are both `true` and the ‘Update’ button is pressed after modifying the event through the event details popup +- When `useFormPopup` and `useDetailPopup` of the calendar instance options are both `true` and the ‘Update’ button is pressed after modifying the event through the event details popup - ![Event execution through popup](../../assets/before-update-event-1.gif) +- When the `useDetailPopup` option is `true` and the `useFormPopup` is `false`, the 'Edit' button inside the event details popup is pressed. - When moving or resizing events by drag and drop, while `isReadOnly` is not `true` among calendar instance options and also `isReadOnly` is not `true` in the properties of individual events. - ![Event execution via drag and drop](../../assets/before-update-event-2.gif) diff --git a/docs/ko/apis/calendar.md b/docs/ko/apis/calendar.md index 4714bcf8b..572577b19 100644 --- a/docs/ko/apis/calendar.md +++ b/docs/ko/apis/calendar.md @@ -815,8 +815,9 @@ interface UpdatedEventInfo { 다음의 경우 `beforeUpdateEvent` 가 실행된다. -- 캘린더 인스턴스 옵션 중 `useFormPopup` 와 `useDetailPopup` 가 모두 `true` 이고, 이벤트 상세 팝업을 통해 이벤트를 수정 후 Update 버튼을 누를 때 +- 캘린더 인스턴스 옵션 중 `useFormPopup` 와 `useDetailPopup` 가 모두 `true` 이고, 이벤트 상세 팝업에서 Edit 버튼을 누른 후 표시되는 이벤트 폼 팝업에서 Update 버튼을 누를 때 - ![팝업을 통한 이벤트 실행](../../assets/before-update-event-1.gif) +- 캘린더의 인스턴스 옵션 중 `useDetailPopup` 이 `true` 이고, `useFormPopup` 이 `false` 일 때, 이벤트 상세 팝업에서 Edit 버튼을 누른 경우 - 캘린더 인스턴스 옵션 중 `isReadOnly` 가 `true` 가 아니며, 개별 이벤트의 속성에 `isReadOnly` 가 `true` 가 아닌 상태에서 드래그 앤 드랍으로 일정을 이동하거나 리사이징할 때 - ![드래그 앤 드랍을 통한 이벤트 실행](../../assets/before-update-event-2.gif)