import { test, expect, describe } from "bun:test"; import { createRegistry } from "../../src/level-blocks/registry.ts"; import { makeDef, makeDefs, ToyCapBlock } from "./pipelineFixtures.ts"; describe("createRegistry — register/get/list", () => { test("registers defs passed to the constructor", () => { const registry = createRegistry(makeDefs()); expect(registry.get("ToyCap")?.code).toBe("ToyCap"); expect(registry.get("ToyGreedy")?.family).toBe("search"); }); test("get returns undefined for unknown codes", () => { const registry = createRegistry(); expect(registry.get("Nope")).toBeUndefined(); }); test("register adds a def after construction", () => { const registry = createRegistry(); registry.register( makeDef("ToyCap", "constraint", ToyCapBlock as never, { resource: "crane", max: 2, }), ); expect(registry.get("ToyCap")).toBeDefined(); }); test("list returns all defs without a filter", () => { const registry = createRegistry(makeDefs()); expect(registry.list()).toHaveLength(4); }); test("list filters by family", () => { const registry = createRegistry(makeDefs()); const constraints = registry.list({ family: "constraint" }); expect(constraints.map((d) => d.code)).toEqual(["ToyCap"]); }); test("list filters by tier", () => { const registry = createRegistry(makeDefs()); const sales = registry.list({ tier: "sales" }); expect(sales.map((d) => d.code)).toEqual(["ToyCap"]); }); test("list filters by family and tier together", () => { const registry = createRegistry(makeDefs()); expect( registry.list({ family: "constraint", tier: "engineer" }), ).toHaveLength(0); expect(registry.list({ family: "search", tier: "engineer" })).toHaveLength( 1, ); }); }); describe("createRegistry — registration validation", () => { test("throws on duplicate code", () => { const registry = createRegistry(makeDefs()); expect(() => registry.register( makeDef("ToyCap", "constraint", ToyCapBlock as never, { resource: "x", max: 1, }), ), ).toThrow(/duplicate/i); }); test("throws when defaultShadow fails the block input schema", () => { expect(() => createRegistry([ makeDef("BadShadow", "constraint", ToyCapBlock as never, { resource: "crane", max: 0, }), ]), ).toThrow(/defaultShadow/); }); test("a failed register leaves the registry unchanged", () => { const registry = createRegistry(); expect(() => registry.register( makeDef("Bad", "constraint", ToyCapBlock as never, { max: 2 }), ), ).toThrow(); expect(registry.get("Bad")).toBeUndefined(); expect(registry.list()).toHaveLength(0); }); test("accepts panelBindings and projectAnchoredPaths that resolve in defaultShadow", () => { const registry = createRegistry(); const def = makeDef("ToyCap", "constraint", ToyCapBlock as never, { resource: "crane", max: 2, }); def.panelBindings = [ { paramPath: "max", label: "Max crews", control: "number" }, ]; def.projectAnchoredPaths = ["resource"]; expect(() => registry.register(def)).not.toThrow(); }); test("throws when a panelBindings paramPath does not resolve in defaultShadow", () => { const registry = createRegistry(); const def = makeDef("ToyCap", "constraint", ToyCapBlock as never, { resource: "crane", max: 2, }); def.panelBindings = [ { paramPath: "maximum", label: "Max crews", control: "number" }, ]; expect(() => registry.register(def)).toThrow(/"maximum" does not resolve/); }); test("throws when a projectAnchoredPath does not resolve in defaultShadow", () => { const registry = createRegistry(); const def = makeDef("ToyCap", "constraint", ToyCapBlock as never, { resource: "crane", max: 2, }); def.projectAnchoredPaths = ["window.start"]; expect(() => registry.register(def)).toThrow( /"window.start" does not resolve/, ); }); });