import { describe, expect, it } from "vitest"; import { classify, classifyAmounts, resolveKakeibo, summarizeGroups, type KakeiboCategory, } from "./kakeibo"; const cat = ( id: string, parentId: string | null, kakeiboGroup: KakeiboCategory["kakeiboGroup"] = null, bankCost = false, ): KakeiboCategory => ({ id, parentId, kakeiboGroup, bankCost }); describe("resolveKakeibo", () => { const map = resolveKakeibo([ cat("market", null, "ZORUNLU"), cat("gida", "market"), cat("bakim", "market", "ISTEK"), cat("banka", null, null, true), cat("faiz", "banka"), cat("diger", null), // Döngü: kendine üst (bozuk veri) sonsuz döngüye girmemeli. cat("dongu", "dongu"), ]); it("alt kategori grubu üstten devralır; kendi değeri önceliklidir", () => { expect(map.get("gida")).toEqual({ group: "ZORUNLU", inherited: true, bankCost: false }); expect(map.get("bakim")).toEqual({ group: "ISTEK", inherited: false, bankCost: false }); expect(map.get("market")!.inherited).toBe(false); }); it("banka maliyeti alt kategorilere geçer; grupsuz ve döngülü kategori güvenli", () => { expect(map.get("faiz")!.bankCost).toBe(true); expect(map.get("diger")).toEqual({ group: null, inherited: false, bankCost: false }); expect(map.get("dongu")!.group).toBeNull(); }); }); describe("classify", () => { const map = resolveKakeibo([cat("market", null, "ZORUNLU"), cat("faiz", null, null, true)]); it("işlem bazında grup kategoriyi ezer; banka maliyeti her zaman banka maliyeti", () => { expect(classify(map.get("market"), null)).toBe("ZORUNLU"); expect(classify(map.get("market"), "ISTEK")).toBe("ISTEK"); expect(classify(map.get("faiz"), "ISTEK")).toBe("BANK_COST"); expect(classify(undefined, null)).toBe("UNGROUPED"); expect(classify(undefined, "KULTUR")).toBe("KULTUR"); }); }); describe("summarizeGroups", () => { it("banka maliyeti harcamaya ve paylara girmez; grupsuz harcamaya girer", () => { const s = summarizeGroups([ { cls: "ZORUNLU", amountMinor: 4_600 }, { cls: "ISTEK", amountMinor: 3_900 }, { cls: "KULTUR", amountMinor: 150 }, { cls: "BEKLENMEYEN", amountMinor: 1_250 }, { cls: "UNGROUPED", amountMinor: 100 }, { cls: "BANK_COST", amountMinor: 999 }, ]); expect(s.spendingMinor).toBe(10_000); expect(s.bankCostMinor).toBe(999); expect(s.shares).toEqual({ ZORUNLU: 46, ISTEK: 39, KULTUR: 1.5, BEKLENMEYEN: 12.5 }); expect(s.ungroupedShare).toBe(1); }); it("harcama yoksa paylar 0 (sıfıra bölme yok)", () => { const s = summarizeGroups([{ cls: "BANK_COST", amountMinor: 50 }]); expect(s.spendingMinor).toBe(0); expect(s.shares.ZORUNLU).toBe(0); expect(s.ungroupedShare).toBe(0); }); }); describe("classifyAmounts", () => { const resolved = resolveKakeibo([ cat("market", null, "ZORUNLU"), cat("faiz", null, null, true), cat("diger", null), ]); it("değiştirilen tutar seçilen gruba taşınır, kategori toplamı korunur", () => { const out = classifyAmounts( new Map([ ["market", 10_000], ["faiz", 500], ["diger", 300], ]), [ { categoryId: "market", group: "ISTEK", amountMinor: 2_500 }, // Banka maliyetinde grup değiştirme etkisiz. { categoryId: "faiz", group: "ISTEK", amountMinor: 500 }, // Kategori toplamını aşan değiştirme kırpılır. { categoryId: "diger", group: "KULTUR", amountMinor: 900 }, ], resolved, ); expect(out).toEqual([ { categoryId: "market", cls: "ISTEK", amountMinor: 2_500 }, { categoryId: "market", cls: "ZORUNLU", amountMinor: 7_500 }, { categoryId: "faiz", cls: "BANK_COST", amountMinor: 500 }, { categoryId: "diger", cls: "KULTUR", amountMinor: 300 }, ]); const s = summarizeGroups(out); expect(s.spendingMinor + s.bankCostMinor).toBe(10_800); }); });