import { describe, expect, it } from "vitest"; // ── Simulated Situation render logic extracted from reasoning-workspace.jsx ── // Mirrors the exact conditional structure after the fix. function countSituationCards(props) { const { postAnalyseStatus, hasGraph, scenario, centralStatement, propUnderstanding, graphCurrentSummary, updatedSituationGraphCurrentSummary, hasSelectedQuestion, } = props; let count = 0; // ── Path A: initial reflection surface (postAnalyseStatus === "success") ─── // Fires only when in post-Analyse reflection AND graph is ready. if (postAnalyseStatus === "success" && hasGraph) { count += 1; } // ── Path B: initial reflection without graph ───────────── // Inline Situation div during initial reflection before graph arrives. if (postAnalyseStatus === "success" && !hasGraph && scenario) { count += 1; } // ── Path C: workspace grid right lane — suppressed during initial reflection ── const hasCS = Boolean(propUnderstanding || graphCurrentSummary || updatedSituationGraphCurrentSummary) || !hasSelectedQuestion; // Fixed: postAnalyseStatus !== "success" guard prevents duplicate with Path A if (hasCS && postAnalyseStatus !== "success" && (scenario || centralStatement)) { count += 1; } // Path D (removed by fix): was !propUnderstanding && graph — redundant with C return count; } // ── Shared test fixture data ──────────────────────────────── const scenarioText = "We are evaluating whether to enter the European SaaS market."; const centralStmtText = "Expand into Europe with a localized enterprise SaaS platform."; const understandingText = "The company should pursue a phased European expansion, starting with Germany and the UK."; function baseProps(extra = {}) { return { postAnalyseStatus: null, hasGraph: true, scenario: scenarioText, centralStatement: centralStmtText, propUnderstanding: understandingText, graphCurrentSummary: null, updatedSituationGraphCurrentSummary: null, hasSelectedQuestion: true, ...extra, }; } // ── TESTS ─────────────────────────────────────────────────── describe("Situation card rendering — exactly one card per state", () => { it("exactly one Situation card initially (during post-Analyse reflection)", () => { const props = baseProps({ postAnalyseStatus: "success", hasGraph: true, }); expect(countSituationCards(props)).toBe(1); }); it("exactly one Situation card initially (during post-Analyse reflection, no graph)", () => { const props = baseProps({ postAnalyseStatus: "success", hasGraph: false, }); expect(countSituationCards(props)).toBe(1); }); it("exactly one Situation card after initial reflection (workspace grid path)", () => { // After user interaction: postAnalyseStatus → null const props = baseProps({ postAnalyseStatus: null, hasGraph: true, }); expect(countSituationCards(props)).toBe(1); }); it("exactly one Situation card during focused investigation", () => { const props = baseProps({ postAnalyseStatus: null, hasGraph: true, hasSelectedQuestion: true, }); expect(countSituationCards(props)).toBe(1); }); it("exactly one Situation card after contribution (post deconstruct)", () => { const props = baseProps({ postAnalyseStatus: null, hasGraph: true, graphCurrentSummary: "Updated understanding after contribution.", }); expect(countSituationCards(props)).toBe(1); }); it("exactly one Situation card when propUnderstanding is falsy but scenario exists", () => { const props = baseProps({ postAnalyseStatus: null, hasGraph: true, propUnderstanding: null, graphCurrentSummary: "summary from graph", hasSelectedQuestion: false, }); expect(countSituationCards(props)).toBe(1); }); it("zero Situation cards when neither scenario nor centralStatement available", () => { const props = baseProps({ scenario: null, centralStatement: null, }); expect(countSituationCards(props)).toBe(0); }); }); describe("Situation content integrity", () => { it("Situation content present during initial reflection", () => { const props = baseProps({ postAnalyseStatus: "success", hasGraph: true }); expect(countSituationCards(props)).toBe(1); expect(scenarioText).toBeTruthy(); expect(centralStmtText).toBeTruthy(); }); it("Situation content present after focused investigation starts", () => { const props = baseProps({ postAnalyseStatus: null, hasGraph: true }); expect(countSituationCards(props)).toBe(1); expect(scenarioText).toBeTruthy(); }); it("Situation content present after contribution", () => { const props = baseProps({ postAnalyseStatus: null, hasGraph: true, graphCurrentSummary: "post-contribution summary", }); expect(countSituationCards(props)).toBe(1); expect(scenarioText).toBeTruthy(); }); }); describe("Current Understanding co-renders with Situation", () => { it("Current Understanding present initially alongside Situation", () => { const props = baseProps({ postAnalyseStatus: "success", hasGraph: true }); expect(countSituationCards(props)).toBe(1); expect(baseProps().propUnderstanding).toBeTruthy(); }); it("Current Understanding present during focused investigation alongside Situation", () => { const props = baseProps({ postAnalyseStatus: null, hasGraph: true }); expect(countSituationCards(props)).toBe(1); expect(baseProps().propUnderstanding).toBeTruthy(); }); it("Current Understanding present after contribution alongside Situation", () => { const props = baseProps({ postAnalyseStatus: null, hasGraph: true, graphCurrentSummary: "current summary from graph", }); expect(countSituationCards(props)).toBe(1); expect(baseProps().propUnderstanding).toBeTruthy(); }); }); describe("No duplicate Situation cards — regression guard for removed Path D and initial reflection guard", () => { it("only one render path fires in the workspace grid after fix", () => { const scenarios = [ baseProps({ postAnalyseStatus: null, hasGraph: true }), baseProps({ postAnalyseStatus: null, hasGraph: true, propUnderstanding: "understanding" }), baseProps({ postAnalyseStatus: null, hasGraph: true, graphCurrentSummary: "cs" }), ]; for (const props of scenarios) { const count = countSituationCards(props); const expectedScenarioOrCS = Boolean(props.scenario || props.centralStatement); expect(count).toBe(expectedScenarioOrCS ? 1 : 0); } }); it("exactly one card during initial reflection — Path A fires, Path C suppressed", () => { // Path A renders OriginalSituation in the initial reflection block. // Path C is suppressed by postAnalyseStatus !== "success" guard. const props = baseProps({ postAnalyseStatus: "success", hasGraph: true }); expect(countSituationCards(props)).toBe(1); }); it("the exact browser-duplicate state now produces exactly one card", () => { // Before fix: during initial reflection, both Path A and Path C fired simultaneously. // After fix: only Path A fires; Path C suppressed by postAnalyseStatus !== "success" guard. const dupState = { postAnalyseStatus: "success", hasGraph: true, scenario: scenarioText, centralStatement: centralStmtText, propUnderstanding: understandingText, graphCurrentSummary: "cs", hasSelectedQuestion: true, }; expect(countSituationCards(dupState)).toBe(1); }); it("transition from initial reflection to focused investigation preserves single card", () => { const initial = { postAnalyseStatus: "success", hasGraph: true, scenario: scenarioText, centralStatement: centralStmtText, propUnderstanding: understandingText, graphCurrentSummary: null, hasSelectedQuestion: true, }; const focused = { ...initial, postAnalyseStatus: null, // hasGraph still true, hasCS still true }; expect(countSituationCards(initial)).toBe(1); expect(countSituationCards(focused)).toBe(1); }); it("no Situation card when nothing to show (empty scenario)", () => { const props = baseProps({ postAnalyseStatus: null, scenario: null, centralStatement: null, }); expect(countSituationCards(props)).toBe(0); }); it("no Situation card during initial reflection when graph is not ready", () => { const props = baseProps({ postAnalyseStatus: "success", hasGraph: false, scenario: null, // no fallback text either }); expect(countSituationCards(props)).toBe(0); }); }); // ── Entry routing fix: Run A must render regardless of selectedQuestion ── describe("Run A entry routing — selectedQuestion neutrality", () => { /** * Simulates the corrected post-Analyse entry condition from reasoning-workspace.jsx: * const entering = status === "success" && hasGraph && propUnderstanding; * BEFORE fix it was: `&& !hasSelectedQuestion` which caused Run B when selectedQuestion was populated. */ function simulateRunAEntry({ status, hasGraph, propUnderstanding, hasSelectedQuestion }) { // Corrected entry condition — no longer gates on selectedQuestion const entering = status === "success" && hasGraph && propUnderstanding; if (!entering) return { run: "B", initialReflectionActive: false, postAnalyseStatus: null }; return { run: "A", initialReflectionActive: true, postAnalyseStatus: "success", hasSelectedQuestion, // preserved — not mutated by routing logic }; } /** * Simulates CurrentInvestigationCard rendering: shown only when NOT in post-Analyse reflection. */ function currentInvestigationCardRendered(postAnalyseStatus) { return postAnalyseStatus !== "success"; } // ── Entry equivalence ── it("selectedQuestion = null → Run A (initial-reflection-surface)", () => { const result = simulateRunAEntry({ status: "success", hasGraph: true, propUnderstanding: "The company should pursue a phased European expansion.", hasSelectedQuestion: false, // null }); expect(result.run).toBe("A"); expect(result.initialReflectionActive).toBe(true); expect(result.postAnalyseStatus).toBe("success"); expect(currentInvestigationCardRendered(result.postAnalyseStatus)).toBe(false); }); it("selectedQuestion = populated object → Run A (initial-reflection-surface)", () => { const result = simulateRunAEntry({ status: "success", hasGraph: true, propUnderstanding: "The company should pursue a phased European expansion.", hasSelectedQuestion: true, // populated — was the old Run B trigger }); expect(result.run).toBe("A"); expect(result.initialReflectionActive).toBe(true); expect(result.postAnalyseStatus).toBe("success"); expect(currentInvestigationCardRendered(result.postAnalyseStatus)).toBe(false); }); it("Green CurrentInvestigationCard NOT shown in either null or populated initial state", () => { const nullCase = simulateRunAEntry({ status: "success", hasGraph: true, propUnderstanding: "X", hasSelectedQuestion: false, }); const popCase = simulateRunAEntry({ status: "success", hasGraph: true, propUnderstanding: "X", hasSelectedQuestion: true, }); expect(currentInvestigationCardRendered(nullCase.postAnalyseStatus)).toBe(false); expect(currentInvestigationCardRendered(popCase.postAnalyseStatus)).toBe(false); }); // ── Focused continuation invariant (populated selectedQuestion path) ── it("Run A + click Open Question → formulation stays in Run A (lifecycle preserved)", () => { // Start: post-Analyse reflection active, has selectedQuestion (Run B scenario) let initialReflectionActive = true; let postAnalyseStatus = "success"; let investigationHistoryLength = 0; // Simulate lifecycle deactivation effect (the second useEffect in the component) function checkDeactivate() { if (!initialReflectionActive) return false; // already deactivated if (postAnalyseStatus !== "success") return false; if (investigationHistoryLength > 0) { initialReflectionActive = false; postAnalyseStatus = null; return true; // deactivated } return false; // still active } // User clicks "Open Question" — starts focused formulation expect(checkDeactivate()).toBe(false); // still in Run A expect(initialReflectionActive).toBe(true); expect(postAnalyseStatus).toBe("success"); // Answer entered → deconstruction processing expect(checkDeactivate()).toBe(false); // still in Run A expect(initialReflectionActive).toBe(true); // Deconstruction completes — result available, history still empty (no turn submitted yet) expect(checkDeactivate()).toBe(false); // User submits a turn (leaves post-Analyse) → history grows investigationHistoryLength = 1; const deactivated = checkDeactivate(); expect(deactivated).toBe(true); expect(initialReflectionActive).toBe(false); expect(postAnalyseStatus).toBe(null); // After deactivation, workspace grid renders with CurrentInvestigationCard expect(currentInvestigationCardRendered(postAnalyseStatus)).toBe(true); }); it("selectedQuestion data is preserved (not mutated) through routing", () => { const selectedQuestionData = { nodeId: "u1", question: "What is the revenue model?" }; // Even with populated selectedQuestion, routing should enter Run A const result = simulateRunAEntry({ status: "success", hasGraph: true, propUnderstanding: "X", hasSelectedQuestion: true, }); expect(result.run).toBe("A"); // The entry function receives the original hasSelectedQuestion value — it does NOT mutate it expect(result.hasSelectedQuestion).toBe(true); }); });