Skip to content

Commit c8c6415

Browse files
committed
delete(svg): remove unused circle.svg file
- Deleted the circle.svg file from the public SVG assets as it is no longer needed in the project. - This cleanup helps streamline the asset management and reduces unnecessary file clutter.
1 parent c23bdf7 commit c8c6415

2 files changed

Lines changed: 204 additions & 5 deletions

File tree

packages/web/src/public/svg/circle.svg

Lines changed: 0 additions & 5 deletions
This file was deleted.

packages/web/src/socket/hooks/useGcalSync.test.ts

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ describe("useGcalSync", () => {
5757

5858
beforeEach(() => {
5959
jest.clearAllMocks();
60+
jest.useFakeTimers();
6061
importingValue = false;
6162
awaitingValue = false;
6263
(useDispatch as jest.Mock).mockReturnValue(mockDispatch);
@@ -71,6 +72,10 @@ describe("useGcalSync", () => {
7172
});
7273
});
7374

75+
afterEach(() => {
76+
jest.useRealTimers();
77+
});
78+
7479
it("sets up socket listeners", () => {
7580
renderHook(() => useGcalSync());
7681

@@ -191,4 +196,203 @@ describe("useGcalSync", () => {
191196
);
192197
});
193198
});
199+
200+
describe("import flow interaction", () => {
201+
it("shows spinner on import start and hides it on successful import end", () => {
202+
// Capture socket handlers to simulate backend events
203+
const handlers: Record<string, (...args: unknown[]) => void> = {};
204+
(socket.on as jest.Mock).mockImplementation((event, handler) => {
205+
handlers[event] = handler;
206+
});
207+
208+
awaitingValue = true;
209+
renderHook(() => useGcalSync());
210+
211+
// Verify handlers are registered
212+
expect(handlers[IMPORT_GCAL_START]).toBeDefined();
213+
expect(handlers[IMPORT_GCAL_END]).toBeDefined();
214+
215+
// Phase 1: Backend signals import start (spinner should appear)
216+
handlers[IMPORT_GCAL_START](true);
217+
218+
expect(mockDispatch).toHaveBeenCalledWith(
219+
importGCalSlice.actions.clearImportResults(undefined),
220+
);
221+
expect(mockDispatch).toHaveBeenCalledWith(
222+
importGCalSlice.actions.importing(true),
223+
);
224+
225+
mockDispatch.mockClear();
226+
227+
// Phase 2: Simulate backend processing time (e.g., 2 seconds)
228+
jest.advanceTimersByTime(2000);
229+
230+
// Phase 3: Backend signals import complete with successful response
231+
const successfulResponse = JSON.stringify({
232+
eventsCount: 25,
233+
calendarsCount: 3,
234+
});
235+
handlers[IMPORT_GCAL_END](successfulResponse);
236+
237+
// Spinner should disappear (importing set to false)
238+
expect(mockDispatch).toHaveBeenCalledWith(
239+
importGCalSlice.actions.importing(false),
240+
);
241+
// Results should be set
242+
expect(mockDispatch).toHaveBeenCalledWith(
243+
importGCalSlice.actions.setImportResults({
244+
eventsCount: 25,
245+
calendarsCount: 3,
246+
}),
247+
);
248+
// Fetch should be triggered to load new events
249+
expect(triggerFetch).toHaveBeenCalledWith({
250+
reason: "IMPORT_COMPLETE",
251+
});
252+
});
253+
254+
it("hides spinner when import completes within timeout", () => {
255+
const handlers: Record<string, (...args: unknown[]) => void> = {};
256+
(socket.on as jest.Mock).mockImplementation((event, handler) => {
257+
handlers[event] = handler;
258+
});
259+
260+
awaitingValue = true;
261+
renderHook(() => useGcalSync());
262+
263+
// Start import
264+
handlers[IMPORT_GCAL_START](true);
265+
mockDispatch.mockClear();
266+
267+
// Simulate a reasonable import duration (under 30 seconds)
268+
const REASONABLE_IMPORT_TIME_MS = 15000;
269+
jest.advanceTimersByTime(REASONABLE_IMPORT_TIME_MS);
270+
271+
// Import completes successfully
272+
handlers[IMPORT_GCAL_END](
273+
JSON.stringify({ eventsCount: 100, calendarsCount: 5 }),
274+
);
275+
276+
// Verify spinner is hidden
277+
expect(mockDispatch).toHaveBeenCalledWith(
278+
importGCalSlice.actions.importing(false),
279+
);
280+
});
281+
282+
it("handles rapid start/end sequence without state inconsistency", () => {
283+
const handlers: Record<string, (...args: unknown[]) => void> = {};
284+
(socket.on as jest.Mock).mockImplementation((event, handler) => {
285+
handlers[event] = handler;
286+
});
287+
288+
awaitingValue = true;
289+
renderHook(() => useGcalSync());
290+
291+
// Rapid sequence: start → end (small import)
292+
handlers[IMPORT_GCAL_START](true);
293+
jest.advanceTimersByTime(100); // Very fast import
294+
handlers[IMPORT_GCAL_END](
295+
JSON.stringify({ eventsCount: 2, calendarsCount: 1 }),
296+
);
297+
298+
// Final state should have importing=false
299+
const importingCalls = mockDispatch.mock.calls.filter(
300+
(call) =>
301+
call[0] === importGCalSlice.actions.importing(true) ||
302+
call[0] === importGCalSlice.actions.importing(false),
303+
);
304+
305+
// Last importing call should be false (spinner hidden)
306+
expect(mockDispatch).toHaveBeenLastCalledWith(
307+
importGCalSlice.actions.setImportResults({
308+
eventsCount: 2,
309+
calendarsCount: 1,
310+
}),
311+
);
312+
});
313+
314+
it("handles import end with empty payload gracefully", () => {
315+
const handlers: Record<string, (...args: unknown[]) => void> = {};
316+
(socket.on as jest.Mock).mockImplementation((event, handler) => {
317+
handlers[event] = handler;
318+
});
319+
320+
awaitingValue = true;
321+
renderHook(() => useGcalSync());
322+
323+
handlers[IMPORT_GCAL_START](true);
324+
mockDispatch.mockClear();
325+
326+
// Backend sends empty response (edge case)
327+
handlers[IMPORT_GCAL_END](JSON.stringify({}));
328+
329+
// Should still hide spinner and set empty results
330+
expect(mockDispatch).toHaveBeenCalledWith(
331+
importGCalSlice.actions.importing(false),
332+
);
333+
expect(mockDispatch).toHaveBeenCalledWith(
334+
importGCalSlice.actions.setImportResults({}),
335+
);
336+
});
337+
338+
it("handles import end with object payload (non-string)", () => {
339+
const handlers: Record<string, (...args: unknown[]) => void> = {};
340+
(socket.on as jest.Mock).mockImplementation((event, handler) => {
341+
handlers[event] = handler;
342+
});
343+
344+
awaitingValue = true;
345+
renderHook(() => useGcalSync());
346+
347+
handlers[IMPORT_GCAL_START](true);
348+
mockDispatch.mockClear();
349+
350+
// Backend sends object directly (alternative format)
351+
handlers[IMPORT_GCAL_END]({ eventsCount: 50, calendarsCount: 4 });
352+
353+
expect(mockDispatch).toHaveBeenCalledWith(
354+
importGCalSlice.actions.importing(false),
355+
);
356+
expect(mockDispatch).toHaveBeenCalledWith(
357+
importGCalSlice.actions.setImportResults({
358+
eventsCount: 50,
359+
calendarsCount: 4,
360+
}),
361+
);
362+
});
363+
364+
it("sets error state when backend returns malformed JSON", () => {
365+
const handlers: Record<string, (...args: unknown[]) => void> = {};
366+
(socket.on as jest.Mock).mockImplementation((event, handler) => {
367+
handlers[event] = handler;
368+
});
369+
const consoleErrorSpy = jest
370+
.spyOn(console, "error")
371+
.mockImplementation(() => {});
372+
373+
awaitingValue = true;
374+
renderHook(() => useGcalSync());
375+
376+
handlers[IMPORT_GCAL_START](true);
377+
mockDispatch.mockClear();
378+
379+
// Backend sends malformed response
380+
handlers[IMPORT_GCAL_END]("not valid json {{{");
381+
382+
// Should hide spinner
383+
expect(mockDispatch).toHaveBeenCalledWith(
384+
importGCalSlice.actions.importing(false),
385+
);
386+
// Should set error
387+
expect(mockDispatch).toHaveBeenCalledWith(
388+
importGCalSlice.actions.setImportError(
389+
"Failed to parse Google Calendar import results.",
390+
),
391+
);
392+
// Should NOT set results
393+
expect(importGCalSlice.actions.setImportResults).not.toHaveBeenCalled();
394+
395+
consoleErrorSpy.mockRestore();
396+
});
397+
});
194398
});

0 commit comments

Comments
 (0)