/** * RTO.27F — Primary lifecycle surface invariant * * Tests that exactly one primary surface renders in every meaningful state: * no zero, no two. Exercises the exported predicates from * scenario-form.jsx directly to avoid SSR/React-Testing-Library complications. */ import { describe, it, expect } from "vitest"; import { hasValidInvestigationContext, derivePrimarySurface } from "@/components/scenario-form"; /* ── Helpers ─────────────────────────────────────────────────────── */ const EMPTY_RESULT = null; const RESULT_WITH_GRAPH = { situationGraph: {}, selectedQuestion: "q1" }; const SCENARIO_EMPTY = ""; const SCENARIO_SET = "Some situation description"; /* ── hasValidInvestigationContext tests ─────────────────────────── */ describe("hasValidInvestigationContext", () => { it("rejects idle + no result + empty scenario", () => { expect(hasValidInvestigationContext(EMPTY_RESULT, "idle", SCENARIO_EMPTY)).toBe(false); }); it("rejects loading state", () => { expect(hasValidInvestigationContext(EMPTY_RESULT, "loading", SCENARIO_SET)).toBe(false); }); it("accepts result with situationGraph", () => { expect(hasValidInvestigationContext(RESULT_WITH_GRAPH, "success", SCENARIO_EMPTY)).toBe(true); }); it("accepts success + non-empty scenario (session restoration)", () => { expect(hasValidInvestigationContext(EMPTY_RESULT, "success", SCENARIO_SET)).toBe(true); }); it("rejects success + empty scenario (no investigation data)", () => { expect(hasValidInvestigationContext(EMPTY_RESULT, "success", SCENARIO_EMPTY)).toBe(false); }); it("rejects error state even with graph (graph is valid but surface is ERROR)", () => { // Note: hasGraph returns true — the derivePrimarySurface function handles this by routing to ERROR_SURFACE first. expect(hasValidInvestigationContext(RESULT_WITH_GRAPH, "error", SCENARIO_SET)).toBe(true); }); it("rejects false scenario input (empty string)", () => { expect(hasValidInvestigationContext(EMPTY_RESULT, "success", "")).toBe(false); }); it("rejects whitespace-only scenario", () => { expect(hasValidInvestigationContext(EMPTY_RESULT, "success", " ")).toBe(false); }); }); /* ── derivePrimarySurface tests (7 required cases) ─────────────── */ describe("derivePrimarySurface — CASE 1: idle / no result / experiment=false", () => { it("returns SCENARIO_ENTRY", () => { const surface = derivePrimarySurface(EMPTY_RESULT, "idle", false, SCENARIO_EMPTY); expect(surface).toBe("SCENARIO_ENTRY"); expect(surface).not.toBeNull(); expect(surface).not.toBe(""); }); }); describe("derivePrimarySurface — CASE 2: idle / no result / experiment=true (stale flag)", () => { it("returns SCENARIO_ENTRY (not blank) — this is the RTO.27F critical fix", () => { const surface = derivePrimarySurface(EMPTY_RESULT, "idle", true, SCENARIO_EMPTY); expect(surface).toBe("SCENARIO_ENTRY"); expect(surface).not.toBeNull(); expect(surface).not.toBe(""); }); }); describe("derivePrimarySurface — CASE 3: valid investigation / experiment=true / branch selected", () => { it("returns EXPERIMENT_NOTEBOOK when a branch is active", () => { const surface = derivePrimarySurface(RESULT_WITH_GRAPH, "success", true, SCENARIO_SET, "branch-a"); expect(surface).toBe("EXPERIMENT_NOTEBOOK"); expect(surface).not.toBeNull(); expect(surface).not.toBe(""); }); }); describe("derivePrimarySurface — RTO.28A: branch-selection surface", () => { it("returns BRANCH_SELECTION when valid investigation exists but activeBranchId is null", () => { const surface = derivePrimarySurface(RESULT_WITH_GRAPH, "success", true, SCENARIO_SET, null); expect(surface).toBe("BRANCH_SELECTION"); expect(surface).not.toBeNull(); expect(surface).not.toBe(""); }); it("returns EXPERIMENT_NOTEBOOK when valid investigation exists and activeBranchId is set", () => { const surface = derivePrimarySurface(RESULT_WITH_GRAPH, "success", true, SCENARIO_SET, "branch-a"); expect(surface).toBe("EXPERIMENT_NOTEBOOK"); expect(surface).not.toBeNull(); expect(surface).not.toBe(""); }); it("returns EXPERIMENT_NOTEBOOK when valid investigation exists and activeBranchId is branch-b", () => { const surface = derivePrimarySurface(RESULT_WITH_GRAPH, "success", true, SCENARIO_SET, "branch-b"); expect(surface).toBe("EXPERIMENT_NOTEBOOK"); expect(surface).not.toBeNull(); expect(surface).not.toBe(""); }); }); describe("derivePrimarySurface — CASE 4: valid investigation / experiment=false", () => { it("returns NORMAL_WORKSPACE", () => { const surface = derivePrimarySurface(RESULT_WITH_GRAPH, "success", false, SCENARIO_SET); expect(surface).toBe("NORMAL_WORKSPACE"); expect(surface).not.toBeNull(); expect(surface).not.toBe(""); }); }); describe("derivePrimarySurface — CASE 5: loading", () => { it("returns LOADING regardless of other flags", () => { const surface = derivePrimarySurface(RESULT_WITH_GRAPH, "loading", true, SCENARIO_SET); expect(surface).toBe("LOADING"); expect(surface).not.toBeNull(); // Even with empty scenario or no graph — loading always wins const surface2 = derivePrimarySurface(null, "loading", false, ""); expect(surface2).toBe("LOADING"); expect(surface2).not.toBeNull(); }); }); describe("derivePrimarySurface — CASE 6: error / valid context", () => { it("returns ERROR_SURFACE when graph exists", () => { const surface = derivePrimarySurface(RESULT_WITH_GRAPH, "error", false, SCENARIO_SET); expect(surface).toBe("ERROR_SURFACE"); expect(surface).not.toBeNull(); }); it("returns ERROR_SURFACE even with no graph (safe fallback)", () => { const surface = derivePrimarySurface(null, "error", false, SCENARIO_SET); expect(surface).toBe("ERROR_SURFACE"); expect(surface).not.toBeNull(); }); }); describe("derivePrimarySurface — CASE 7: partial/stale restored session", () => { it("returns SCENARIO_ENTRY when no graph and idle (stale experiment flag)", () => { const surface = derivePrimarySurface(EMPTY_RESULT, "idle", true, SCENARIO_EMPTY); expect(surface).toBe("SCENARIO_ENTRY"); expect(surface).not.toBeNull(); }); it("returns BRANCH_SELECTION when session has scenario + success (valid context, no branch)", () => { const surface = derivePrimarySurface(EMPTY_RESULT, "success", true, SCENARIO_SET); expect(surface).toBe("BRANCH_SELECTION"); expect(surface).not.toBeNull(); }); it("returns SCENARIO_ENTRY for stale experiment preference without any session data", () => { const surface = derivePrimarySurface(EMPTY_RESULT, "idle", true, ""); expect(surface).toBe("SCENARIO_ENTRY"); expect(surface).not.toBeNull(); }); }); /* ── Exhaustive invariant: every state maps to exactly one surface ─ */ describe("PRIMARY_SURFACE_COUNT — exhaustive invariant (zero allowed, two forbidden)", () => { const testCases = [ // idle variants { result: null, status: "idle", exp: false, scenario: "", ab: null, expected: "SCENARIO_ENTRY" }, { result: null, status: "idle", exp: true, scenario: "", ab: null, expected: "SCENARIO_ENTRY" }, { result: null, status: "idle", exp: false, scenario: "x", ab: null, expected: "SCENARIO_ENTRY" }, // loading variants { result: null, status: "loading", exp: false, scenario: "", ab: null, expected: "LOADING" }, { result: null, status: "loading", exp: true, scenario: "x", ab: null, expected: "LOADING" }, { result: RESULT_WITH_GRAPH, status: "loading", exp: false, scenario: "x", ab: null, expected: "LOADING" }, // error variants { result: null, status: "error", exp: false, scenario: "x", ab: null, expected: "ERROR_SURFACE" }, { result: RESULT_WITH_GRAPH, status: "error", exp: true, scenario: "x", ab: null, expected: "ERROR_SURFACE" }, // success — no graph, empty scenario (no valid context) { result: null, status: "success", exp: false, scenario: "", ab: null, expected: "SCENARIO_ENTRY" }, { result: null, status: "success", exp: true, scenario: "", ab: null, expected: "SCENARIO_ENTRY" }, // success — no graph, non-empty scenario (session restored without graph) { result: null, status: "success", exp: false, scenario: "x", ab: null, expected: "NORMAL_WORKSPACE" }, { result: null, status: "success", exp: true, scenario: "x", ab: null, expected: "BRANCH_SELECTION" }, // success — with graph, no branch → BRANCH_SELECTION { result: RESULT_WITH_GRAPH, status: "success", exp: true, scenario: "x", ab: null, expected: "BRANCH_SELECTION" }, // success — with graph, branch-a selected → EXPERIMENT_NOTEBOOK { result: RESULT_WITH_GRAPH, status: "success", exp: true, scenario: "x", ab: "branch-a", expected: "EXPERIMENT_NOTEBOOK" }, // success — with graph, branch-b selected → EXPERIMENT_NOTEBOOK { result: RESULT_WITH_GRAPH, status: "success", exp: true, scenario: "x", ab: "branch-b", expected: "EXPERIMENT_NOTEBOOK" }, // success — with graph, experiment=false (production) { result: RESULT_WITH_GRAPH, status: "success", exp: false, scenario: "x", ab: null, expected: "NORMAL_WORKSPACE" }, ]; for (const tc of testCases) { it( `${tc.status}|exp=${tc.exp}|graph=${!!tc.result?.situationGraph}|ab=${tc.ab}|"${tc.scenario}" → ${tc.expected}`, () => { const surface = derivePrimarySurface(tc.result, tc.status, tc.exp, tc.scenario, tc.ab); expect(surface).toBe(tc.expected); // The invariant: exactly one surface, never null/undefined/blank expect(surface).not.toBeNull(); expect(surface).not.toBe(""); expect(surface).toBeDefined(); } ); } });