// IMP-44 (#73) u3 — vitest coverage for `validateZoneGeometriesAgainstLayout`. // // Pairs with the backend [override-warning] guards added in u1 (1-D // horizontal-2 / vertical-2 branches of `build_layout_css`) and u2 (2-D // `_override_to_grid_tracks` call site). Same WARN+DROP unknown / KEEP known // contract; this helper lets handleGenerate (u4) validate against the active // layout before forwarding so the user sees a toast on dropped keys rather // than the backend silently even-splitting non-overridden zones with a false // `computation=user_override_geometry` signal. // // Cases (Stage 2 scope-lock): // 1) horizontal-2 → vertical-2 mismatch (all keys dropped) // 2) passthrough (all keys recognized) // 3) partial mix (some kept, some dropped) // 4) empty input ({} on a known layout) // 5) unknown-layout fail-safe (preset null / undefined / unknown string) import { describe, it, expect } from "vitest"; import { validateZoneGeometriesAgainstLayout } from "../src/utils/slidePlanUtils"; const g = (x: number, y: number, w: number, h: number) => ({ x, y, w, h }); describe("validateZoneGeometriesAgainstLayout (IMP-44 u3)", () => { // ── 1. mismatch ────────────────────────────────────────────────────────── it("drops horizontal-2 keys when the active layout is vertical-2", () => { const result = validateZoneGeometriesAgainstLayout( { top: g(0, 0, 1, 0.4), bottom: g(0, 0.4, 1, 0.6) }, "vertical-2", ); expect(result.kept).toEqual({}); expect(result.dropped).toEqual({ top: g(0, 0, 1, 0.4), bottom: g(0, 0.4, 1, 0.6), }); expect(result.expectedPositions).toEqual(["left", "right"]); expect(result.valid).toBe(false); }); it("drops vertical-2 keys when the active layout is horizontal-2", () => { const result = validateZoneGeometriesAgainstLayout( { left: g(0, 0, 0.5, 1), right: g(0.5, 0, 0.5, 1) }, "horizontal-2", ); expect(result.kept).toEqual({}); expect(Object.keys(result.dropped).sort()).toEqual(["left", "right"]); expect(result.expectedPositions).toEqual(["top", "bottom"]); expect(result.valid).toBe(false); }); // ── 2. passthrough ─────────────────────────────────────────────────────── it("keeps all keys when every input key is in the active layout positions", () => { const input = { top: g(0, 0, 1, 0.4), bottom: g(0, 0.4, 1, 0.6), }; const result = validateZoneGeometriesAgainstLayout(input, "horizontal-2"); expect(result.kept).toEqual(input); expect(result.dropped).toEqual({}); expect(result.expectedPositions).toEqual(["top", "bottom"]); expect(result.valid).toBe(true); }); it("passes a single 'primary' key through on the 'single' preset", () => { const result = validateZoneGeometriesAgainstLayout( { primary: g(0, 0, 1, 1) }, "single", ); expect(result.kept).toEqual({ primary: g(0, 0, 1, 1) }); expect(result.dropped).toEqual({}); expect(result.expectedPositions).toEqual(["primary"]); expect(result.valid).toBe(true); }); it("recognizes the 2-D preset positions reported by computeZonePositions (top-1-bottom-2)", () => { const input = { top: g(0, 0, 1, 0.5), "bottom-left": g(0, 0.5, 0.5, 0.5), "bottom-right": g(0.5, 0.5, 0.5, 0.5), }; const result = validateZoneGeometriesAgainstLayout(input, "top-1-bottom-2"); expect(result.kept).toEqual(input); expect(result.dropped).toEqual({}); expect(result.expectedPositions).toEqual([ "top", "bottom-left", "bottom-right", ]); expect(result.valid).toBe(true); }); // ── 3. partial mix ─────────────────────────────────────────────────────── it("keeps known keys and drops unknown keys on a partial-mix input", () => { const result = validateZoneGeometriesAgainstLayout( { top: g(0, 0, 1, 0.4), foo: g(0, 0, 1, 1) }, "horizontal-2", ); expect(result.kept).toEqual({ top: g(0, 0, 1, 0.4) }); expect(result.dropped).toEqual({ foo: g(0, 0, 1, 1) }); expect(result.expectedPositions).toEqual(["top", "bottom"]); expect(result.valid).toBe(false); }); it("on a 2-D preset, keeps known 2-D track keys and drops legacy 1-D keys", () => { // Simulates the user resizing under top-1-bottom-2, then flipping to // grid-2x2 — legacy `bottom-left` stays valid; `top` (no longer a 2x2 // position) gets dropped. const result = validateZoneGeometriesAgainstLayout( { top: g(0, 0, 1, 0.5), "bottom-left": g(0, 0.5, 0.5, 0.5), "top-left": g(0, 0, 0.5, 0.5), }, "grid-2x2", ); expect(result.kept).toEqual({ "bottom-left": g(0, 0.5, 0.5, 0.5), "top-left": g(0, 0, 0.5, 0.5), }); expect(result.dropped).toEqual({ top: g(0, 0, 1, 0.5) }); expect(result.expectedPositions).toEqual([ "top-left", "top-right", "bottom-left", "bottom-right", ]); expect(result.valid).toBe(false); }); // ── 4. empty input ─────────────────────────────────────────────────────── it("returns empty kept/dropped and valid=true on an empty {} input", () => { const result = validateZoneGeometriesAgainstLayout({}, "horizontal-2"); expect(result.kept).toEqual({}); expect(result.dropped).toEqual({}); expect(result.expectedPositions).toEqual(["top", "bottom"]); expect(result.valid).toBe(true); }); it("treats null / undefined geoms as empty input (no throw, valid=true on a known layout)", () => { const nullResult = validateZoneGeometriesAgainstLayout(null, "vertical-2"); expect(nullResult.kept).toEqual({}); expect(nullResult.dropped).toEqual({}); expect(nullResult.expectedPositions).toEqual(["left", "right"]); expect(nullResult.valid).toBe(true); const undefResult = validateZoneGeometriesAgainstLayout( undefined, "vertical-2", ); expect(undefResult.kept).toEqual({}); expect(undefResult.dropped).toEqual({}); expect(undefResult.expectedPositions).toEqual(["left", "right"]); expect(undefResult.valid).toBe(true); }); it("ignores array payloads (defensive against hand-edited persisted files)", () => { const result = validateZoneGeometriesAgainstLayout( [] as unknown as Record, "horizontal-2", ); expect(result.kept).toEqual({}); expect(result.dropped).toEqual({}); expect(result.expectedPositions).toEqual(["top", "bottom"]); expect(result.valid).toBe(true); }); // ── 5. unknown-layout fail-safe ────────────────────────────────────────── it("drops every input key when layout is null (fail-safe)", () => { const result = validateZoneGeometriesAgainstLayout( { top: g(0, 0, 1, 0.4), bottom: g(0, 0.4, 1, 0.6) }, null, ); expect(result.kept).toEqual({}); expect(result.dropped).toEqual({ top: g(0, 0, 1, 0.4), bottom: g(0, 0.4, 1, 0.6), }); expect(result.expectedPositions).toEqual([]); expect(result.valid).toBe(false); }); it("drops every input key when layout is undefined (fail-safe)", () => { const result = validateZoneGeometriesAgainstLayout( { primary: g(0, 0, 1, 1) }, undefined, ); expect(result.kept).toEqual({}); expect(result.dropped).toEqual({ primary: g(0, 0, 1, 1) }); expect(result.expectedPositions).toEqual([]); expect(result.valid).toBe(false); }); it("drops every input key when layout is an unknown preset string (fail-safe)", () => { const result = validateZoneGeometriesAgainstLayout( { top: g(0, 0, 1, 0.4) }, "rogue-preset" as unknown as string, ); expect(result.kept).toEqual({}); expect(result.dropped).toEqual({ top: g(0, 0, 1, 0.4) }); expect(result.expectedPositions).toEqual([]); expect(result.valid).toBe(false); }); it("returns empty kept/dropped/expectedPositions when layout is unknown AND geoms is empty", () => { const result = validateZoneGeometriesAgainstLayout({}, null); expect(result.kept).toEqual({}); expect(result.dropped).toEqual({}); expect(result.expectedPositions).toEqual([]); // No keys to drop ⇒ vacuously valid; handleGenerate (u4) gates the toast // on `Object.keys(dropped).length > 0`, not `valid`, so this is safe. expect(result.valid).toBe(true); }); // ── purity / mutation safety ───────────────────────────────────────────── it("does not mutate the input geometries object", () => { const input = { top: g(0, 0, 1, 0.4), foo: g(0, 0, 1, 1) }; const inputKeysBefore = Object.keys(input).sort(); validateZoneGeometriesAgainstLayout(input, "horizontal-2"); expect(Object.keys(input).sort()).toEqual(inputKeysBefore); // Sample value still pristine. expect(input.top).toEqual(g(0, 0, 1, 0.4)); }); });