Implements computeImpactDeltas pure function with 8 TDD tests covering replace/add/none modes and null weight/price handling. Adds useImpactDeltas hook, categoryId to ThreadWithCandidates, and selectedSetupId state to uiStore. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
88 lines
3.3 KiB
TypeScript
88 lines
3.3 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import { computeImpactDeltas } from "../../src/client/lib/impactDeltas";
|
|
|
|
describe("computeImpactDeltas", () => {
|
|
const candidate = { id: 1, weightGrams: 500, priceCents: 20000 };
|
|
const candidate2 = { id: 2, weightGrams: 300, priceCents: 15000 };
|
|
|
|
it("returns mode 'none' when setupItems is undefined", () => {
|
|
const result = computeImpactDeltas([candidate], undefined, 1);
|
|
expect(result.mode).toBe("none");
|
|
expect(Object.keys(result.deltas)).toHaveLength(0);
|
|
});
|
|
|
|
it("returns replace mode when setup item matches thread category", () => {
|
|
const setupItems = [
|
|
{ categoryId: 5, weightGrams: 800, priceCents: 30000, name: "Old Tent" },
|
|
];
|
|
const result = computeImpactDeltas([candidate], setupItems, 5);
|
|
expect(result.mode).toBe("replace");
|
|
expect(result.deltas[1].weightDelta).toBe(-300); // 500 - 800
|
|
expect(result.deltas[1].priceDelta).toBe(-10000); // 20000 - 30000
|
|
expect(result.deltas[1].replacedItemName).toBe("Old Tent");
|
|
});
|
|
|
|
it("returns add mode when no setup item matches thread category", () => {
|
|
const setupItems = [
|
|
{ categoryId: 99, weightGrams: 200, priceCents: 5000, name: "Unrelated" },
|
|
];
|
|
const result = computeImpactDeltas([candidate], setupItems, 5);
|
|
expect(result.mode).toBe("add");
|
|
expect(result.deltas[1].weightDelta).toBe(500);
|
|
expect(result.deltas[1].priceDelta).toBe(20000);
|
|
expect(result.deltas[1].replacedItemName).toBeNull();
|
|
});
|
|
|
|
it("returns null weightDelta when candidate weight is null", () => {
|
|
const nullWeight = { id: 3, weightGrams: null, priceCents: 10000 };
|
|
const setupItems = [
|
|
{ categoryId: 5, weightGrams: 200, priceCents: 5000, name: "Item" },
|
|
];
|
|
const result = computeImpactDeltas([nullWeight], setupItems, 5);
|
|
expect(result.deltas[3].weightDelta).toBeNull();
|
|
expect(result.deltas[3].priceDelta).toBe(5000); // 10000 - 5000
|
|
});
|
|
|
|
it("returns null priceDelta when candidate price is null", () => {
|
|
const nullPrice = { id: 4, weightGrams: 500, priceCents: null };
|
|
const setupItems = [
|
|
{ categoryId: 5, weightGrams: 200, priceCents: 5000, name: "Item" },
|
|
];
|
|
const result = computeImpactDeltas([nullPrice], setupItems, 5);
|
|
expect(result.deltas[4].weightDelta).toBe(300);
|
|
expect(result.deltas[4].priceDelta).toBeNull();
|
|
});
|
|
|
|
it("handles replace mode with null replaced item weight", () => {
|
|
const setupItems = [
|
|
{
|
|
categoryId: 5,
|
|
weightGrams: null,
|
|
priceCents: 5000,
|
|
name: "Unknown Weight",
|
|
},
|
|
];
|
|
const result = computeImpactDeltas([candidate], setupItems, 5);
|
|
expect(result.deltas[1].weightDelta).toBe(500); // treat as add for weight
|
|
expect(result.deltas[1].priceDelta).toBe(15000); // 20000 - 5000
|
|
});
|
|
|
|
it("shows negative delta when candidate is lighter", () => {
|
|
const setupItems = [
|
|
{ categoryId: 5, weightGrams: 1000, priceCents: 50000, name: "Heavy" },
|
|
];
|
|
const result = computeImpactDeltas([candidate], setupItems, 5);
|
|
expect(result.deltas[1].weightDelta).toBe(-500);
|
|
expect(result.deltas[1].priceDelta).toBe(-30000);
|
|
});
|
|
|
|
it("handles multiple candidates", () => {
|
|
const setupItems = [
|
|
{ categoryId: 5, weightGrams: 400, priceCents: 18000, name: "Current" },
|
|
];
|
|
const result = computeImpactDeltas([candidate, candidate2], setupItems, 5);
|
|
expect(result.deltas[1].weightDelta).toBe(100); // 500 - 400
|
|
expect(result.deltas[2].weightDelta).toBe(-100); // 300 - 400
|
|
});
|
|
});
|