85 lines
3.3 KiB
TypeScript
85 lines
3.3 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 fetch happens still works", async () => {
|
|
const calls = stubFetch();
|
|
await useApp.getState().refreshLive();
|
|
expect(useApp.getState().mode).toBe("live");
|
|
expect(calls.length).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it("refreshLive() handles an empty EA2 response without throwing", async () => {
|
|
stubFetch();
|
|
await useApp.getState().setMode("live");
|
|
expect(useApp.getState().mode).toBe("live");
|
|
expect(Array.isArray(useApp.getState().scenarios)).toBe(true);
|
|
});
|
|
|
|
it("refreshLive() leaves liveError null when the stub returns success", async () => {
|
|
stubFetch();
|
|
await useApp.getState().setMode("live");
|
|
await useApp.getState().refreshLive();
|
|
expect(useApp.getState().liveError).toBeNull();
|
|
});
|
|
});
|