Full 'pnpm test' previously hung indefinitely (killed at CI walls, misdiagnosed as teardown slowness). Two root causes found and fixed: 1. RightInspector.test.tsx called setSubject() during render with a fresh object every pass -> infinite synchronous render loop (100% CPU spin, blocks the event loop so no timeout can fire). Moved into useEffect. 2. Test QueryClients used retry:false but default gcTime (5 min) -> a live GC timer per cached query kept workers from exiting. New shared createTestQueryClient() with gcTime:0 adopted across all 7 test files. Hardening so any future leak fails loudly instead of hanging: - vite.config.ts: testTimeout/hookTimeout 10s, teardownTimeout 5s - useManagedTimeout hook owns previously-bare setTimeouts in DigestBadge, MetadataTable, RatificationCommandPanel (4 sites), CommandPalette focus First-ever frontend CI lane (.github/workflows/workbench-ui.yml): path-filtered to workbench-ui/** AND the workflow file itself, pnpm install --frozen-lockfile + build + vitest, timeout-minutes 15. Honesty fix: KeyboardHelp no longer advertises j/k, /, Enter (unbuilt until R0d restores them registry-backed). Route conformance test (ADR-0162 §6, executable): empty/error/loading contracts asserted for Chat/Proposals/Evals/Replay; fixed the two gaps it caught (ReplayRoute bare-div error branch -> ErrorState contract; ArtifactList empty state gained a next action). Result: 27 files / 181 tests pass, suite EXITS in 4.9s wall-clock (was: indefinite hang).
63 lines
2.1 KiB
TypeScript
63 lines
2.1 KiB
TypeScript
import { QueryClientProvider } from "@tanstack/react-query";
|
|
import { createTestQueryClient } from "../test/createTestQueryClient";
|
|
import { renderHook, waitFor } from "@testing-library/react";
|
|
import type { ReactNode } from "react";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { useChatTurn } from "./queries";
|
|
import { WorkbenchApiError } from "./client";
|
|
import { happyChatTurn } from "../app/chat/fixtures";
|
|
|
|
function wrapper({ children }: { children: ReactNode }) {
|
|
return (
|
|
<QueryClientProvider client={createTestQueryClient()}>
|
|
{children}
|
|
</QueryClientProvider>
|
|
);
|
|
}
|
|
|
|
describe("useChatTurn", () => {
|
|
afterEach(() => vi.restoreAllMocks());
|
|
|
|
it("posts /chat/turn with the prompt body", async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue({
|
|
json: vi.fn().mockResolvedValue({ ok: true, generated_at: "now", data: happyChatTurn }),
|
|
});
|
|
vi.stubGlobal("fetch", fetchMock);
|
|
const { result } = renderHook(() => useChatTurn(), { wrapper });
|
|
|
|
result.current.mutate({ prompt: "What is truth?" });
|
|
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"http://127.0.0.1:8765/chat/turn",
|
|
expect.objectContaining({
|
|
method: "POST",
|
|
body: JSON.stringify({ prompt: "What is truth?" }),
|
|
headers: { "Content-Type": "application/json" },
|
|
}),
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
[400, "bad_request"],
|
|
[413, "read_error"],
|
|
])("%s surfaces as WorkbenchApiError %s", async (_status, code) => {
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn().mockResolvedValue({
|
|
json: vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
generated_at: "now",
|
|
error: { code, message: "failed" },
|
|
}),
|
|
}),
|
|
);
|
|
const { result } = renderHook(() => useChatTurn(), { wrapper });
|
|
|
|
result.current.mutate({ prompt: "" });
|
|
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
|
|
expect(result.current.error).toBeInstanceOf(WorkbenchApiError);
|
|
expect(result.current.error).toMatchObject({ code });
|
|
});
|
|
});
|