refactor(confidence-engine): migrate scenario persistence to storage provider
This commit is contained in:
@@ -6,6 +6,7 @@ import DiagnosticsView from "@/components/diagnostics-view";
|
|||||||
import ReasoningWorkspace, { LoadingOverlay, ContinueLaterBanner } from "@/components/reasoning-workspace";
|
import ReasoningWorkspace, { LoadingOverlay, ContinueLaterBanner } from "@/components/reasoning-workspace";
|
||||||
import { mockFetch, AVAILABLE_SCENARIOS } from "@/lib/mocks/confidence-engine/mock-client";
|
import { mockFetch, AVAILABLE_SCENARIOS } from "@/lib/mocks/confidence-engine/mock-client";
|
||||||
import { deriveFindingsFromContributions, normalizeFindings } from "@/lib/graph/finding-helpers";
|
import { deriveFindingsFromContributions, normalizeFindings } from "@/lib/graph/finding-helpers";
|
||||||
|
import { loadInvestigation, saveInvestigation, clearInvestigation } from "@/lib/storage/investigation-storage";
|
||||||
|
|
||||||
/* Compile-time env resolution — NEXT_PUBLIC_ vars are injected by Next.js at build */
|
/* Compile-time env resolution — NEXT_PUBLIC_ vars are injected by Next.js at build */
|
||||||
const MOCK_ENABLED = process.env.NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCKS === "true";
|
const MOCK_ENABLED = process.env.NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCKS === "true";
|
||||||
@@ -192,26 +193,6 @@ export function UpdateErrorPanel({ updateError }) {
|
|||||||
|
|
||||||
export { INITIAL_MESSAGES, UPDATE_MESSAGES, useLoadingStatus };
|
export { INITIAL_MESSAGES, UPDATE_MESSAGES, useLoadingStatus };
|
||||||
|
|
||||||
// ── Session key ────────────────────────────────────────────────
|
|
||||||
const SESSION_KEY = "confidence-engine-session";
|
|
||||||
|
|
||||||
function getSession() {
|
|
||||||
if (typeof sessionStorage === "undefined") return null;
|
|
||||||
try {
|
|
||||||
const raw = sessionStorage.getItem(SESSION_KEY);
|
|
||||||
return raw ? JSON.parse(raw) : null;
|
|
||||||
} catch (_) { return null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveSession(state) {
|
|
||||||
if (typeof sessionStorage === "undefined") return;
|
|
||||||
try { sessionStorage.setItem(SESSION_KEY, JSON.stringify(state)); } catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearSession() {
|
|
||||||
if (typeof sessionStorage === "undefined") return;
|
|
||||||
try { sessionStorage.removeItem(SESSION_KEY); } catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives whether the current component state represents a valid investigation
|
* Derives whether the current component state represents a valid investigation
|
||||||
@@ -310,10 +291,10 @@ export default function ScenarioForm() {
|
|||||||
// Delegated to the exported utility below.
|
// Delegated to the exported utility below.
|
||||||
const validCtx = hasValidInvestigationContext(result, status, scenario);
|
const validCtx = hasValidInvestigationContext(result, status, scenario);
|
||||||
|
|
||||||
/* Restore persisted session on mount (Phase 3) ─────────── */
|
/* Restore persisted session on mount ─────────── */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
const saved = getSession();
|
const saved = loadInvestigation();
|
||||||
if (!saved) return;
|
if (!saved) return;
|
||||||
|
|
||||||
const hasGraph = Boolean(saved.situationGraph);
|
const hasGraph = Boolean(saved.situationGraph);
|
||||||
@@ -407,7 +388,7 @@ export default function ScenarioForm() {
|
|||||||
setCurrentUnderstanding(data.summary ?? null);
|
setCurrentUnderstanding(data.summary ?? null);
|
||||||
const normalised = normaliseStartResult(data);
|
const normalised = normaliseStartResult(data);
|
||||||
setResult(normalised);
|
setResult(normalised);
|
||||||
saveSession({ scenario, situationGraph: normalised.situationGraph, selectedQuestion: normalised.selectedQuestion, summary: data.summary ?? null, updatedAt: new Date().toISOString(), focusedContributions, findings: [] });
|
saveInvestigation({ scenario, situationGraph: normalised.situationGraph, selectedQuestion: normalised.selectedQuestion, summary: data.summary ?? null, updatedAt: new Date().toISOString(), focusedContributions, findings: [] });
|
||||||
} else {
|
} else {
|
||||||
setStatus("error");
|
setStatus("error");
|
||||||
setCurrentUnderstanding(data.summary ?? null);
|
setCurrentUnderstanding(data.summary ?? null);
|
||||||
@@ -480,7 +461,7 @@ export default function ScenarioForm() {
|
|||||||
}));
|
}));
|
||||||
setAnswer("");
|
setAnswer("");
|
||||||
// Persist after successful update turn — include findings
|
// Persist after successful update turn — include findings
|
||||||
saveSession({ scenario, situationGraph: outcome.updatedSituationGraph, selectedQuestion: normaliseUpdateSelectedQuestion(outcome.selectedQuestion), summary: outcome.summary ?? currentUnderstanding, updatedAt: new Date().toISOString(), focusedContributions, findings: newFindings });
|
saveInvestigation({ scenario, situationGraph: outcome.updatedSituationGraph, selectedQuestion: normaliseUpdateSelectedQuestion(outcome.selectedQuestion), summary: outcome.summary ?? currentUnderstanding, updatedAt: new Date().toISOString(), focusedContributions, findings: newFindings });
|
||||||
} else {
|
} else {
|
||||||
setUpdateStatus("error");
|
setUpdateStatus("error");
|
||||||
setUpdateError(outcome);
|
setUpdateError(outcome);
|
||||||
@@ -642,7 +623,7 @@ export default function ScenarioForm() {
|
|||||||
onUpdateFindingDisposition={updateFindingDisposition}
|
onUpdateFindingDisposition={updateFindingDisposition}
|
||||||
onUpdateFindingProposition={updateFindingProposition}
|
onUpdateFindingProposition={updateFindingProposition}
|
||||||
onRestart={() => {
|
onRestart={() => {
|
||||||
clearSession();
|
clearInvestigation();
|
||||||
setStatus("idle");
|
setStatus("idle");
|
||||||
setResult(null);
|
setResult(null);
|
||||||
setAnswer("");
|
setAnswer("");
|
||||||
@@ -662,7 +643,7 @@ export default function ScenarioForm() {
|
|||||||
|
|
||||||
{/* ── Continue later banner when session was restored ── */}
|
{/* ── Continue later banner when session was restored ── */}
|
||||||
{status === "success" && result?.updatedAt && (
|
{status === "success" && result?.updatedAt && (
|
||||||
<ContinueLaterBanner onRestart={() => { clearSession(); setStatus("idle"); setResult(null); setAnswer(""); setUpdateStatus("idle"); setCurrentUnderstanding(null); setFocusedContributions([]); setFindings([]); }} />
|
<ContinueLaterBanner onRestart={() => { clearInvestigation(); setStatus("idle"); setResult(null); setAnswer(""); setUpdateStatus("idle"); setCurrentUnderstanding(null); setFocusedContributions([]); setFindings([]); }} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Reset button after successful analysis */}
|
{/* Reset button after successful analysis */}
|
||||||
@@ -670,7 +651,7 @@ export default function ScenarioForm() {
|
|||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
clearSession();
|
clearInvestigation();
|
||||||
setScenario("");
|
setScenario("");
|
||||||
setStatus("idle");
|
setStatus("idle");
|
||||||
setResult(null);
|
setResult(null);
|
||||||
|
|||||||
@@ -0,0 +1,331 @@
|
|||||||
|
/**
|
||||||
|
* Focused integration contract tests verifying ScenarioForm uses the new
|
||||||
|
* investigation-storage provider (loadInvestigation / saveInvestigation
|
||||||
|
* / clearInvestigation) at ALL its call sites.
|
||||||
|
*
|
||||||
|
* Deterministic in-memory mocks — no React, no Playwright, no LLM calls.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi } from "vitest";
|
||||||
|
|
||||||
|
// ── test-time storage mock (injected into globalThis.window) ───────────
|
||||||
|
|
||||||
|
class MockStorageMap {
|
||||||
|
constructor() { this._data = new Map(); }
|
||||||
|
getItem(k) { return this._data.has(k) ? this._data.get(k) : null; }
|
||||||
|
setItem(k, v){ this._data.set(k, v); }
|
||||||
|
removeItem(k){ this._data.delete(k); }
|
||||||
|
clear() { this._data.clear(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function installMockStorage() {
|
||||||
|
Object.defineProperty(globalThis, "window", {
|
||||||
|
value: globalThis.window || {},
|
||||||
|
writable: true, configurable: true,
|
||||||
|
});
|
||||||
|
if (!globalThis.window.localStorage) {
|
||||||
|
Object.defineProperty(globalThis.window, "localStorage", {
|
||||||
|
value: new MockStorageMap(), writable: true, configurable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!globalThis.window.sessionStorage) {
|
||||||
|
Object.defineProperty(globalThis.window, "sessionStorage", {
|
||||||
|
value: new MockStorageMap(), writable: true, configurable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function uninstallMockStorage() {
|
||||||
|
const desc = Object.getOwnPropertyDescriptor(globalThis, "window");
|
||||||
|
if (desc && !("localStorage" in globalThis.window)) return;
|
||||||
|
delete globalThis.window.localStorage;
|
||||||
|
delete globalThis.window.sessionStorage;
|
||||||
|
if (Object.getOwnPropertyNames(globalThis.window).length === 0) {
|
||||||
|
delete globalThis.window;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── lifecycle ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
beforeEach(() => { installMockStorage(); });
|
||||||
|
afterEach (() => { uninstallMockStorage(); });
|
||||||
|
|
||||||
|
// ── import under test (loaded once, module cache preserved) ───────────
|
||||||
|
|
||||||
|
let storage = null;
|
||||||
|
async function getStorageModule() {
|
||||||
|
if (!storage) {
|
||||||
|
const m = await import("../../lib/storage/investigation-storage.js");
|
||||||
|
storage = {
|
||||||
|
load: m.loadInvestigation,
|
||||||
|
save: m.saveInvestigation,
|
||||||
|
clear: m.clearInvestigation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Shared snapshot factories mirroring ScenarioForm's call shapes ────
|
||||||
|
|
||||||
|
function makeStartCaseSnapshot(overrides = {}) {
|
||||||
|
return {
|
||||||
|
scenario: overrides.scenario || "Test scenario text",
|
||||||
|
situationGraph: {
|
||||||
|
centralStatement: "Central statement",
|
||||||
|
nodes: [{ id: "n-1", kind: "observation" }],
|
||||||
|
edges: [],
|
||||||
|
},
|
||||||
|
selectedQuestion: { question: "Next question?" },
|
||||||
|
summary: overrides.summary ?? "Current understanding",
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
focusedContributions: overrides.focusedContributions ?? [],
|
||||||
|
findings: overrides.findings ?? [],
|
||||||
|
schemaVersion: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeUpdateCaseSnapshot(overrides = {}) {
|
||||||
|
return {
|
||||||
|
scenario: overrides.scenario || "Test scenario text",
|
||||||
|
situationGraph: {
|
||||||
|
centralStatement: "Central statement",
|
||||||
|
nodes: [{ id: "n-1", kind: "observation" }, { id: "n-2", kind: "conclusion" }],
|
||||||
|
edges: [],
|
||||||
|
activeUnknownNodeId: "n-child-1",
|
||||||
|
},
|
||||||
|
selectedQuestion: { question: "Updated question?" },
|
||||||
|
summary: overrides.summary ?? "Updated understanding",
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
focusedContributions: overrides.focusedContributions ?? [
|
||||||
|
{ id: "contrib-0001", sequence: 1, targetNodeId: "nk-001" },
|
||||||
|
],
|
||||||
|
findings: overrides.findings ?? [
|
||||||
|
{ id: "finding-a1", proposition: "A finding", status: "provisional", evaluation: "considered" },
|
||||||
|
],
|
||||||
|
schemaVersion: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════
|
||||||
|
// Tests — ScenarioForm persistence integration with new provider
|
||||||
|
// ════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
describe("ScenarioForm → investigation-storage integration", () => {
|
||||||
|
|
||||||
|
// ── A. start-case save/load round-trip (handleSubmit path) ────
|
||||||
|
|
||||||
|
it("start-case snapshot: save then load preserves all fields", async () => {
|
||||||
|
const { load, save } = await getStorageModule();
|
||||||
|
const snap = makeStartCaseSnapshot({
|
||||||
|
scenario: "My investigation topic",
|
||||||
|
summary: "Initial understanding",
|
||||||
|
});
|
||||||
|
|
||||||
|
save(snap);
|
||||||
|
const loaded = load();
|
||||||
|
|
||||||
|
expect(loaded).not.toBeNull();
|
||||||
|
expect(loaded.scenario).toBe("My investigation topic");
|
||||||
|
expect(loaded.situationGraph.centralStatement).toBe("Central statement");
|
||||||
|
expect(loaded.selectedQuestion.question).toBe("Next question?");
|
||||||
|
expect(loaded.summary).toBe("Initial understanding");
|
||||||
|
expect(loaded.focusedContributions).toEqual([]);
|
||||||
|
expect(loaded.findings).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("start-case snapshot: schemaVersion = 1 persisted", async () => {
|
||||||
|
const { load, save } = await getStorageModule();
|
||||||
|
save(makeStartCaseSnapshot({ schemaVersion: undefined }));
|
||||||
|
const loaded = load();
|
||||||
|
expect(loaded.schemaVersion).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── B. update-case save/load round-trip (handleUpdate path) ────
|
||||||
|
|
||||||
|
it("update-case snapshot: save then load preserves focusedContributions and findings", async () => {
|
||||||
|
const { load, save } = await getStorageModule();
|
||||||
|
const snap = makeUpdateCaseSnapshot({
|
||||||
|
focusedContributions: [
|
||||||
|
{ id: "contrib-0001", sequence: 1, targetNodeId: "nk-001", question: "Q1?", answer: "A1" },
|
||||||
|
{ id: "contrib-0002", sequence: 2, targetNodeId: "nk-002", question: "Q2?", answer: "A2" },
|
||||||
|
],
|
||||||
|
findings: [
|
||||||
|
{ id: "finding-a1", proposition: "Finding A", status: "provisional", evaluation: "considered" },
|
||||||
|
{ id: "finding-b1", proposition: "Finding B", status: "provisional", evaluation: "rejected" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
save(snap);
|
||||||
|
const loaded = load();
|
||||||
|
|
||||||
|
expect(loaded).not.toBeNull();
|
||||||
|
expect(loaded.focusedContributions).toHaveLength(2);
|
||||||
|
expect(loaded.focusedContributions[0].id).toBe("contrib-0001");
|
||||||
|
expect(loaded.focusedContributions[1].id).toBe("contrib-0002");
|
||||||
|
expect(loaded.findings).toHaveLength(2);
|
||||||
|
expect(loaded.findings[0].id).toBe("finding-a1");
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── C. clearInvestigation clears start-case data (onRestart) ────
|
||||||
|
|
||||||
|
it("clearInvestigation removes start-case snapshot (matches onRestart behavior)", async () => {
|
||||||
|
const { load, save, clear } = await getStorageModule();
|
||||||
|
save(makeStartCaseSnapshot());
|
||||||
|
|
||||||
|
expect(load()).not.toBeNull();
|
||||||
|
|
||||||
|
clear(); // mirrors: onRestart -> clearInvestigation()
|
||||||
|
|
||||||
|
expect(load()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clearInvestigation removes update-case snapshot (matches ContinueLaterBanner behavior)", async () => {
|
||||||
|
const { load, save, clear } = await getStorageModule();
|
||||||
|
save(makeUpdateCaseSnapshot());
|
||||||
|
|
||||||
|
expect(load()).not.toBeNull();
|
||||||
|
|
||||||
|
clear(); // mirrors: ContinueLaterBanner.onRestart -> clearInvestigation()
|
||||||
|
|
||||||
|
expect(load()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clearInvestigation removes start-case snapshot (matches reset button behavior)", async () => {
|
||||||
|
const { load, save, clear } = await getStorageModule();
|
||||||
|
save(makeStartCaseSnapshot({ scenario: "To be cleared" }));
|
||||||
|
|
||||||
|
clear(); // mirrors: reset button onClick -> clearInvestigation()
|
||||||
|
|
||||||
|
expect(load()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── D. ScenarioForm lifecycle: save → reload → second save → clear ───
|
||||||
|
|
||||||
|
it("full lifecycle: start-case → restore partial → update-case → reload → verify → clear", async () => {
|
||||||
|
const { load, save, clear } = await getStorageModule();
|
||||||
|
|
||||||
|
// Phase 1: Start case (handleSubmit path)
|
||||||
|
const startSnap = makeStartCaseSnapshot({ scenario: "Phase 1" });
|
||||||
|
save(startSnap);
|
||||||
|
|
||||||
|
expect(load().scenario).toBe("Phase 1");
|
||||||
|
|
||||||
|
// Phase 2: User adds focused contributions and findings (simulates user session)
|
||||||
|
const updateSnap = makeUpdateCaseSnapshot({
|
||||||
|
scenario: "Phase 2",
|
||||||
|
summary: "Updated understanding",
|
||||||
|
focusedContributions: [
|
||||||
|
{ id: "contrib-0001", sequence: 1, targetNodeId: "nk-ph2" },
|
||||||
|
],
|
||||||
|
findings: [{ id: "finding-x", proposition: "X", status: "provisional" }],
|
||||||
|
});
|
||||||
|
save(updateSnap);
|
||||||
|
|
||||||
|
const loaded = load();
|
||||||
|
expect(loaded.scenario).toBe("Phase 2");
|
||||||
|
expect(loaded.summary).toBe("Updated understanding");
|
||||||
|
expect(loaded.focusedContributions).toHaveLength(1);
|
||||||
|
expect(loaded.findings).toHaveLength(1);
|
||||||
|
|
||||||
|
// Phase 3: User restarts (clearInvestigation)
|
||||||
|
clear();
|
||||||
|
expect(load()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── E. ScenarioForm partial session restoration (no graph → idle) ───
|
||||||
|
|
||||||
|
it("load returns snapshot without situationGraph → triggers scenario-entry form", async () => {
|
||||||
|
const { load, save } = await getStorageModule();
|
||||||
|
const partialSnap = {
|
||||||
|
scenario: "Incomplete investigation",
|
||||||
|
summary: null,
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
focusedContributions: [],
|
||||||
|
findings: [],
|
||||||
|
schemaVersion: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
save(partialSnap);
|
||||||
|
const loaded = load();
|
||||||
|
|
||||||
|
expect(loaded).not.toBeNull();
|
||||||
|
expect(loaded.scenario).toBe("Incomplete investigation");
|
||||||
|
expect(loaded.situationGraph).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── F. start-case snapshot shape fidelity (exact ScenarioForm call) ───
|
||||||
|
|
||||||
|
it("start-case save exactly mirrors ScenarioForm handleSubmit shape", async () => {
|
||||||
|
const { load, save } = await getStorageModule();
|
||||||
|
|
||||||
|
// Exact shape from scenario-form.jsx line ~403:
|
||||||
|
// saveInvestigation({ scenario, situationGraph, selectedQuestion, summary, updatedAt, focusedContributions, findings: [] })
|
||||||
|
const snap = {
|
||||||
|
scenario: "test-scenario",
|
||||||
|
situationGraph: { centralStatement: "CS", nodes: [], edges: [] },
|
||||||
|
selectedQuestion: "What next?",
|
||||||
|
summary: null,
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
focusedContributions: [
|
||||||
|
{ id: "contrib-0001", sequence: 1, targetNodeId: "nk-test", question: "Q" },
|
||||||
|
],
|
||||||
|
findings: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
save(snap);
|
||||||
|
const loaded = load();
|
||||||
|
|
||||||
|
expect(loaded.scenario).toBe("test-scenario");
|
||||||
|
expect(loaded.situationGraph.centralStatement).toBe("CS");
|
||||||
|
expect(loaded.selectedQuestion).toBe("What next?");
|
||||||
|
expect(loaded.summary).toBeNull();
|
||||||
|
expect(loaded.focusedContributions).toHaveLength(1);
|
||||||
|
expect(loaded.findings).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── G. update-case snapshot shape fidelity (exact ScenarioForm call) ───
|
||||||
|
|
||||||
|
it("update-case save exactly mirrors ScenarioForm handleUpdate shape", async () => {
|
||||||
|
const { load, save } = await getStorageModule();
|
||||||
|
|
||||||
|
// Exact shape from scenario-form.jsx line ~471:
|
||||||
|
// saveInvestigation({ scenario, situationGraph, selectedQuestion, summary, updatedAt, focusedContributions, findings: newFindings })
|
||||||
|
const snap = {
|
||||||
|
scenario: "test-scenario",
|
||||||
|
situationGraph: { centralStatement: "CS", nodes: [], edges: [] },
|
||||||
|
selectedQuestion: "Updated question?",
|
||||||
|
summary: "Updated understanding text",
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
focusedContributions: [
|
||||||
|
{ id: "contrib-0001", sequence: 1, targetNodeId: "nk-test" },
|
||||||
|
],
|
||||||
|
findings: [
|
||||||
|
{ id: "finding-a1", proposition: "A finding", status: "provisional", evaluation: "considered" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
save(snap);
|
||||||
|
const loaded = load();
|
||||||
|
|
||||||
|
expect(loaded.scenario).toBe("test-scenario");
|
||||||
|
expect(loaded.summary).toBe("Updated understanding text");
|
||||||
|
expect(loaded.findings).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── H. localStorage vs sessionStorage — ScenarioForm no longer writes session-level data ───
|
||||||
|
|
||||||
|
it("ScenarioForm saves to localStorage (canonical key), not sessionStorage", async () => {
|
||||||
|
const { load, save } = await getStorageModule();
|
||||||
|
|
||||||
|
save(makeStartCaseSnapshot());
|
||||||
|
|
||||||
|
// Should be in localStorage (canonical key)
|
||||||
|
const localVal = globalThis.window.localStorage.getItem("confidence-engine-investigation");
|
||||||
|
expect(localVal).not.toBeNull();
|
||||||
|
|
||||||
|
// Should NOT be in sessionStorage at the legacy key
|
||||||
|
const sessionVal = globalThis.window.sessionStorage.getItem("confidence-engine-session");
|
||||||
|
expect(sessionVal).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user