98 lines
3.9 KiB
TypeScript
98 lines
3.9 KiB
TypeScript
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const toast = vi.hoisted(() => ({ success: vi.fn(), error: vi.fn(), info: vi.fn() }));
|
|
vi.mock("sonner", () => ({ toast }));
|
|
vi.mock("@/lib/pocketbase", () => ({
|
|
default: () => ({ authStore: { token: "tok" } }),
|
|
}));
|
|
|
|
import { AiFillQuoteButton } from "./ai-fill-quote-button";
|
|
|
|
let fetchMock: ReturnType<typeof vi.fn>;
|
|
|
|
beforeEach(() => {
|
|
fetchMock = vi.fn();
|
|
vi.stubGlobal("fetch", fetchMock);
|
|
toast.success.mockReset();
|
|
toast.error.mockReset();
|
|
toast.info.mockReset();
|
|
});
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
const json = (status: number, body: unknown) =>
|
|
Promise.resolve(new Response(JSON.stringify(body), { status }));
|
|
|
|
describe("AiFillQuoteButton", () => {
|
|
it("POSTs the run route with the bearer token and reports the result", async () => {
|
|
fetchMock.mockReturnValue(
|
|
json(200, { quoteId: "q1", filled: 3, unmatched: 2, skipped: 0, error: null }),
|
|
);
|
|
const onDone = vi.fn();
|
|
render(<AiFillQuoteButton quoteId="q1" onDone={onDone} />);
|
|
fireEvent.click(screen.getByRole("button", { name: /let ai fill this quote/i }));
|
|
|
|
await waitFor(() => expect(onDone).toHaveBeenCalledTimes(1));
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe("/api/v1/quotes/q1/assistant/run");
|
|
expect(init.method).toBe("POST");
|
|
expect(init.headers.Authorization).toBe("Bearer tok");
|
|
expect(toast.success).toHaveBeenCalledWith("AI filled 3 lines · 2 without a match");
|
|
});
|
|
|
|
it("uses the singular for one line and omits the unmatched suffix", async () => {
|
|
fetchMock.mockReturnValue(json(200, { filled: 1, unmatched: 0 }));
|
|
render(<AiFillQuoteButton quoteId="q1" onDone={() => {}} />);
|
|
fireEvent.click(screen.getByRole("button"));
|
|
await waitFor(() => expect(toast.success).toHaveBeenCalledWith("AI filled 1 line"));
|
|
});
|
|
|
|
it("shows an info toast when there was nothing to fill", async () => {
|
|
fetchMock.mockReturnValue(json(200, { filled: 0, unmatched: 0 }));
|
|
const onDone = vi.fn();
|
|
render(<AiFillQuoteButton quoteId="q1" onDone={onDone} />);
|
|
fireEvent.click(screen.getByRole("button"));
|
|
await waitFor(() => expect(toast.info).toHaveBeenCalledWith("AI had no open lines to fill"));
|
|
expect(toast.success).not.toHaveBeenCalled();
|
|
expect(onDone).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("toasts the error on a 200 that carries an error, and still refreshes", async () => {
|
|
fetchMock.mockReturnValue(json(200, { filled: 0, unmatched: 0, error: "AI is switched off" }));
|
|
const onDone = vi.fn();
|
|
render(<AiFillQuoteButton quoteId="q1" onDone={onDone} />);
|
|
fireEvent.click(screen.getByRole("button"));
|
|
await waitFor(() => expect(toast.error).toHaveBeenCalledWith("AI is switched off"));
|
|
expect(toast.success).not.toHaveBeenCalled();
|
|
expect(onDone).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("toasts the error text on a 409", async () => {
|
|
fetchMock.mockReturnValue(json(409, { error: "AI is already working on this quote" }));
|
|
const onDone = vi.fn();
|
|
render(<AiFillQuoteButton quoteId="q1" onDone={onDone} />);
|
|
fireEvent.click(screen.getByRole("button"));
|
|
|
|
await waitFor(() =>
|
|
expect(toast.error).toHaveBeenCalledWith("AI is already working on this quote"),
|
|
);
|
|
expect(onDone).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("is disabled while the request runs", async () => {
|
|
let resolve!: (r: Response) => void;
|
|
fetchMock.mockReturnValue(new Promise<Response>((r) => (resolve = r)));
|
|
render(<AiFillQuoteButton quoteId="q1" onDone={() => {}} />);
|
|
const btn = screen.getByRole("button");
|
|
fireEvent.click(btn);
|
|
await waitFor(() => expect(btn).toBeDisabled());
|
|
fireEvent.click(btn);
|
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
|
|
resolve(new Response(JSON.stringify({ filled: 0, unmatched: 0 }), { status: 200 }));
|
|
await waitFor(() => expect(btn).not.toBeDisabled());
|
|
});
|
|
});
|