test: ≥95% coverage — daemon 96.9% (go), web 95.5-99% (vitest 142 tests); store.ts hooks-order crash fix

This commit is contained in:
Raphael Westphal
2026-08-18 14:56:08 +02:00
parent ecb91e941c
commit 0ecef2a9bd
30 changed files with 6305 additions and 54 deletions
+87
View File
@@ -0,0 +1,87 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";
import { getSettings } from "./settings";
import SettingsGate from "./SettingsGate";
import { jsonResponse } from "./test/setup";
afterEach(() => {
vi.restoreAllMocks();
localStorage.clear();
});
function setup(): { connect: () => Promise<void> } {
render(<SettingsGate onSaved={() => undefined} />);
return {
connect: async (): Promise<void> => {
await userEvent.click(screen.getByRole("button", { name: "Connect" }));
},
};
}
describe("SettingsGate", () => {
it("defaults the server url to the current origin", () => {
setup();
const input = screen.getByLabelText("Server URL") as HTMLInputElement;
expect(input.value).toBe("http://localhost:3000");
});
it("requires a token", async () => {
setup();
await userEvent.click(screen.getByRole("button", { name: "Connect" }));
expect(screen.getByRole("alert")).toHaveTextContent("token required");
expect(getSettings()).toBeNull();
});
it("validation failure shows the server error and does not save", async () => {
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(jsonResponse({ error: "unauthorized" }, 401));
setup();
await userEvent.type(screen.getByLabelText("Bearer token"), "bad");
await userEvent.click(screen.getByRole("button", { name: "Connect" }));
expect(screen.getByRole("alert")).toHaveTextContent("connection failed (401)");
expect(getSettings()).toBeNull();
expect(fetchMock).toHaveBeenCalledWith(
"http://localhost:3000/api/sessions",
expect.objectContaining({ headers: { Authorization: "Bearer bad" } })
);
});
it("network rejection surfaces the thrown message", async () => {
vi.spyOn(globalThis, "fetch").mockRejectedValue(new TypeError("fetch failed"));
setup();
await userEvent.type(screen.getByLabelText("Bearer token"), "tok");
await userEvent.click(screen.getByRole("button", { name: "Connect" }));
expect(screen.getByRole("alert")).toHaveTextContent("fetch failed");
});
it("saves normalized url + trimmed token and calls onSaved", async () => {
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(jsonResponse([]));
const onSaved = vi.fn();
render(<SettingsGate onSaved={onSaved} />);
await userEvent.clear(screen.getByLabelText("Server URL"));
await userEvent.type(screen.getByLabelText("Server URL"), "http://daemon:8686///");
await userEvent.type(screen.getByLabelText("Bearer token"), " tok ");
await userEvent.click(screen.getByRole("button", { name: "Connect" }));
expect(fetchMock).toHaveBeenCalledWith("http://daemon:8686/api/sessions", expect.anything());
expect(getSettings()).toEqual({ serverUrl: "http://daemon:8686", token: "tok" });
expect(onSaved).toHaveBeenCalledTimes(1);
});
it("non-Error rejections stringify via String(err)", async () => {
vi.spyOn(globalThis, "fetch").mockRejectedValue("plain-string" as never);
setup();
await userEvent.type(screen.getByLabelText("Bearer token"), "tok");
await userEvent.click(screen.getByRole("button", { name: "Connect" }));
expect(screen.getByRole("alert")).toHaveTextContent("plain-string");
});
it("empty server url falls back to the current origin", async () => {
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(jsonResponse([]));
setup();
await userEvent.clear(screen.getByLabelText("Server URL"));
await userEvent.type(screen.getByLabelText("Bearer token"), "tok");
await userEvent.click(screen.getByRole("button", { name: "Connect" }));
expect(fetchMock).toHaveBeenCalledWith("http://localhost:3000/api/sessions", expect.anything());
expect(getSettings()?.serverUrl).toBe("http://localhost:3000");
});
});