Skip to content

Commit c18b08c

Browse files
authored
🐛 Fix: Runtime error when dragging all-day draft (#289)
* ✨ feat: differentiate between click and drag depending on mouse behavior if mousing up before threshold: click if mouse is still down after threshold: drag if mouse down + move before threshold: drag * 🐛 Fix: Improve event form interaction and prevent unintended behavior - Update event form to stop event propagation on mouse events - Add `type="button"` to delete buttons to prevent form submission - Modify calendar test to use `document.body` for closing form - Simplify event form mouse interaction logic * 🧹 Chore: delete commented code in Calendar.form.test https://docs.compasscalendar.com/docs/contribute/convention-guide#no-dead-code * 🧹 Chore: Uncomment test assertion in EventForm.test.tsx Remove commented-out test assertion to improve test coverage and clarity * 🐛 Fix: adjust behavior based on whether form is open * ♻️ Refactor: rename to useGridEventMouseDown and cleanup * 🐛 Fix: prevent smart scrolling after adjusting all day event
1 parent c9c32b4 commit c18b08c

File tree

13 files changed

+376
-210
lines changed

13 files changed

+376
-210
lines changed
Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { act } from "react";
22
import "@testing-library/jest-dom";
33
import { screen, waitFor, within } from "@testing-library/react";
4-
import userEvent, { UserEvent } from "@testing-library/user-event";
4+
import userEvent from "@testing-library/user-event";
55
import { CLIMB } from "@core/__mocks__/events/events.misc";
66
import { render } from "@web/__tests__/__mocks__/mock.render";
77
import { preloadedState } from "@web/__tests__/__mocks__/state/state.weekEvents";
@@ -12,54 +12,20 @@ jest.mock("@web/views/Calendar/hooks/mouse/useEventListener", () => ({
1212
}));
1313

1414
describe("Event Form", () => {
15-
// it("opens when clicking events", async () => {
16-
// const user = userEvent.setup();
17-
// render(<CalendarView />, { state: preloadedState });
18-
// expect(screen.queryByRole("form")).not.toBeInTheDocument();
19-
/* timed event */
20-
// await act(async () => {
21-
// await user.click(screen.getByRole("button", { name: /Ty & Tim/i }));
22-
// await _confirmCorrectEventFormIsOpen(TY_TIM.title);
23-
// });
24-
/* multi-week event */
25-
// await act(async () => {
26-
// await _clickHeading(user);
27-
// await user.click(
28-
// screen.getByRole("button", { name: /multiweek event/i })
29-
// );
30-
// });
31-
// await waitFor(
32-
// () => {
33-
// expect(screen.getByRole("form")).toBeInTheDocument();
34-
// },
35-
// { timeout: 10000 }
36-
// );
37-
// await _confirmCorrectEventFormIsOpen(MULTI_WEEK.title);
38-
// await waitFor(() => {
39-
// expect(
40-
// within(screen.getByRole("form")).getByText(MULTI_WEEK.title)
41-
// ).toBeInTheDocument();
42-
// });
43-
// /* someday event */
44-
// await _clickHeading(user);
45-
// await user.click(screen.getByRole("button", { name: /takeover world/i }));
46-
// expect(
47-
// within(screen.getRole("form", name: {"Someday Event Form"})).getByText("Takeover world")
48-
// ).toBeInTheDocument();
49-
// }, 20000);
5015
it("closes after clicking outside", async () => {
5116
render(<CalendarView />, { state: preloadedState });
5217
const user = userEvent.setup();
5318

5419
await act(async () => {
5520
await user.click(screen.getByRole("button", { name: CLIMB.title }));
56-
await _clickHeading(user);
5721
});
5822

59-
await waitFor(() => {
60-
expect(screen.queryByRole("form")).not.toBeInTheDocument();
23+
await act(async () => {
24+
await user.click(document.body);
6125
});
62-
}, 10000);
26+
27+
expect(screen.queryByRole("form")).not.toBeInTheDocument();
28+
});
6329
it("closes after clicking trash icon", async () => {
6430
const user = userEvent.setup();
6531
render(<CalendarView />, { state: preloadedState });
@@ -101,18 +67,3 @@ describe("Event Form", () => {
10167
});
10268
});
10369
});
104-
105-
/***********
106-
* Helpers *
107-
***********/
108-
const _clickHeading = async (user: UserEvent) => {
109-
await user.click(screen.getByRole("heading", { level: 1 }));
110-
};
111-
112-
const _confirmCorrectEventFormIsOpen = async (eventName: string) => {
113-
await waitFor(() => {
114-
expect(
115-
within(screen.getByRole("form")).getByText(eventName),
116-
).toBeInTheDocument();
117-
});
118-
};

packages/web/src/views/Calendar/components/Draft/grid/GridDraft.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { FloatingFocusManager } from "@floating-ui/react";
33
import { YEAR_MONTH_DAY_FORMAT } from "@core/constants/date.constants";
44
import { Categories_Event } from "@core/types/event.types";
55
import { Schema_GridEvent } from "@web/common/types/web.event.types";
6-
import { useGridEventMouseHold } from "@web/views/Calendar/hooks/grid/useGridEventMouseHold";
6+
import { useGridEventMouseDown } from "@web/views/Calendar/hooks/grid/useGridEventMouseDown";
77
import { Measurements_Grid } from "@web/views/Calendar/hooks/grid/useGridLayout";
88
import { WeekProps } from "@web/views/Calendar/hooks/useWeek";
99
import { EventForm } from "@web/views/Forms/EventForm";
@@ -35,9 +35,16 @@ export const GridDraft: FC<Props> = ({ measurements, weekProps }) => {
3535
actions.convert(start, end);
3636
};
3737

38-
const { onMouseDown } = useGridEventMouseHold((event) => {
38+
const handleClick = () => {};
39+
const handleDrag = () => {
3940
setIsDragging(true);
40-
}, Categories_Event.TIMED);
41+
};
42+
43+
const { onMouseDown } = useGridEventMouseDown(
44+
Categories_Event.TIMED,
45+
handleClick,
46+
handleDrag,
47+
);
4148

4249
if (!draft) return null;
4350

packages/web/src/views/Calendar/components/Draft/hooks/actions/useDraftActions.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,12 @@ export const useDraftActions = (
147147
};
148148

149149
const discard = useCallback(() => {
150-
if (draft) {
151-
setDraft(null);
152-
}
150+
reset();
153151

154152
if (reduxDraft || reduxDraftType) {
155153
dispatch(draftSlice.actions.discard());
156154
}
157-
}, [dispatch, draft, reduxDraft, reduxDraftType]);
155+
}, [dispatch, draft, reduxDraft, reduxDraftType, setDraft]);
158156

