// IMP-90 (#90) u14 — vitest coverage for the pure helpers exported by // `StructureEditOverlay`. The React component itself is not rendered // (jsdom / @testing-library NOT in Front devDependencies — verified in // `Front/package.json`); we test the deterministic pieces that drive its // JSX: `resolveEffectiveSlotOrder` (effective-order resolution under // override) and `moveItem` (immutable reorder primitive). // // Upstream / downstream contracts (verified by prior units): // - u2 KNOWN_AXES += structure_overrides (Python backend). // - u3 vite allowlist += structure_overrides. // - u6 structure_override_resolver — inner shape locked to // {slot_order, hidden_slots}; frame swap REJECTED to existing // frames axis. // - u10 typed-client `StructureOverridePerZone` + extract helper. // - u15 (next) will debounce + PUT the emitted capture. // // u14 scope: pure helpers only. React render path is verified by Codex // auditor via static read of the JSX (no runtime test possible without // jsdom). Tests below are intentionally side-effect-free. import { describe, it, expect } from "vitest"; import { resolveEffectiveSlotOrder, moveItem, } from "../src/components/StructureEditOverlay"; // ───────────────────────────────────────────────────────────────────── // resolveEffectiveSlotOrder // ───────────────────────────────────────────────────────────────────── describe("resolveEffectiveSlotOrder — no override", () => { it("returns a fresh copy of the discovered keys when slotOrder is undefined", () => { const discovered = ["a", "b", "c"]; const out = resolveEffectiveSlotOrder(discovered, undefined); expect(out).toEqual(["a", "b", "c"]); expect(out).not.toBe(discovered); }); it("returns a fresh copy when slotOrder is null", () => { const out = resolveEffectiveSlotOrder(["a", "b"], null); expect(out).toEqual(["a", "b"]); }); it("returns a fresh copy when slotOrder is empty []", () => { const out = resolveEffectiveSlotOrder(["a", "b"], []); expect(out).toEqual(["a", "b"]); }); it("handles empty discovered list (no slots in zone)", () => { expect(resolveEffectiveSlotOrder([], undefined)).toEqual([]); expect(resolveEffectiveSlotOrder([], ["x"])).toEqual([]); }); }); describe("resolveEffectiveSlotOrder — full override", () => { it("reorders all discovered keys per slotOrder", () => { expect( resolveEffectiveSlotOrder(["a", "b", "c"], ["c", "a", "b"]), ).toEqual(["c", "a", "b"]); }); it("is idempotent when slotOrder matches discovered order", () => { expect( resolveEffectiveSlotOrder(["a", "b", "c"], ["a", "b", "c"]), ).toEqual(["a", "b", "c"]); }); }); describe("resolveEffectiveSlotOrder — partial / drift override", () => { it("appends missing discovered keys in backend order at the tail", () => { // user reordered b -> first, but c was added later by backend. expect( resolveEffectiveSlotOrder(["a", "b", "c"], ["b", "a"]), ).toEqual(["b", "a", "c"]); }); it("drops override entries that no longer exist in discovered keys", () => { // user had slot 'x' before; backend dropped it. expect( resolveEffectiveSlotOrder(["a", "b"], ["x", "a", "b"]), ).toEqual(["a", "b"]); }); it("dedupes duplicate entries within slotOrder", () => { expect( resolveEffectiveSlotOrder(["a", "b", "c"], ["a", "a", "b"]), ).toEqual(["a", "b", "c"]); }); it("dedupe + drop + append all together (stress)", () => { expect( resolveEffectiveSlotOrder( ["a", "b", "c", "d"], ["d", "x", "d", "a", "ghost"], ), ).toEqual(["d", "a", "b", "c"]); }); it("ignores non-string entries in slotOrder", () => { const bogus = ["a", null as unknown as string, undefined as unknown as string, "b"]; expect(resolveEffectiveSlotOrder(["a", "b"], bogus)).toEqual(["a", "b"]); }); }); // ───────────────────────────────────────────────────────────────────── // moveItem // ───────────────────────────────────────────────────────────────────── describe("moveItem — happy paths", () => { it("moves index 0 down by 1 (swap with index 1)", () => { expect(moveItem(["a", "b", "c"], 0, 1)).toEqual(["b", "a", "c"]); }); it("moves index 2 up by 1 (swap with index 1)", () => { expect(moveItem(["a", "b", "c"], 2, -1)).toEqual(["a", "c", "b"]); }); it("moves across larger delta (swap with target)", () => { expect(moveItem(["a", "b", "c", "d"], 0, 2)).toEqual(["c", "b", "a", "d"]); }); }); describe("moveItem — bounds", () => { it("no-op (fresh copy) when moving first up", () => { const src = ["a", "b", "c"]; const out = moveItem(src, 0, -1); expect(out).toEqual(["a", "b", "c"]); expect(out).not.toBe(src); }); it("no-op when moving last down", () => { expect(moveItem(["a", "b", "c"], 2, 1)).toEqual(["a", "b", "c"]); }); it("no-op when index negative", () => { expect(moveItem(["a", "b"], -1, 1)).toEqual(["a", "b"]); }); it("no-op when index past end", () => { expect(moveItem(["a", "b"], 5, -1)).toEqual(["a", "b"]); }); it("no-op when target falls out of range from large delta", () => { expect(moveItem(["a", "b", "c"], 1, 99)).toEqual(["a", "b", "c"]); }); it("no-op on empty array (any index)", () => { expect(moveItem([], 0, 1)).toEqual([]); }); }); describe("moveItem — immutability", () => { it("never mutates the input array", () => { const src = ["a", "b", "c"]; moveItem(src, 0, 1); expect(src).toEqual(["a", "b", "c"]); }); it("returns a new reference even when no-op", () => { const src = ["a", "b"]; expect(moveItem(src, 0, -1)).not.toBe(src); }); it("preserves T-typed values (number array)", () => { expect(moveItem([1, 2, 3], 0, 1)).toEqual([2, 1, 3]); }); });