Files
confidence-engine/tests/situation-rendering.test.jsx
T

383 lines
14 KiB
React

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);
});
});