[Test] UI: Add vitest coverage for 10 previously untested components

Add unit tests for:
- SimpleToolCallBlock, SimpleMessageBlock, CollapsibleMessage, HistoryTree (log details drawer)
- OnboardingForm (onboarding flow)
- TeamsHeaderTabs, TeamsTable (teams page)
- transform_key_info, filter_helpers (key/team helpers)
- queryKeysFactory (query key generation utility)

47 new tests covering conditional rendering, user interactions, data transformation, and error handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yuneng-jiang
2026-03-19 10:30:03 -07:00
co-authored by Claude Opus 4.6
parent e5baa2232f
commit 0b07f628ff
10 changed files with 674 additions and 0 deletions
@@ -0,0 +1,34 @@
import { describe, it, expect } from "vitest";
import { createQueryKeys } from "./queryKeysFactory";
describe("createQueryKeys", () => {
const keys = createQueryKeys("books");
it("should return the resource name as the base key", () => {
expect(keys.all).toEqual(["books"]);
});
it("should generate a lists key", () => {
expect(keys.lists()).toEqual(["books", "list"]);
});
it("should generate a list key with params", () => {
expect(keys.list({ page: 1, limit: 10 })).toEqual([
"books",
"list",
{ params: { page: 1, limit: 10 } },
]);
});
it("should generate a list key with undefined params when none provided", () => {
expect(keys.list()).toEqual(["books", "list", { params: undefined }]);
});
it("should generate a details key", () => {
expect(keys.details()).toEqual(["books", "detail"]);
});
it("should generate a detail key for a specific ID", () => {
expect(keys.detail("123")).toEqual(["books", "detail", "123"]);
});
});
@@ -0,0 +1,54 @@
import { render, screen } from "@testing-library/react";
import React from "react";
import { describe, expect, it, vi } from "vitest";
import TeamsHeaderTabs from "./TeamsHeaderTabs";
vi.mock("@tremor/react", () => ({
TabGroup: ({ children, ...props }: any) => <div data-testid="tab-group" {...props}>{children}</div>,
TabList: ({ children, ...props }: any) => <div data-testid="tab-list" {...props}>{children}</div>,
Tab: ({ children, ...props }: any) => <button {...props}>{children}</button>,
TabPanels: ({ children, ...props }: any) => <div data-testid="tab-panels" {...props}>{children}</div>,
Text: ({ children, ...props }: any) => <span {...props}>{children}</span>,
Icon: ({ onClick, ...props }: any) => <button data-testid="refresh-icon" onClick={onClick} />,
}));
vi.mock("@heroicons/react/outline", () => ({
RefreshIcon: () => <svg data-testid="refresh-svg" />,
}));
const renderTabs = (props: Partial<Parameters<typeof TeamsHeaderTabs>[0]> = {}) => {
const defaults = {
lastRefreshed: "",
onRefresh: vi.fn(),
userRole: "Internal User",
children: <div data-testid="panel-content">Panel</div>,
};
return render(<TeamsHeaderTabs {...defaults} {...props} />);
};
describe("TeamsHeaderTabs", () => {
it("should render 'Your Teams' and 'Available Teams' tabs", () => {
renderTabs();
expect(screen.getByText("Your Teams")).toBeInTheDocument();
expect(screen.getByText("Available Teams")).toBeInTheDocument();
});
it("should render 'Default Team Settings' tab when user is Admin", () => {
renderTabs({ userRole: "Admin" });
expect(screen.getByText("Default Team Settings")).toBeInTheDocument();
});
it("should not render 'Default Team Settings' tab for non-admin users", () => {
renderTabs({ userRole: "Internal User" });
expect(screen.queryByText("Default Team Settings")).not.toBeInTheDocument();
});
it("should display last refreshed time when provided", () => {
renderTabs({ lastRefreshed: "2024-06-01 12:00:00" });
expect(screen.getByText("Last Refreshed: 2024-06-01 12:00:00")).toBeInTheDocument();
});
});
@@ -0,0 +1,129 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { describe, expect, it, vi } from "vitest";
import { Team } from "@/components/key_team_helpers/key_list";
import TeamsTable from "./TeamsTable";
vi.mock("@tremor/react", () => ({
Button: React.forwardRef<HTMLButtonElement, any>(({ children, ...props }, ref) =>
React.createElement("button", { ...props, ref }, children),
),
Icon: ({ onClick, ...props }: any) => <button data-testid={props["data-testid"] || "icon-btn"} onClick={onClick} aria-label={props["aria-label"]} />,
Table: ({ children }: any) => <table>{children}</table>,
TableHead: ({ children }: any) => <thead>{children}</thead>,
TableBody: ({ children }: any) => <tbody>{children}</tbody>,
TableRow: ({ children }: any) => <tr>{children}</tr>,
TableHeaderCell: ({ children }: any) => <th>{children}</th>,
TableCell: ({ children, ...props }: any) => <td {...props}>{children}</td>,
Text: ({ children }: any) => <span>{children}</span>,
}));
vi.mock("antd", () => ({
Tooltip: ({ children }: any) => <>{children}</>,
}));
vi.mock("@heroicons/react/outline", () => ({
PencilAltIcon: () => <svg data-testid="pencil-icon" />,
TrashIcon: () => <svg data-testid="trash-icon" />,
}));
vi.mock("@/utils/dataUtils", () => ({
formatNumberWithCommas: (val: number, decimals: number) =>
val != null ? val.toFixed(decimals) : "N/A",
}));
vi.mock("@/app/(dashboard)/teams/components/TeamsTable/ModelsCell", () => ({
default: ({ team }: any) => <td data-testid="models-cell">{team.models.join(",")}</td>,
}));
vi.mock("@/app/(dashboard)/teams/components/TeamsTable/YourRoleCell/YourRoleCell", () => ({
default: ({ team }: any) => <td data-testid="role-cell">{team.team_id}</td>,
}));
const makeTeam = (overrides: Partial<Team> = {}): Team => ({
team_id: "team-abc1234",
team_alias: "Platform",
models: ["gpt-4"],
max_budget: 500,
budget_duration: null,
tpm_limit: null,
rpm_limit: null,
organization_id: "org-1",
created_at: "2024-06-01T00:00:00Z",
keys: [],
members_with_roles: [],
spend: 123.4567,
...overrides,
});
const defaultPerTeamInfo = {
"team-abc1234": {
keys: [{ token: "tok-1" } as any, { token: "tok-2" } as any],
team_info: {
members_with_roles: [{ user_id: "u1", role: "admin" } as any],
},
},
};
const renderTable = (overrides: Partial<Parameters<typeof TeamsTable>[0]> = {}) => {
const defaults = {
teams: [makeTeam()],
currentOrg: null,
perTeamInfo: defaultPerTeamInfo,
userRole: "Admin",
userId: "user-1",
setSelectedTeamId: vi.fn(),
setEditTeam: vi.fn(),
onDeleteTeam: vi.fn(),
};
return render(<TeamsTable {...defaults} {...overrides} />);
};
describe("TeamsTable", () => {
it("should render table headers", () => {
renderTable();
expect(screen.getByText("Team Name")).toBeInTheDocument();
expect(screen.getByText("Team ID")).toBeInTheDocument();
expect(screen.getByText("Created")).toBeInTheDocument();
expect(screen.getByText("Spend (USD)")).toBeInTheDocument();
expect(screen.getByText("Budget (USD)")).toBeInTheDocument();
expect(screen.getByText("Models")).toBeInTheDocument();
expect(screen.getByText("Organization")).toBeInTheDocument();
expect(screen.getByText("Your Role")).toBeInTheDocument();
expect(screen.getByText("Info")).toBeInTheDocument();
});
it("should render team rows with team data", () => {
renderTable();
expect(screen.getByText("Platform")).toBeInTheDocument();
expect(screen.getByText("team-ab...")).toBeInTheDocument();
expect(screen.getByText("org-1")).toBeInTheDocument();
});
it("should show edit and delete icons for Admin users", () => {
renderTable({ userRole: "Admin" });
expect(screen.getAllByTestId("icon-btn").length).toBeGreaterThanOrEqual(2);
});
it("should not show edit and delete icons for non-Admin users", () => {
renderTable({ userRole: "Internal User" });
// Only the team ID button should be present, no icon-btn for edit/delete
const iconBtns = screen.queryAllByTestId("icon-btn");
expect(iconBtns).toHaveLength(0);
});
it("should call setSelectedTeamId when team ID button is clicked", async () => {
const user = userEvent.setup();
const setSelectedTeamId = vi.fn();
renderTable({ setSelectedTeamId });
await user.click(screen.getByText("team-ab..."));
expect(setSelectedTeamId).toHaveBeenCalledWith("team-abc1234");
});
});
@@ -0,0 +1,95 @@
import { render, screen } from "@testing-library/react";
import React from "react";
import { describe, expect, it, vi } from "vitest";
import { OnboardingForm } from "./OnboardingForm";
const mockUseOnboardingCredentials = vi.fn();
const mockClaimToken = vi.fn();
vi.mock("next/navigation", () => ({
useSearchParams: () => new URLSearchParams("invitation_id=inv-123"),
}));
vi.mock("jwt-decode", () => ({
jwtDecode: vi.fn(() => ({
user_email: "alice@example.com",
user_id: "user-1",
key: "access-tok",
})),
}));
vi.mock("@/app/(dashboard)/hooks/onboarding/useOnboarding", () => ({
useOnboardingCredentials: (...args: unknown[]) => mockUseOnboardingCredentials(...args),
useClaimOnboardingToken: () => ({ mutate: mockClaimToken, isPending: false }),
}));
vi.mock("@/components/networking", () => ({
getProxyBaseUrl: vi.fn(() => ""),
}));
vi.mock("./OnboardingLoadingView", () => ({
OnboardingLoadingView: () => <div data-testid="loading-view">Loading</div>,
}));
vi.mock("./OnboardingErrorView", () => ({
OnboardingErrorView: () => <div data-testid="error-view">Error</div>,
}));
vi.mock("./OnboardingFormBody", () => ({
OnboardingFormBody: ({ variant, userEmail }: { variant: string; userEmail: string }) => (
<div data-testid="form-body" data-variant={variant} data-email={userEmail}>
Form Body
</div>
),
}));
describe("OnboardingForm", () => {
it("should render loading view when credentials are loading", () => {
mockUseOnboardingCredentials.mockReturnValue({
data: undefined,
isLoading: true,
isError: false,
});
render(<OnboardingForm variant="signup" />);
expect(screen.getByTestId("loading-view")).toBeInTheDocument();
});
it("should render error view when credentials fail to load", () => {
mockUseOnboardingCredentials.mockReturnValue({
data: undefined,
isLoading: false,
isError: true,
});
render(<OnboardingForm variant="signup" />);
expect(screen.getByTestId("error-view")).toBeInTheDocument();
});
it("should render form body with decoded email when credentials are loaded", () => {
mockUseOnboardingCredentials.mockReturnValue({
data: { token: "fake-jwt-token" },
isLoading: false,
isError: false,
});
render(<OnboardingForm variant="signup" />);
expect(screen.getByTestId("form-body")).toBeInTheDocument();
expect(screen.getByTestId("form-body")).toHaveAttribute("data-email", "alice@example.com");
});
it("should pass variant prop to OnboardingFormBody", () => {
mockUseOnboardingCredentials.mockReturnValue({
data: { token: "fake-jwt-token" },
isLoading: false,
isError: false,
});
render(<OnboardingForm variant="reset_password" />);
expect(screen.getByTestId("form-body")).toHaveAttribute("data-variant", "reset_password");
});
});
@@ -0,0 +1,90 @@
import { describe, expect, it, vi } from "vitest";
import { fetchTeamFilterOptions } from "./filter_helpers";
const mockKeyListCall = vi.fn();
vi.mock("@/components/networking", () => ({
keyListCall: (...args: unknown[]) => mockKeyListCall(...args),
teamListCall: vi.fn(),
organizationListCall: vi.fn(),
}));
describe("fetchTeamFilterOptions", () => {
it("should return empty arrays when accessToken is null", async () => {
const result = await fetchTeamFilterOptions(null, "team-1");
expect(result).toEqual({ keyAliases: [], organizationIds: [], userIds: [] });
expect(mockKeyListCall).not.toHaveBeenCalled();
});
it("should return empty arrays when teamId is empty", async () => {
const result = await fetchTeamFilterOptions("tok-123", "");
expect(result).toEqual({ keyAliases: [], organizationIds: [], userIds: [] });
expect(mockKeyListCall).not.toHaveBeenCalled();
});
it("should return sorted key aliases from fetched keys", async () => {
mockKeyListCall.mockResolvedValue({
keys: [
{ key_alias: "zeta-key" },
{ key_alias: "alpha-key" },
{ key_alias: "mid-key" },
],
total_pages: 1,
});
const result = await fetchTeamFilterOptions("tok-123", "team-1");
expect(result.keyAliases).toEqual(["alpha-key", "mid-key", "zeta-key"]);
});
it("should deduplicate organization IDs across pages", async () => {
mockKeyListCall
.mockResolvedValueOnce({
keys: [
{ organization_id: "org-b" },
{ organization_id: "org-a" },
],
total_pages: 2,
})
.mockResolvedValueOnce({
keys: [
{ organization_id: "org-a" },
{ organization_id: "org-c" },
],
total_pages: 2,
});
const result = await fetchTeamFilterOptions("tok-123", "team-1");
expect(result.organizationIds).toEqual(["org-a", "org-b", "org-c"]);
});
it("should map user IDs with email addresses", async () => {
mockKeyListCall.mockResolvedValue({
keys: [
{ user_id: "u1", user: { user_email: "alice@example.com" } },
{ user_id: "u2", user: { user_email: "bob@example.com" } },
],
total_pages: 1,
});
const result = await fetchTeamFilterOptions("tok-123", "team-1");
expect(result.userIds).toEqual(
expect.arrayContaining([
{ id: "u1", email: "alice@example.com" },
{ id: "u2", email: "bob@example.com" },
]),
);
});
it("should handle API errors gracefully and return empty arrays", async () => {
mockKeyListCall.mockRejectedValue(new Error("Network error"));
const result = await fetchTeamFilterOptions("tok-123", "team-1");
expect(result).toEqual({ keyAliases: [], organizationIds: [], userIds: [] });
});
});
@@ -0,0 +1,62 @@
import { describe, it, expect } from "vitest";
import { transformKeyInfo } from "./transform_key_info";
describe("transformKeyInfo", () => {
it("should combine key and info fields into a single object", () => {
const apiResponse = {
key: "sk-abc123",
info: {
token_id: "tok_1",
key_name: "my-key",
spend: 10.5,
},
};
const result = transformKeyInfo(apiResponse);
expect(result).toEqual({
token: "sk-abc123",
token_id: "tok_1",
key_name: "my-key",
spend: 10.5,
});
});
it("should set the token field from the key property", () => {
const apiResponse = {
key: "sk-xyz789",
info: { key_name: "test" },
};
const result = transformKeyInfo(apiResponse);
expect(result.token).toBe("sk-xyz789");
});
it("should preserve all info fields in the result", () => {
const apiResponse = {
key: "sk-abc",
info: {
token_id: "tok_2",
key_name: "prod-key",
spend: 42,
models: ["gpt-4"],
team_id: "team-1",
metadata: { env: "production" },
},
};
const result = transformKeyInfo(apiResponse);
expect(result.token_id).toBe("tok_2");
expect(result.key_name).toBe("prod-key");
expect(result.spend).toBe(42);
expect(result.models).toEqual(["gpt-4"]);
expect(result.team_id).toBe("team-1");
expect(result.metadata).toEqual({ env: "production" });
});
it("should handle empty info object", () => {
const apiResponse = {
key: "sk-empty",
info: {},
};
const result = transformKeyInfo(apiResponse);
expect(result.token).toBe("sk-empty");
expect(Object.keys(result)).toContain("token");
});
});
@@ -0,0 +1,54 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect } from "vitest";
import { CollapsibleMessage } from "./CollapsibleMessage";
describe("CollapsibleMessage", () => {
it("should return null when content is empty", () => {
const { container } = render(
<CollapsibleMessage label="SYSTEM" content="" />
);
expect(container.innerHTML).toBe("");
});
it("should return null when content is undefined", () => {
const { container } = render(<CollapsibleMessage label="SYSTEM" />);
expect(container.innerHTML).toBe("");
});
it("should render the label and char count", () => {
render(<CollapsibleMessage label="SYSTEM" content="Hello" />);
expect(screen.getByText("SYSTEM")).toBeInTheDocument();
expect(screen.getByText("(5 chars)")).toBeInTheDocument();
});
it("should show content when defaultExpanded is true", () => {
render(
<CollapsibleMessage
label="SYSTEM"
content="Visible text"
defaultExpanded={true}
/>
);
expect(screen.getByText("Visible text")).toBeInTheDocument();
});
it("should toggle expanded state when header is clicked", async () => {
const user = userEvent.setup();
render(
<CollapsibleMessage
label="SYSTEM"
content="Toggle me"
defaultExpanded={false}
/>
);
// Content is rendered in DOM but collapsed by default
expect(screen.getByText("Toggle me")).toBeInTheDocument();
// Click the header to expand - should still show content
await user.click(screen.getByText("SYSTEM"));
expect(screen.getByText("Toggle me")).toBeInTheDocument();
});
});
@@ -0,0 +1,50 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect } from "vitest";
import { HistoryTree } from "./HistoryTree";
import { ParsedMessage } from "./prettyMessagesTypes";
describe("HistoryTree", () => {
it("should return null when messages array is empty", () => {
const { container } = render(<HistoryTree messages={[]} />);
expect(container.innerHTML).toBe("");
});
it('should render message count with plural "messages" for multiple messages', () => {
const messages: ParsedMessage[] = [
{ role: "user", content: "Hello" },
{ role: "assistant", content: "Hi there" },
{ role: "user", content: "How are you?" },
];
render(<HistoryTree messages={messages} />);
expect(
screen.getByText("HISTORY (3 messages)")
).toBeInTheDocument();
});
it('should render message count with singular "message" for one message', () => {
const messages: ParsedMessage[] = [
{ role: "user", content: "Hello" },
];
render(<HistoryTree messages={messages} />);
expect(
screen.getByText("HISTORY (1 message)")
).toBeInTheDocument();
});
it("should expand and show messages when header is clicked", async () => {
const user = userEvent.setup();
const messages: ParsedMessage[] = [
{ role: "user", content: "Hello" },
{ role: "assistant", content: "Hi there" },
];
render(<HistoryTree messages={messages} />);
// Click to expand
await user.click(screen.getByText("HISTORY (2 messages)"));
expect(screen.getByText("Hello")).toBeInTheDocument();
expect(screen.getByText("Hi there")).toBeInTheDocument();
});
});
@@ -0,0 +1,55 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { SimpleMessageBlock } from "./SimpleMessageBlock";
describe("SimpleMessageBlock", () => {
it("should render the label and content", () => {
render(<SimpleMessageBlock label="USER" content="Hello world" />);
expect(screen.getByText("USER")).toBeInTheDocument();
expect(screen.getByText("Hello world")).toBeInTheDocument();
});
it("should return null when content is empty and no tool calls", () => {
const { container } = render(
<SimpleMessageBlock label="USER" content="" />
);
expect(container.innerHTML).toBe("");
});
it('should return null when content is "null" string and no tool calls', () => {
const { container } = render(
<SimpleMessageBlock label="USER" content="null" />
);
expect(container.innerHTML).toBe("");
});
it("should render tool calls when present", () => {
render(
<SimpleMessageBlock
label="ASSISTANT"
toolCalls={[
{ id: "tc1", name: "get_weather", arguments: { city: "Paris" } },
]}
/>
);
expect(screen.getByText("ASSISTANT")).toBeInTheDocument();
expect(screen.getByText("get_weather")).toBeInTheDocument();
});
it("should render content and tool calls together", () => {
render(
<SimpleMessageBlock
label="ASSISTANT"
content="Let me check the weather."
toolCalls={[
{ id: "tc1", name: "get_weather", arguments: { city: "Paris" } },
]}
/>
);
expect(
screen.getByText("Let me check the weather.")
).toBeInTheDocument();
expect(screen.getByText("get_weather")).toBeInTheDocument();
});
});
@@ -0,0 +1,51 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { SimpleToolCallBlock } from "./SimpleToolCallBlock";
describe("SimpleToolCallBlock", () => {
it("should render the tool name", () => {
render(
<SimpleToolCallBlock
tool={{ id: "1", name: "get_weather", arguments: {} }}
/>
);
expect(screen.getByText("get_weather")).toBeInTheDocument();
});
it('should display "function" badge', () => {
render(
<SimpleToolCallBlock
tool={{ id: "1", name: "get_weather", arguments: {} }}
/>
);
expect(screen.getByText("function")).toBeInTheDocument();
});
it("should render arguments when present", () => {
render(
<SimpleToolCallBlock
tool={{
id: "1",
name: "get_weather",
arguments: { city: "London", units: "metric" },
}}
/>
);
expect(screen.getByText("city:")).toBeInTheDocument();
expect(screen.getByText('"London"')).toBeInTheDocument();
expect(screen.getByText("units:")).toBeInTheDocument();
expect(screen.getByText('"metric"')).toBeInTheDocument();
});
it("should not render arguments section when arguments are empty", () => {
const { container } = render(
<SimpleToolCallBlock
tool={{ id: "1", name: "get_weather", arguments: {} }}
/>
);
// The tool name and "function" badge should be there, but no key: value pairs
expect(screen.getByText("get_weather")).toBeInTheDocument();
expect(screen.queryByText(/:$/)).not.toBeInTheDocument();
});
});