Files
ThothII/frontend/src/shell/AppShell.new-session.test.tsx
T

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