import { describe, expect, it } from "vitest"; import { groupSuggestions, roundTarget } from "./grouping"; import type { Suggestion } from "./types"; function suggestion(overrides: Partial): Suggestion { return { id: "s", ruleKey: "INSPECTION_INTERVAL", source: "AUTOPILOT", mode: "DO", intent: "INSPECT", status: "OPEN", priority: "NORMAL", relatedType: "COLONY", relatedId: null, colonyId: "c1", colonyCode: "A1", apiaryId: "a1", apiaryName: "Hang", title: "Durchsicht fällig", reasons: [], dueDate: null, expiresAt: null, plan: null, decidedAt: null, decisionNote: null, createdAt: "2026-05-01T00:00:00.000Z", cleanup: null, ...overrides, }; } describe("groupSuggestions", () => { it("groups by apiary, then „Betrieb“, and combines the same intent per apiary", () => { const list = [ suggestion({ id: "1", colonyId: "c1", colonyCode: "A1" }), suggestion({ id: "2", intent: "HONEY_SUPER", mode: "PLAN", apiaryId: "a2", apiaryName: "Aue", colonyId: "c9", colonyCode: "B1" }), suggestion({ id: "3", colonyId: "c2", colonyCode: "A2" }), suggestion({ id: "4", intent: "STOCK_REORDER", mode: "PLAN", apiaryId: null, apiaryName: null, colonyId: null, colonyCode: null }), ]; const groups = groupSuggestions(list); expect(groups.map((g) => g.label)).toEqual(["Aue", "Hang", "Betrieb"]); const hang = groups[1]; expect(hang.apiaryId).toBe("a1"); expect(hang.rows).toHaveLength(1); const [row] = hang.rows; expect(row.kind).toBe("combined"); if (row.kind === "combined") { expect(row.intent).toBe("INSPECT"); expect(row.suggestions.map((s) => s.id)).toEqual(["1", "3"]); } expect(groups[0].rows[0].kind).toBe("single"); expect(groups[2].apiaryId).toBeNull(); expect(groups[2].key).toBe("operation"); }); it("keeps the order of the list inside a group", () => { const groups = groupSuggestions([ suggestion({ id: "1", intent: "FEED", colonyId: "c1" }), suggestion({ id: "2", intent: "INSPECT", colonyId: "c2" }), suggestion({ id: "3", intent: "FEED", colonyId: "c3" }), ]); const kinds = groups[0].rows.map((r) => (r.kind === "combined" ? r.intent : r.suggestion.intent)); expect(kinds).toEqual(["FEED", "INSPECT"]); }); it("returns no groups for an empty list", () => { expect(groupSuggestions([])).toEqual([]); }); }); describe("roundTarget", () => { it("lists the due colonies in code order and leads to the first one", () => { const target = roundTarget([ suggestion({ id: "1", colonyId: "c10", colonyCode: "A10" }), suggestion({ id: "2", colonyId: "c2", colonyCode: "A2" }), suggestion({ id: "3", colonyId: "c2", colonyCode: "A2" }), ]); expect(target?.apiary).toEqual({ id: "a1", name: "Hang" }); expect(target?.colonies).toEqual([ { id: "c2", code: "A2" }, { id: "c10", code: "A10" }, ]); expect(target?.href).toBe("/inspections/new?colonyId=c2&round=a1"); }); it("sorts codes with long numbers naturally (V-2 before V-010)", () => { const target = roundTarget([ suggestion({ id: "1", colonyId: "c1", colonyCode: "V-010" }), suggestion({ id: "2", colonyId: "c2", colonyCode: "V-2" }), ]); expect(target?.colonies.map((colony) => colony.code)).toEqual(["V-2", "V-010"]); }); it("is null without a colony at an apiary", () => { expect(roundTarget([suggestion({ apiaryId: null, apiaryName: null })])).toBeNull(); expect(roundTarget([])).toBeNull(); }); });