import { describe, it, expect } from "vitest"; import { buildBuckets, periodDays, periodLabel, resolvePeriod, metricsTotal, type RepMetrics, } from "./rep-performance-service"; /** * These cover the pure date maths behind the rep-performance report. Every * case here is a bug that actually shipped once: * - bucket labels rendered "01 Sept – 31 Aug" (double off-by-one), * - single-day buckets ended at midnight so a 05:46 record fell outside * its own bucket and the whole report read zero, * - a Jul–Sep range collapsed its label to "July 2026". */ describe("resolvePeriod", () => { const anchor = "2026-09-15"; // a Tuesday it("defaults to the current month when nothing is supplied", () => { const p = resolvePeriod({ now: new Date("2026-09-28T10:00:00.000Z") }); expect(p.preset).toBe("month"); expect(p.from).toBe("2026-09-01"); expect(p.to).toBe("2026-09-30"); expect(p.granularity).toBe("week"); }); it("day preset covers exactly that day", () => { const p = resolvePeriod({ preset: "day", anchor, now: new Date("2026-09-28T10:00:00.000Z") }); expect(p.from).toBe("2026-09-15"); expect(p.to).toBe("2026-09-15"); expect(p.granularity).toBe("day"); }); it("week preset is Monday-anchored", () => { const p = resolvePeriod({ preset: "week", anchor, now: new Date("2026-09-28T10:00:00.000Z") }); expect(p.from).toBe("2026-09-14"); expect(p.to).toBe("2026-09-20"); expect(p.granularity).toBe("day"); }); it("month preset ignores the day of the anchor", () => { const p = resolvePeriod({ preset: "month", anchor, now: new Date("2026-09-28T10:00:00.000Z") }); expect(p.from).toBe("2026-09-01"); expect(p.to).toBe("2026-09-30"); }); it("quarter preset snaps to the containing quarter", () => { const p = resolvePeriod({ preset: "quarter", anchor, now: new Date("2026-09-28T10:00:00.000Z") }); expect(p.from).toBe("2026-07-01"); expect(p.to).toBe("2026-09-30"); expect(p.granularity).toBe("month"); }); it("year preset is the whole calendar year", () => { const p = resolvePeriod({ preset: "year", anchor, now: new Date("2026-09-28T10:00:00.000Z") }); expect(p.from).toBe("2026-01-01"); expect(p.to).toBe("2026-12-31"); }); it("custom preset honours from/to", () => { const p = resolvePeriod({ preset: "custom", from: "2026-08-01", to: "2026-08-31", now: new Date("2026-09-28T10:00:00.000Z"), }); expect(p.from).toBe("2026-08-01"); expect(p.to).toBe("2026-08-31"); expect(p.granularity).toBe("week"); }); it("custom with a reversed range is normalised rather than rejected", () => { const p = resolvePeriod({ preset: "custom", from: "2026-08-31", to: "2026-08-01", now: new Date("2026-09-28T10:00:00.000Z"), }); expect(p.from).toBe("2026-08-31"); expect(p.to).toBe("2026-08-31"); }); it("falls back to the anchor month when custom is half-specified", () => { const p = resolvePeriod({ preset: "custom", from: "2026-08-01", now: new Date("2026-09-28T10:00:00.000Z"), }); expect(p.from).toBe("2026-09-01"); expect(p.to).toBe("2026-09-30"); }); it("rejects junk rather than throwing", () => { const p = resolvePeriod({ preset: "nonsense" as never, from: "not-a-date", to: "2026-13-45", granularity: "hourly" as never, now: new Date("2026-09-28T10:00:00.000Z"), }); expect(p.preset).toBe("month"); expect(p.granularity).toBe("week"); expect(p.from).toBe("2026-09-01"); }); it("explicit granularity always wins over the preset default", () => { const p = resolvePeriod({ preset: "month", granularity: "day", anchor }); expect(p.granularity).toBe("day"); }); it("clamps an absurd custom range instead of scanning years of rows", () => { const p = resolvePeriod({ preset: "custom", from: "2000-01-01", to: "2026-09-30", now: new Date("2026-09-28T10:00:00.000Z"), }); expect(periodDays(p)).toBe(401); // capped at 400 days + the inclusive end }); }); describe("buildBuckets", () => { const september = { preset: "month" as const, from: "2026-09-01", to: "2026-09-30" }; it("weekly buckets are contiguous, exhaustive and start at the window start", () => { const buckets = buildBuckets({ ...september, granularity: "week" }); expect(buckets[0].start).toBe("2026-09-01"); expect(buckets.at(-1)!.end).toBe("2026-09-30"); for (let i = 1; i < buckets.length; i++) { const prev = new Date(`${buckets[i - 1].end}T00:00:00.000Z`).getTime(); const next = new Date(`${buckets[i].start}T00:00:00.000Z`).getTime(); expect(next - prev).toBe(86_400_000); // no gap, no overlap } }); it("bucket labels never end before they start", () => { // Regression: labels used to render "01 Sept – 31 Aug". for (const granularity of ["day", "week", "month"] as const) { for (const b of buildBuckets({ ...september, granularity })) { expect(b.start <= b.end).toBe(true); } } }); it("monthly fold of a month range is a single bucket", () => { const buckets = buildBuckets({ ...september, granularity: "month" }); expect(buckets).toHaveLength(1); expect(buckets[0]).toMatchObject({ start: "2026-09-01", end: "2026-09-30" }); }); it("daily fold yields one bucket per day in the window", () => { const buckets = buildBuckets({ ...september, granularity: "day" }); expect(buckets).toHaveLength(30); expect(buckets[0]).toMatchObject({ start: "2026-09-01", end: "2026-09-01" }); expect(buckets.at(-1)).toMatchObject({ start: "2026-09-30", end: "2026-09-30" }); }); it("handles a single-day period", () => { const buckets = buildBuckets({ preset: "day", from: "2026-09-15", to: "2026-09-15", granularity: "day", }); expect(buckets).toHaveLength(1); }); it("returns nothing for unparseable dates", () => { expect( buildBuckets({ preset: "custom", from: "nope", to: "also-nope", granularity: "day" }), ).toEqual([]); }); }); describe("periodLabel", () => { it("names a whole month", () => { expect( periodLabel({ preset: "month", from: "2026-09-01", to: "2026-09-30", granularity: "week" }), ).toBe("September 2026"); }); it("names a single day", () => { expect( periodLabel({ preset: "day", from: "2026-09-15", to: "2026-09-15", granularity: "day" }), ).toBe("15 September 2026"); }); it("names a whole week", () => { expect( periodLabel({ preset: "week", from: "2026-09-14", to: "2026-09-20", granularity: "day" }), ).toBe("Week of 14 September 2026"); }); it("does not collapse a multi-month range to its first month", () => { // Regression: a Jul–Sep range used to report "July 2026". const label = periodLabel({ preset: "custom", from: "2026-07-01", to: "2026-09-30", granularity: "month", }); expect(label).not.toBe("July 2026"); expect(label).toContain("1 July"); expect(label).toContain("30 September 2026"); }); it("spells out an arbitrary range", () => { const label = periodLabel({ preset: "custom", from: "2026-09-03", to: "2026-09-19", granularity: "week", }); expect(label).toBe("3 September – 19 September 2026"); }); }); describe("metricsTotal", () => { it("sums every metric", () => { const m: RepMetrics = { quotesLoaded: 1, quotesCompleted: 2, ordersPlaced: 3, ordersReceived: 4, deliveryNotesRaised: 5, }; expect(metricsTotal(m)).toBe(15); }); it("is zero for an all-zero row", () => { expect( metricsTotal({ quotesLoaded: 0, quotesCompleted: 0, ordersPlaced: 0, ordersReceived: 0, deliveryNotesRaised: 0, }), ).toBe(0); }); });