Merge pull request #39085 from BerriAI/litellm_ui_dom_query_sweep

test(ui): query the screen instead of the render result
This commit is contained in:
yuneng-jiang 2026-09-01 10:35:42 -07:00 • committed by GitHub
commit f2a4172c89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 336 additions and 358 deletions

View file

@ -6,6 +6,6 @@
"local/no-large-inline-object-arg": { "max": 559, "target": 300 },
"local/no-long-condition-chain": { "max": 265, "target": 120 },
"testing-library/no-container": { "max": 133, "target": 50 },
"testing-library/no-node-access": { "max": 723, "target": 500 },
"testing-library/prefer-screen-queries": { "max": 221, "target": 0 }
"testing-library/no-node-access": { "max": 716, "target": 500 },
"testing-library/prefer-screen-queries": { "max": 18, "target": 18 }
}

View file

@ -13,17 +13,17 @@ describe("APIReferenceView", () => {
it("uses the API doc base url when provided", () => {
const apiDocUrl = "https://docs.litellm.test";
const { getAllByTestId } = render(<APIReferenceView proxySettings={{ LITELLM_UI_API_DOC_BASE_URL: apiDocUrl }} />);
render(<APIReferenceView proxySettings={{ LITELLM_UI_API_DOC_BASE_URL: apiDocUrl }} />);
const codeBlocks = getAllByTestId(codeBlockTestId);
const codeBlocks = screen.getAllByTestId(codeBlockTestId);
expect(codeBlocks[0]).toHaveTextContent(new RegExp(apiDocUrl));
});
it("falls back to the proxy base url when the docs url is missing", () => {
const proxyUrl = "https://proxy.litellm.test";
const { getAllByTestId } = render(<APIReferenceView proxySettings={{ PROXY_BASE_URL: proxyUrl }} />);
render(<APIReferenceView proxySettings={{ PROXY_BASE_URL: proxyUrl }} />);
const codeBlocks = getAllByTestId(codeBlockTestId);
const codeBlocks = screen.getAllByTestId(codeBlockTestId);
expect(codeBlocks[0]).toHaveTextContent(new RegExp(proxyUrl));
});
@ -31,7 +31,7 @@ describe("APIReferenceView", () => {
const apiDocUrl = "https://docs-preferred.litellm.test";
const proxyUrl = "https://proxy-backup.litellm.test";
const { getAllByTestId } = render(
render(
<APIReferenceView
proxySettings={{
LITELLM_UI_API_DOC_BASE_URL: apiDocUrl,
@ -40,7 +40,7 @@ describe("APIReferenceView", () => {
/>,
);
const codeBlocks = getAllByTestId(codeBlockTestId);
const codeBlocks = screen.getAllByTestId(codeBlockTestId);
const renderedCode = codeBlocks[0].textContent ?? "";
expect(renderedCode).toContain(apiDocUrl);
expect(renderedCode).not.toContain(proxyUrl);

View file

@ -1,12 +1,10 @@
import { describe, expect, it } from "vitest";
import RedisTypeSelector from "./RedisTypeSelector";
import { render } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
describe("RedisTypeSelector", () => {
it("should render the component", () => {
const { getAllByText } = render(
<RedisTypeSelector redisType="redis" redisTypeDescriptions={{}} onTypeChange={() => {}} />,
);
expect(getAllByText(/Redis/i).length).toBeGreaterThan(0);
render(<RedisTypeSelector redisType="redis" redisTypeDescriptions={{}} onTypeChange={() => {}} />);
expect(screen.getAllByText(/Redis/i).length).toBeGreaterThan(0);
});
});

View file

@ -1,4 +1,4 @@
import { fireEvent, render } from "@testing-library/react";
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import type { DailyData, KeyMetricWithMetadata, SpendMetrics } from "@/components/UsagePage/types";
@ -79,25 +79,25 @@ const renderWith = (results: DailyData[], overrides: Partial<DailyActivityRange>
describe("CacheLeakageCard", () => {
it("ranks leaking keys by uncached prompt tokens and shows cache hit ratio", () => {
const { getByText, getByLabelText } = renderWith([
renderWith([
dayWithKeys("2026-07-12", {
"hash-caching": key("caching-key", { prompt_tokens: 1000, cache_read_input_tokens: 900 }),
"hash-leaky": key("leaky-key", { prompt_tokens: 10000, cache_read_input_tokens: 0 }),
}),
]);
expect(getByText("leaky-key")).toBeInTheDocument();
expect(getByText("0.0%")).toBeInTheDocument();
expect(getByText("90.0%")).toBeInTheDocument();
expect(screen.getByText("leaky-key")).toBeInTheDocument();
expect(screen.getByText("0.0%")).toBeInTheDocument();
expect(screen.getByText("90.0%")).toBeInTheDocument();
[
"Input tokens you sent in this range that weren't served from or written to the cache",
"Share of your input tokens that were served from the cache",
"About how much you'd save if this uncached input used prompt caching. Estimated as uncached input tokens times what your cached traffic already nets per cached token (realized cache savings, after write premiums, ÷ cache read and write tokens). Blank when caching is not currently saving anything overall.",
].forEach((info) => expect(getByLabelText(info)).toBeInTheDocument());
].forEach((info) => expect(screen.getByLabelText(info)).toBeInTheDocument());
});
it("sorts by the clicked column, worst cache hit rate first", () => {
const { getAllByRole, getByText } = renderWith([
renderWith([
dayWithKeys("2026-07-12", {
"hash-a": key("alpha", {
prompt_tokens: 10000,
@ -111,48 +111,48 @@ describe("CacheLeakageCard", () => {
}),
}),
]);
const firstDataRow = () => getAllByRole("row")[1];
const firstDataRow = () => screen.getAllByRole("row")[1];
expect(firstDataRow()).toHaveTextContent("alpha");
fireEvent.click(getByText("Cache hit rate"));
fireEvent.click(screen.getByText("Cache hit rate"));
expect(firstDataRow()).toHaveTextContent("bravo");
fireEvent.click(getByText("Cache hit rate"));
fireEvent.click(screen.getByText("Cache hit rate"));
expect(firstDataRow()).toHaveTextContent("alpha");
});
it("switches to the model view and lists only Anthropic models", () => {
const { getByText, queryByText } = renderWith([
renderWith([
dayWithModels("2026-07-12", {
"claude-sonnet-5": { prompt_tokens: 5000, cache_read_input_tokens: 0 },
"gpt-4o": { prompt_tokens: 8000, cache_read_input_tokens: 0 },
}),
]);
fireEvent.click(getByText("By model"));
fireEvent.click(screen.getByText("By model"));
expect(getByText("Cache leakage by model")).toBeInTheDocument();
expect(getByText("claude-sonnet-5")).toBeInTheDocument();
expect(queryByText("gpt-4o")).not.toBeInTheDocument();
expect(screen.getByText("Cache leakage by model")).toBeInTheDocument();
expect(screen.getByText("claude-sonnet-5")).toBeInTheDocument();
expect(screen.queryByText("gpt-4o")).not.toBeInTheDocument();
});
it("shows an empty state when no key used tokens in the range", () => {
const { getByText, queryByRole } = renderWith([dayWithKeys("2026-07-12", {})]);
renderWith([dayWithKeys("2026-07-12", {})]);
expect(getByText("No key usage in this range.")).toBeInTheDocument();
expect(queryByRole("table")).not.toBeInTheDocument();
expect(screen.getByText("No key usage in this range.")).toBeInTheDocument();
expect(screen.queryByRole("table")).not.toBeInTheDocument();
});
it("tells the user the table is still filling in while fallback pages stream", () => {
const day = dayWithKeys("2026-07-12", {
"hash-leaky": key("leaky-key", { prompt_tokens: 10000, cache_read_input_tokens: 0 }),
});
const { getByText, getByRole } = renderWith([day], { isFetchingMore: true });
renderWith([day], { isFetchingMore: true });
expect(getByRole("table")).toBeInTheDocument();
expect(screen.getByRole("table")).toBeInTheDocument();
expect(
getByText("Data is still loading; rows and totals will update as the rest of the range arrives."),
screen.getByText("Data is still loading; rows and totals will update as the rest of the range arrives."),
).toBeInTheDocument();
});
@ -160,10 +160,10 @@ describe("CacheLeakageCard", () => {
const day = dayWithKeys("2026-07-12", {
"hash-leaky": key("leaky-key", { prompt_tokens: 10000, cache_read_input_tokens: 0 }),
});
const { queryByText } = renderWith([day], { loading: true });
renderWith([day], { loading: true });
expect(
queryByText("Data is still loading; rows and totals will update as the rest of the range arrives."),
screen.queryByText("Data is still loading; rows and totals will update as the rest of the range arrives."),
).not.toBeInTheDocument();
});
@ -171,10 +171,10 @@ describe("CacheLeakageCard", () => {
const day = dayWithKeys("2026-07-12", {
"hash-leaky": key("leaky-key", { prompt_tokens: 10000, cache_read_input_tokens: 0 }),
});
const { queryByText } = renderWith([day]);
renderWith([day]);
expect(
queryByText("Data is still loading; rows and totals will update as the rest of the range arrives."),
screen.queryByText("Data is still loading; rows and totals will update as the rest of the range arrives."),
).not.toBeInTheDocument();
});
});

View file

@ -1,5 +1,5 @@
import React from "react";
import { fireEvent, render, waitFor } from "@testing-library/react";
import { fireEvent, render, waitFor, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
@ -56,7 +56,7 @@ describe("CostOptimizationView daily activity", () => {
useAuthorizedMock.mockReturnValue({ accessToken: "test-token", userId: "u1", userRole: "proxy_admin" });
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
const { getByRole, getByTestId, findByTestId, queryByText } = render(
render(
<QueryClientProvider client={queryClient}>
<CostOptimizationView accessToken="test-token" userId="u1" userRole="proxy_admin" />
</QueryClientProvider>,
@ -64,12 +64,12 @@ describe("CostOptimizationView daily activity", () => {
await waitFor(() => expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalledTimes(1));
fireEvent.click(getByRole("tab", { name: "Prompt Caching" }));
await findByTestId("caching-settings");
fireEvent.click(screen.getByRole("tab", { name: "Prompt Caching" }));
await screen.findByTestId("caching-settings");
expect(mockUserDailyActivityAggregatedCall).toHaveBeenCalledTimes(1);
expect(mockUserDailyActivityCall).not.toHaveBeenCalled();
expect(queryByText(/Currently fetching spend data/)).not.toBeInTheDocument();
expect(screen.queryByText(/Currently fetching spend data/)).not.toBeInTheDocument();
});
it("shows the fetch-progress banner while the paginated fallback streams pages in", async () => {
@ -84,13 +84,13 @@ describe("CostOptimizationView daily activity", () => {
useAuthorizedMock.mockReturnValue({ accessToken: "test-token", userId: "u1", userRole: "proxy_admin" });
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
const { findByText, getByRole } = render(
render(
<QueryClientProvider client={queryClient}>
<CostOptimizationView accessToken="test-token" userId="u1" userRole="proxy_admin" />
</QueryClientProvider>,
);
expect(await findByText(/Currently fetching spend data: fetched 1 \/ 3 pages/)).toBeInTheDocument();
expect(getByRole("button", { name: "Stop" })).toBeInTheDocument();
expect(await screen.findByText(/Currently fetching spend data: fetched 1 \/ 3 pages/)).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Stop" })).toBeInTheDocument();
});
});

View file

@ -1,5 +1,5 @@
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import { fireEvent, render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
@ -45,32 +45,32 @@ describe("CostOptimizationView", () => {
});
it("renders the standard page header with the sidebar's Cost Optimization icon", () => {
const { container, getByRole, getByText } = renderView();
const { container } = renderView();
expect(getByRole("heading", { level: 1, name: "Cost Optimization" })).toBeInTheDocument();
expect(getByText(/Track and configure the mechanisms that save you money/)).toBeInTheDocument();
expect(screen.getByRole("heading", { level: 1, name: "Cost Optimization" })).toBeInTheDocument();
expect(screen.getByText(/Track and configure the mechanisms that save you money/)).toBeInTheDocument();
expect(container.querySelector(".lucide-piggy-bank")).not.toBeNull();
});
it("renders the four cost-optimization tabs", () => {
const { getByText } = renderView();
renderView();
expect(getByText("Overall")).toBeInTheDocument();
expect(getByText("Prompt Compression")).toBeInTheDocument();
expect(getByText("Prompt Caching")).toBeInTheDocument();
expect(getByText("Auto-Router")).toBeInTheDocument();
expect(screen.getByText("Overall")).toBeInTheDocument();
expect(screen.getByText("Prompt Compression")).toBeInTheDocument();
expect(screen.getByText("Prompt Caching")).toBeInTheDocument();
expect(screen.getByText("Auto-Router")).toBeInTheDocument();
});
it("defaults to the Overall tab and switches the active tab on click", () => {
const { getByRole } = renderView();
renderView();
expect(getByRole("tab", { name: "Overall" })).toHaveAttribute("aria-selected", "true");
expect(getByRole("tab", { name: "Prompt Compression" })).toHaveAttribute("aria-selected", "false");
expect(screen.getByRole("tab", { name: "Overall" })).toHaveAttribute("aria-selected", "true");
expect(screen.getByRole("tab", { name: "Prompt Compression" })).toHaveAttribute("aria-selected", "false");
fireEvent.click(getByRole("tab", { name: "Prompt Compression" }));
fireEvent.click(screen.getByRole("tab", { name: "Prompt Compression" }));
expect(getByRole("tab", { name: "Overall" })).toHaveAttribute("aria-selected", "false");
expect(getByRole("tab", { name: "Prompt Compression" })).toHaveAttribute("aria-selected", "true");
expect(screen.getByRole("tab", { name: "Overall" })).toHaveAttribute("aria-selected", "false");
expect(screen.getByRole("tab", { name: "Prompt Compression" })).toHaveAttribute("aria-selected", "true");
});
// Unlike the other three pages in this cleanup, Cost Optimization keeps its
@ -80,21 +80,21 @@ describe("CostOptimizationView", () => {
// are proxy-admin-only, so those are what disappear.
describe("proxy-admin-only tabs", () => {
it.each(["Internal User", "Internal Viewer", "Org Admin"])("shows %s the Overall tab only", (userRole) => {
const { getByRole, queryByRole } = renderView(userRole);
renderView(userRole);
expect(getByRole("tab", { name: "Overall" })).toBeInTheDocument();
expect(queryByRole("tab", { name: "Prompt Compression" })).not.toBeInTheDocument();
expect(queryByRole("tab", { name: "Prompt Caching" })).not.toBeInTheDocument();
expect(queryByRole("tab", { name: "Auto-Router" })).not.toBeInTheDocument();
expect(screen.getByRole("tab", { name: "Overall" })).toBeInTheDocument();
expect(screen.queryByRole("tab", { name: "Prompt Compression" })).not.toBeInTheDocument();
expect(screen.queryByRole("tab", { name: "Prompt Caching" })).not.toBeInTheDocument();
expect(screen.queryByRole("tab", { name: "Auto-Router" })).not.toBeInTheDocument();
});
it("never mounts the panels behind the admin-only endpoints for an internal user", () => {
const { getByTestId, queryByTestId } = renderView("Internal User");
renderView("Internal User");
expect(getByTestId("usage-tab")).toBeInTheDocument();
expect(queryByTestId("compression-tab")).not.toBeInTheDocument();
expect(queryByTestId("caching-tab")).not.toBeInTheDocument();
expect(queryByTestId("autorouter-benchmarks-tab")).not.toBeInTheDocument();
expect(screen.getByTestId("usage-tab")).toBeInTheDocument();
expect(screen.queryByTestId("compression-tab")).not.toBeInTheDocument();
expect(screen.queryByTestId("caching-tab")).not.toBeInTheDocument();
expect(screen.queryByTestId("autorouter-benchmarks-tab")).not.toBeInTheDocument();
});
});
});

View file

@ -1,4 +1,4 @@
import { render, waitFor } from "@testing-library/react";
import { render, waitFor, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
const mockGetGeneralSettingsCall = vi.fn();
@ -37,10 +37,10 @@ describe("PromptCachingTab", () => {
cancelled: false,
cancel: vi.fn(),
};
const { getByTestId } = render(<PromptCachingTab accessToken="test-token" activity={activity} />);
render(<PromptCachingTab accessToken="test-token" activity={activity} />);
expect(getByTestId("caching-settings")).toBeInTheDocument();
expect(getByTestId("cache-leakage-card")).toBeInTheDocument();
expect(screen.getByTestId("caching-settings")).toBeInTheDocument();
expect(screen.getByTestId("cache-leakage-card")).toBeInTheDocument();
await waitFor(() => expect(mockCacheLeakageCard).toHaveBeenCalledWith(expect.objectContaining({ activity })));
});
});

View file

@ -1,4 +1,4 @@
import { render } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ToolSpendResponse } from "@/components/networking";
@ -152,13 +152,13 @@ describe("UsageTab", () => {
gateway_injected_caching_savings_spend: 0.006,
compression_saved_tokens: 100000,
};
const { getByText } = renderWith([day("2026-07-12", firstDay), day("2026-07-13", secondDay)]);
renderWith([day("2026-07-12", firstDay), day("2026-07-13", secondDay)]);
expect(getByText("$0.1500")).toBeInTheDocument();
expect(getByText("$0.1400")).toBeInTheDocument();
expect(getByText("$0.0100")).toBeInTheDocument();
expect(getByText("$0.0160")).toBeInTheDocument();
expect(getByText("140,000 tokens compressed")).toBeInTheDocument();
expect(screen.getByText("$0.1500")).toBeInTheDocument();
expect(screen.getByText("$0.1400")).toBeInTheDocument();
expect(screen.getByText("$0.0100")).toBeInTheDocument();
expect(screen.getByText("$0.0160")).toBeInTheDocument();
expect(screen.getByText("140,000 tokens compressed")).toBeInTheDocument();
});
const twoDays = () => [
@ -167,11 +167,11 @@ describe("UsageTab", () => {
];
it("opens on a running total anchored at $0 at the start of the range", () => {
const { getByTestId } = renderWith(twoDays());
renderWith(twoDays());
// Cumulative prepends a synthetic $0 point at the range start (Jul 1) so the
// line rises from zero rather than floating; the daily running totals follow.
const series = readSeries(getByTestId("area-chart"));
const series = readSeries(screen.getByTestId("area-chart"));
expect(series).toHaveLength(3);
expect(series[0]).toMatchObject({ date: "Jul 1", Compression: 0, "Prompt caching": 0 });
expect(series[1]).toMatchObject({ Compression: 0.04, "Prompt caching": 0.006 });
@ -183,12 +183,12 @@ describe("UsageTab", () => {
// The original complaint: a one-day range plotted a single floating dot. The
// synthetic start anchor gives the line a zero origin to climb from.
const oneDay = new Date(2026, 6, 24);
const { getByTestId } = renderWith(
[day("2026-07-24", { compression_savings_spend: 0.2, gateway_injected_caching_savings_spend: 0.05 })],
{ from: oneDay, to: oneDay },
);
renderWith([day("2026-07-24", { compression_savings_spend: 0.2, gateway_injected_caching_savings_spend: 0.05 })], {
from: oneDay,
to: oneDay,
});
const series = readSeries(getByTestId("area-chart"));
const series = readSeries(screen.getByTestId("area-chart"));
expect(series).toHaveLength(2);
expect(series[0]).toMatchObject({ date: "Jul 24", Compression: 0, "Prompt caching": 0 });
expect(series[1]).toMatchObject({ date: "Jul 24", Compression: 0.2, "Prompt caching": 0.05 });
@ -202,49 +202,49 @@ describe("UsageTab", () => {
day("2026-07-13", { gateway_injected_caching_savings_spend: 0.1 }),
day("2026-07-12", { gateway_injected_caching_savings_spend: 0.04 }),
];
const { getByTestId, getByRole } = renderWith(newestFirst);
renderWith(newestFirst);
// The $0 anchor leads, then the days climb oldest to newest.
const cumulative = readSeries(getByTestId("area-chart"));
const cumulative = readSeries(screen.getByTestId("area-chart"));
expect(cumulative.map((p: { date: string }) => p.date)).toEqual(["Jul 1", "Jul 12", "Jul 13"]);
expect(cumulative[1]["Prompt caching"]).toBeCloseTo(0.04, 5);
expect(cumulative[2]["Prompt caching"]).toBeCloseTo(0.14, 5);
expect(cumulative[2]["Prompt caching"]).toBeGreaterThan(cumulative[1]["Prompt caching"]);
await userEvent.click(getByRole("tab", { name: "Per day" }));
const perDay = readSeries(getByTestId("bar-chart"));
await userEvent.click(screen.getByRole("tab", { name: "Per day" }));
const perDay = readSeries(screen.getByTestId("bar-chart"));
expect(perDay.map((p: { date: string }) => p.date)).toEqual(["Jul 12", "Jul 13"]);
});
it("draws bars of the raw per-interval readings on the other tab", async () => {
const { getByRole, getByTestId, queryByTestId } = renderWith(twoDays());
renderWith(twoDays());
// Cumulative opens on the area line.
expect(getByTestId("area-chart")).toBeInTheDocument();
expect(screen.getByTestId("area-chart")).toBeInTheDocument();
await userEvent.click(getByRole("tab", { name: "Per day" }));
await userEvent.click(screen.getByRole("tab", { name: "Per day" }));
// Per day switches to a bar chart of the unaccumulated daily savings, with no
// synthetic anchor prepended.
expect(queryByTestId("area-chart")).not.toBeInTheDocument();
const series = readSeries(getByTestId("bar-chart"));
expect(screen.queryByTestId("area-chart")).not.toBeInTheDocument();
const series = readSeries(screen.getByTestId("bar-chart"));
expect(series).toHaveLength(2);
expect(series[0]).toMatchObject({ Compression: 0.04, "Prompt caching": 0.006 });
expect(series[1]).toMatchObject({ Compression: 0.1, "Prompt caching": 0.01 });
});
it("says what the line means and over what range", async () => {
const { getByText, getByRole } = renderWith(twoDays());
renderWith(twoDays());
expect(getByText("Running total saved · Jul 1 – Jul 14 (UTC)")).toBeInTheDocument();
await userEvent.click(getByRole("tab", { name: "Per day" }));
expect(getByText("Saved per day · Jul 1 – Jul 14 (UTC)")).toBeInTheDocument();
expect(screen.getByText("Running total saved · Jul 1 – Jul 14 (UTC)")).toBeInTheDocument();
await userEvent.click(screen.getByRole("tab", { name: "Per day" }));
expect(screen.getByText("Saved per day · Jul 1 – Jul 14 (UTC)")).toBeInTheDocument();
});
it("builds the per-driver donut from the range totals, not the running total", () => {
const { getByTestId } = renderWith(twoDays());
renderWith(twoDays());
const slices = JSON.parse(getByTestId("donut-chart").getAttribute("data-slices") ?? "[]");
const slices = JSON.parse(screen.getByTestId("donut-chart").getAttribute("data-slices") ?? "[]");
expect(slices).toEqual([
{ driver: "Compression", color: "emerald", usd: expect.closeTo(0.14, 5) },
{ driver: "Prompt caching", color: "blue", usd: expect.closeTo(0.016, 5) },
@ -252,9 +252,9 @@ describe("UsageTab", () => {
});
it("omits a driver slice when that driver has no savings", () => {
const { getByTestId } = renderWith([day("2026-07-12", { compression_savings_spend: 0.04 })]);
renderWith([day("2026-07-12", { compression_savings_spend: 0.04 })]);
const slices = JSON.parse(getByTestId("donut-chart").getAttribute("data-slices") ?? "[]");
const slices = JSON.parse(screen.getByTestId("donut-chart").getAttribute("data-slices") ?? "[]");
expect(slices).toEqual([{ driver: "Compression", color: "emerald", usd: expect.closeTo(0.04, 5) }]);
});
@ -262,7 +262,7 @@ describe("UsageTab", () => {
// Stacking sums the series into one bar. Auto-router savings go negative when a
// model switch pays for a cold cache, and that segment would be drawn below the
// axis while the rest of the bar still read as the day's total.
const { getByRole, getByTestId } = renderWith([
renderWith([
day("2026-07-12", {
compression_savings_spend: 0.1,
gateway_injected_caching_savings_spend: 0.02,
@ -270,8 +270,8 @@ describe("UsageTab", () => {
}),
]);
await userEvent.click(getByRole("tab", { name: "Per day" }));
const bars = getByTestId("bar-chart");
await userEvent.click(screen.getByRole("tab", { name: "Per day" }));
const bars = screen.getByTestId("bar-chart");
expect(bars).toHaveAttribute("data-stack", "false");
expect(readSeries(bars)[0]).toMatchObject({ "Auto-router": -0.05 });
});
@ -281,10 +281,10 @@ describe("UsageTab", () => {
// per day"). Hand-rolled rows made it compete with the legend and the toggle for
// width, so the header grew a line on one tab and the chart moved with it. CardHeader
// sizes the action column to its content and gives the rest to the title column.
const { getByRole, getByTestId, container } = renderWith(twoDays());
const { container } = renderWith(twoDays());
const header = () => {
const legend = getByTestId("chart-legend");
const legend = screen.getByTestId("chart-legend");
const action = legend.closest('[data-slot="card-action"]') as HTMLElement;
const cardHeader = action.parentElement as HTMLElement;
const description = cardHeader.querySelector('[data-slot="card-description"]') as HTMLElement;
@ -295,12 +295,12 @@ describe("UsageTab", () => {
expect(before.action).toBeTruthy();
expect(before.description).toBeTruthy();
// the toggle rides in the same action slot as the legend, so neither moves alone
expect(before.action.contains(getByRole("tablist"))).toBe(true);
expect(before.action.contains(screen.getByRole("tablist"))).toBe(true);
// the subtitle lives outside that slot, so its length cannot reposition the controls
expect(before.action.contains(before.description)).toBe(false);
expect(before.description).toHaveTextContent(/Running total saved/);
await userEvent.click(getByRole("tab", { name: "Per day" }));
await userEvent.click(screen.getByRole("tab", { name: "Per day" }));
const after = header();
expect(after.action).toBe(before.action);
@ -314,7 +314,7 @@ describe("UsageTab", () => {
// Switching models leaves the new one with a cold cache, so a route can cost more
// than the baseline would have. A negative slice is meaningless in a donut, but the
// total has to keep the loss or the page can only ever report good news.
const { getByText, getByTestId } = renderWith([
renderWith([
day("2026-07-12", {
compression_savings_spend: 0.1,
gateway_injected_caching_savings_spend: 0.02,
@ -322,16 +322,16 @@ describe("UsageTab", () => {
}),
]);
expect(getByText("$0.0700")).toBeInTheDocument();
expect(getByText("-$0.0500")).toBeInTheDocument();
expect(screen.getByText("$0.0700")).toBeInTheDocument();
expect(screen.getByText("-$0.0500")).toBeInTheDocument();
const slices = JSON.parse(getByTestId("donut-chart").getAttribute("data-slices") ?? "[]");
const slices = JSON.parse(screen.getByTestId("donut-chart").getAttribute("data-slices") ?? "[]");
expect(slices.map((d: { driver: string }) => d.driver)).toEqual(["Compression", "Prompt caching"]);
expect(getByTestId("donut-chart")).toHaveAttribute("data-label", "$0.1200");
expect(screen.getByTestId("donut-chart")).toHaveAttribute("data-label", "$0.1200");
});
it("carries auto-router savings into the summary card, donut slice, and cumulative series", () => {
const { getByText, getByTestId } = renderWith([
renderWith([
day("2026-07-12", {
compression_savings_spend: 0.04,
gateway_injected_caching_savings_spend: 0.006,
@ -345,11 +345,11 @@ describe("UsageTab", () => {
]);
// Total saved now sums three drivers, and the auto-router card carries its own total.
expect(getByText("$0.2260")).toBeInTheDocument();
expect(getByText("$0.0700")).toBeInTheDocument();
expect(screen.getByText("$0.2260")).toBeInTheDocument();
expect(screen.getByText("$0.0700")).toBeInTheDocument();
// The driver donut gains a third slice priced from the range totals.
const slices = JSON.parse(getByTestId("donut-chart").getAttribute("data-slices") ?? "[]");
const slices = JSON.parse(screen.getByTestId("donut-chart").getAttribute("data-slices") ?? "[]");
expect(slices).toEqual([
{ driver: "Compression", color: "emerald", usd: expect.closeTo(0.14, 5) },
{ driver: "Prompt caching", color: "blue", usd: expect.closeTo(0.016, 5) },
@ -357,7 +357,7 @@ describe("UsageTab", () => {
]);
// And the cumulative line accumulates the auto-router series alongside the others.
const series = readSeries(getByTestId("area-chart"));
const series = readSeries(screen.getByTestId("area-chart"));
expect(series[2]["Auto-router"]).toBeCloseTo(0.07, 5);
});
@ -371,9 +371,9 @@ describe("UsageTab", () => {
start_date: "2026-07-12",
end_date: "2026-07-12",
};
const { findAllByTestId } = renderWith([day("2026-07-12", {})], { toolSpend });
renderWith([day("2026-07-12", {})], { toolSpend });
const bars = await findAllByTestId("bar-chart");
const bars = await screen.findAllByTestId("bar-chart");
const series = JSON.parse(bars[0].getAttribute("data-series") ?? "[]");
expect(series[0]).toMatchObject({ tool_name: "search", spend: 4.0 });
// The 64px bar cap is this card's opt-in; the shared BarChart must not cap
@ -391,14 +391,16 @@ describe("UsageTab", () => {
start_date: "2026-07-12",
end_date: "2026-07-12",
};
const { findAllByTestId, getAllByTestId } = renderWith([day("2026-07-12", {})], { toolSpend });
renderWith([day("2026-07-12", {})], { toolSpend });
const bars = await findAllByTestId("bar-chart");
const bars = await screen.findAllByTestId("bar-chart");
const [totalByTool, dailyByTool] = bars.slice(-2);
expect(dailyByTool).toHaveAttribute("data-show-legend", "false");
expect(totalByTool).toHaveAttribute("data-colors", dailyByTool.getAttribute("data-colors"));
const toolLegends = getAllByTestId("chart-legend").filter((legend) => legend.textContent === "search,read_file");
const toolLegends = screen
.getAllByTestId("chart-legend")
.filter((legend) => legend.textContent === "search,read_file");
expect(toolLegends).toHaveLength(1);
});
@ -415,23 +417,23 @@ describe("UsageTab", () => {
it.each(["Internal User", "Internal Viewer", "Org Admin"])(
"hides the card and never calls the endpoint for %s",
async (userRole) => {
const { queryByText, getByTestId } = renderWith([day("2026-07-12", { compression_savings_spend: 0.04 })], {
renderWith([day("2026-07-12", { compression_savings_spend: 0.04 })], {
toolSpend,
userRole,
});
// Liveness gate: the daily-activity charts still render for this role,
// so the absence below is the gate, not an empty tab.
expect(getByTestId("donut-chart")).toBeInTheDocument();
expect(queryByText("Spend by tool")).not.toBeInTheDocument();
expect(screen.getByTestId("donut-chart")).toBeInTheDocument();
expect(screen.queryByText("Spend by tool")).not.toBeInTheDocument();
await vi.waitFor(() => expect(mockGetToolSpend).not.toHaveBeenCalled());
},
);
it("keeps the card and the endpoint call for an admin", async () => {
const { findByText } = renderWith([day("2026-07-12", { compression_savings_spend: 0.04 })], { toolSpend });
renderWith([day("2026-07-12", { compression_savings_spend: 0.04 })], { toolSpend });
expect(await findByText("Spend by tool")).toBeInTheDocument();
expect(await screen.findByText("Spend by tool")).toBeInTheDocument();
expect(mockGetToolSpend).toHaveBeenCalled();
});
});

View file

@ -1,5 +1,5 @@
import * as networking from "@/components/networking";
import { fireEvent, render, waitFor, within } from "@testing-library/react";
import { fireEvent, render, waitFor, within, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";
import GuardrailInfoView from "./guardrail_info";
@ -65,21 +65,19 @@ describe("Guardrail Info", () => {
vi.mocked(networking.getGuardrailProviderSpecificParams).mockResolvedValue({});
const { getAllByText, getByText } = render(
<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />,
);
render(<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />);
// Wait for the loading to complete and data to be rendered
await waitFor(() => {
// The guardrail name appears in multiple places (title and settings tab)
const elements = getAllByText("Test Guardrail");
const elements = screen.getAllByText("Test Guardrail");
expect(elements.length).toBeGreaterThan(0);
});
// Verify other key elements are present
expect(getByText("Back to Guardrails")).toBeInTheDocument();
expect(getByText("Overview")).toBeInTheDocument();
expect(getByText("Settings")).toBeInTheDocument();
expect(screen.getByText("Back to Guardrails")).toBeInTheDocument();
expect(screen.getByText("Overview")).toBeInTheDocument();
expect(screen.getByText("Settings")).toBeInTheDocument();
});
it("should render a tag-based mode object rather than crashing the detail view", async () => {
@ -105,11 +103,9 @@ describe("Guardrail Info", () => {
vi.mocked(networking.getGuardrailProviderSpecificParams).mockResolvedValue({});
const { findAllByText } = render(
<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />,
);
render(<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />);
expect(await findAllByText("pre_call, post_call (tag-based)")).not.toHaveLength(0);
expect(await screen.findAllByText("pre_call, post_call (tag-based)")).not.toHaveLength(0);
});
it("should render the provider logo from the bundled guardrail logo map", async () => {
@ -135,11 +131,9 @@ describe("Guardrail Info", () => {
vi.mocked(networking.getGuardrailProviderSpecificParams).mockResolvedValue({});
const { findByAltText } = render(
<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />,
);
render(<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />);
const logo = await findByAltText("Presidio PII logo");
const logo = await screen.findByAltText("Presidio PII logo");
expect(logo).toHaveAttribute("src", expect.stringContaining("microsoft_azure.svg"));
});
@ -167,25 +161,27 @@ describe("Guardrail Info", () => {
vi.mocked(networking.getGuardrailProviderSpecificParams).mockResolvedValue({});
const { getByText, findByText, container } = render(
const { container } = render(
<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />,
);
await waitFor(() => {
expect(getByText("Settings")).toBeInTheDocument();
expect(screen.getByText("Settings")).toBeInTheDocument();
});
// Click the Settings tab
fireEvent.click(getByText("Settings"));
fireEvent.click(screen.getByText("Settings"));
// Wait for the Settings panel to render
await waitFor(() => {
expect(getByText("Guardrail Settings")).toBeInTheDocument();
expect(screen.getByText("Guardrail Settings")).toBeInTheDocument();
});
await userEvent.hover(within(container).getByRole("img", { name: "Config guardrail details" }));
expect(await findByText("Guardrail is defined in the config file and cannot be edited.")).toBeInTheDocument();
expect(
await screen.findByText("Guardrail is defined in the config file and cannot be edited."),
).toBeInTheDocument();
});
it("should render the guardrail info", async () => {
@ -216,12 +212,10 @@ describe("Guardrail Info", () => {
vi.mocked(networking.getGuardrailProviderSpecificParams).mockResolvedValue({});
const { getByText } = render(
<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />,
);
render(<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />);
await waitFor(() => {
expect(getByText("PII Entity Configuration")).toBeInTheDocument();
expect(screen.getByText("PII Entity Configuration")).toBeInTheDocument();
});
});
it("should handle content filter updates correctly", async () => {
@ -251,30 +245,28 @@ describe("Guardrail Info", () => {
vi.mocked(networking.getGuardrailProviderSpecificParams).mockResolvedValue({});
vi.mocked(networking.updateGuardrailCall).mockResolvedValue({ status: "success" });
const { getByText, getByLabelText } = render(
<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />,
);
render(<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />);
await waitFor(() => {
expect(getByText("Settings")).toBeInTheDocument();
expect(screen.getByText("Settings")).toBeInTheDocument();
});
// Go to Settings tab
fireEvent.click(getByText("Settings"));
fireEvent.click(screen.getByText("Settings"));
await waitFor(() => {
expect(getByText("Guardrail Settings")).toBeInTheDocument();
expect(screen.getByText("Guardrail Settings")).toBeInTheDocument();
});
// Enter Edit Mode
fireEvent.click(getByText("Edit Settings"));
fireEvent.click(screen.getByText("Edit Settings"));
// Modify Guardrail Name to force an update
const nameInput = getByLabelText("Guardrail Name");
const nameInput = screen.getByLabelText("Guardrail Name");
fireEvent.change(nameInput, { target: { value: "Updated Name" } });
// Save with only name change
const saveButton = getByText("Save Changes");
const saveButton = screen.getByText("Save Changes");
fireEvent.click(saveButton);
await waitFor(() => {
@ -300,16 +292,16 @@ describe("Guardrail Info", () => {
// Enter Edit Mode again to make changes
await waitFor(() => {
expect(getByText("Edit Settings")).toBeInTheDocument();
expect(screen.getByText("Edit Settings")).toBeInTheDocument();
});
fireEvent.click(getByText("Edit Settings"));
fireEvent.click(screen.getByText("Edit Settings"));
// Now modify the values using the mock button
const simulateChangeButton = getByText("Simulate Change");
const simulateChangeButton = screen.getByText("Simulate Change");
fireEvent.click(simulateChangeButton);
// Save again
fireEvent.click(getByText("Save Changes"));
fireEvent.click(screen.getByText("Save Changes"));
await waitFor(() => {
expect(networking.updateGuardrailCall).toHaveBeenCalled();
@ -339,12 +331,10 @@ describe("Guardrail Info", () => {
});
vi.mocked(networking.getGuardrailProviderSpecificParams).mockResolvedValue({});
const { findByRole, getByRole, getByText } = render(
<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />,
);
render(<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />);
expect(await findByRole("tab", { name: "Overview" })).toHaveAttribute("aria-selected", "true");
expect(getByRole("tab", { name: "Settings" })).toHaveAttribute("aria-selected", "false");
expect(getByText("Guardrail Settings")).toBeInTheDocument();
expect(await screen.findByRole("tab", { name: "Overview" })).toHaveAttribute("aria-selected", "true");
expect(screen.getByRole("tab", { name: "Settings" })).toHaveAttribute("aria-selected", "false");
expect(screen.getByText("Guardrail Settings")).toBeInTheDocument();
});
});

View file

@ -1,4 +1,4 @@
import { render } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { CategoryFilter, QuickActions, PiiEntityList } from "./pii_components";
import type { PiiEntityCategory } from "@/components/guardrails/types";
@ -6,25 +6,21 @@ import type { PiiEntityCategory } from "@/components/guardrails/types";
describe("CategoryFilter", () => {
it("should render", () => {
const emptyCategories: PiiEntityCategory[] = [];
const { getByText } = render(
<CategoryFilter categories={emptyCategories} selectedCategories={[]} onChange={() => {}} />,
);
expect(getByText("Filter by category")).toBeInTheDocument();
render(<CategoryFilter categories={emptyCategories} selectedCategories={[]} onChange={() => {}} />);
expect(screen.getByText("Filter by category")).toBeInTheDocument();
});
});
describe("QuickActions", () => {
it("should render", () => {
const { getByText } = render(
<QuickActions onSelectAll={() => {}} onUnselectAll={() => {}} hasSelectedEntities={false} />,
);
expect(getByText("Quick Actions")).toBeInTheDocument();
render(<QuickActions onSelectAll={() => {}} onUnselectAll={() => {}} hasSelectedEntities={false} />);
expect(screen.getByText("Quick Actions")).toBeInTheDocument();
});
});
describe("PiiEntityList", () => {
it("should render", () => {
const { getByText } = render(
render(
<PiiEntityList
entities={[]}
selectedEntities={[]}
@ -35,6 +31,6 @@ describe("PiiEntityList", () => {
entityToCategoryMap={new Map()}
/>,
);
expect(getByText("No PII types match your filter criteria")).toBeInTheDocument();
expect(screen.getByText("No PII types match your filter criteria")).toBeInTheDocument();
});
});

View file

@ -1,10 +1,10 @@
import { render } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import PiiConfiguration from "./pii_configuration";
describe("PiiConfiguration", () => {
it("should render", () => {
const { getByText } = render(
render(
<PiiConfiguration
entities={[]}
actions={[]}
@ -15,6 +15,6 @@ describe("PiiConfiguration", () => {
entityCategories={[]}
/>,
);
expect(getByText("Configure PII Protection")).toBeInTheDocument();
expect(screen.getByText("Configure PII Protection")).toBeInTheDocument();
});
});

View file

@ -45,7 +45,7 @@ describe("MCPServers", () => {
vi.mocked(networking.fetchMCPServers).mockResolvedValue([]);
const queryClient = createQueryClient();
const { getByText } = render(
render(
<QueryClientProvider client={queryClient}>
<MCPServers {...defaultProps} />
</QueryClientProvider>,
@ -53,11 +53,11 @@ describe("MCPServers", () => {
// Wait for the component to load and check if title renders
await waitFor(() => {
expect(getByText("MCP Servers")).toBeInTheDocument();
expect(screen.getByText("MCP Servers")).toBeInTheDocument();
});
// Verify the title is rendered
expect(getByText("MCP Servers")).toBeInTheDocument();
expect(screen.getByText("MCP Servers")).toBeInTheDocument();
});
it("should render mocked MCP servers data in the table", async () => {
@ -96,7 +96,7 @@ describe("MCPServers", () => {
vi.mocked(networking.fetchMCPServers).mockResolvedValue(mockServers);
const queryClient = createQueryClient();
const { getByText, getAllByText } = render(
render(
<QueryClientProvider client={queryClient}>
<MCPServers {...defaultProps} />
</QueryClientProvider>,
@ -104,19 +104,19 @@ describe("MCPServers", () => {
// Wait for the component to load
await waitFor(() => {
expect(getByText("MCP Servers")).toBeInTheDocument();
expect(screen.getByText("MCP Servers")).toBeInTheDocument();
});
// Wait for the mocked data to render in the table
await waitFor(() => {
expect(getByText("Test Server 1")).toBeInTheDocument();
expect(screen.getByText("Test Server 1")).toBeInTheDocument();
});
// Verify the mocked server data is rendered in the table
expect(getByText("Test Server 1")).toBeInTheDocument();
expect(getByText("Test Server 2")).toBeInTheDocument();
expect(getAllByText("test-server-1").length).toBeGreaterThan(0);
expect(getAllByText("test-server-2").length).toBeGreaterThan(0);
expect(screen.getByText("Test Server 1")).toBeInTheDocument();
expect(screen.getByText("Test Server 2")).toBeInTheDocument();
expect(screen.getAllByText("test-server-1").length).toBeGreaterThan(0);
expect(screen.getAllByText("test-server-2").length).toBeGreaterThan(0);
// Verify the API was called
// Note: useMCPServers uses useAuthorized() internally, which returns "123" from global mock
@ -168,7 +168,7 @@ describe("MCPServers", () => {
vi.mocked(networking.fetchMCPServerHealth).mockResolvedValue(mockHealthStatuses);
const queryClient = createQueryClient();
const { getByText } = render(
render(
<QueryClientProvider client={queryClient}>
<MCPServers {...defaultProps} />
</QueryClientProvider>,
@ -176,7 +176,7 @@ describe("MCPServers", () => {
// Wait for the component to load
await waitFor(() => {
expect(getByText("MCP Servers")).toBeInTheDocument();
expect(screen.getByText("MCP Servers")).toBeInTheDocument();
});
// Verify the health check API was called (without a server ID filter — the hook always
@ -211,7 +211,7 @@ describe("MCPServers", () => {
);
const queryClient = createQueryClient();
const { getByText } = render(
render(
<QueryClientProvider client={queryClient}>
<MCPServers {...defaultProps} />
</QueryClientProvider>,
@ -219,7 +219,7 @@ describe("MCPServers", () => {
// Wait for the component to load
await waitFor(() => {
expect(getByText("MCP Servers")).toBeInTheDocument();
expect(screen.getByText("MCP Servers")).toBeInTheDocument();
});
// Verify that health check was initiated

View file

@ -1,5 +1,5 @@
/* @vitest-environment jsdom */
import { render } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import PriceDataManagementTab from "./PriceDataManagementTab";
@ -11,7 +11,7 @@ vi.mock("@/app/(dashboard)/hooks/models/useModelCostMap", () => ({
describe("PriceDataManagementTab", () => {
it("renders its content standalone, without a tab-panel ancestor", () => {
const { getByText } = render(<PriceDataManagementTab />);
expect(getByText("Price Data Management")).toBeInTheDocument();
render(<PriceDataManagementTab />);
expect(screen.getByText("Price Data Management")).toBeInTheDocument();
});
});

View file

@ -1,6 +1,6 @@
/* @vitest-environment jsdom */
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
import ModelsAndEndpointsPage from "./page";
@ -64,48 +64,48 @@ describe("ModelsAndEndpointsPage", () => {
});
it("renders the admin tab bar and the All Models panel by default", () => {
const { getByRole, getByTestId } = renderPage();
expect(getByRole("tab", { name: "All Models" })).toBeInTheDocument();
expect(getByRole("tab", { name: "LLM Credentials" })).toBeInTheDocument();
expect(getByRole("tab", { name: "Health Status" })).toBeInTheDocument();
expect(getByTestId("panel-all-models")).toBeInTheDocument();
renderPage();
expect(screen.getByRole("tab", { name: "All Models" })).toBeInTheDocument();
expect(screen.getByRole("tab", { name: "LLM Credentials" })).toBeInTheDocument();
expect(screen.getByRole("tab", { name: "Health Status" })).toBeInTheDocument();
expect(screen.getByTestId("panel-all-models")).toBeInTheDocument();
});
it("switches tabs in-memory, mounting only the active panel", async () => {
const user = userEvent.setup();
const { getByRole, getByTestId, queryByTestId } = renderPage();
await user.click(getByRole("tab", { name: "Health Status" }));
expect(getByTestId("panel-health")).toBeInTheDocument();
expect(queryByTestId("panel-all-models")).not.toBeInTheDocument();
renderPage();
await user.click(screen.getByRole("tab", { name: "Health Status" }));
expect(screen.getByTestId("panel-health")).toBeInTheDocument();
expect(screen.queryByTestId("panel-all-models")).not.toBeInTheDocument();
});
it("renders the model detail overlay from the ?model drill-in and hides the tabs", () => {
detailState.modelId = "abc-123";
const { getByTestId, queryByRole } = renderPage();
expect(getByTestId("model-info")).toHaveTextContent("model:abc-123");
expect(queryByRole("tab", { name: "All Models" })).not.toBeInTheDocument();
renderPage();
expect(screen.getByTestId("model-info")).toHaveTextContent("model:abc-123");
expect(screen.queryByRole("tab", { name: "All Models" })).not.toBeInTheDocument();
});
it("renders the team detail overlay from the ?team drill-in", () => {
detailState.teamId = "team-9";
const { getByTestId } = renderPage();
expect(getByTestId("team-info")).toHaveTextContent("team:team-9");
renderPage();
expect(screen.getByTestId("team-info")).toHaveTextContent("team:team-9");
});
it("hides admin-only tabs for a non-admin user", () => {
mockUseAuthorized.mockReturnValue(NON_ADMIN);
const { queryByRole } = renderPage();
expect(queryByRole("tab", { name: "LLM Credentials" })).not.toBeInTheDocument();
expect(queryByRole("tab", { name: "Health Status" })).not.toBeInTheDocument();
renderPage();
expect(screen.queryByRole("tab", { name: "LLM Credentials" })).not.toBeInTheDocument();
expect(screen.queryByRole("tab", { name: "Health Status" })).not.toBeInTheDocument();
});
// Auto-routers are excluded from the All Models table, so this tab is their home: the only
// place in the product to list, create, edit or delete one.
describe("Auto-Routers tab", () => {
it("sits third, after All Models and Add Model", () => {
const { getAllByRole } = renderPage();
renderPage();
const tabs = getAllByRole("tab").map((tab) => tab.textContent);
const tabs = screen.getAllByRole("tab").map((tab) => tab.textContent);
expect(tabs[0]).toContain("All Models");
expect(tabs[1]).toBe("Add Model");
expect(tabs[2]).toContain("Auto-Routers");
@ -115,17 +115,17 @@ describe("ModelsAndEndpointsPage", () => {
it("renders its panel when selected", async () => {
const user = userEvent.setup();
const { getByRole, getByTestId } = renderPage();
renderPage();
await user.click(getByRole("tab", { name: /Auto-Routers/ }));
expect(getByTestId("panel-auto-routers")).toBeInTheDocument();
await user.click(screen.getByRole("tab", { name: /Auto-Routers/ }));
expect(screen.getByTestId("panel-auto-routers")).toBeInTheDocument();
});
it("is hidden from non-admins, who cannot write models", () => {
mockUseAuthorized.mockReturnValue(NON_ADMIN);
const { queryByRole } = renderPage();
renderPage();
expect(queryByRole("tab", { name: /Auto-Routers/ })).not.toBeInTheDocument();
expect(screen.queryByRole("tab", { name: /Auto-Routers/ })).not.toBeInTheDocument();
});
});
});

View file

@ -93,13 +93,13 @@ describe("ChatMessageBubble", () => {
])("should paint the $role surface from theme tokens, not fixed colours", ({ role, bubble, avatar }) => {
render(<ChatMessageBubble {...defaultProps} message={{ role, content: "Hello" }} />);
const header = screen.getByText(role).closest("div") as HTMLElement;
const surface = header.parentElement as HTMLElement;
const surface = screen.getByTestId("message-surface");
const avatarEl = screen.getByTestId("message-avatar");
expect(surface).toHaveClass(...bubble);
expect(surface).not.toHaveAttribute("style");
expect(header.firstElementChild).toHaveClass(avatar);
expect(header.firstElementChild).not.toHaveAttribute("style");
expect(avatarEl).toHaveClass(avatar);
expect(avatarEl).not.toHaveAttribute("style");
});
it("should show model badge for assistant messages when model is provided", () => {

View file

@ -46,6 +46,7 @@ function ChatMessageBubble({
return (
<div className={`mb-4 min-w-0 ${isUser ? "text-right" : "text-left"}`}>
<div
data-testid="message-surface"
className={`inline-block min-w-0 max-w-[92%] overflow-hidden rounded-lg border p-3 text-left text-card-foreground shadow-xs sm:max-w-[85%] sm:px-4 ${
isUser ? "border-info/20 bg-info/10" : "border-border bg-card"
}`}
@ -53,6 +54,7 @@ function ChatMessageBubble({
{/* Header: role icon + name + model badge */}
<div className="mb-1.5 flex min-w-0 items-center gap-2">
<div
data-testid="message-avatar"
className={`flex items-center justify-center w-6 h-6 rounded-full mr-1 ${
isUser ? "bg-info/20" : "bg-muted"
}`}

View file

@ -90,21 +90,19 @@ beforeEach(() => {
describe("CompareUI", () => {
it("should render", () => {
const { getByTestId } = render(<CompareUI accessToken="test-token" disabledPersonalKeyCreation={false} />);
expect(getByTestId("comparison-panel-1")).toBeInTheDocument();
expect(getByTestId("comparison-panel-2")).toBeInTheDocument();
expect(getByTestId("message-input")).toBeInTheDocument();
render(<CompareUI accessToken="test-token" disabledPersonalKeyCreation={false} />);
expect(screen.getByTestId("comparison-panel-1")).toBeInTheDocument();
expect(screen.getByTestId("comparison-panel-2")).toBeInTheDocument();
expect(screen.getByTestId("message-input")).toBeInTheDocument();
});
it("adds a comparison when Add Comparison button is clicked", async () => {
const user = userEvent.setup();
const { container, getByTestId } = render(
<CompareUI accessToken="test-token" disabledPersonalKeyCreation={false} />,
);
const { container } = render(<CompareUI accessToken="test-token" disabledPersonalKeyCreation={false} />);
// Verify initial state: 2 comparison panels
expect(getByTestId("comparison-panel-1")).toBeInTheDocument();
expect(getByTestId("comparison-panel-2")).toBeInTheDocument();
expect(screen.getByTestId("comparison-panel-1")).toBeInTheDocument();
expect(screen.getByTestId("comparison-panel-2")).toBeInTheDocument();
let comparisonPanels = container.querySelectorAll('[data-testid^="comparison-panel-"]');
expect(comparisonPanels).toHaveLength(2);
@ -117,15 +115,13 @@ describe("CompareUI", () => {
});
// Verify the original 2 panels are still there
expect(getByTestId("comparison-panel-1")).toBeInTheDocument();
expect(getByTestId("comparison-panel-2")).toBeInTheDocument();
expect(screen.getByTestId("comparison-panel-1")).toBeInTheDocument();
expect(screen.getByTestId("comparison-panel-2")).toBeInTheDocument();
});
it("should handle image upload and send message with attachment", async () => {
const user = userEvent.setup();
const { getByTestId, queryByTestId } = render(
<CompareUI accessToken="test-token" disabledPersonalKeyCreation={false} />,
);
render(<CompareUI accessToken="test-token" disabledPersonalKeyCreation={false} />);
const file = new File(["test content"], "test-image.png", { type: "image/png" });
@ -138,13 +134,13 @@ describe("CompareUI", () => {
}
await waitFor(() => {
expect(getByTestId("has-attachment")).toBeInTheDocument();
expect(screen.getByTestId("has-attachment")).toBeInTheDocument();
});
const textarea = getByTestId("message-textarea");
const textarea = screen.getByTestId("message-textarea");
fireEvent.change(textarea, { target: { value: "Describe this image" } });
const sendButton = getByTestId("send-button");
const sendButton = screen.getByTestId("send-button");
expect(sendButton).toBeEnabled();
await user.click(sendButton);

View file

@ -1,4 +1,4 @@
import { render } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import type { MessageType } from "@/components/chat_ui/types";
import { MessageDisplay } from "./MessageDisplay";
@ -39,9 +39,9 @@ describe("MessageDisplay", () => {
model: "gpt-4",
},
];
const { getByText } = render(<MessageDisplay messages={messages} isLoading={false} />);
expect(getByText("Hello")).toBeInTheDocument();
expect(getByText("Hi there!")).toBeInTheDocument();
render(<MessageDisplay messages={messages} isLoading={false} />);
expect(screen.getByText("Hello")).toBeInTheDocument();
expect(screen.getByText("Hi there!")).toBeInTheDocument();
});
it("displays user and assistant messages with proper grouping and shows loading state", () => {
@ -64,13 +64,13 @@ describe("MessageDisplay", () => {
},
},
];
const { getByText, getByTestId } = render(<MessageDisplay messages={messages} isLoading={false} />);
expect(getByText("You")).toBeInTheDocument();
expect(getByText("What is 2+2?")).toBeInTheDocument();
expect(getByText("gpt-4")).toBeInTheDocument();
expect(getByText("calculator")).toBeInTheDocument();
expect(getByText("2+2 equals 4")).toBeInTheDocument();
expect(getByTestId("response-metrics")).toBeInTheDocument();
render(<MessageDisplay messages={messages} isLoading={false} />);
expect(screen.getByText("You")).toBeInTheDocument();
expect(screen.getByText("What is 2+2?")).toBeInTheDocument();
expect(screen.getByText("gpt-4")).toBeInTheDocument();
expect(screen.getByText("calculator")).toBeInTheDocument();
expect(screen.getByText("2+2 equals 4")).toBeInTheDocument();
expect(screen.getByTestId("response-metrics")).toBeInTheDocument();
});
it("should display image attachment in user message", () => {
@ -86,10 +86,10 @@ describe("MessageDisplay", () => {
model: "gpt-4",
},
];
const { getByTestId, getByText } = render(<MessageDisplay messages={messages} isLoading={false} />);
expect(getByText("What is in this image? [Image attached]")).toBeInTheDocument();
expect(getByTestId("chat-image-renderer")).toBeInTheDocument();
const image = getByTestId("chat-image-renderer").querySelector("img");
render(<MessageDisplay messages={messages} isLoading={false} />);
expect(screen.getByText("What is in this image? [Image attached]")).toBeInTheDocument();
expect(screen.getByTestId("chat-image-renderer")).toBeInTheDocument();
const image = screen.getByTestId("chat-image-renderer").querySelector("img");
expect(image).toHaveAttribute("src", "blob:test-image-url");
});
});

View file

@ -10,6 +10,7 @@
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import { screen } from "@testing-library/react";
import { renderWithProviders } from "../../../tests/test-utils";
import userEvent from "@testing-library/user-event";
import EntityUsageExportModal from "./EntityUsageExportModal";
@ -73,13 +74,13 @@ describe("EntityUsageExportModal", () => {
const user = userEvent.setup();
const { handleExportCSV } = await import("./utils");
const { getByRole } = renderWithProviders(<EntityUsageExportModal {...baseProps} />);
renderWithProviders(<EntityUsageExportModal {...baseProps} />);
// Default primary action reflects CSV export
expect(getByRole("button", { name: /Export CSV/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /Export CSV/i })).toBeInTheDocument();
// Click export
await user.click(getByRole("button", { name: /Export CSV/i }));
await user.click(screen.getByRole("button", { name: /Export CSV/i }));
// Verifies export function was invoked with correct parameters
expect(handleExportCSV).toHaveBeenCalledWith(baseProps.spendData, "daily", "Tag", "tag", {});
@ -97,14 +98,14 @@ describe("EntityUsageExportModal", () => {
const user = userEvent.setup();
const { handleExportCSV } = await import("./utils");
const { getByText, getByRole } = renderWithProviders(<EntityUsageExportModal {...baseProps} />);
renderWithProviders(<EntityUsageExportModal {...baseProps} />);
// Choose the alternate export type - click the label to trigger radio
const dailyModelLabel = getByText(/Day-by-day by tag and model/i);
const dailyModelLabel = screen.getByText(/Day-by-day by tag and model/i);
await user.click(dailyModelLabel);
// Export with default CSV format
const exportBtn = getByRole("button", { name: /Export CSV/i });
const exportBtn = screen.getByRole("button", { name: /Export CSV/i });
await user.click(exportBtn);
// Ensure the selected scope flowed through

View file

@ -1,4 +1,4 @@
import { act, fireEvent, render, waitFor } from "@testing-library/react";
import { act, fireEvent, render, waitFor, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { MountedFormHost } from "../../../tests/mounted-form-host";
import AdvancedSettings from "./advanced_settings";
@ -35,51 +35,51 @@ describe("AdvancedSettings", () => {
});
it("should render tags list", async () => {
const { getByText } = renderAdvancedSettings();
fireEvent.click(getByText("Advanced Settings"));
renderAdvancedSettings();
fireEvent.click(screen.getByText("Advanced Settings"));
await waitFor(() => {
expect(getByText("Tags")).toBeInTheDocument();
expect(screen.getByText("Tags")).toBeInTheDocument();
});
});
it("should render the litellm params", async () => {
const { getByText } = renderAdvancedSettings();
renderAdvancedSettings();
act(() => {
fireEvent.click(getByText("Advanced Settings"));
fireEvent.click(screen.getByText("Advanced Settings"));
});
await waitFor(() => {
expect(getByText("LiteLLM Params")).toBeInTheDocument();
expect(screen.getByText("LiteLLM Params")).toBeInTheDocument();
});
});
it("hides every PTU field when PTU cost attribution is disabled", async () => {
const { getByText, queryByText } = renderAdvancedSettings();
renderAdvancedSettings();
act(() => {
fireEvent.click(getByText("Advanced Settings"));
fireEvent.click(screen.getByText("Advanced Settings"));
});
await waitFor(() => {
expect(getByText("Tags")).toBeInTheDocument();
expect(screen.getByText("Tags")).toBeInTheDocument();
});
for (const label of PTU_LABELS) {
expect(queryByText(label)).not.toBeInTheDocument();
expect(screen.queryByText(label)).not.toBeInTheDocument();
}
expect(queryByText("PTU Effective To (UTC)")).not.toBeInTheDocument();
expect(screen.queryByText("PTU Effective To (UTC)")).not.toBeInTheDocument();
});
it("shows every PTU field when PTU cost attribution is enabled", async () => {
mockUsePtuCostAttributionEnabled.mockReturnValue(true);
const { getByText } = renderAdvancedSettings();
renderAdvancedSettings();
act(() => {
fireEvent.click(getByText("Advanced Settings"));
fireEvent.click(screen.getByText("Advanced Settings"));
});
await waitFor(() => {
expect(getByText("PTU Count")).toBeInTheDocument();
expect(screen.getByText("PTU Count")).toBeInTheDocument();
});
for (const label of PTU_LABELS) {
expect(getByText(label)).toBeInTheDocument();
expect(screen.getByText(label)).toBeInTheDocument();
}
expect(getByText("PTU Effective To (UTC)")).toBeInTheDocument();
expect(screen.getByText("PTU Effective To (UTC)")).toBeInTheDocument();
});
});

View file

@ -1,4 +1,4 @@
import { render } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { getPlaceholder, Providers } from "../provider_info_helpers";
import { MountedFormHost } from "../../../tests/mounted-form-host";
@ -6,7 +6,7 @@ import LiteLLMModelNameField from "./litellm_model_name";
describe("LitellmModelNameField", () => {
it("should render", () => {
const { getByText } = render(
render(
<MountedFormHost>
<LiteLLMModelNameField
selectedProvider={Providers.OpenAI}
@ -15,16 +15,16 @@ describe("LitellmModelNameField", () => {
/>
</MountedFormHost>,
);
expect(getByText("LiteLLM Model Name(s)")).toBeInTheDocument();
expect(screen.getByText("LiteLLM Model Name(s)")).toBeInTheDocument();
});
it("should show Azure placeholder as 'my-deployment'", () => {
const { getByPlaceholderText, queryByPlaceholderText } = render(
render(
<MountedFormHost>
<LiteLLMModelNameField selectedProvider={Providers.Azure} providerModels={[]} getPlaceholder={getPlaceholder} />
</MountedFormHost>,
);
expect(getByPlaceholderText("my-deployment")).toBeInTheDocument();
expect(queryByPlaceholderText("gpt-3.5-turbo")).not.toBeInTheDocument();
expect(screen.getByPlaceholderText("my-deployment")).toBeInTheDocument();
expect(screen.queryByPlaceholderText("gpt-3.5-turbo")).not.toBeInTheDocument();
});
});

View file

@ -26,8 +26,8 @@ const openUploadStep = async () => {
describe("BulkCreateUsersButton", () => {
it("should render", () => {
const { getByText } = render(<BulkCreateUsersButton accessToken="test-token" teams={[]} possibleUIRoles={null} />);
expect(getByText("+ Bulk Invite Users")).toBeInTheDocument();
render(<BulkCreateUsersButton accessToken="test-token" teams={[]} possibleUIRoles={null} />);
expect(screen.getByText("+ Bulk Invite Users")).toBeInTheDocument();
});
it("parses a CSV chosen through the file input", async () => {

View file

@ -1,4 +1,4 @@
import { fireEvent, render } from "@testing-library/react";
import { fireEvent, render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it } from "vitest";
import CostOptimizationFeedbackBanner from "./cost_optimization_feedback_banner";
@ -10,24 +10,24 @@ describe("CostOptimizationFeedbackBanner", () => {
});
it("renders with a link to the feedback discussion", () => {
const { getByText } = render(<CostOptimizationFeedbackBanner />);
const link = getByText("Share Feedback").closest("a");
render(<CostOptimizationFeedbackBanner />);
const link = screen.getByText("Share Feedback").closest("a");
expect(link).toHaveAttribute("href", "https://github.com/BerriAI/litellm/discussions/32172");
});
it("hides itself and persists the dismissal when the dismiss button is clicked", () => {
const { getByText, queryByText, getByLabelText } = render(<CostOptimizationFeedbackBanner />);
expect(getByText("Help shape cost optimization")).toBeInTheDocument();
render(<CostOptimizationFeedbackBanner />);
expect(screen.getByText("Help shape cost optimization")).toBeInTheDocument();
fireEvent.click(getByLabelText("Dismiss banner"));
fireEvent.click(screen.getByLabelText("Dismiss banner"));
expect(queryByText("Help shape cost optimization")).not.toBeInTheDocument();
expect(screen.queryByText("Help shape cost optimization")).not.toBeInTheDocument();
expect(localStorage.getItem(STORAGE_KEY)).toBe("true");
});
it("stays dismissed on remount once persisted", () => {
localStorage.setItem(STORAGE_KEY, "true");
const { queryByText } = render(<CostOptimizationFeedbackBanner />);
expect(queryByText("Help shape cost optimization")).not.toBeInTheDocument();
render(<CostOptimizationFeedbackBanner />);
expect(screen.queryByText("Help shape cost optimization")).not.toBeInTheDocument();
});
});

View file

@ -108,7 +108,7 @@ beforeEach(() => {
test("renders organization view after loading data", async () => {
mockUseOrganization.mockReturnValue({ data: mockOrg, isLoading: false } as any);
const { findAllByText } = renderWithProviders(
renderWithProviders(
<OrganizationInfoView
organizationId="org_123"
onClose={() => {}}
@ -120,7 +120,7 @@ test("renders organization view after loading data", async () => {
/>,
);
const [orgName] = await findAllByText("Acme Corp");
const [orgName] = await screen.findAllByText("Acme Corp");
expect(orgName).toBeInTheDocument();
});

View file

@ -77,21 +77,21 @@ describe("Settings", () => {
});
it("should render the logging callbacks tab when access token is provided", async () => {
const { getByText } = render(<Settings {...defaultProps} />);
render(<Settings {...defaultProps} />);
await waitFor(() => {
expect(getByText("Active Logging Callbacks")).toBeInTheDocument();
expect(screen.getByText("Active Logging Callbacks")).toBeInTheDocument();
});
});
it("should display additional settings tabs", async () => {
const { getByText } = render(<Settings {...defaultProps} />);
render(<Settings {...defaultProps} />);
await waitFor(() => {
expect(getByText("CloudZero Cost Tracking")).toBeInTheDocument();
expect(getByText("Alerting Types")).toBeInTheDocument();
expect(getByText("Alerting Settings")).toBeInTheDocument();
expect(getByText("Email Alerts")).toBeInTheDocument();
expect(screen.getByText("CloudZero Cost Tracking")).toBeInTheDocument();
expect(screen.getByText("Alerting Types")).toBeInTheDocument();
expect(screen.getByText("Alerting Settings")).toBeInTheDocument();
expect(screen.getByText("Email Alerts")).toBeInTheDocument();
});
});
@ -279,13 +279,13 @@ describe("Settings", () => {
});
it("should display CloudZero Cost Tracking tab", async () => {
const { getByText } = render(<Settings {...defaultProps} />);
render(<Settings {...defaultProps} />);
await waitFor(() => {
expect(getByText("Active Logging Callbacks")).toBeInTheDocument();
expect(screen.getByText("Active Logging Callbacks")).toBeInTheDocument();
});
expect(getByText("CloudZero Cost Tracking")).toBeInTheDocument();
expect(screen.getByText("CloudZero Cost Tracking")).toBeInTheDocument();
});
});

View file

@ -1,4 +1,4 @@
import { fireEvent, render } from "@testing-library/react";
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import PaginationStatusAlerts from "./PaginationStatusAlerts";
@ -6,7 +6,7 @@ import PaginationStatusAlerts from "./PaginationStatusAlerts";
describe("PaginationStatusAlerts", () => {
it("shows page progress and wires the Stop button while fetching", () => {
const cancel = vi.fn();
const { getByRole, getByText } = render(
render(
<PaginationStatusAlerts
isFetchingMore={true}
cancelled={false}
@ -15,13 +15,13 @@ describe("PaginationStatusAlerts", () => {
/>,
);
expect(getByText(/Currently fetching spend data: fetched 7 \/ 42 pages/)).toBeInTheDocument();
fireEvent.click(getByRole("button", { name: "Stop" }));
expect(screen.getByText(/Currently fetching spend data: fetched 7 \/ 42 pages/)).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Stop" }));
expect(cancel).toHaveBeenCalledTimes(1);
});
it("shows the partial-data notice after a cancel, frozen at the last fetched page", () => {
const { getByText } = render(
render(
<PaginationStatusAlerts
isFetchingMore={false}
cancelled={true}
@ -30,11 +30,11 @@ describe("PaginationStatusAlerts", () => {
/>,
);
expect(getByText("Showing partial spend data (7/42 pages loaded)")).toBeInTheDocument();
expect(screen.getByText("Showing partial spend data (7/42 pages loaded)")).toBeInTheDocument();
});
it("names the subject it is fetching", () => {
const { getByText } = render(
render(
<PaginationStatusAlerts
isFetchingMore={true}
cancelled={false}
@ -44,7 +44,7 @@ describe("PaginationStatusAlerts", () => {
/>,
);
expect(getByText(/Currently fetching agent data: fetched 1 \/ 3 pages/)).toBeInTheDocument();
expect(screen.getByText(/Currently fetching agent data: fetched 1 \/ 3 pages/)).toBeInTheDocument();
});
it("renders nothing when idle", () => {

View file

@ -1,4 +1,4 @@
import { render } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import React from "react";
import { describe, expect, it } from "vitest";
import { AreaChart } from "./area_chart";
@ -21,9 +21,9 @@ describe("AreaChart", () => {
});
it("renders the No data placeholder instead of a chart when data is empty", () => {
const { container, getByText } = render(<AreaChart data={[]} index="date" categories={["tokens"]} />);
const { container } = render(<AreaChart data={[]} index="date" categories={["tokens"]} />);
expect(getByText("No data")).toBeInTheDocument();
expect(screen.getByText("No data")).toBeInTheDocument();
expect(container.querySelector('[data-slot="chart"]')).toBeNull();
});

View file

@ -21,9 +21,9 @@ describe("BarChart", () => {
});
it("renders the No data placeholder instead of a chart when data is empty", () => {
const { container, getByText } = render(<BarChart data={[]} index="date" categories={["passed"]} />);
const { container } = render(<BarChart data={[]} index="date" categories={["passed"]} />);
expect(getByText("No data")).toBeInTheDocument();
expect(screen.getByText("No data")).toBeInTheDocument();
expect(container.querySelector('[data-slot="chart"]')).toBeNull();
});

View file

@ -445,7 +445,7 @@ describe("KeyInfoView handleKeyUpdate budget_duration", () => {
);
fireEvent.click(screen.getByText("Settings"));
expect(screen.getByText("Budget Reset").parentElement?.textContent).toContain("Every 30d");
expect(screen.getByTestId("budget-reset-value")).toHaveTextContent("Every 30d");
fireEvent.click(screen.getByText("Edit Settings"));
(globalThis as any).__TEST_FORM_VALUES = {
@ -456,7 +456,7 @@ describe("KeyInfoView handleKeyUpdate budget_duration", () => {
fireEvent.click(screen.getByText("Mock Submit"));
await waitFor(() => {
expect(screen.getByText("Budget Reset").parentElement?.textContent).toBe("Budget ResetNever");
expect(screen.getByTestId("budget-reset-value")).toHaveTextContent("Never");
});
});
});

View file

@ -1,7 +1,7 @@
import { fireEvent, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { renderWithProviders } from "../../../tests/test-utils";
import { chooseSelectOption, renderWithProviders } from "../../../tests/test-utils";
import { KeyResponse } from "../key_team_helpers/key_list";
import { MODEL_MAX_BUDGET_PREMIUM_HINT } from "../key_team_helpers/ModelMaxBudgetEditor";
import {
@ -300,7 +300,7 @@ describe("KeyEditView", () => {
});
it("should render", async () => {
const { getByText } = renderWithProviders(
renderWithProviders(
<KeyEditView
keyData={MOCK_KEY_DATA}
onCancel={() => {}}
@ -313,12 +313,12 @@ describe("KeyEditView", () => {
);
await waitFor(() => {
expect(getByText("Save Changes")).toBeInTheDocument();
expect(screen.getByText("Save Changes")).toBeInTheDocument();
});
});
it("should render tags", async () => {
const { getByText } = renderWithProviders(
renderWithProviders(
<KeyEditView
keyData={MOCK_KEY_DATA}
onCancel={() => {}}
@ -331,12 +331,12 @@ describe("KeyEditView", () => {
);
await waitFor(() => {
expect(getByText("test-tag")).toBeInTheDocument();
expect(screen.getByText("test-tag")).toBeInTheDocument();
});
});
it("should not render tags in metadata textarea", async () => {
const { getByLabelText } = renderWithProviders(
renderWithProviders(
<KeyEditView
keyData={MOCK_KEY_DATA}
onCancel={() => {}}
@ -348,7 +348,7 @@ describe("KeyEditView", () => {
/>,
);
const metadataTextarea = getByLabelText("Metadata") as HTMLTextAreaElement;
const metadataTextarea = screen.getByLabelText("Metadata") as HTMLTextAreaElement;
await waitFor(() => {
expect(metadataTextarea).toHaveValue("{}");
});
@ -963,10 +963,7 @@ describe("KeyEditView", () => {
/>,
);
await userEvent.click(await screen.findByLabelText("Reset Budget"));
const weeklyOption = await screen.findByText("weekly");
await userEvent.click(weeklyOption);
await chooseSelectOption(userEvent, await screen.findByLabelText("Reset Budget"), "weekly");
const submitButton = screen.getByRole("button", { name: /save changes/i });
await userEvent.click(submitButton);
@ -1042,8 +1039,7 @@ describe("KeyEditView", () => {
);
const resetBudget = await screen.findByLabelText("Reset Budget");
await userEvent.click(resetBudget);
await userEvent.click(await screen.findByText("Never resets"));
await chooseSelectOption(userEvent, resetBudget, "Never resets");
await waitFor(() => {
expect(resetBudget).toHaveTextContent("Never resets");
@ -1074,8 +1070,7 @@ describe("KeyEditView", () => {
/>,
);
await userEvent.click(await screen.findByLabelText("Reset Budget"));
await userEvent.click(await screen.findByText("Never resets"));
await chooseSelectOption(userEvent, await screen.findByLabelText("Reset Budget"), "Never resets");
await userEvent.click(screen.getByRole("button", { name: /save changes/i }));
@ -1946,8 +1941,7 @@ describe("KeyEditView", () => {
await userEvent.clear(duration);
await userEvent.type(duration, "45d");
await userEvent.click(screen.getByLabelText(/TPM Rate Limit Type/));
await userEvent.click(await screen.findByTitle("Guaranteed throughput"));
await chooseSelectOption(userEvent, screen.getByLabelText(/TPM Rate Limit Type/), /^Guaranteed throughput/);
await userEvent.click(screen.getByRole("button", { name: /save changes/i }));
@ -2103,8 +2097,7 @@ describe("KeyEditView", () => {
renderForPayload(onSubmitMock);
await screen.findByRole("button", { name: /save changes/i });
await userEvent.click(screen.getByLabelText(/RPM Rate Limit Type/));
await userEvent.click(await screen.findByTitle("Guaranteed throughput"));
await chooseSelectOption(userEvent, screen.getByLabelText(/RPM Rate Limit Type/), /^Guaranteed throughput/);
await userEvent.click(screen.getByRole("button", { name: /save changes/i }));

View file

@ -381,6 +381,6 @@ describe("KeyInfoView budget reset visibility", () => {
await waitFor(() => {
expect(screen.getByText("Budget Reset")).toBeInTheDocument();
});
expect(screen.getByText("Budget Reset").parentElement).toHaveTextContent("Never");
expect(screen.getByTestId("budget-reset-value")).toHaveTextContent("Never");
});
});

View file

@ -895,7 +895,7 @@ export default function KeyInfoView({
<div>
<p className="text-sm font-medium">Budget Reset</p>
<p className="text-sm">
<p data-testid="budget-reset-value" className="text-sm">
{currentKeyData.budget_reset_at
? `${currentKeyData.budget_duration ? `Every ${currentKeyData.budget_duration}, next ` : ""}${formatTimestamp(currentKeyData.budget_reset_at)}`
: "Never"}

View file

@ -52,7 +52,7 @@ const pointerBlocked = (element: HTMLElement): boolean => {
* the option text alone is a race that React 19's flush timing loses.
*/
export const chooseSelectOption = async (
user: ReturnType<typeof userEvent.setup>,
user: Pick<ReturnType<typeof userEvent.setup>, "click">,
trigger: HTMLElement,
optionName: string | RegExp,
) => {