diff --git a/apps/web/src/components/timeline/dragMath.test.ts b/apps/web/src/components/timeline/dragMath.test.ts new file mode 100644 index 0000000..e8fa77b --- /dev/null +++ b/apps/web/src/components/timeline/dragMath.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "vitest"; +import { computeDragDates, pixelsToDays, shiftDate } from "./dragMath.js"; + +describe("dragMath", () => { + it("returns zero days for invalid cell widths instead of leaking invalid drag deltas", () => { + expect(pixelsToDays(40, 0)).toBe(0); + expect(pixelsToDays(40, Number.NaN)).toBe(0); + expect(pixelsToDays(40, Number.POSITIVE_INFINITY)).toBe(0); + }); + + it("shifts dates without mutating the original date", () => { + const original = new Date("2026-04-10T00:00:00.000Z"); + + const shifted = shiftDate(original, 3); + + expect(shifted.toISOString()).toBe("2026-04-13T00:00:00.000Z"); + expect(original.toISOString()).toBe("2026-04-10T00:00:00.000Z"); + }); + + it("clamps resize-start drags so the start does not cross the end", () => { + const { start, end } = computeDragDates( + "resize-start", + new Date("2026-04-10T00:00:00.000Z"), + new Date("2026-04-12T00:00:00.000Z"), + 5, + ); + + expect(start.toISOString()).toBe("2026-04-12T00:00:00.000Z"); + expect(end.toISOString()).toBe("2026-04-12T00:00:00.000Z"); + }); + + it("clamps resize-end drags so the end does not cross the start", () => { + const { start, end } = computeDragDates( + "resize-end", + new Date("2026-04-10T00:00:00.000Z"), + new Date("2026-04-12T00:00:00.000Z"), + -5, + ); + + expect(start.toISOString()).toBe("2026-04-10T00:00:00.000Z"); + expect(end.toISOString()).toBe("2026-04-10T00:00:00.000Z"); + }); +}); diff --git a/apps/web/src/components/timeline/dragMath.ts b/apps/web/src/components/timeline/dragMath.ts index 9735c08..0bbd6eb 100644 --- a/apps/web/src/components/timeline/dragMath.ts +++ b/apps/web/src/components/timeline/dragMath.ts @@ -6,6 +6,9 @@ /** Convert a pixel delta to a number of whole days based on cell width. */ export function pixelsToDays(deltaX: number, cellWidth: number): number { + if (!Number.isFinite(cellWidth) || cellWidth <= 0) { + return 0; + } return Math.round(deltaX / cellWidth); }