Files
C.E.L_Slide_test2/Front/client/tests/zone_geometries_validation.test.ts
kyeongmin e0c39f1bc1
Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 23s
feat(#73): IMP-44 u1~u5 layout override unknown-key guard + frontend zone_geometries validation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:12:24 +09:00

223 lines
9.3 KiB
TypeScript

// 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<string, { x: number; y: number; w: number; h: number }>,
"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));
});
});