95 lines
3.3 KiB
TypeScript
95 lines
3.3 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();
|
|
});
|