import { afterEach, beforeAll, expect, mock, test } from "bun:test";
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import { act, type ComponentProps, type ReactNode } from "react";
import { createRoot, type Root } from "react-dom/client";
mock.module("../../lib/navigation", () => ({
Link: ({
children,
to,
params,
search,
}: {
children: ReactNode;
to: string;
params: { workspaceId: string };
search: { section: string };
}) => (
{children}
),
}));
const { CreditRefusalNotice } = await import("./credit-refusal-notice");
beforeAll(() => {
try {
GlobalRegistrator.register();
} catch {
// Another web test in this process already installed Happy DOM.
}
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
});
let root: Root | undefined;
afterEach(async () => {
await act(async () => root?.unmount());
root = undefined;
document.body.replaceChildren();
});
const SERVER_MESSAGE =
"No credits available for this model. Choose a model covered by your promotional credits or add credits.";
async function render(props: Partial> = {}) {
const container = document.createElement("div");
document.body.append(container);
root = createRoot(container);
await act(async () =>
root!.render(
,
),
);
return container;
}
test("a refused Send names the model, the reason, what the credit covers, and the next steps", async () => {
const onChooseModel = mock((_id: string) => undefined);
const container = await render({ onChooseModel });
const alert = container.querySelector('[role="alert"]');
expect(alert).not.toBeNull();
expect(alert!.textContent).toContain("Your credits don't cover Gemini 4 Argon");
expect(alert!.textContent).toContain(SERVER_MESSAGE);
expect(alert!.textContent).toContain("Your credits cover GPT-6.1 Sol and GPT-6 Luna.");
expect(alert!.textContent).not.toContain("—");
const use = [...container.querySelectorAll("button")].find(
(button) => button.textContent === "Use GPT-6.1 Sol",
);
await act(async () => use!.click());
expect(onChooseModel).toHaveBeenCalledWith("azure-sol/gpt-6.1-sol");
const links = [...container.querySelectorAll("a")].map((link) => link.getAttribute("href"));
expect(links).toEqual(["/workspaces/ws/organization?section=billing"]);
});
test("after switching to a model that can run, the notice says to send again", async () => {
const container = await render({
gap: {
modelLabel: "Gemini 4 Argon",
covered: [{ id: "gpt-6-luna", label: "GPT-6 Luna" }],
selectionUsable: true,
},
onChooseModel: () => undefined,
});
expect(container.textContent).toContain("Send again to use the selected model.");
expect(container.textContent).not.toContain("Use GPT-6 Luna");
});
test("without covered models it offers credits and a connection, and can be dismissed", async () => {
const onDismiss = mock(() => undefined);
const container = await render({
gap: { modelLabel: "Gemini 4 Argon", covered: [], selectionUsable: false },
onDismiss,
});
expect(container.textContent).not.toContain("Your credits cover");
const links = [...container.querySelectorAll("a")].map((link) => link.textContent);
expect(links).toEqual(["Buy credits", "Connect a model"]);
await act(async () =>
container.querySelector('[aria-label="Dismiss"]')!.click(),
);
expect(onDismiss).toHaveBeenCalledTimes(1);
});
test("people who cannot buy credits are not offered a purchase", async () => {
const container = await render({ canBuyCredits: false, canConnectModel: false });
expect(container.querySelectorAll("a")).toHaveLength(0);
expect(container.textContent).toContain(SERVER_MESSAGE);
});
test("the refusal shows the server's sentence, not the SDK's status prefix or reference", async () => {
// OpenGeniApiError.message for a 402 from the API, as the routes pass it.
const container = await render({
message: `API 402: ${SERVER_MESSAGE} Reference: 9f1c2b7e-4d1a-4c55-9a0e-3b8f2d6c1a47.`,
});
const alert = container.querySelector('[role="alert"]')!;
expect(alert.textContent).toContain(SERVER_MESSAGE);
expect(alert.textContent).not.toContain("API 402");
expect(alert.textContent).not.toContain("Reference");
});