Files
butcem/src/shared/kakeibo.test.ts
T
suphicakir afc6da1606 feat(kakeibo): dört harcama grubu, banka maliyeti ve geri alınabilir migration
- shared/kakeibo: grup devralma, banka maliyeti, kayıt bazında grup, grup özeti (saf, testli)
- kategori ve işlem/kalem modellerine kakeiboGroup ve bankCost; form şemaları
- migration 016 varsayılanları doldurur; npm run migrate -- --down <ad> ile geri alma
- Kakeibo rapor servisi: tutarlar bütçeyle aynı kaynaktan, grup değiştirmeler taşınır

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 04:51:35 +03:00

106 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});
});