import { describe, expect, it } from "vitest"; import { addPrerequisite, buildPrereqRows, dependsOn, removePrerequisite, setPrerequisiteKind, withPrereqs, } from "@/components/admin/tech-tree/prereqEdits"; import type { TreeTech } from "@/components/admin/tech-tree/types"; const tech = (over: Partial & { id: number }): TreeTech => ({ name: `Tech ${over.id}`, type: "upgrade", approvalStatus: "in_progress", summary: "", categoryId: null, prereqAnd: [], prereqOr: [], resourceCosts: [], treePosition: null, ...over, }); describe("tech tree prerequisite edits (pure)", () => { it("adds an AND prerequisite", () => { const techs = [tech({ id: 1 }), tech({ id: 2 })]; const result = addPrerequisite(techs, 2, 1, "and"); expect(result.ok).toBe(true); if (result.ok) expect(result.lists).toEqual({ and: [1], or: [] }); }); it("adds an OR prerequisite", () => { const techs = [tech({ id: 1 }), tech({ id: 2 })]; const result = addPrerequisite(techs, 2, 1, "or"); expect(result.ok).toBe(true); if (result.ok) expect(result.lists).toEqual({ and: [], or: [1] }); }); it("rejects self references", () => { const result = addPrerequisite([tech({ id: 1 })], 1, 1, "and"); expect(result.ok).toBe(false); if (!result.ok) expect(result.error).toMatch(/own prerequisite/i); }); it("rejects duplicates", () => { const techs = [tech({ id: 1 }), tech({ id: 2, prereqAnd: [1] })]; const result = addPrerequisite(techs, 2, 1, "or"); expect(result.ok).toBe(false); if (!result.ok) expect(result.error).toMatch(/already a prerequisite/i); }); it("rejects a direct cycle", () => { const techs = [tech({ id: 1, prereqAnd: [2] }), tech({ id: 2 })]; // Adding 1 as a prerequisite of 2 would close 2 -> 1 -> 2. const result = addPrerequisite(techs, 2, 1, "and"); expect(result.ok).toBe(false); if (!result.ok) expect(result.error).toMatch(/circular/i); }); it("rejects a transitive cycle", () => { const techs = [ tech({ id: 1 }), tech({ id: 2, prereqAnd: [1] }), tech({ id: 3, prereqAnd: [2] }), ]; // 3 depends on 1 transitively; making 3 a prerequisite of 1 closes a loop. expect(dependsOn(techs, 3, 1)).toBe(true); const result = addPrerequisite(techs, 1, 3, "and"); expect(result.ok).toBe(false); }); it("allows a forward chain", () => { const techs = [tech({ id: 1 }), tech({ id: 2, prereqAnd: [1] }), tech({ id: 3 })]; const result = addPrerequisite(techs, 3, 2, "and"); expect(result.ok).toBe(true); if (result.ok) expect(result.lists).toEqual({ and: [2], or: [] }); }); it("removes a prerequisite from both groups", () => { const techs = [tech({ id: 1 }), tech({ id: 2, prereqAnd: [1], prereqOr: [1, 3] })]; const result = removePrerequisite(techs, 2, 1); expect(result.ok).toBe(true); if (result.ok) expect(result.lists).toEqual({ and: [], or: [3] }); }); it("moves a prerequisite between AND and OR", () => { const techs = [tech({ id: 1 }), tech({ id: 2, prereqAnd: [1] })]; const toOr = setPrerequisiteKind(techs, 2, 1, "or"); expect(toOr.ok).toBe(true); if (toOr.ok) expect(toOr.lists).toEqual({ and: [], or: [1] }); const back = setPrerequisiteKind(techs, 2, 1, "and"); expect(back.ok).toBe(true); if (back.ok) expect(back.lists).toEqual({ and: [1], or: [] }); }); it("builds a single document-shaped prerequisite row", () => { expect(buildPrereqRows({ and: [1, 2], or: [3] })).toEqual([ { requiresAnd: { technology: [1, 2] }, requiresOr: { technology: [3] } }, ]); }); it("withPrereqs returns a tech carrying the new lists", () => { const updated = withPrereqs(tech({ id: 1 }), { and: [4], or: [5] }); expect(updated.prereqAnd).toEqual([4]); expect(updated.prereqOr).toEqual([5]); }); });