import { describe, expect, it } from "vitest"; import { ancestorIds, buildTree, checkCreate, checkMove, descendantIds, rollupTotals, toOptions, type FlatCategory, } from "./tree"; const c = ( id: string, name: string, parentId: string | null, order = 0, kind: "expense" | "income" = "expense", ): FlatCategory => ({ id, kind, name, parentId, order, archived: false, }); const items: FlatCategory[] = [ c("fat", "Faturalar", null, 1), c("elk", "Elektrik", "fat", 0), c("su", "Su", "fat", 1), c("mar", "Market", null, 0), c("gida", "Gıda", "mar", 0), c("meyve", "Meyve", "gida", 0), c("maas", "Maaş", null, 0, "income"), ]; describe("buildTree / toOptions", () => { it("sıra ve derinlikle ağaç kurar, yol etiketleri üretir", () => { const tree = buildTree(items.filter((i) => i.kind === "expense")); expect(tree.map((n) => n.name)).toEqual(["Market", "Faturalar"]); expect(toOptions(tree).map((o) => [o.path, o.depth])).toEqual([ ["Market", 0], ["Market › Gıda", 1], ["Market › Gıda › Meyve", 2], ["Faturalar", 0], ["Faturalar › Elektrik", 1], ["Faturalar › Su", 1], ]); }); it("arşivlenenleri varsayılan olarak seçeneklere koymaz", () => { const tree = buildTree([c("a", "A", null), { ...c("b", "B", "a"), archived: true }]); expect(toOptions(tree).map((o) => o.id)).toEqual(["a"]); expect(toOptions(tree, { includeArchived: true }).map((o) => o.id)).toEqual(["a", "b"]); }); it("üstü olmayan düğümü köke alır, döngüde takılmaz", () => { const loop = [c("x", "X", "y"), c("y", "Y", "x"), c("z", "Z", "yok")]; expect(() => buildTree(loop)).not.toThrow(); expect(buildTree(loop).map((n) => n.id)).toContain("z"); }); }); describe("ancestorIds / descendantIds", () => { it("üst ve alt kategorileri bulur", () => { expect(ancestorIds(items, "meyve")).toEqual(["mar", "gida"]); expect(descendantIds(items, "mar").sort()).toEqual(["gida", "meyve"]); expect(descendantIds(items, "elk")).toEqual([]); }); }); describe("checkMove", () => { it("kendine, alt kategorisine ve farklı türe taşımayı reddeder", () => { expect(checkMove(items, "mar", "mar")).toBe("self"); expect(checkMove(items, "mar", "meyve")).toBe("descendant"); expect(checkMove(items, "elk", "maas")).toBe("kind"); expect(checkMove(items, "elk", "yok")).toBe("parentNotFound"); }); it("geçerli taşımaya ve köke taşımaya izin verir", () => { expect(checkMove(items, "su", "mar")).toBeNull(); expect(checkMove(items, "gida", null)).toBeNull(); }); it("derinlik sınırını uygular (en fazla 5 seviye: derinlik 0–4)", () => { const deep = [ c("1", "1", null), c("2", "2", "1"), c("3", "3", "2"), c("4", "4", "3"), c("5", "5", "4"), c("x", "X", null), c("y", "Y", "x"), ]; expect(checkCreate(deep, "expense", "4")).toBeNull(); // yeni düğüm derinlik 4 expect(checkCreate(deep, "expense", "5")).toBe("depth"); // derinlik 5 expect(checkMove(deep, "y", "4")).toBeNull(); expect(checkMove(deep, "y", "5")).toBe("depth"); expect(checkMove(deep, "x", "4")).toBe("depth"); // x derinlik 4, alt kategorisi y derinlik 5 expect(checkCreate(deep, "income", "3")).toBe("kind"); }); }); describe("rollupTotals", () => { it("alt kategori tutarlarını üst kategorilere toplar", () => { const totals = rollupTotals( items, new Map([ ["meyve", 300], ["gida", 500], ["elk", 1000], ["su", 250], ]), ); expect(totals.get("mar")).toBe(800); expect(totals.get("gida")).toBe(800); expect(totals.get("fat")).toBe(1250); expect(totals.get("maas") ?? 0).toBe(0); }); });