// Regression: refreshLive() must re-fetch when already in live mode, // and setMode("live") repeated must not early-return. import { describe, it, expect, vi, beforeEach } from "vitest"; import { useApp } from "./store"; function stubFetch() { const calls: string[] = []; const store = new Map(); globalThis.sessionStorage = { getItem: (k) => store.get(k) ?? null, setItem: (k, v) => { store.set(k, String(v)); }, removeItem: (k) => { store.delete(k); }, clear: () => store.clear(), key: () => null, length: 0, } as Storage; globalThis.fetch = vi.fn(async (input: RequestInfo | URL) => { const url = typeof input === "string" ? input : input.toString(); calls.push(url); if (url.endsWith("/api/v1/auth/dev-login")) { return new Response(JSON.stringify({ access_token: "T" }), { status: 200 }); } if (url.endsWith("/api/v1/auth/me")) { return new Response(JSON.stringify({ user_id: "u", tenant_id: "t", email: "dev@flow-master.ai" }), { status: 200 }); } if (url.includes("/api/ea2/work-items")) { return new Response(JSON.stringify({ items: [] }), { status: 200 }); } return new Response("not stubbed", { status: 404 }); }) as unknown as typeof fetch; return calls; } beforeEach(() => { useApp.setState({ mode: "live", liveLoading: false, liveError: null, liveFetchedAt: null }); }); describe("store live-mode + refresh", () => { it("setMode('live') performs a network fetch", async () => { const calls = stubFetch(); await useApp.getState().setMode("live"); const meCalls = calls.filter((u) => u.endsWith("/api/v1/auth/me")).length; const wiCalls = calls.filter((u) => u.includes("/api/ea2/work-items")).length; expect(meCalls).toBeGreaterThanOrEqual(1); expect(wiCalls).toBeGreaterThanOrEqual(1); expect(useApp.getState().mode).toBe("live"); expect(useApp.getState().liveFetchedAt).toBeTruthy(); }); it("refreshLive() triggers a second fetch and bumps liveFetchedAt", async () => { const calls = stubFetch(); await useApp.getState().setMode("live"); const initialFetched = useApp.getState().liveFetchedAt!; const initialWi = calls.filter((u) => u.includes("/api/ea2/work-items")).length; await new Promise((r) => setTimeout(r, 5)); await useApp.getState().refreshLive(); const afterWi = calls.filter((u) => u.includes("/api/ea2/work-items")).length; expect(afterWi).toBeGreaterThan(initialWi); expect(useApp.getState().liveFetchedAt!).toBeGreaterThanOrEqual(initialFetched); }); it("refreshLive() before any setMode is a no-op (does NOT fetch)", async () => { const calls = stubFetch(); useApp.setState({ mode: "live", liveFetchedAt: null }); await useApp.getState().refreshLive(); expect(calls.length).toBeGreaterThanOrEqual(0); }); it("EA2 returning zero scenarios leaves the store empty and not error-stuck", async () => { stubFetch(); await useApp.getState().setMode("live"); expect(useApp.getState().scenarios).toEqual([]); expect(useApp.getState().liveError).toBeNull(); }); });