Skip to content

Commit f1adaa8

Browse files
committed
feat(tests): add unit tests for LocalEventRepository and LocalTaskRepository
- Introduce comprehensive unit tests for LocalEventRepository, covering create, get, edit, delete, and reorder functionalities. - Implement unit tests for LocalTaskRepository, validating task retrieval, saving, deletion, and reordering operations. - Utilize factory functions to create test events and tasks with sensible defaults, ensuring robust test coverage. - Mock storage utilities to isolate tests and verify interactions with the local storage layer.
1 parent 18b7a52 commit f1adaa8

File tree

8 files changed

+883
-23
lines changed

8 files changed

+883
-23
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { Origin, Priorities } from "@core/constants/core.constants";
2+
import { Event_Core } from "@core/types/event.types";
3+
import dayjs from "@core/util/date/dayjs";
4+
import { createMockStandaloneEvent } from "@core/util/test/ccal.event.factory";
5+
import { Task } from "@web/common/types/task.types";
6+
7+
/**
8+
* Factory function to create a test Event_Core with sensible defaults.
9+
* @param overrides - Partial event properties to override defaults
10+
* @returns A complete Event_Core object
11+
*/
12+
export const createTestEvent = (
13+
overrides: Partial<Event_Core & { order?: number }> = {},
14+
): Event_Core & { order?: number } => {
15+
const dateStr = dayjs().format(dayjs.DateFormat.YEAR_MONTH_DAY_FORMAT);
16+
return {
17+
_id: `event-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
18+
title: "Test Event",
19+
startDate: dateStr,
20+
endDate: dateStr,
21+
origin: Origin.COMPASS,
22+
priority: Priorities.UNASSIGNED,
23+
user: "user-1",
24+
...overrides,
25+
};
26+
};
27+
28+
/**
29+
* Factory function to create multiple test events.
30+
* @param count - Number of events to create
31+
* @param overrides - Partial event properties to override defaults (applied to all events)
32+
* @returns Array of Event_Core objects
33+
*/
34+
export const createTestEvents = (
35+
count: number,
36+
overrides: Partial<Event_Core> = {},
37+
): Event_Core[] => {
38+
return Array.from({ length: count }, (_, index) =>
39+
createTestEvent({
40+
...overrides,
41+
_id: overrides._id || `event-${index + 1}`,
42+
title: overrides.title || `Event ${index + 1}`,
43+
}),
44+
);
45+
};
46+
47+
/**
48+
* Factory function to create a test CompassCoreEvent (for edit operations).
49+
* Uses the existing factory from @core/util/test/ccal.event.factory.
50+
* @param overrides - Partial event properties to override defaults
51+
* @returns A complete event object compatible with CompassCoreEvent
52+
*/
53+
export const createTestCompassEvent = (
54+
overrides: Parameters<typeof createMockStandaloneEvent>[0] = {},
55+
) => {
56+
return createMockStandaloneEvent(overrides);
57+
};
58+
59+
/**
60+
* Factory function to create a test Task with sensible defaults.
61+
* @param overrides - Partial task properties to override defaults
62+
* @returns A complete Task object
63+
*/
64+
export const createTestTask = (overrides: Partial<Task> = {}): Task => {
65+
return {
66+
id: `task-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
67+
title: "Test Task",
68+
status: "todo",
69+
order: 0,
70+
createdAt: new Date().toISOString(),
71+
...overrides,
72+
};
73+
};
74+
75+
/**
76+
* Factory function to create multiple test tasks.
77+
* @param count - Number of tasks to create
78+
* @param overrides - Partial task properties to override defaults (applied to all tasks)
79+
* @returns Array of Task objects
80+
*/
81+
export const createTestTasks = (
82+
count: number,
83+
overrides: Partial<Task> = {},
84+
): Task[] => {
85+
return Array.from({ length: count }, (_, index) =>
86+
createTestTask({
87+
...overrides,
88+
id: overrides.id || `task-${index + 1}`,
89+
title: overrides.title || `Task ${index + 1}`,
90+
order: overrides.order !== undefined ? overrides.order : index,
91+
}),
92+
);
93+
};
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
import {
2+
CompassCoreEvent,
3+
Event_Core,
4+
Params_Events,
5+
Payload_Order,
6+
} from "@core/types/event.types";
7+
import dayjs from "@core/util/date/dayjs";
8+
import {
9+
createTestCompassEvent,
10+
createTestEvent,
11+
} from "@web/__tests__/utils/repositories/repository.test.factory";
12+
import { LocalEventRepository } from "./local.event.repository";
13+
14+
const mockEvents = new Map<string, Event_Core>();
15+
16+
jest.mock("@web/common/utils/storage/event.storage.util", () => {
17+
// eslint-disable-next-line @typescript-eslint/no-require-imports
18+
const dayjsModule = require("@core/util/date/dayjs");
19+
const dayjs = dayjsModule.default;
20+
return {
21+
saveEventToIndexedDB: jest.fn(async (event: Event_Core) => {
22+
mockEvents.set(event._id!, event);
23+
}),
24+
loadEventsFromIndexedDB: jest.fn(
25+
async (startDate: string, endDate: string, isSomeday?: boolean) => {
26+
const allEvents = Array.from(mockEvents.values());
27+
// Simple date filtering for tests
28+
const start = dayjs(startDate);
29+
const end = dayjs(endDate);
30+
let filtered = allEvents.filter((event) => {
31+
if (!event.startDate) return false;
32+
const eventStart = dayjs(event.startDate);
33+
return eventStart.isBetween(start, end, "day", "[]");
34+
});
35+
if (isSomeday !== undefined) {
36+
filtered = filtered.filter((event) => event.isSomeday === isSomeday);
37+
}
38+
return filtered;
39+
},
40+
),
41+
deleteEventFromIndexedDB: jest.fn(async (id: string) => {
42+
mockEvents.delete(id);
43+
}),
44+
};
45+
});
46+
47+
jest.mock("@web/common/utils/storage/compass-local.db", () => {
48+
return {
49+
compassLocalDB: {
50+
events: {
51+
toArray: jest.fn(async () => Array.from(mockEvents.values())),
52+
},
53+
},
54+
};
55+
});
56+
57+
describe("LocalEventRepository", () => {
58+
let repository: LocalEventRepository;
59+
60+
beforeEach(async () => {
61+
repository = new LocalEventRepository();
62+
mockEvents.clear();
63+
});
64+
65+
afterEach(async () => {
66+
mockEvents.clear();
67+
});
68+
69+
describe("create", () => {
70+
it("should save a single event to IndexedDB", async () => {
71+
const event = createTestEvent({
72+
_id: "event-1",
73+
title: "Test Event",
74+
});
75+
76+
await repository.create(event);
77+
78+
expect(mockEvents.has("event-1")).toBe(true);
79+
const savedEvent = mockEvents.get("event-1");
80+
expect(savedEvent).toBeDefined();
81+
expect(savedEvent?._id).toBe("event-1");
82+
expect(savedEvent?.title).toBe("Test Event");
83+
});
84+
85+
it("should save multiple events to IndexedDB", async () => {
86+
const events = [
87+
createTestEvent({ _id: "event-1", title: "Event 1" }),
88+
createTestEvent({ _id: "event-2", title: "Event 2" }),
89+
];
90+
91+
await repository.create(events);
92+
93+
expect(mockEvents.has("event-1")).toBe(true);
94+
expect(mockEvents.has("event-2")).toBe(true);
95+
});
96+
});
97+
98+
describe("get", () => {
99+
it("should load events from IndexedDB filtered by date range", async () => {
100+
const today = dayjs().startOf("day");
101+
const tomorrow = today.add(1, "day").startOf("day");
102+
103+
const todayStr = today.format(dayjs.DateFormat.YEAR_MONTH_DAY_FORMAT);
104+
const tomorrowStr = tomorrow.format(
105+
dayjs.DateFormat.YEAR_MONTH_DAY_FORMAT,
106+
);
107+
108+
const event1 = createTestEvent({
109+
_id: "event-1",
110+
title: "Today Event",
111+
startDate: todayStr,
112+
endDate: todayStr,
113+
});
114+
115+
await repository.create([event1]);
116+
117+
// Verify event was created
118+
expect(mockEvents.size).toBe(1);
119+
120+
const params: Params_Events = {
121+
startDate: todayStr,
122+
endDate: tomorrowStr,
123+
someday: false,
124+
};
125+
126+
const result = await repository.get(params);
127+
128+
// Verify the result structure
129+
expect(result).toHaveProperty("data");
130+
expect(result).toHaveProperty("count");
131+
expect(result).toHaveProperty("startDate");
132+
expect(result).toHaveProperty("endDate");
133+
expect(result.startDate).toBe(params.startDate);
134+
expect(result.endDate).toBe(params.endDate);
135+
expect(Array.isArray(result.data)).toBe(true);
136+
});
137+
138+
it("should filter by isSomeday flag", async () => {
139+
const today = dayjs().startOf("day");
140+
const dateStr = today.format(dayjs.DateFormat.YEAR_MONTH_DAY_FORMAT);
141+
142+
const somedayEvent = createTestEvent({
143+
_id: "someday-1",
144+
title: "Someday Event",
145+
startDate: dateStr,
146+
endDate: dateStr,
147+
isSomeday: true,
148+
});
149+
150+
const regularEvent = createTestEvent({
151+
_id: "regular-1",
152+
title: "Regular Event",
153+
startDate: dateStr,
154+
endDate: dateStr,
155+
isSomeday: false,
156+
});
157+
158+
await repository.create([somedayEvent, regularEvent]);
159+
160+
const params: Params_Events = {
161+
startDate: dateStr,
162+
endDate: dateStr,
163+
someday: true,
164+
};
165+
166+
const result = await repository.get(params);
167+
168+
expect(result.data).toHaveLength(1);
169+
expect(result.data[0]._id).toBe("someday-1");
170+
});
171+
172+
it("should return correct pagination metadata", async () => {
173+
const dateStr = dayjs().format(dayjs.DateFormat.YEAR_MONTH_DAY_FORMAT);
174+
const params: Params_Events = {
175+
startDate: dateStr,
176+
endDate: dateStr,
177+
someday: false,
178+
};
179+
180+
const result = await repository.get(params);
181+
182+
expect(result.page).toBe(1);
183+
expect(result.pageSize).toBeGreaterThanOrEqual(0);
184+
expect(result.offset).toBe(0);
185+
expect(result.count).toBeGreaterThanOrEqual(0);
186+
});
187+
});
188+
189+
describe("edit", () => {
190+
it("should update an event in IndexedDB", async () => {
191+
const event = createTestEvent({
192+
_id: "event-1",
193+
title: "Original Title",
194+
});
195+
196+
await repository.create(event);
197+
198+
const updatedEvent = createTestCompassEvent({
199+
_id: event._id!,
200+
title: "Updated Title",
201+
startDate: event.startDate,
202+
origin: event.origin,
203+
priority: event.priority,
204+
user: event.user,
205+
});
206+
207+
// createMockStandaloneEvent always generates _id and endDate at runtime
208+
await repository.edit("event-1", updatedEvent as CompassCoreEvent);
209+
210+
const savedEvent = mockEvents.get("event-1");
211+
expect(savedEvent?.title).toBe("Updated Title");
212+
});
213+
});
214+
215+
describe("delete", () => {
216+
it("should delete an event from IndexedDB", async () => {
217+
const event = createTestEvent({
218+
_id: "event-1",
219+
title: "Test Event",
220+
});
221+
222+
await repository.create(event);
223+
await repository.delete("event-1");
224+
225+
expect(mockEvents.has("event-1")).toBe(false);
226+
});
227+
});
228+
229+
describe("reorder", () => {
230+
it("should update event order in IndexedDB", async () => {
231+
const event1 = createTestEvent({
232+
_id: "event-1",
233+
title: "Event 1",
234+
});
235+
(event1 as Event_Core & { order?: number }).order = 0;
236+
237+
const event2 = createTestEvent({
238+
_id: "event-2",
239+
title: "Event 2",
240+
});
241+
(event2 as Event_Core & { order?: number }).order = 1;
242+
243+
await repository.create([event1, event2]);
244+
245+
const order: Payload_Order[] = [
246+
{ _id: "event-2", order: 0 },
247+
{ _id: "event-1", order: 1 },
248+
];
249+
250+
await repository.reorder(order);
251+
252+
const updatedEvent1 = mockEvents.get("event-1");
253+
const updatedEvent2 = mockEvents.get("event-2");
254+
expect((updatedEvent1 as Event_Core & { order?: number })?.order).toBe(1);
255+
expect((updatedEvent2 as Event_Core & { order?: number })?.order).toBe(0);
256+
});
257+
258+
it("should only update order for events in the order array", async () => {
259+
const event1 = createTestEvent({
260+
_id: "event-1",
261+
title: "Event 1",
262+
});
263+
(event1 as Event_Core & { order?: number }).order = 0;
264+
265+
const event2 = createTestEvent({
266+
_id: "event-2",
267+
title: "Event 2",
268+
});
269+
(event2 as Event_Core & { order?: number }).order = 1;
270+
271+
const event3 = createTestEvent({
272+
_id: "event-3",
273+
title: "Event 3",
274+
});
275+
(event3 as Event_Core & { order?: number }).order = 2;
276+
277+
await repository.create([event1, event2, event3]);
278+
279+
const order: Payload_Order[] = [{ _id: "event-1", order: 5 }];
280+
281+
await repository.reorder(order);
282+
283+
const updatedEvent1 = mockEvents.get("event-1");
284+
const updatedEvent2 = mockEvents.get("event-2");
285+
const updatedEvent3 = mockEvents.get("event-3");
286+
expect((updatedEvent1 as Event_Core & { order?: number })?.order).toBe(5);
287+
expect((updatedEvent2 as Event_Core & { order?: number })?.order).toBe(1); // Unchanged
288+
expect((updatedEvent3 as Event_Core & { order?: number })?.order).toBe(2); // Unchanged
289+
});
290+
});
291+
});

0 commit comments

Comments
 (0)