import { describe, it, expect } from "vitest"; import { fractionToDate, shiftDatesByFraction, resizeEndToFraction, moveCardByFraction, } from "@/lib/roadmap/bar-math"; import type { TimelineRange } from "@/lib/roadmap/timeline"; const RANGE: TimelineRange = { from: "2026-02-01", to: "2026-02-22" }; describe("fractionToDate", () => { it("maps 0 to range.from", () => { expect(fractionToDate(0, RANGE)).toBe("2026-02-01"); }); it("maps 1 to range.to (rounded)", () => { expect(fractionToDate(1, RANGE)).toBe("2026-02-22"); }); it("maps 0.5 to the midpoint", () => { expect(fractionToDate(0.5, RANGE)).toBe("2026-02-11"); // 21 days span (Feb 1 to Feb 22 inclusive diff = 21); half = ~10.5 → 11 }); it("clamps out-of-range inputs", () => { expect(fractionToDate(-0.5, RANGE)).toBe("2026-02-01"); expect(fractionToDate(1.5, RANGE)).toBe("2026-02-22"); }); }); describe("shiftDatesByFraction — moving a bar", () => { it("shifts both dates by the same number of days when both are set", () => { const next = shiftDatesByFraction( "2026-02-05", "2026-02-10", 21 / 21 / 2, // half the visible range ≈ 10.5 days → 11 RANGE ); expect(next.startDate).toBe("2026-02-16"); expect(next.targetDate).toBe("2026-02-21"); }); it("returns nulls when neither date is set", () => { const next = shiftDatesByFraction("", "", 0.5, RANGE); expect(next).toEqual({ startDate: null, targetDate: null }); }); it("moves only the target when only the target is set", () => { const next = shiftDatesByFraction("", "2026-02-10", 0.1, RANGE); expect(next.startDate).toBeNull(); expect(next.targetDate).toBe("2026-02-12"); }); it("clamps the target to range.to when dragged past the right edge", () => { const next = shiftDatesByFraction( "2026-02-18", "2026-02-20", 10, // way past the right edge RANGE ); // Both dates would clamp to range.to and collide; start is dropped // so the bar still renders as a point at the right edge. expect(next.startDate).toBeNull(); expect(next.targetDate).toBe("2026-02-22"); }); it("keeps a 1-day span when only the target clamps", () => { // start=18 target=20 (2-day span). Drag 0.2 of the range (~4 days) right. // Target=20+4 = 24 → clamps to 22. Start=18+4 = 22, equal to target → drop. const next = shiftDatesByFraction("2026-02-18", "2026-02-20", 0.2, RANGE); expect(next.targetDate).toBe("2026-02-22"); }); }); describe("resizeEndToFraction — right-edge resize", () => { it("returns the date at the new fraction", () => { expect(resizeEndToFraction("2026-02-05", 0.5, RANGE)).toBe("2026-02-11"); }); }); describe("moveCardByFraction — action shape", () => { it("mirrors shiftDatesByFraction so the action gets the same shape", () => { const a = shiftDatesByFraction("2026-02-05", "2026-02-10", 0.1, RANGE); const b = moveCardByFraction("2026-02-05", "2026-02-10", 0.1, RANGE); expect(a).toEqual(b); }); });