90 lines
3.7 KiB
TypeScript
90 lines
3.7 KiB
TypeScript
import { render, screen, waitFor } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { http, HttpResponse, delay } from "msw";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { server } from "../test/msw";
|
|
import { FakeEventSource } from "../test/fakeEventSource";
|
|
import { useSessionStore } from "../store/sessionStore";
|
|
import { AppShell } from "./AppShell";
|
|
|
|
function renderShell() {
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
return render(<QueryClientProvider client={client}><AppShell /></QueryClientProvider>);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
FakeEventSource.instances = [];
|
|
(globalThis as any).EventSource = FakeEventSource;
|
|
useSessionStore.getState().resetSession();
|
|
server.use(
|
|
http.get("http://localhost:8787/sessions", () => HttpResponse.json([])),
|
|
http.get("http://localhost:8787/settings", () =>
|
|
HttpResponse.json({ workspace: "default", provider: "test", model: "test", thinking: "low" })),
|
|
http.get("http://localhost:8787/workspaces", () => HttpResponse.json([])),
|
|
http.get("http://localhost:8787/models", () => HttpResponse.json({ models: [] })),
|
|
);
|
|
});
|
|
|
|
test("New session starts prewarm without delaying composer focus", async () => {
|
|
let prewarmStarted = false;
|
|
server.use(
|
|
http.post("http://localhost:8787/runtime/prewarm", async () => {
|
|
prewarmStarted = true;
|
|
await delay(100);
|
|
return new HttpResponse(null, { status: 202 });
|
|
}),
|
|
);
|
|
renderShell();
|
|
|
|
await userEvent.click(screen.getByRole("button", { name: /^new session$/i }));
|
|
|
|
const composer = screen.getByRole("textbox", { name: /new question/i });
|
|
await waitFor(() => expect(prewarmStarted).toBe(true));
|
|
await waitFor(() => expect(composer).toHaveFocus());
|
|
});
|
|
|
|
test("shows the question and creation timer before POST /sessions resolves, then opens SSE", async () => {
|
|
let releaseCreate!: () => void;
|
|
const createMayFinish = new Promise<void>((resolve) => { releaseCreate = resolve; });
|
|
server.use(
|
|
http.post("http://localhost:8787/sessions", async () => {
|
|
await createMayFinish;
|
|
return HttpResponse.json({ id: "s-new" });
|
|
}),
|
|
);
|
|
renderShell();
|
|
|
|
const composer = screen.getByRole("textbox", { name: /new question/i });
|
|
await userEvent.type(composer, "How many patients?");
|
|
await userEvent.click(screen.getByRole("button", { name: /send/i }));
|
|
|
|
expect(await screen.findByText("How many patients?", { selector: "p" })).toBeInTheDocument();
|
|
expect(screen.getByText("Creating session…")).toBeInTheDocument();
|
|
expect(screen.getByText(/\d+s/)).toBeInTheDocument();
|
|
expect(FakeEventSource.instances).toHaveLength(0);
|
|
|
|
releaseCreate();
|
|
await waitFor(() => expect(FakeEventSource.instances).toHaveLength(1));
|
|
expect(screen.getByText("How many patients?", { selector: "p" })).toBeInTheDocument();
|
|
expect(screen.getByText("Analyzing question…")).toBeInTheDocument();
|
|
expect(useSessionStore.getState().currentPhase).toBe("F1");
|
|
});
|
|
|
|
test("a failed create restores the landing view and preserves the question for retry", async () => {
|
|
server.use(
|
|
http.post("http://localhost:8787/sessions", () =>
|
|
new HttpResponse("unavailable", { status: 503 })),
|
|
);
|
|
renderShell();
|
|
|
|
const composer = screen.getByRole("textbox", { name: /new question/i });
|
|
await userEvent.type(composer, "Keep this question");
|
|
await userEvent.click(screen.getByRole("button", { name: /send/i }));
|
|
|
|
expect(await screen.findByText(/failed to create session/i)).toBeInTheDocument();
|
|
expect(screen.getByText(/type your question/i)).toBeInTheDocument();
|
|
expect(composer).toHaveValue("Keep this question");
|
|
expect(useSessionStore.getState().lastUserEntry).toBeNull();
|
|
expect(FakeEventSource.instances).toHaveLength(0);
|
|
});
|