import { describe, expect, it } from "vitest"; import { buildReconciliationCandidates } from "../src/reconciliation.js"; const PINNED = new Date("2026-08-07T00:00:00.000Z"); function baseItem( overrides: Partial[0][number]> = {}, ) { return { id: "itm-1", displayName: "Mjölk", quantity: 1, unit: "l", locationName: "Kylen", confidence: 1, verifiedByUser: true, lastVerifiedAt: PINNED, updatedAt: PINNED, ...overrides, }; } describe("buildReconciliationCandidates", () => { it("returns empty when no items match", () => { const result = buildReconciliationCandidates( [baseItem({ quantity: 0, depletedAt: PINNED })], { plannedRecipeIngredientIds: new Map(), daysLeftByItemId: new Map(), dailyConsumptionRate: new Map(), priceMinorByItemId: new Map(), }, PINNED, ); expect(result).toEqual([]); }); it("prioritises planned recipe ingredients", () => { const a = baseItem({ id: "a", displayName: "Grädde", confidence: 1, verifiedByUser: true }); const b = baseItem({ id: "b", displayName: "Ost", confidence: 1, verifiedByUser: true }); const result = buildReconciliationCandidates( [b, a], { plannedRecipeIngredientIds: new Map([["a", ["rec-1"]]]), daysLeftByItemId: new Map(), dailyConsumptionRate: new Map(), priceMinorByItemId: new Map(), }, PINNED, 15, ); expect(result.map((r) => r.itemId)).toEqual(["a"]); expect(result[0]?.reasons).toContainEqual({ kind: "planned_recipe", recipeIds: ["rec-1"] }); }); it("flags expiring soon and sorts by urgency", () => { const a = baseItem({ id: "a", displayName: "Yoghurt", confidence: 0.5 }); const b = baseItem({ id: "b", displayName: "Juice", confidence: 0.5 }); const result = buildReconciliationCandidates( [b, a], { plannedRecipeIngredientIds: new Map(), daysLeftByItemId: new Map([ ["a", 1], ["b", 6], ]), dailyConsumptionRate: new Map(), priceMinorByItemId: new Map(), }, PINNED, ); expect(result.map((r) => r.itemId)).toEqual(["a", "b"]); }); it("flags low confidence", () => { const item = baseItem({ confidence: 0.3, verifiedByUser: false }); const result = buildReconciliationCandidates( [item], { plannedRecipeIngredientIds: new Map(), daysLeftByItemId: new Map(), dailyConsumptionRate: new Map(), priceMinorByItemId: new Map(), }, PINNED, ); expect(result[0]?.reasons).toContainEqual({ kind: "low_confidence", confidence: 0.3 }); }); it("suggests depleted when remaining days < 0.5", () => { const item = baseItem({ quantity: 0.1 }); const result = buildReconciliationCandidates( [item], { plannedRecipeIngredientIds: new Map(), daysLeftByItemId: new Map(), dailyConsumptionRate: new Map([["itm-1", 1]]), priceMinorByItemId: new Map(), }, PINNED, ); expect(result[0]?.suggestedAction).toBe("depleted"); }); it("caps results at maxItems", () => { const items = Array.from({ length: 20 }, (_, i) => baseItem({ id: `itm-${i}`, displayName: `Vara ${String(i).padStart(2, "0")}`, confidence: 0.1, }), ); const result = buildReconciliationCandidates( items, { plannedRecipeIngredientIds: new Map(), daysLeftByItemId: new Map(), dailyConsumptionRate: new Map(), priceMinorByItemId: new Map(), }, PINNED, 5, ); expect(result).toHaveLength(5); }); });