[Test] UI - Add vitest unit tests for Teams, Models, and Usage components

Add v8-coverage-targeted unit tests for 5 previously untested files across
the Teams, Models + Endpoints, and Usage workflows:

- value_formatters.test.ts: full branch coverage of valueFormatter and
  valueFormatterSpend (M/k/plain formatting, zero-value edge cases)
- DeleteTeamModal.test.tsx: confirmation input validation, key-count warning
  singular/plural, onConfirm/onCancel callbacks, input reset on cancel
- TeamsFilters.test.tsx: search input binding, Filters toggle, Reset callback,
  additional-filters visibility, active-filter dot indicator
- ModelsCell.test.tsx: empty/single/overflow model rendering, truncation at
  30 chars, accordion expand/collapse, all-proxy-models badge in overflow
- ModelRetrySettingsTab.test.tsx: global vs model-scope headings, defaultRetry
  fallback chain, setGlobalRetryPolicy/setModelGroupRetryPolicy updater
  functions, Save button callback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
yuneng-jiang
2026-02-20 10:43:16 -08:00
co-authored by Claude Sonnet 4.6
parent 8356dfa045
commit 1c1c84974f
5 changed files with 737 additions and 0 deletions
@@ -0,0 +1,217 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { describe, beforeEach, expect, it, vi } from "vitest";
import ModelRetrySettingsTab from "./ModelRetrySettingsTab";
// TabPanel requires a parent Tabs context in Tremor. We stub it to render children
// directly so the component can be tested in isolation.
vi.mock("@tremor/react", async (importOriginal) => {
const actual = await importOriginal<typeof import("@tremor/react")>();
return {
...actual,
TabPanel: ({ children }: { children: React.ReactNode }) => React.createElement("div", null, children),
// Keep Select/SelectItem as the real implementation so scope-switching is testable
};
});
type GlobalRetryPolicy = { [key: string]: number };
type ModelGroupRetryPolicy = { [key: string]: { [key: string]: number } | undefined };
const DEFAULT_RETRY = 0;
const buildProps = (overrides: Record<string, unknown> = {}) => ({
selectedModelGroup: "global" as string | null,
setSelectedModelGroup: vi.fn(),
availableModelGroups: ["gpt-4", "claude-3-opus"],
globalRetryPolicy: null as GlobalRetryPolicy | null,
setGlobalRetryPolicy: vi.fn(),
defaultRetry: DEFAULT_RETRY,
modelGroupRetryPolicy: null as ModelGroupRetryPolicy | null,
setModelGroupRetryPolicy: vi.fn(),
handleSaveRetrySettings: vi.fn(),
...overrides,
});
describe("ModelRetrySettingsTab", () => {
it("renders the 'Global Retry Policy' heading when selectedModelGroup is 'global'", () => {
render(<ModelRetrySettingsTab {...buildProps()} />);
expect(screen.getByText("Global Retry Policy")).toBeInTheDocument();
});
it("renders a model-specific heading when a model group is selected", () => {
render(<ModelRetrySettingsTab {...buildProps({ selectedModelGroup: "gpt-4" })} />);
expect(screen.getByText("Retry Policy for gpt-4")).toBeInTheDocument();
});
it("renders a row for every error type in the retry policy map", () => {
render(<ModelRetrySettingsTab {...buildProps()} />);
expect(screen.getByText(/BadRequestError \(400\)/)).toBeInTheDocument();
expect(screen.getByText(/AuthenticationError/)).toBeInTheDocument();
expect(screen.getByText(/TimeoutError \(408\)/)).toBeInTheDocument();
expect(screen.getByText(/RateLimitError \(429\)/)).toBeInTheDocument();
expect(screen.getByText(/ContentPolicyViolationError \(400\)/)).toBeInTheDocument();
expect(screen.getByText(/InternalServerError \(500\)/)).toBeInTheDocument();
});
it("uses defaultRetry when globalRetryPolicy is null (global scope)", () => {
render(<ModelRetrySettingsTab {...buildProps({ defaultRetry: 3 })} />);
// All 6 spinbutton inputs should show the defaultRetry value
const inputs = screen.getAllByRole("spinbutton");
inputs.forEach((input) => {
expect(input).toHaveValue("3");
});
});
it("shows globalRetryPolicy values when they are set (global scope)", () => {
const globalRetryPolicy: GlobalRetryPolicy = {
RateLimitErrorRetries: 5,
};
render(<ModelRetrySettingsTab {...buildProps({ globalRetryPolicy, defaultRetry: 0 })} />);
// The RateLimitError row is the 4th entry in the map
const inputs = screen.getAllByRole("spinbutton");
const rateLimitInput = inputs[3]; // 0-indexed: Bad(0), Auth(1), Timeout(2), Rate(3)
expect(rateLimitInput).toHaveValue("5");
// Unset entries fall back to defaultRetry (0)
expect(inputs[0]).toHaveValue("0");
});
it("falls back to globalRetryPolicy when no model-specific value is set (model scope)", () => {
const globalRetryPolicy: GlobalRetryPolicy = {
TimeoutErrorRetries: 7,
};
render(
<ModelRetrySettingsTab
{...buildProps({
selectedModelGroup: "gpt-4",
globalRetryPolicy,
modelGroupRetryPolicy: null,
defaultRetry: 1,
})}
/>,
);
// The TimeoutError row is 3rd (index 2)
const inputs = screen.getAllByRole("spinbutton");
expect(inputs[2]).toHaveValue("7");
// Rows without a global value fall back to defaultRetry
expect(inputs[0]).toHaveValue("1");
});
it("prefers model-specific retry count over the global value (model scope)", () => {
const globalRetryPolicy: GlobalRetryPolicy = {
RateLimitErrorRetries: 3,
};
const modelGroupRetryPolicy: ModelGroupRetryPolicy = {
"gpt-4": { RateLimitErrorRetries: 9 },
};
render(
<ModelRetrySettingsTab
{...buildProps({
selectedModelGroup: "gpt-4",
globalRetryPolicy,
modelGroupRetryPolicy,
defaultRetry: 0,
})}
/>,
);
// The model-specific value (9) should win over global (3)
const inputs = screen.getAllByRole("spinbutton");
expect(inputs[3]).toHaveValue("9");
});
it("shows the global reference value text for each row in model-specific scope", () => {
const globalRetryPolicy: GlobalRetryPolicy = { BadRequestErrorRetries: 2 };
render(
<ModelRetrySettingsTab
{...buildProps({
selectedModelGroup: "gpt-4",
globalRetryPolicy,
defaultRetry: 0,
})}
/>,
);
// "(Global: X)" annotations are shown next to each row label in model scope
expect(screen.getByText("(Global: 2)")).toBeInTheDocument();
});
it("does not show global reference annotations in global scope", () => {
render(<ModelRetrySettingsTab {...buildProps({ selectedModelGroup: "global" })} />);
expect(screen.queryByText(/Global:/)).not.toBeInTheDocument();
});
it("calls handleSaveRetrySettings when the Save button is clicked", async () => {
const user = userEvent.setup();
const handleSaveRetrySettings = vi.fn();
render(<ModelRetrySettingsTab {...buildProps({ handleSaveRetrySettings })} />);
await user.click(screen.getByRole("button", { name: /save/i }));
expect(handleSaveRetrySettings).toHaveBeenCalledTimes(1);
});
it("calls setGlobalRetryPolicy with an updater function when an input changes (global scope)", async () => {
const user = userEvent.setup();
const setGlobalRetryPolicy = vi.fn();
render(
<ModelRetrySettingsTab
{...buildProps({
selectedModelGroup: "global",
globalRetryPolicy: { BadRequestErrorRetries: 0 },
setGlobalRetryPolicy,
defaultRetry: 0,
})}
/>,
);
const inputs = screen.getAllByRole("spinbutton");
await user.clear(inputs[0]);
await user.type(inputs[0], "4");
// setGlobalRetryPolicy is called with a function updater
expect(setGlobalRetryPolicy).toHaveBeenCalled();
const updater = setGlobalRetryPolicy.mock.calls.at(-1)![0];
expect(typeof updater).toBe("function");
// Calling the updater returns the merged policy
const result = updater({ BadRequestErrorRetries: 0 });
expect(result).toMatchObject({ BadRequestErrorRetries: 4 });
});
it("calls setModelGroupRetryPolicy with an updater function when an input changes (model scope)", async () => {
const user = userEvent.setup();
const setModelGroupRetryPolicy = vi.fn();
render(
<ModelRetrySettingsTab
{...buildProps({
selectedModelGroup: "gpt-4",
modelGroupRetryPolicy: { "gpt-4": { BadRequestErrorRetries: 0 } },
setModelGroupRetryPolicy,
defaultRetry: 0,
})}
/>,
);
const inputs = screen.getAllByRole("spinbutton");
await user.clear(inputs[0]);
await user.type(inputs[0], "2");
expect(setModelGroupRetryPolicy).toHaveBeenCalled();
const updater = setModelGroupRetryPolicy.mock.calls.at(-1)![0];
expect(typeof updater).toBe("function");
// Calling the updater returns the merged model-group policy
const result = updater({ "gpt-4": { BadRequestErrorRetries: 0 } });
expect(result["gpt-4"]).toMatchObject({ BadRequestErrorRetries: 2 });
});
});
@@ -0,0 +1,151 @@
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 { Organization } from "@/components/networking";
import TeamsFilters from "./TeamsFilters";
type FilterState = {
team_id: string;
team_alias: string;
organization_id: string;
sort_by: string;
sort_order: "asc" | "desc";
};
const emptyFilters: FilterState = {
team_alias: "",
team_id: "",
organization_id: "",
sort_by: "",
sort_order: "asc",
};
const mockOrganizations: Organization[] = [
{ organization_id: "org-1", organization_alias: "Acme Corp" } as Organization,
{ organization_id: "org-2", organization_alias: "Globex" } as Organization,
];
const renderFilters = (overrides: Partial<Parameters<typeof TeamsFilters>[0]> = {}) => {
const defaults = {
filters: emptyFilters,
organizations: mockOrganizations,
showFilters: false,
onToggleFilters: vi.fn(),
onChange: vi.fn(),
onReset: vi.fn(),
};
return render(<TeamsFilters {...defaults} {...overrides} />);
};
describe("TeamsFilters", () => {
it("renders the team name search input, Filters button, and Reset Filters button", () => {
renderFilters();
expect(screen.getByPlaceholderText("Search by Team Name...")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /^filters$/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /reset filters/i })).toBeInTheDocument();
});
it("reflects the current team_alias filter value in the search input", () => {
renderFilters({ filters: { ...emptyFilters, team_alias: "Platform" } });
expect(screen.getByPlaceholderText("Search by Team Name...")).toHaveValue("Platform");
});
it("calls onChange with 'team_alias' key when the search input changes", async () => {
const user = userEvent.setup();
const onChange = vi.fn();
renderFilters({ onChange });
await user.type(screen.getByPlaceholderText("Search by Team Name..."), "Dev");
expect(onChange).toHaveBeenCalledWith("team_alias", expect.stringContaining("D"));
});
it("calls onToggleFilters with the inverted boolean when the Filters button is clicked", async () => {
const user = userEvent.setup();
const onToggleFilters = vi.fn();
renderFilters({ showFilters: false, onToggleFilters });
await user.click(screen.getByRole("button", { name: /^filters$/i }));
expect(onToggleFilters).toHaveBeenCalledWith(true);
});
it("calls onToggleFilters(false) when filters are currently expanded", async () => {
const user = userEvent.setup();
const onToggleFilters = vi.fn();
renderFilters({ showFilters: true, onToggleFilters });
await user.click(screen.getByRole("button", { name: /^filters$/i }));
expect(onToggleFilters).toHaveBeenCalledWith(false);
});
it("calls onReset when the Reset Filters button is clicked", async () => {
const user = userEvent.setup();
const onReset = vi.fn();
renderFilters({ onReset });
await user.click(screen.getByRole("button", { name: /reset filters/i }));
expect(onReset).toHaveBeenCalledTimes(1);
});
it("does not show the Team ID input when showFilters is false", () => {
renderFilters({ showFilters: false });
expect(screen.queryByPlaceholderText("Enter Team ID")).not.toBeInTheDocument();
});
it("shows the Team ID input when showFilters is true", () => {
renderFilters({ showFilters: true });
expect(screen.getByPlaceholderText("Enter Team ID")).toBeInTheDocument();
});
it("calls onChange with 'team_id' key when the Team ID input changes", async () => {
const user = userEvent.setup();
const onChange = vi.fn();
renderFilters({ showFilters: true, onChange });
await user.type(screen.getByPlaceholderText("Enter Team ID"), "abc");
expect(onChange).toHaveBeenCalledWith("team_id", expect.stringContaining("a"));
});
it("reflects the current team_id filter value in the Team ID input", () => {
renderFilters({ showFilters: true, filters: { ...emptyFilters, team_id: "team-xyz" } });
expect(screen.getByPlaceholderText("Enter Team ID")).toHaveValue("team-xyz");
});
it("shows a blue dot indicator on the Filters button when team_alias filter is active", () => {
renderFilters({ filters: { ...emptyFilters, team_alias: "Platform" } });
const filtersButton = screen.getByRole("button", { name: /^filters$/i });
expect(filtersButton.querySelector(".bg-blue-500")).toBeInTheDocument();
});
it("shows a blue dot indicator on the Filters button when team_id filter is active", () => {
renderFilters({ filters: { ...emptyFilters, team_id: "team-123" } });
const filtersButton = screen.getByRole("button", { name: /^filters$/i });
expect(filtersButton.querySelector(".bg-blue-500")).toBeInTheDocument();
});
it("shows a blue dot indicator on the Filters button when organization_id filter is active", () => {
renderFilters({ filters: { ...emptyFilters, organization_id: "org-1" } });
const filtersButton = screen.getByRole("button", { name: /^filters$/i });
expect(filtersButton.querySelector(".bg-blue-500")).toBeInTheDocument();
});
it("does not show the blue dot indicator when all filters are empty", () => {
renderFilters({ filters: emptyFilters });
const filtersButton = screen.getByRole("button", { name: /^filters$/i });
expect(filtersButton.querySelector(".bg-blue-500")).not.toBeInTheDocument();
});
});
@@ -0,0 +1,130 @@
import { render, screen, fireEvent } from "@testing-library/react";
import React from "react";
import { describe, expect, it, vi } from "vitest";
import { Team } from "@/components/key_team_helpers/key_list";
import ModelsCell from "./ModelsCell";
// The Icon component from @tremor/react does not forward onClick to the rendered element
// by default in the test environment, so we stub it with a clickable button so accordion
// interaction can be tested end-to-end.
vi.mock("@tremor/react", async (importOriginal) => {
const actual = await importOriginal<typeof import("@tremor/react")>();
return {
...actual,
Icon: ({ onClick, "aria-label": ariaLabel }: { onClick?: () => void; "aria-label"?: string }) =>
React.createElement("button", { onClick, "aria-label": ariaLabel ?? "accordion-toggle", type: "button" }),
};
});
const makeTeam = (models: string[], overrides: Partial<Team> = {}): Team => ({
team_id: "team-1",
team_alias: "Engineering",
models,
max_budget: null,
budget_duration: null,
tpm_limit: null,
rpm_limit: null,
organization_id: "org-1",
created_at: "2024-01-01T00:00:00Z",
keys: [],
members_with_roles: [],
spend: 0,
...overrides,
});
// Wrap in a table so the <td> from TableCell renders without HTML warnings.
const renderModelsCell = (team: Team) =>
render(
<table>
<tbody>
<tr>
<ModelsCell team={team} />
</tr>
</tbody>
</table>,
);
describe("ModelsCell", () => {
it("shows 'All Proxy Models' badge when the models array is empty", () => {
renderModelsCell(makeTeam([]));
expect(screen.getByText("All Proxy Models")).toBeInTheDocument();
});
it("shows an 'All Proxy Models' badge when the model value is 'all-proxy-models'", () => {
renderModelsCell(makeTeam(["all-proxy-models"]));
expect(screen.getByText("All Proxy Models")).toBeInTheDocument();
});
it("displays individual model badges for up to 3 models without an accordion", () => {
renderModelsCell(makeTeam(["gpt-4", "gpt-3.5-turbo", "claude-3"]));
expect(screen.getByText("gpt-4")).toBeInTheDocument();
expect(screen.getByText("gpt-3.5-turbo")).toBeInTheDocument();
expect(screen.getByText("claude-3")).toBeInTheDocument();
expect(screen.queryByRole("button", { name: /accordion/i })).not.toBeInTheDocument();
});
it("truncates model names longer than 30 characters with an ellipsis", () => {
const longName = "a-very-long-model-name-exceeding-thirty-chars";
renderModelsCell(makeTeam([longName]));
const badge = screen.getByText((text) => text.endsWith("..."));
expect(badge).toBeInTheDocument();
expect(badge.textContent!.length).toBeLessThanOrEqual(33); // 30 chars + "..."
});
it("shows the first 3 models and a '+N more models' badge when there are more than 3 models", () => {
renderModelsCell(makeTeam(["m1", "m2", "m3", "m4", "m5"]));
expect(screen.getByText("m1")).toBeInTheDocument();
expect(screen.getByText("m2")).toBeInTheDocument();
expect(screen.getByText("m3")).toBeInTheDocument();
expect(screen.getByText("+2 more models")).toBeInTheDocument();
expect(screen.queryByText("m4")).not.toBeInTheDocument();
expect(screen.queryByText("m5")).not.toBeInTheDocument();
});
it("uses singular 'more model' when there is exactly 1 overflow model", () => {
renderModelsCell(makeTeam(["m1", "m2", "m3", "m4"]));
expect(screen.getByText("+1 more model")).toBeInTheDocument();
});
it("shows the accordion toggle button when there are more than 3 models", () => {
renderModelsCell(makeTeam(["m1", "m2", "m3", "m4"]));
expect(screen.getByRole("button", { name: /accordion/i })).toBeInTheDocument();
});
it("expands to show all models when the accordion toggle is clicked", () => {
renderModelsCell(makeTeam(["m1", "m2", "m3", "m4", "m5"]));
fireEvent.click(screen.getByRole("button", { name: /accordion/i }));
expect(screen.getByText("m4")).toBeInTheDocument();
expect(screen.getByText("m5")).toBeInTheDocument();
expect(screen.queryByText("+2 more models")).not.toBeInTheDocument();
});
it("collapses back to show the overflow badge after a second click on the toggle", () => {
renderModelsCell(makeTeam(["m1", "m2", "m3", "m4", "m5"]));
const toggle = screen.getByRole("button", { name: /accordion/i });
fireEvent.click(toggle);
fireEvent.click(toggle);
expect(screen.queryByText("m4")).not.toBeInTheDocument();
expect(screen.getByText("+2 more models")).toBeInTheDocument();
});
it("renders 'all-proxy-models' entries in the overflow section as 'All Proxy Models' badges", () => {
renderModelsCell(makeTeam(["m1", "m2", "m3", "all-proxy-models"]));
fireEvent.click(screen.getByRole("button", { name: /accordion/i }));
// There should now be an "All Proxy Models" badge in the expanded section
expect(screen.getByText("All Proxy Models")).toBeInTheDocument();
});
});
@@ -0,0 +1,175 @@
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 DeleteTeamModal from "./DeleteTeamModal";
const makeTeam = (overrides: Partial<Team> = {}): Team => ({
team_id: "team-1",
team_alias: "Engineering",
models: [],
max_budget: null,
budget_duration: null,
tpm_limit: null,
rpm_limit: null,
organization_id: "org-1",
created_at: "2024-01-01T00:00:00Z",
keys: [],
members_with_roles: [],
spend: 0,
...overrides,
});
const renderModal = (props: Partial<Parameters<typeof DeleteTeamModal>[0]> = {}) => {
const defaults = {
teams: [makeTeam()],
teamToDelete: "team-1",
onCancel: vi.fn(),
onConfirm: vi.fn(),
};
return render(<DeleteTeamModal {...defaults} {...props} />);
};
describe("DeleteTeamModal", () => {
it("renders the title, team name label, and confirmation input", () => {
renderModal();
expect(screen.getByText("Delete Team")).toBeInTheDocument();
expect(screen.getByText("Engineering")).toBeInTheDocument();
expect(screen.getByPlaceholderText("Enter team name exactly")).toBeInTheDocument();
});
it("renders Cancel and Force Delete buttons", () => {
renderModal();
expect(screen.getByRole("button", { name: /^cancel$/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /force delete/i })).toBeInTheDocument();
});
it("does not show the warning banner when the team has no keys", () => {
renderModal({ teams: [makeTeam({ keys: [] })] });
expect(screen.queryByText(/Warning/i)).not.toBeInTheDocument();
});
it("shows a warning with singular 'key' when the team has exactly 1 key", () => {
const team = makeTeam({ keys: [{ token: "tok-1" } as any] });
renderModal({ teams: [team] });
expect(screen.getByText(/This team has 1 associated key\./)).toBeInTheDocument();
});
it("shows a warning with plural 'keys' when the team has multiple keys", () => {
const team = makeTeam({
keys: [{ token: "tok-1" } as any, { token: "tok-2" } as any, { token: "tok-3" } as any],
});
renderModal({ teams: [team] });
expect(screen.getByText(/This team has 3 associated keys\./)).toBeInTheDocument();
});
it("notes that associated keys will also be deleted in the warning", () => {
const team = makeTeam({ keys: [{ token: "tok-1" } as any] });
renderModal({ teams: [team] });
expect(screen.getByText(/Deleting the team will also delete all associated keys/)).toBeInTheDocument();
});
it("Force Delete button is disabled when the input is empty", () => {
renderModal();
expect(screen.getByRole("button", { name: /force delete/i })).toBeDisabled();
});
it("Force Delete button remains disabled when the input does not exactly match the team name", async () => {
const user = userEvent.setup();
renderModal();
await user.type(screen.getByPlaceholderText("Enter team name exactly"), "engineer");
expect(screen.getByRole("button", { name: /force delete/i })).toBeDisabled();
});
it("enables Force Delete only after typing the exact team name (case-sensitive)", async () => {
const user = userEvent.setup();
renderModal();
const input = screen.getByPlaceholderText("Enter team name exactly");
await user.type(input, "Engineering");
expect(screen.getByRole("button", { name: /force delete/i })).toBeEnabled();
});
it("calls onConfirm when Force Delete is clicked with a valid input", async () => {
const user = userEvent.setup();
const onConfirm = vi.fn();
renderModal({ onConfirm });
await user.type(screen.getByPlaceholderText("Enter team name exactly"), "Engineering");
await user.click(screen.getByRole("button", { name: /force delete/i }));
expect(onConfirm).toHaveBeenCalledTimes(1);
});
it("does not call onConfirm when Force Delete is clicked with an invalid input", async () => {
const user = userEvent.setup();
const onConfirm = vi.fn();
renderModal({ onConfirm });
// Button is disabled so click has no effect
await user.click(screen.getByRole("button", { name: /force delete/i }));
expect(onConfirm).not.toHaveBeenCalled();
});
it("calls onCancel when the Cancel button is clicked", async () => {
const user = userEvent.setup();
const onCancel = vi.fn();
renderModal({ onCancel });
await user.click(screen.getByRole("button", { name: /^cancel$/i }));
expect(onCancel).toHaveBeenCalledTimes(1);
});
it("calls onCancel when the X close button is clicked", async () => {
const user = userEvent.setup();
const onCancel = vi.fn();
renderModal({ onCancel });
// The first button in the header is the X close button (no accessible label)
const allButtons = screen.getAllByRole("button");
const xButton = allButtons[0];
await user.click(xButton);
expect(onCancel).toHaveBeenCalledTimes(1);
});
it("resets the confirmation input when Cancel is clicked", async () => {
const user = userEvent.setup();
renderModal();
const input = screen.getByPlaceholderText("Enter team name exactly");
await user.type(input, "Engineering");
expect(input).toHaveValue("Engineering");
await user.click(screen.getByRole("button", { name: /^cancel$/i }));
expect(input).toHaveValue("");
});
it("resets the confirmation input when the X close button is clicked", async () => {
const user = userEvent.setup();
renderModal();
const input = screen.getByPlaceholderText("Enter team name exactly");
await user.type(input, "Engineering");
const allButtons = screen.getAllByRole("button");
await user.click(allButtons[0]);
expect(input).toHaveValue("");
});
});
@@ -0,0 +1,64 @@
import { describe, expect, it } from "vitest";
import { valueFormatter, valueFormatterSpend } from "./value_formatters";
describe("valueFormatter", () => {
it("formats numbers >= 1,000,000 as millions with 2 decimal places", () => {
expect(valueFormatter(1_000_000)).toBe("1.00M");
expect(valueFormatter(1_500_000)).toBe("1.50M");
expect(valueFormatter(2_750_000)).toBe("2.75M");
expect(valueFormatter(10_000_000)).toBe("10.00M");
});
it("formats numbers in the thousands range as 'k' suffix", () => {
expect(valueFormatter(1_000)).toBe("1k");
expect(valueFormatter(5_500)).toBe("5.5k");
expect(valueFormatter(999_999)).toBe("999.999k");
});
it("returns the plain string for numbers below 1,000", () => {
expect(valueFormatter(0)).toBe("0");
expect(valueFormatter(1)).toBe("1");
expect(valueFormatter(999)).toBe("999");
expect(valueFormatter(42)).toBe("42");
});
it("treats exactly 1,000,000 as millions boundary", () => {
expect(valueFormatter(1_000_000)).toBe("1.00M");
});
it("treats exactly 1,000 as thousands boundary", () => {
expect(valueFormatter(1_000)).toBe("1k");
});
});
describe("valueFormatterSpend", () => {
it("returns '$0' when the value is exactly zero", () => {
expect(valueFormatterSpend(0)).toBe("$0");
});
it("formats numbers >= 1,000,000 as dollar millions", () => {
expect(valueFormatterSpend(1_000_000)).toBe("$1M");
expect(valueFormatterSpend(2_500_000)).toBe("$2.5M");
expect(valueFormatterSpend(10_000_000)).toBe("$10M");
});
it("formats numbers >= 1,000 as dollar thousands", () => {
expect(valueFormatterSpend(1_000)).toBe("$1k");
expect(valueFormatterSpend(5_500)).toBe("$5.5k");
expect(valueFormatterSpend(999_999)).toBe("$999.999k");
});
it("formats numbers below 1,000 as plain dollar amounts", () => {
expect(valueFormatterSpend(1)).toBe("$1");
expect(valueFormatterSpend(99.99)).toBe("$99.99");
expect(valueFormatterSpend(999)).toBe("$999");
});
it("treats exactly 1,000,000 as millions boundary", () => {
expect(valueFormatterSpend(1_000_000)).toBe("$1M");
});
it("treats exactly 1,000 as thousands boundary", () => {
expect(valueFormatterSpend(1_000)).toBe("$1k");
});
});