/** * RTO.A1 — Multi-thread apparatus verification. * * Validates that the experimental fixture can deterministically represent: * - a fixed case understanding * - three open investigation items (none compulsory) * - one engine recommendation * - user selection of any item * - chosen-item becomes visually primary in presentation * - other items remain visible as context * - no claim that production reasoning ownership changed */ import { describe, it, expect, beforeEach } from "vitest"; import { MULTI_THREAD_FIXTURE, MULTI_THREAD_CASE, MULTI_THREAD_ITEMS, buildScenarioFixture, } from "@/lib/mocks/scenarios.js"; describe("RTO.A1 multi-thread apparatus", () => { /* Reset shared experimental state between tests */ beforeEach(() => { const exp = MULTI_THREAD_FIXTURE._experimental; if (exp) exp.selectedThreadId = null; }); describe("fixed case representation", () => { it("exposes a case summary without implying a single active unknown", () => { expect(MULTI_THREAD_FIXTURE.centralStatement).toBeDefined(); expect(MULTI_THREAD_FIXTURE._experimental.caseContext.caseSummary.length).toBeGreaterThan(0); expect(MULTI_THREAD_FIXTURE.active).toBeNull(); }); it("uses the existing launch-vs-wait central statement", () => { expect(MULTI_THREAD_CASE.centralStatement).toContain("launching"); expect(MULTI_THREAD_CASE.centralStatement).toContain("waiting twelve months"); }); }); describe("open investigation items", () => { it("represents exactly three open items", () => { expect(MULTI_THREAD_ITEMS.length).toBe(3); }); it("each item has an id, label and description", () => { MULTI_THREAD_ITEMS.forEach((item) => { expect(item.id).toBeDefined(); expect(item.label).toBeDefined(); expect(item.description).toBeDefined(); }); }); it("none is compulsory — all items are selectable", () => { MULTI_THREAD_ITEMS.forEach((item) => { // Items lack a `compulsory` flag; selection is always open expect(item.compulsory).not.toBe(true); }); }); it("items map to the three investigation areas from RTO.01", () => { const labels = MULTI_THREAD_ITEMS.map((i) => i.label.toLowerCase()); expect(labels).toContainEqual(expect.stringContaining("enterprise customer")); expect(labels).toContainEqual(expect.stringContaining("competitor timing")); expect(labels).toContainEqual(expect.stringContaining("financial viability")); }); }); describe("engine recommendation", () => { it("marks exactly one item as the engine recommendation", () => { const recommended = MULTI_THREAD_FIXTURE._experimental.recommendedThreadId; expect(recommended).toBe("mt-ent-customer-signing"); const matched = MULTI_THREAD_ITEMS.find( (i) => i.id === recommended ); expect(matched).toBeDefined(); }); it("the recommendation is the first item by ordering", () => { const ordered = [...MULTI_THREAD_ITEMS].sort( (a, b) => a.recommendationOrder - b.recommendationOrder ); expect(ordered[0].id).toBe("mt-ent-customer-signing"); }); }); describe("experimental user selection", () => { it("allows selection of any available thread", () => { const exp = MULTI_THREAD_FIXTURE._experimental; // Initially no selection expect(exp.selectedThreadId).toBeNull(); // Simulate user selecting the second item exp.selectedThreadId = "mt-competitor-timing"; expect(exp.selectedThreadId).toBe("mt-competitor-timing"); // Verify it is still a valid available thread const found = MULTI_THREAD_ITEMS.find( (i) => i.id === exp.selectedThreadId ); expect(found).toBeDefined(); expect(found.label).toContain("Competitor timing"); }); it("changing selection does not affect other items", () => { const exp = MULTI_THREAD_FIXTURE._experimental; const othersBefore = MULTI_THREAD_ITEMS.map((i) => ({ id: i.id, recommended: i.id === exp.recommendedThreadId, })); // Switch to third item exp.selectedThreadId = "mt-financial-comparison"; expect(exp.selectedThreadId).toBe("mt-financial-comparison"); const othersAfter = MULTI_THREAD_ITEMS.map((i) => ({ id: i.id, recommended: i.id === exp.recommendedThreadId, })); // Structure is preserved — only selectedThreadId changed expect(othersBefore).toEqual(othersAfter); }); }); describe("chosen-thread primary state", () => { it("marks the selected item as visually primary when set", () => { const exp = MULTI_THREAD_FIXTURE._experimental; const targetId = "mt-ent-customer-signing"; exp.selectedThreadId = targetId; const primaryItem = MULTI_THREAD_ITEMS.find( (i) => i.id === targetId ); expect(primaryItem).toBeDefined(); }); it("other items remain visible as case-file context", () => { const exp = MULTI_THREAD_FIXTURE._experimental; exp.selectedThreadId = "mt-competitor-timing"; // All three items are still present — none were removed expect(MULTI_THREAD_ITEMS.length).toBe(3); // The non-selected items are still available const remaining = MULTI_THREAD_ITEMS.filter( (i) => i.id !== exp.selectedThreadId ); expect(remaining.length).toBe(2); }); }); describe("production boundary", () => { it("does not set production activeUnknownNodeId semantics", () => { const graph = MULTI_THREAD_FIXTURE.situationGraph; expect(graph.activeUnknownNodeId).toBeNull(); }); it("keeps _experimental separate from situationGraph", () => { const exp = MULTI_THREAD_FIXTURE._experimental; expect(exp.apparatusMode).toBe("selection"); expect(exp.selectedThreadId).toBeNull(); // The experimental state is NOT in situationGraph expect(MULTI_THREAD_FIXTURE.situationGraph.exp).toBeUndefined(); }); }); describe("buildScenarioFixture integration", () => { it("returns the multi-thread fixture for scenario 'multi-thread'", () => { const fixture = buildScenarioFixture("multi-thread", 0); expect(fixture).not.toBeNull(); expect(fixture.success).toBe(true); expect(fixture._experimentalState).toBeDefined(); expect(fixture._experimentalState.availableThreads.length).toBe(3); expect(fixture._experimentalState.recommendedThreadId).toBe("mt-ent-customer-signing"); }); it("preserves the case context through buildScenarioFixture", () => { const fixture = buildScenarioFixture("multi-thread", 0); expect(fixture.situationGraph.centralStatement).toBe( MULTI_THREAD_CASE.centralStatement ); }); it("does not return a production question when multi-thread is active", () => { const fixture = buildScenarioFixture("multi-thread", 0); expect(fixture.selectedQuestion).toBeNull(); expect(fixture.noQuestionReason).toContain("Multi-thread"); }); it("returns null for unknown scenario name", () => { expect(buildScenarioFixture("nonexistent-scenario", 0)).toBeNull(); }); it("returns null when scenario has no turns or scenario field", () => { // This should not break — SCENARIOS entries without either field const oldEntry = { someField: "value" }; expect(buildScenarioFixture("__nonexistent__", 0)).toBeNull(); }); }); describe("scenario fixture structure correctness", () => { it("includes nodes and edges in the situation graph", () => { const g = MULTI_THREAD_FIXTURE.situationGraph; expect(g.nodes.length).toBeGreaterThan(0); expect(g.edges.length).toBeGreaterThan(0); }); it("nodes contain valid observation kind entries", () => { const observationNodes = MULTI_THREAD_FIXTURE.situationGraph.nodes.filter( (n) => n.kind === "observation" ); expect(observationNodes.length).toBeGreaterThanOrEqual(2); }); it("includes the experimental scenario identifier", () => { expect(MULTI_THREAD_FIXTURE._experimental.scenario).toBe("multi-thread"); }); }); });