Files
flowmaster-mission-control-…/src/state/store.test.ts
T
canvas-bot 58026b2486
build-and-publish / test (pull_request) Has been cancelled
build-and-publish / image (pull_request) Has been cancelled
feat(canvas): strip demo data, EA2 is the only source
- 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.
2026-06-15 01:08:28 +04:00

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