Skip to content

Commit 2de11df

Browse files
Copilottyler-dane
andauthored
fix(web): resolve save button disabled after recurrence changes (#991)
* Initial plan * fix(web): resolve save button disabled after recurrence changes Co-authored-by: tyler-dane <30163055+tyler-dane@users.noreply.github.com> * refactor(web): remove isEventDirty logic from draft actions and related components * refactor(web): remove disableSaveBtn prop from EventForm and related components * feat(web): add helper functions for creating draft and web events in tests * feat(web): implement DraftParser and associated tests for event modification detection * refactor(web): update event schemas in draft utilities and slice types * refactor(web): update draft actions to use Schema_DraftEvent and improve dirty check logic * refactor(web): enhance submit action handling in draft actions for improved logic flow * refactor(web): remove unnecessary test for disabled save button in SaveSection * fix(web): log error details in handleError function for better debugging * refactor(web): remove unused draft conversion functions from sidebar actions * refactor(web): remove obsolete test file for draft actions * refactor(web): simplify dependencies in useDraftActions for cleaner logic * docs(web): add documentation comment for web-specific event utility functions --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tyler-dane <30163055+tyler-dane@users.noreply.github.com> Co-authored-by: Tyler Dane <tyler@switchback.tech>
1 parent fbb965a commit 2de11df

File tree

15 files changed

+476
-279
lines changed

15 files changed

+476
-279
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { faker } from "@faker-js/faker";
2+
import { Origin, Priorities } from "@core/constants/core.constants";
3+
import dayjs from "@core/util/date/dayjs";
4+
import { Schema_DraftEvent } from "@web/common/schemas/events/draft.event.schemas";
5+
import { Schema_WebEvent } from "@web/common/schemas/events/web.event.schemas";
6+
7+
/**
8+
* These utils focus on generating web-specific schemas.
9+
* For generating API-compatible events, see utils in `@core`
10+
*/
11+
12+
/**
13+
* Helper function to create a base draft event for testing with faker.js generated data
14+
*/
15+
export const createDraftEvent = (
16+
overrides: Partial<Schema_DraftEvent> = {},
17+
): Schema_DraftEvent => {
18+
const start = faker.date.future();
19+
const end = dayjs(start).add(1, "hour");
20+
21+
return {
22+
_id: undefined,
23+
origin: Origin.COMPASS,
24+
title: faker.lorem.sentence(),
25+
description: faker.lorem.paragraph(),
26+
startDate: start.toISOString(),
27+
endDate: end.toISOString(),
28+
priority: faker.helpers.arrayElement(Object.values(Priorities)),
29+
recurrence: undefined,
30+
user: faker.string.uuid(),
31+
...overrides,
32+
};
33+
};
34+
35+
/**
36+
* Helper function to create a base web event for testing with faker.js generated data
37+
*/
38+
export const createWebEvent = (
39+
overrides: Partial<Schema_WebEvent> = {},
40+
): Schema_WebEvent => {
41+
const start = faker.date.future();
42+
const end = dayjs(start).add(1, "hour");
43+
44+
return {
45+
_id: faker.string.uuid(),
46+
origin: Origin.COMPASS,
47+
title: faker.lorem.sentence(),
48+
description: faker.lorem.paragraph(),
49+
startDate: start.toISOString(),
50+
endDate: end.toISOString(),
51+
priority: faker.helpers.arrayElement(Object.values(Priorities)),
52+
recurrence: undefined,
53+
user: faker.string.uuid(),
54+
...overrides,
55+
};
56+
};
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
import { faker } from "@faker-js/faker";
2+
import { Priorities } from "@core/constants/core.constants";
3+
import {
4+
createDraftEvent,
5+
createWebEvent,
6+
} from "../../__tests__/utils/event.util/test.draft.util";
7+
import { DraftParser, isDraftDirty } from "./draft.parser";
8+
9+
describe("DraftParser", () => {
10+
it("should return false when draft and original events are identical", () => {
11+
const originalEvent = createWebEvent();
12+
const draftEvent = createDraftEvent({
13+
title: originalEvent.title,
14+
description: originalEvent.description,
15+
startDate: originalEvent.startDate,
16+
endDate: originalEvent.endDate,
17+
priority: originalEvent.priority,
18+
recurrence: originalEvent.recurrence,
19+
});
20+
21+
const parser = new DraftParser(draftEvent, originalEvent);
22+
expect(parser.isDirty()).toBe(false);
23+
});
24+
25+
it("should return true when title has changed", () => {
26+
const originalEvent = createWebEvent();
27+
const draftEvent = createDraftEvent({
28+
title: "Different Title",
29+
description: originalEvent.description,
30+
startDate: originalEvent.startDate,
31+
endDate: originalEvent.endDate,
32+
priority: originalEvent.priority,
33+
recurrence: originalEvent.recurrence,
34+
});
35+
36+
const parser = new DraftParser(draftEvent, originalEvent);
37+
expect(parser.isDirty()).toBe(true);
38+
});
39+
40+
it("should return true when description has changed", () => {
41+
const originalEvent = createWebEvent();
42+
const draftEvent = createDraftEvent({
43+
title: originalEvent.title,
44+
description: "Different Description",
45+
startDate: originalEvent.startDate,
46+
endDate: originalEvent.endDate,
47+
priority: originalEvent.priority,
48+
recurrence: originalEvent.recurrence,
49+
});
50+
51+
const parser = new DraftParser(draftEvent, originalEvent);
52+
expect(parser.isDirty()).toBe(true);
53+
});
54+
55+
it("should return true when startDate has changed", () => {
56+
const originalEvent = createWebEvent();
57+
const draftEvent = createDraftEvent({
58+
title: originalEvent.title,
59+
description: originalEvent.description,
60+
startDate: faker.date.future().toISOString(),
61+
endDate: originalEvent.endDate,
62+
priority: originalEvent.priority,
63+
recurrence: originalEvent.recurrence,
64+
});
65+
66+
const parser = new DraftParser(draftEvent, originalEvent);
67+
expect(parser.isDirty()).toBe(true);
68+
});
69+
70+
it("should return true when endDate has changed", () => {
71+
const originalEvent = createWebEvent();
72+
const draftEvent = createDraftEvent({
73+
title: originalEvent.title,
74+
description: originalEvent.description,
75+
startDate: originalEvent.startDate,
76+
endDate: faker.date.future().toISOString(),
77+
priority: originalEvent.priority,
78+
recurrence: originalEvent.recurrence,
79+
});
80+
81+
const parser = new DraftParser(draftEvent, originalEvent);
82+
expect(parser.isDirty()).toBe(true);
83+
});
84+
85+
it("should return true when priority has changed", () => {
86+
const originalEvent = createWebEvent({ priority: Priorities.WORK });
87+
const draftEvent = createDraftEvent({
88+
title: originalEvent.title,
89+
description: originalEvent.description,
90+
startDate: originalEvent.startDate,
91+
endDate: originalEvent.endDate,
92+
priority: Priorities.SELF,
93+
recurrence: originalEvent.recurrence,
94+
});
95+
96+
const parser = new DraftParser(draftEvent, originalEvent);
97+
expect(parser.isDirty()).toBe(true);
98+
});
99+
100+
it("should return true when recurrence is added to non-recurring event", () => {
101+
const originalEvent = createWebEvent({ recurrence: undefined });
102+
const draftEvent = createDraftEvent({
103+
title: originalEvent.title,
104+
description: originalEvent.description,
105+
startDate: originalEvent.startDate,
106+
endDate: originalEvent.endDate,
107+
priority: originalEvent.priority,
108+
recurrence: { rule: ["RRULE:FREQ=WEEKLY"] },
109+
});
110+
111+
const parser = new DraftParser(draftEvent, originalEvent);
112+
expect(parser.isDirty()).toBe(true);
113+
});
114+
115+
it("should return true when recurrence is removed from recurring event", () => {
116+
const originalEvent = createWebEvent({
117+
recurrence: { rule: ["RRULE:FREQ=WEEKLY"] },
118+
});
119+
const draftEvent = createDraftEvent({
120+
title: originalEvent.title,
121+
description: originalEvent.description,
122+
startDate: originalEvent.startDate,
123+
endDate: originalEvent.endDate,
124+
priority: originalEvent.priority,
125+
recurrence: undefined,
126+
});
127+
128+
const parser = new DraftParser(draftEvent, originalEvent);
129+
expect(parser.isDirty()).toBe(true);
130+
});
131+
132+
it("should return true when recurrence rules have changed", () => {
133+
const originalEvent = createWebEvent({
134+
recurrence: { rule: ["RRULE:FREQ=WEEKLY"] },
135+
});
136+
const draftEvent = createDraftEvent({
137+
title: originalEvent.title,
138+
description: originalEvent.description,
139+
startDate: originalEvent.startDate,
140+
endDate: originalEvent.endDate,
141+
priority: originalEvent.priority,
142+
recurrence: { rule: ["RRULE:FREQ=DAILY"] },
143+
});
144+
145+
const parser = new DraftParser(draftEvent, originalEvent);
146+
expect(parser.isDirty()).toBe(true);
147+
});
148+
149+
it("should return true when recurrence rules array length has changed", () => {
150+
const originalEvent = createWebEvent({
151+
recurrence: { rule: ["RRULE:FREQ=WEEKLY"] },
152+
});
153+
const draftEvent = createDraftEvent({
154+
title: originalEvent.title,
155+
description: originalEvent.description,
156+
startDate: originalEvent.startDate,
157+
endDate: originalEvent.endDate,
158+
priority: originalEvent.priority,
159+
recurrence: { rule: ["RRULE:FREQ=WEEKLY", "RRULE:BYDAY=MO"] },
160+
});
161+
162+
const parser = new DraftParser(draftEvent, originalEvent);
163+
expect(parser.isDirty()).toBe(true);
164+
});
165+
166+
it("should return true when dates change in recurring event", () => {
167+
const originalEvent = createWebEvent({
168+
recurrence: { rule: ["RRULE:FREQ=WEEKLY"] },
169+
startDate: "2024-01-01T10:00:00Z",
170+
endDate: "2024-01-01T11:00:00Z",
171+
});
172+
const draftEvent = createDraftEvent({
173+
title: originalEvent.title,
174+
description: originalEvent.description,
175+
startDate: "2024-01-01T11:00:00Z", // Different start time
176+
endDate: "2024-01-01T12:00:00Z", // Different end time
177+
priority: originalEvent.priority,
178+
recurrence: { rule: ["RRULE:FREQ=WEEKLY"] }, // Same recurrence
179+
});
180+
181+
const parser = new DraftParser(draftEvent, originalEvent);
182+
expect(parser.isDirty()).toBe(true);
183+
});
184+
185+
it("should return false when only non-tracked fields change", () => {
186+
const originalEvent = createWebEvent();
187+
const draftEvent = createDraftEvent({
188+
title: originalEvent.title,
189+
description: originalEvent.description,
190+
startDate: originalEvent.startDate,
191+
endDate: originalEvent.endDate,
192+
priority: originalEvent.priority,
193+
recurrence: originalEvent.recurrence,
194+
user: "different-user", // This field is not tracked
195+
});
196+
197+
const parser = new DraftParser(draftEvent, originalEvent);
198+
expect(parser.isDirty()).toBe(false);
199+
});
200+
201+
it("should handle undefined recurrence gracefully", () => {
202+
const originalEvent = createWebEvent({ recurrence: undefined });
203+
const draftEvent = createDraftEvent({
204+
title: originalEvent.title,
205+
description: originalEvent.description,
206+
startDate: originalEvent.startDate,
207+
endDate: originalEvent.endDate,
208+
priority: originalEvent.priority,
209+
recurrence: undefined,
210+
});
211+
212+
const parser = new DraftParser(draftEvent, originalEvent);
213+
expect(parser.isDirty()).toBe(false);
214+
});
215+
216+
it("should handle empty recurrence rules", () => {
217+
const originalEvent = createWebEvent({
218+
recurrence: { rule: [] },
219+
});
220+
const draftEvent = createDraftEvent({
221+
title: originalEvent.title,
222+
description: originalEvent.description,
223+
startDate: originalEvent.startDate,
224+
endDate: originalEvent.endDate,
225+
priority: originalEvent.priority,
226+
recurrence: { rule: [] },
227+
});
228+
229+
const parser = new DraftParser(draftEvent, originalEvent);
230+
expect(parser.isDirty()).toBe(false);
231+
});
232+
});
233+
234+
describe("isDraftDirty", () => {
235+
it("should work as a standalone function", () => {
236+
const originalEvent = createWebEvent();
237+
const draftEvent = createDraftEvent({
238+
title: "Different Title",
239+
description: originalEvent.description,
240+
startDate: originalEvent.startDate,
241+
endDate: originalEvent.endDate,
242+
priority: originalEvent.priority,
243+
recurrence: originalEvent.recurrence,
244+
});
245+
246+
expect(isDraftDirty(draftEvent, originalEvent)).toBe(true);
247+
});
248+
249+
it("should return false for identical events", () => {
250+
const originalEvent = createWebEvent();
251+
const draftEvent = createDraftEvent({
252+
title: originalEvent.title,
253+
description: originalEvent.description,
254+
startDate: originalEvent.startDate,
255+
endDate: originalEvent.endDate,
256+
priority: originalEvent.priority,
257+
recurrence: originalEvent.recurrence,
258+
});
259+
260+
expect(isDraftDirty(draftEvent, originalEvent)).toBe(false);
261+
});
262+
});

0 commit comments

Comments
 (0)