Files
ThothII/frontend/src/shell/WidgetHost.response.test.tsx
T

129 lines
4.6 KiB
TypeScript

import { act, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { http, HttpResponse } from "msw";
import { server } from "../test/msw";
import { useSessionStore } from "../store/sessionStore";
import { WidgetHost } from "./WidgetHost";
import { setAuthState } from "../auth/authState";
const userA = {
issuer: "local", subject: "user-a", roles: ["user"] as const,
permissions: ["session.use"], isAdmin: false, csrfToken: "a".repeat(43), session: null,
};
beforeEach(() => {
useSessionStore.getState().resetSession();
setAuthState(userA);
});
test("a gate choice shows progress, prevents duplicate clicks, and stays silent on success", async () => {
let release!: () => void;
const held = new Promise<void>((resolve) => { release = resolve; });
let requests = 0;
server.use(http.post("/api/sessions/s1/response", async () => {
requests += 1;
await held;
return new HttpResponse(null, { status: 204 });
}));
useSessionStore.setState({
pendingWidget: {
id: "gate-1",
widget: "select",
options: [{ id: "recommended", label: "Recommended answer" }],
},
});
const user = userEvent.setup();
render(<WidgetHost sessionId="s1" />);
const choice = screen.getByRole("button", { name: "Recommended answer" });
await user.click(choice);
expect(screen.getByRole("status")).toHaveTextContent("Sending response");
expect(choice).toBeDisabled();
await user.click(choice);
expect(requests).toBe(1);
act(() => release());
await waitFor(() => expect(useSessionStore.getState().pendingWidget).toBeNull());
expect(useSessionStore.getState().toasts).toEqual([]);
act(() => useSessionStore.setState({
pendingWidget: {
id: "gate-2",
widget: "select",
options: [{ id: "next", label: "Next answer" }],
},
}));
expect(screen.getByRole("button", { name: "Next answer" })).toBeEnabled();
expect(screen.queryByRole("status")).not.toBeInTheDocument();
});
test("a delayed gate response from user A cannot clear user B's pending gate", async () => {
let release!: () => void;
let started!: () => void;
let settled!: () => void;
const held = new Promise<void>((resolve) => { release = resolve; });
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
server.use(http.post("/api/sessions/s1/response", async () => {
started();
try {
await held;
return new HttpResponse(null, { status: 204 });
} finally {
settled();
}
}));
setAuthState(userA);
useSessionStore.setState({
pendingWidget: { id: "gate-a", widget: "select", options: [{ id: "a", label: "A answer" }] },
});
render(<WidgetHost sessionId="s1" />);
await userEvent.click(screen.getByRole("button", { name: "A answer" }));
await requestStarted;
act(() => setAuthState({ ...userA, subject: "user-b", csrfToken: "b".repeat(43) }));
act(() => useSessionStore.setState({
pendingWidget: { id: "gate-b", widget: "select", options: [{ id: "b", label: "B answer" }] },
}));
release();
await act(async () => { await requestSettled; });
expect(useSessionStore.getState().pendingWidget?.id).toBe("gate-b");
expect(useSessionStore.getState().lastUserEntry).toBeNull();
});
test("a held s1 gate response cannot clear the pending s2 gate", async () => {
let release!: () => void;
let started!: () => void;
let settled!: () => void;
const held = new Promise<void>((resolve) => { release = resolve; });
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
server.use(http.post("/api/sessions/s1/response", async () => {
started();
try {
await held;
return new HttpResponse(null, { status: 204 });
} finally {
settled();
}
}));
useSessionStore.setState({
pendingWidget: { id: "gate-s1", widget: "select", options: [{ id: "s1", label: "Answer s1" }] },
});
const view = render(<WidgetHost sessionId="s1" />);
await userEvent.click(screen.getByRole("button", { name: "Answer s1" }));
await requestStarted;
view.rerender(<WidgetHost sessionId="s2" />);
act(() => useSessionStore.setState({
pendingWidget: { id: "gate-s2", widget: "select", options: [{ id: "s2", label: "Answer s2" }] },
}));
release();
await act(async () => { await requestSettled; });
expect(useSessionStore.getState().pendingWidget?.id).toBe("gate-s2");
expect(useSessionStore.getState().lastUserEntry).toBeNull();
});