import { layoutDonut, type DonutSlice } from "./donutLayout"; function slice(key: string, value: number): DonutSlice { return { key, label: key, value, color: "#000000" }; } test("an empty breakdown has no total and no arcs to draw", () => { expect(layoutDonut([], 100, 20)).toEqual({ size: 100, total: 0, arcs: [] }); }); test("a breakdown of nothing but zeroes is empty, not a division by zero", () => { const layout = layoutDonut([slice("a", 0), slice("b", 0)], 100, 20); expect(layout.total).toBe(0); expect(layout.arcs).toEqual([]); }); test("zero-valued entries are dropped rather than legended at 0%", () => { const layout = layoutDonut([slice("a", 3), slice("b", 0), slice("c", 1)], 100, 20); expect(layout.arcs.map((arc) => arc.slice.key)).toEqual(["a", "c"]); expect(layout.total).toBe(4); }); test("shares are of the drawn total and add up to one", () => { const layout = layoutDonut([slice("a", 3), slice("b", 1)], 100, 20); expect(layout.arcs.map((arc) => arc.share)).toEqual([0.75, 0.25]); }); test("slices keep the order they were ranked in, starting at twelve o'clock", () => { const layout = layoutDonut([slice("a", 1), slice("b", 1)], 100, 20); expect(layout.arcs[0].d.startsWith("M 50.000 0.000")).toBe(true); // The second slice begins where the first ended, half a turn round. expect(layout.arcs[1].d.startsWith("M 50.000 100.000")).toBe(true); }); test("a slice over half the ring takes the large-arc flag", () => { const layout = layoutDonut([slice("a", 9), slice("b", 1)], 100, 20); expect(layout.arcs[0].d).toContain("A 50 50 0 1 1"); expect(layout.arcs[1].d).toContain("A 50 50 0 0 1"); }); test("a single entry is a closed ring, not a zero-length arc that draws nothing", () => { const layout = layoutDonut([slice("only", 7)], 100, 20); expect(layout.arcs).toHaveLength(1); expect(layout.arcs[0].share).toBe(1); // Two half arcs out and two back: a lone `A` from a point to itself is a no-op. expect(layout.arcs[0].d.match(/A /g)).toHaveLength(4); });