- Remove src/data/synthetic.ts + its test (AR refund, HCM onboarding, GL close, Service Ops blueprints were hand-modelled fakes). - Remove DataMode='snapshot'; the store now boots straight into live EA2 with no fallback. EA2 errors surface as toasts, not fake data. - Topbar / Settings / CommandBar wording: 'SNAPSHOT'/'LIVE' becomes the unambiguous 'EA2' badge. No more user-facing mode switch. - package.json name flowmaster-mission-control-demo → flowmaster-canvas. - README rewritten — no longer calls the product a demo. - store.test: drop the two tests that relied on synthetic scenarios always being present; add one asserting an EA2-empty response leaves the store cleanly empty (no error-stuck state). All 23 vitest tests pass. tsc + vite build green. Bundle dropped from ~238 kB gz → ~234 kB gz.
78 lines
3.0 KiB
TypeScript
78 lines
3.0 KiB
TypeScript
// 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<string, string>();
|
|
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();
|
|
});
|
|
});
|