159157
const drag = useCallback(
160158
(e: MouseEvent) => {

packages/web/src/views/Calendar/components/Draft/hooks/effects/useDraftEffects.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ export const useDraftEffects = (
4040
}, [dateBeingChanged, isResizing]);
4141

4242
useEffect(() => {
43-
// const isStaleDraft = !isDrafting && (isResizing || isDragging);
4443
const isStaleDraft = !isDrafting;
4544
if (isStaleDraft) {
4645
setDraft(null);
@@ -51,26 +50,28 @@ export const useDraftEffects = (
5150
setResizeStatus(null);
5251
setDateBeingChanged(null);
5352
}
54-
}, [isDrafting]);
53+
}, [
54+
isDrafting,
55+
setDateBeingChanged,
56+
setDraft,
57+
setDragStatus,
58+
setIsDragging,
59+
setIsFormOpen,
60+
setIsResizing,
61+
setResizeStatus,
62+
]);
5563

5664
useEffect(() => {
5765
handleChange();
5866
}, [handleChange]);
5967

6068
useEffect(() => {
61-
if (isDragging) {
69+
if (isDragging && draft) {
6270
setIsFormOpen(false);
63-
setDraft((_draft) => {
64-
const durationMin = dayjs(_draft.endDate).diff(
65-
_draft.startDate,
66-
"minutes",
67-
);
68-
69-
setDragStatus({
70-
durationMin,
71-
});
71+
const durationMin = dayjs(draft.endDate).diff(draft.startDate, "minutes");
7272

73-
return draft;
73+
setDragStatus({
74+
durationMin,
7475
});
7576
}
7677
}, [isDragging]);

packages/web/src/views/Calendar/components/Grid/AllDayRow/AllDayEvents.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ import React from "react";
22
import { Categories_Event } from "@core/types/event.types";
33
import { ID_GRID_EVENTS_ALLDAY } from "@web/common/constants/web.constants";
44
import { Schema_GridEvent } from "@web/common/types/web.event.types";
5-
import { isSomedayEventFormOpen } from "@web/common/utils";
65
import { isLeftClick } from "@web/common/utils/mouse/mouse.util";
76
import { selectDraftId } from "@web/ducks/events/selectors/draft.selectors";
87
import { selectAllDayEvents } from "@web/ducks/events/selectors/event.selectors";
98
import { draftSlice } from "@web/ducks/events/slices/draft.slice";
109
import { useAppDispatch, useAppSelector } from "@web/store/store.hooks";
11-
import { useGridEventMouseHold } from "@web/views/Calendar/hooks/grid/useGridEventMouseHold";
10+
import { useGridEventMouseDown } from "@web/views/Calendar/hooks/grid/useGridEventMouseDown";
1211
import { Measurements_Grid } from "@web/views/Calendar/hooks/grid/useGridLayout";
1312
import { WeekProps } from "@web/views/Calendar/hooks/useWeek";
1413
import { AllDayEventMemo } from "./AllDayEvent";
@@ -28,18 +27,30 @@ export const AllDayEvents = ({
2827
const draftId = useAppSelector(selectDraftId);
2928
const dispatch = useAppDispatch();
3029

31-
const { onMouseDown } = useGridEventMouseHold((event) => {
32-
if (isSomedayEventFormOpen()) {
33-
dispatch(draftSlice.actions.discard());
34-
}
30+
const handleClick = (event: Schema_GridEvent) => {
31+
dispatch(
32+
draftSlice.actions.start({
33+
activity: "gridClick",
34+
event,
35+
eventType: Categories_Event.ALLDAY,
36+
}),
37+
);
38+
};
3539

40+
const handleDrag = (event: Schema_GridEvent) => {
3641
dispatch(
3742
draftSlice.actions.startDragging({
3843
category: Categories_Event.ALLDAY,
3944
event,
4045
}),
4146
);
42-
}, Categories_Event.ALLDAY);
47+
};
48+
49+
const { onMouseDown } = useGridEventMouseDown(
50+
Categories_Event.ALLDAY,
51+
handleClick,
52+
handleDrag,
53+
);
4354

4455
return (
4556
<StyledEvents id={ID_GRID_EVENTS_ALLDAY}>

packages/web/src/views/Calendar/components/Grid/MainGrid/MainGridEvents.tsx

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ import React from "react";
22
import { Categories_Event } from "@core/types/event.types";
33
import { ID_GRID_EVENTS_TIMED } from "@web/common/constants/web.constants";
44
import { Schema_GridEvent } from "@web/common/types/web.event.types";
5-
import { isEventFormOpen } from "@web/common/utils";
65
import { adjustOverlappingEvents } from "@web/common/utils/overlap/overlap";
76
import { selectDraftId } from "@web/ducks/events/selectors/draft.selectors";
87
import { selectGridEvents } from "@web/ducks/events/selectors/event.selectors";
98
import { draftSlice } from "@web/ducks/events/slices/draft.slice";
109
import { useAppDispatch, useAppSelector } from "@web/store/store.hooks";
11-
import { useGridEventMouseHold } from "@web/views/Calendar/hooks/grid/useGridEventMouseHold";
10+
import { useGridEventMouseDown } from "@web/views/Calendar/hooks/grid/useGridEventMouseDown";
1211
import { Measurements_Grid } from "@web/views/Calendar/hooks/grid/useGridLayout";
1312
import { WeekProps } from "@web/views/Calendar/hooks/useWeek";
1413
import { GridEventMemo } from "../../Event/Grid/GridEvent/GridEvent";
@@ -27,34 +26,40 @@ export const MainGridEvents = ({ measurements, weekProps }: Props) => {
2726
const adjustedEvents = adjustOverlappingEvents(timedEvents);
2827
const category = Categories_Event.TIMED;
2928

30-
const { onMouseDown } = useGridEventMouseHold((event) => {
31-
if (isEventFormOpen()) {
32-
dispatch(
33-
draftSlice.actions.swap({ event, category: Categories_Event.TIMED }),
34-
);
35-
return;
36-
}
37-
editTimedEvent(event);
38-
}, Categories_Event.TIMED);
29+
const handleClick = (event: Schema_GridEvent) => {
30+
dispatch(
31+
draftSlice.actions.start({
32+
activity: "gridClick",
33+
event,
34+
eventType: category,
35+
}),
36+
);
37+
};
3938

40-
const resizeTimedEvent = (
41-
event: Schema_GridEvent,
42-
dateToChange: "startDate" | "endDate",
43-
) => {
39+
const handleDrag = (event: Schema_GridEvent) => {
4440
dispatch(
45-
draftSlice.actions.startResizing({
41+
draftSlice.actions.startDragging({
4642
category,
4743
event,
48-
dateToChange,
4944
}),
5045
);
5146
};
5247

53-
const editTimedEvent = (event: Schema_GridEvent) => {
48+
const { onMouseDown } = useGridEventMouseDown(
49+
Categories_Event.TIMED,
50+
handleClick,
51+
handleDrag,
52+
);
53+
54+
const resizeTimedEvent = (
55+
event: Schema_GridEvent,
56+
dateToChange: "startDate" | "endDate",
57+
) => {
5458
dispatch(
55-
draftSlice.actions.startDragging({
59+
draftSlice.actions.startResizing({
5660
category,
5761
event,
62+
dateToChange,
5863
}),
5964
);
6065
};

packages/web/src/views/Calendar/hooks/grid/useDragEventSmartScroll.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const useDragEventSmartScroll = (
1313

1414
useEffect(() => {
1515
if (!state.isDragging) return;
16-
if (state.draft?.isAllDay) return;
16+
if (state.draft?.isAllDay !== false) return;
1717

1818
const updateMousePosition = (event: MouseEvent) => {
1919
setMousePosition({ x: event.clientX, y: event.clientY });
@@ -24,15 +24,16 @@ export const useDragEventSmartScroll = (
2424
return () => {
2525
window.removeEventListener("mousemove", updateMousePosition);
2626
};
27-
}, [state.isDragging]);
27+
}, [state.draft?.isAllDay, state.isDragging]);
2828

2929
useEffect(() => {
3030
if (!mainGridRef.current) return;
31-
3231
const container = mainGridRef.current;
3332

3433
const scrollIfNeeded = () => {
34+
if (!state.isDragging) return;
3535
if (!container) return;
36+
if (state.draft?.isAllDay !== false) return;
3637

3738
const { top, bottom } = container.getBoundingClientRect();
3839
const { y } = mousePosition;
@@ -67,6 +68,5 @@ export const useDragEventSmartScroll = (
6768
scrollRef.current = null;
6869
}
6970
};
70-
// eslint-disable-next-line react-hooks/exhaustive-deps
7171
}, [mousePosition]);
7272
};

0 commit comments

Comments
 (0)