/** * L4 — behavioural tests for {@link MockLayoutGateway}: it defaults to a single * empty leaf, applies mutations and returns the new tree, and keeps a separate * in-memory tree per project. */ import { describe, it, expect } from "vitest"; import { leaves } from "@/features/layout/layout"; import { MockLayoutGateway } from "./index"; describe("MockLayoutGateway", () => { it("loadLayout defaults to a single empty leaf", async () => { const gw = new MockLayoutGateway(); const tree = await gw.loadLayout("p1"); expect(tree.root.type).toBe("leaf"); const ls = leaves(tree); expect(ls).toHaveLength(1); expect(ls[0].session ?? null).toBeNull(); }); it("loadLayout returns a stable tree for the same project", async () => { const gw = new MockLayoutGateway(); const a = await gw.loadLayout("p1"); const b = await gw.loadLayout("p1"); expect(b).toEqual(a); // same persisted tree (id preserved) }); it("mutateLayout applies the op and returns the mutated tree", async () => { const gw = new MockLayoutGateway(); const initial = await gw.loadLayout("p1"); const leafId = leaves(initial)[0].id; const next = await gw.mutateLayout("p1", { type: "split", target: leafId, direction: "row", newLeaf: "b", container: "c", }); expect(next.root.type).toBe("split"); expect(leaves(next)).toHaveLength(2); }); it("persists the mutation in memory (next load reflects it)", async () => { const gw = new MockLayoutGateway(); const initial = await gw.loadLayout("p1"); const leafId = leaves(initial)[0].id; await gw.mutateLayout("p1", { type: "setSession", target: leafId, session: "s9", }); const reloaded = await gw.loadLayout("p1"); expect(leaves(reloaded)[0].session).toBe("s9"); }); it("keeps a separate tree per project", async () => { const gw = new MockLayoutGateway(); const p1 = await gw.loadLayout("p1"); const p2 = await gw.loadLayout("p2"); await gw.mutateLayout("p1", { type: "split", target: leaves(p1)[0].id, direction: "row", newLeaf: "b", container: "c", }); // p2 is untouched. const p2After = await gw.loadLayout("p2"); expect(p2After).toEqual(p2); expect(leaves(p2After)).toHaveLength(1); }); it("returns clones, so mutating a returned tree does not corrupt the store", async () => { const gw = new MockLayoutGateway(); const tree = await gw.loadLayout("p1"); if (tree.root.type === "leaf") tree.root.node.session = "tampered"; const fresh = await gw.loadLayout("p1"); expect(leaves(fresh)[0].session ?? null).toBeNull(); }); // ── Multi-layout tests (#4) ──────────────────────────────────────────────── it("listLayouts returns a 'Default' layout on first call", async () => { const gw = new MockLayoutGateway(); const { layouts, activeId } = await gw.listLayouts("p1"); expect(layouts).toHaveLength(1); expect(layouts[0].name).toBe("Default"); expect(activeId).toBe(layouts[0].id); }); it("createLayout adds a new named layout", async () => { const gw = new MockLayoutGateway(); const { layoutId } = await gw.createLayout("p1", "Beta"); const { layouts } = await gw.listLayouts("p1"); expect(layouts).toHaveLength(2); expect(layouts.some((l) => l.id === layoutId && l.name === "Beta")).toBe(true); }); it("renameLayout changes the layout name", async () => { const gw = new MockLayoutGateway(); const { layouts } = await gw.listLayouts("p1"); await gw.renameLayout("p1", layouts[0].id, "Renamed"); const { layouts: updated } = await gw.listLayouts("p1"); expect(updated[0].name).toBe("Renamed"); }); it("deleteLayout removes a layout and adjusts the active id", async () => { const gw = new MockLayoutGateway(); const { layoutId: secondId } = await gw.createLayout("p1", "Second"); await gw.setActiveLayout("p1", secondId); const { activeId } = await gw.deleteLayout("p1", secondId); const { layouts, activeId: newActive } = await gw.listLayouts("p1"); expect(layouts).toHaveLength(1); expect(layouts.some((l) => l.id === secondId)).toBe(false); // active should have switched back to the Default. expect(activeId).toBe(newActive); }); it("setActiveLayout switches the active layout", async () => { const gw = new MockLayoutGateway(); const { layoutId } = await gw.createLayout("p1", "Alt"); await gw.setActiveLayout("p1", layoutId); const { activeId } = await gw.listLayouts("p1"); expect(activeId).toBe(layoutId); }); it("loadLayout with a specific layoutId loads that layout's tree", async () => { const gw = new MockLayoutGateway(); const { layoutId } = await gw.createLayout("p1", "Named"); // Mutate the named layout. const tree = await gw.loadLayout("p1", layoutId); const leafId = leaves(tree)[0].id; await gw.mutateLayout("p1", { type: "setSession", target: leafId, session: "s-named" }, layoutId); // The default (active) layout should be untouched. const defaultTree = await gw.loadLayout("p1"); expect(leaves(defaultTree)[0].session ?? null).toBeNull(); // The named layout should have the session. const namedTree = await gw.loadLayout("p1", layoutId); expect(leaves(namedTree)[0].session).toBe("s-named"); }); it("setCellAgent persists and clears correctly in applyOperation", async () => { const gw = new MockLayoutGateway(); const tree = await gw.loadLayout("p1"); const leafId = leaves(tree)[0].id; const withAgent = await gw.mutateLayout("p1", { type: "setCellAgent", target: leafId, agent: "agent-99", }); expect(leaves(withAgent)[0].agent).toBe("agent-99"); const cleared = await gw.mutateLayout("p1", { type: "setCellAgent", target: leafId, agent: null, }); expect(leaves(cleared)[0].agent).toBeUndefined(); }); });