fix(ui): drop the error toast for a superseded user search

This commit is contained in:
mateo-berri 2026-08-19 15:13:56 -07:00
parent 81c975cff8
commit 103830ee86
2 changed files with 59 additions and 1 deletions

View file

@ -3,6 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { act, renderWithProviders, screen, testQueryClient, waitFor } from "../../../tests/test-utils";
import type { Team } from "../key_team_helpers/key_list";
import { keyCreateCall, keyCreateServiceAccountCall, modelAvailableCall, userFilterUICall } from "../networking";
import { toast } from "@/lib/toast";
import CreateKey from "./create_key_button";
const state = vi.hoisted(() => ({
@ -21,6 +22,16 @@ const state = vi.hoisted(() => ({
projects: [] as { project_id: string; project_alias: string; team_id?: string; models?: string[] }[],
}));
vi.mock("@/lib/toast", () => ({
toast: {
success: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
error: vi.fn(),
fromError: vi.fn(),
dismiss: vi.fn(),
},
}));
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ default: () => state.authorized }));
vi.mock("@/app/(dashboard)/hooks/useCan", () => ({
default: (capability: string) => state.can[capability] ?? true,
@ -225,6 +236,7 @@ describe("CreateKey", () => {
.mockClear()
.mockResolvedValue({ key: "sk-service-account", soft_budget: null });
vi.mocked(userFilterUICall).mockClear().mockResolvedValue([]);
vi.mocked(toast.fromError).mockClear();
vi.mocked(modelAvailableCall)
.mockClear()
.mockResolvedValue({ data: [{ id: "gpt-4" }] });
@ -895,6 +907,52 @@ describe("CreateKey", () => {
});
await screen.findByTitle("alice.smith@example.com (u-smith)");
});
it("only warns about a failed search when it is the one the box is waiting on", async () => {
const answers = new Map<
string,
{ resolve: (users: { user_id: string; user_email: string }[]) => void; reject: (error: Error) => void }
>();
vi.mocked(userFilterUICall).mockImplementation(
(_accessToken, params) =>
new Promise((resolve, reject) => {
answers.set(params.get("user_email") ?? "", { resolve, reject });
}) as never,
);
const user = userEvent.setup();
renderCreateKey({ autoOpenCreate: true, prefillData: { owned_by: "another_user" } });
const search = antdSearchInput(await screen.findByText("Type email to search for users"));
await user.type(search, "ali");
await waitFor(() => expect(answers.has("ali")).toBe(true), { timeout: 3000 });
await user.type(search, "ce.smith@example.com");
await waitFor(() => expect(answers.has("alice.smith@example.com")).toBe(true), { timeout: 3000 });
await act(async () => {
answers
.get("alice.smith@example.com")
?.resolve([{ user_id: "u-smith", user_email: "alice.smith@example.com" }]);
});
await screen.findByTitle("alice.smith@example.com (u-smith)");
await act(async () => {
answers.get("ali")?.reject(new Error("search failed"));
});
expect(toast.fromError).not.toHaveBeenCalled();
expect(screen.getByTitle("alice.smith@example.com (u-smith)")).toBeInTheDocument();
await user.type(search, "x");
await waitFor(() => expect(answers.has("alice.smith@example.comx")).toBe(true), { timeout: 3000 });
await act(async () => {
answers.get("alice.smith@example.comx")?.reject(new Error("search failed"));
});
expect(toast.fromError).toHaveBeenCalledTimes(1);
});
});
describe("created key display", () => {

View file

@ -587,7 +587,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey, autoOp
setUserOptions(options);
} catch (error) {
console.error("Error fetching users:", error);
toast.fromError("Failed to search for users");
if (isLatestSearch()) toast.fromError("Failed to search for users");
} finally {
if (isLatestSearch()) setUserSearchLoading(false);
}