mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
test(ui): assert presence and state so dashboard tests fail when behaviour breaks
One test could not fail: it asserted on the Promise returned by an unawaited
findAllByText, and a pending Promise is always truthy, so the organization
detail view had no working coverage at all.
Rewrites 316 assertions across 128 files onto matchers that describe what the
user perceives, and awaits the queries that were being compared as Promises.
Most of this was mechanical, but the fixers behind these rules are not
trustworthy, so every site they damaged was repaired by hand. The quiet one
worth naming: prefer-to-have-text-content wraps strings in new RegExp() without
escaping, turning toContain("100K+ requests") into a pattern meaning "100
followed by one-or-more K". That compiles, lints clean, and keeps passing while
no longer asserting what it claims.
This commit is contained in:
parent
32e6346455
commit
6670061a33
128 changed files with 371 additions and 404 deletions
|
|
@ -33,7 +33,7 @@ describe("AgentControlPlaneView iframe", () => {
|
|||
expect(src).toBe("http://localhost:3300/");
|
||||
expect(src).not.toContain("/sessions");
|
||||
// title comes from the plugin's display_name, not a hardcoded label
|
||||
expect(iframe!.getAttribute("title")).toBe("Chat UI");
|
||||
expect(iframe!).toHaveAttribute("title", "Chat UI");
|
||||
});
|
||||
|
||||
it("does not double the slash when the plugin url has a trailing slash", () => {
|
||||
|
|
@ -43,7 +43,7 @@ describe("AgentControlPlaneView iframe", () => {
|
|||
url: "http://localhost:3300/",
|
||||
};
|
||||
const { container } = render(<AgentControlPlaneView />);
|
||||
expect(container.querySelector("iframe")!.getAttribute("src")).toBe("http://localhost:3300/");
|
||||
expect(container.querySelector("iframe")!).toHaveAttribute("src", "http://localhost:3300/");
|
||||
pluginModeValue.activePlugin = {
|
||||
name: "litellm-platform-plugin",
|
||||
display_name: "Chat UI",
|
||||
|
|
|
|||
|
|
@ -101,8 +101,8 @@ describe("AgentsTable", () => {
|
|||
);
|
||||
|
||||
const bodyRows = screen.getAllByRole("row").slice(1);
|
||||
expect(bodyRows[0].textContent).toContain("Beta Agent");
|
||||
expect(bodyRows[1].textContent).toContain("Alpha Agent");
|
||||
expect(bodyRows[0]).toHaveTextContent(/Beta Agent/);
|
||||
expect(bodyRows[1]).toHaveTextContent(/Alpha Agent/);
|
||||
});
|
||||
|
||||
it("sorts agents with no created_at last, never ahead of dated ones", () => {
|
||||
|
|
@ -118,9 +118,9 @@ describe("AgentsTable", () => {
|
|||
);
|
||||
|
||||
const bodyRows = screen.getAllByRole("row").slice(1);
|
||||
expect(bodyRows[0].textContent).toContain("Beta Agent");
|
||||
expect(bodyRows[1].textContent).toContain("Alpha Agent");
|
||||
expect(bodyRows[2].textContent).toContain("Undated Agent");
|
||||
expect(bodyRows[0]).toHaveTextContent(/Beta Agent/);
|
||||
expect(bodyRows[1]).toHaveTextContent(/Alpha Agent/);
|
||||
expect(bodyRows[2]).toHaveTextContent(/Undated Agent/);
|
||||
});
|
||||
|
||||
it("shows a rich empty state when there are no agents", () => {
|
||||
|
|
|
|||
|
|
@ -196,12 +196,10 @@ describe("AgentCardDiscovery", () => {
|
|||
);
|
||||
|
||||
expect(
|
||||
(
|
||||
screen.getByRole("button", {
|
||||
name: /discover/i,
|
||||
}) as HTMLButtonElement
|
||||
).disabled,
|
||||
).toBe(true);
|
||||
screen.getByRole("button", {
|
||||
name: /discover/i,
|
||||
}),
|
||||
).toBeDisabled();
|
||||
expect(mockDiscover).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ describe("APIReferenceView", () => {
|
|||
const { getAllByTestId } = render(<APIReferenceView proxySettings={{ LITELLM_UI_API_DOC_BASE_URL: apiDocUrl }} />);
|
||||
|
||||
const codeBlocks = getAllByTestId(codeBlockTestId);
|
||||
expect(codeBlocks[0].textContent).toContain(apiDocUrl);
|
||||
expect(codeBlocks[0]).toHaveTextContent(new RegExp(apiDocUrl));
|
||||
});
|
||||
|
||||
it("falls back to the proxy base url when the docs url is missing", () => {
|
||||
|
|
@ -24,7 +24,7 @@ describe("APIReferenceView", () => {
|
|||
const { getAllByTestId } = render(<APIReferenceView proxySettings={{ PROXY_BASE_URL: proxyUrl }} />);
|
||||
|
||||
const codeBlocks = getAllByTestId(codeBlockTestId);
|
||||
expect(codeBlocks[0].textContent).toContain(proxyUrl);
|
||||
expect(codeBlocks[0]).toHaveTextContent(new RegExp(proxyUrl));
|
||||
});
|
||||
|
||||
it("prefers the docs url when both urls are provided", () => {
|
||||
|
|
@ -49,12 +49,12 @@ describe("APIReferenceView", () => {
|
|||
it("renders the page title, blurb and docs link", () => {
|
||||
render(<APIReferenceView proxySettings={{ PROXY_BASE_URL: "https://proxy.litellm.test" }} />);
|
||||
|
||||
expect(screen.getByText("OpenAI Compatible Proxy: API Reference")).toBeTruthy();
|
||||
expect(screen.getByText(/LiteLLM is OpenAI Compatible/)).toBeTruthy();
|
||||
expect(screen.getByText("OpenAI Compatible Proxy: API Reference")).toBeInTheDocument();
|
||||
expect(screen.getByText(/LiteLLM is OpenAI Compatible/)).toBeInTheDocument();
|
||||
|
||||
const docsLink = screen.getByRole("link", { name: /API Reference Docs/ });
|
||||
expect(docsLink.getAttribute("href")).toBe("https://docs.litellm.ai/docs/proxy/user_keys");
|
||||
expect(docsLink.getAttribute("target")).toBe("_blank");
|
||||
expect(docsLink).toHaveAttribute("href", "https://docs.litellm.ai/docs/proxy/user_keys");
|
||||
expect(docsLink).toHaveAttribute("target", "_blank");
|
||||
});
|
||||
|
||||
it("exposes the three SDK tabs with the first selected by default", () => {
|
||||
|
|
@ -83,10 +83,10 @@ describe("APIReferenceView", () => {
|
|||
|
||||
await user.click(screen.getByRole("tab", { name: tabName }));
|
||||
|
||||
expect(screen.getByRole("tab", { name: tabName }).getAttribute("aria-selected")).toBe("true");
|
||||
expect(screen.getByRole("tab", { name: tabName })).toHaveAttribute("aria-selected", "true");
|
||||
|
||||
const selectedPanel = screen.getByRole("tabpanel");
|
||||
expect(selectedPanel.textContent).toContain(marker);
|
||||
expect(selectedPanel.textContent).toContain(proxyUrl);
|
||||
expect(selectedPanel).toHaveTextContent(new RegExp(marker));
|
||||
expect(selectedPanel).toHaveTextContent(new RegExp(proxyUrl));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ describe("BudgetTable", () => {
|
|||
const list = makeList({ rows: [makeBudget({ budget_id: budgetId })] });
|
||||
renderWithProviders(<BudgetTable {...defaultProps} list={list} />);
|
||||
const idCell = screen.getByText(budgetId);
|
||||
expect(idCell.className).not.toContain("truncate");
|
||||
expect(idCell).not.toHaveClass("truncate");
|
||||
expect(idCell.className).not.toMatch(/max-w-\[\d+(ch|rem|px)\]/);
|
||||
});
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ describe("BudgetTable", () => {
|
|||
const budgetId = "ecc1869c-6231-4380-a56d-1a0be457477d";
|
||||
const list = makeList({ rows: [makeBudget({ budget_id: budgetId })] });
|
||||
renderWithProviders(<BudgetTable {...defaultProps} list={list} />);
|
||||
expect(screen.getByText(budgetId).className).toContain("whitespace-nowrap");
|
||||
expect(screen.getByText(budgetId)).toHaveClass("whitespace-nowrap");
|
||||
});
|
||||
|
||||
it("should copy the budget id from the cell's copy button", async () => {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,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 } = render(
|
||||
const { getByRole, getByTestId, findByTestId } = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<CostOptimizationView accessToken="test-token" userId="u1" userRole="proxy_admin" />
|
||||
</QueryClientProvider>,
|
||||
|
|
@ -61,7 +61,7 @@ describe("CostOptimizationView daily activity", () => {
|
|||
await waitFor(() => expect(mockUserDailyActivityCall).toHaveBeenCalledTimes(1));
|
||||
|
||||
fireEvent.click(getByRole("tab", { name: "Prompt Caching" }));
|
||||
await waitFor(() => expect(getByTestId("caching-settings")).toBeInTheDocument());
|
||||
await findByTestId("caching-settings");
|
||||
|
||||
expect(mockUserDailyActivityCall).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ describe("UsageTab", () => {
|
|||
|
||||
// Per day switches to a bar chart of the unaccumulated daily savings, with no
|
||||
// synthetic anchor prepended.
|
||||
expect(queryByTestId("area-chart")).toBeNull();
|
||||
expect(queryByTestId("area-chart")).not.toBeInTheDocument();
|
||||
const series = readSeries(getByTestId("bar-chart"));
|
||||
expect(series).toHaveLength(2);
|
||||
expect(series[0]).toMatchObject({ Compression: 0.04, "Prompt caching": 0.006 });
|
||||
|
|
@ -264,7 +264,7 @@ describe("UsageTab", () => {
|
|||
|
||||
await userEvent.click(getByRole("tab", { name: "Per day" }));
|
||||
const bars = getByTestId("bar-chart");
|
||||
expect(bars.getAttribute("data-stack")).toBe("false");
|
||||
expect(bars).toHaveAttribute("data-stack", "false");
|
||||
expect(readSeries(bars)[0]).toMatchObject({ "Auto-router": -0.05 });
|
||||
});
|
||||
|
||||
|
|
@ -290,7 +290,7 @@ describe("UsageTab", () => {
|
|||
expect(before.action.contains(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.textContent).toContain("Running total saved");
|
||||
expect(before.description).toHaveTextContent(/Running total saved/);
|
||||
|
||||
await userEvent.click(getByRole("tab", { name: "Per day" }));
|
||||
|
||||
|
|
@ -298,8 +298,8 @@ describe("UsageTab", () => {
|
|||
expect(after.action).toBe(before.action);
|
||||
expect(after.cardHeader).toBe(before.cardHeader);
|
||||
expect(after.action.contains(after.description)).toBe(false);
|
||||
expect(after.description.textContent).toContain("Saved per day");
|
||||
expect(container.textContent).toContain("Savings");
|
||||
expect(after.description).toHaveTextContent(/Saved per day/);
|
||||
expect(container).toHaveTextContent(/Savings/);
|
||||
});
|
||||
|
||||
it("subtracts a losing auto-router route from the total and keeps it out of the donut", () => {
|
||||
|
|
@ -319,7 +319,7 @@ describe("UsageTab", () => {
|
|||
|
||||
const slices = JSON.parse(getByTestId("donut-chart").getAttribute("data-slices") ?? "[]");
|
||||
expect(slices.map((d: { driver: string }) => d.driver)).toEqual(["Compression", "Prompt caching"]);
|
||||
expect(getByTestId("donut-chart").getAttribute("data-label")).toBe("$0.1200");
|
||||
expect(getByTestId("donut-chart")).toHaveAttribute("data-label", "$0.1200");
|
||||
});
|
||||
|
||||
it("carries auto-router savings into the summary card, donut slice, and cumulative series", () => {
|
||||
|
|
@ -370,7 +370,7 @@ describe("UsageTab", () => {
|
|||
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
|
||||
// by default (other consumers keep their pre-existing geometry).
|
||||
expect(bars[0].getAttribute("data-max-bar-size")).toBe("64");
|
||||
expect(bars[0]).toHaveAttribute("data-max-bar-size", "64");
|
||||
});
|
||||
|
||||
it("renders the tool legend once outside the charts, with both charts sharing the tool colors", async () => {
|
||||
|
|
@ -387,8 +387,8 @@ describe("UsageTab", () => {
|
|||
|
||||
const bars = await findAllByTestId("bar-chart");
|
||||
const [totalByTool, dailyByTool] = bars.slice(-2);
|
||||
expect(dailyByTool.getAttribute("data-show-legend")).toBe("false");
|
||||
expect(totalByTool.getAttribute("data-colors")).toBe(dailyByTool.getAttribute("data-colors"));
|
||||
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");
|
||||
expect(toolLegends).toHaveLength(1);
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ describe("AddMarginForm", () => {
|
|||
|
||||
it("should enable the submit button when provider and percentage value are both provided", () => {
|
||||
renderWithProviders(<AddMarginForm {...DEFAULT_PROPS} selectedProvider="OpenAI" percentageValue="10" />);
|
||||
expect(screen.getByRole("button", { name: /add provider margin/i })).not.toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: /add provider margin/i })).toBeEnabled();
|
||||
});
|
||||
|
||||
it("should disable the submit button in fixed mode when no fixed amount is provided", () => {
|
||||
|
|
@ -81,7 +81,7 @@ describe("AddMarginForm", () => {
|
|||
renderWithProviders(
|
||||
<AddMarginForm {...DEFAULT_PROPS} selectedProvider="OpenAI" marginType="fixed" fixedAmountValue="0.001" />,
|
||||
);
|
||||
expect(screen.getByRole("button", { name: /add provider margin/i })).not.toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: /add provider margin/i })).toBeEnabled();
|
||||
});
|
||||
|
||||
it("should call onAddProvider when the enabled submit button is clicked", async () => {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ describe("AddProviderForm", () => {
|
|||
|
||||
it("should enable the submit button when both a provider and a discount value are provided", () => {
|
||||
renderWithProviders(<AddProviderForm {...DEFAULT_PROPS} selectedProvider="OpenAI" newDiscount="5" />);
|
||||
expect(screen.getByRole("button", { name: /add provider discount/i })).not.toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: /add provider discount/i })).toBeEnabled();
|
||||
});
|
||||
|
||||
it("should call onAddProvider when the enabled submit button is clicked", async () => {
|
||||
|
|
@ -71,7 +71,7 @@ describe("AddProviderForm", () => {
|
|||
renderWithProviders(<AddProviderForm {...DEFAULT_PROPS} selectedProvider="OpenAI" />);
|
||||
|
||||
const logo = await screen.findByRole("img", { name: `${Providers.OpenAI} logo` });
|
||||
expect(logo.getAttribute("src")).toBe(providerLogoMap[Providers.OpenAI]);
|
||||
expect(logo).toHaveAttribute("src", providerLogoMap[Providers.OpenAI]);
|
||||
});
|
||||
|
||||
it("falls back to a letter avatar for a selected provider that has no bundled logo", () => {
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ describe("CostTrackingSettings", () => {
|
|||
const { container } = renderWithProviders(
|
||||
<CostTrackingSettings userID="user-1" userRole="proxy_admin" accessToken={null} />,
|
||||
);
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("should render the page title", () => {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ describe("HowItWorks", () => {
|
|||
it("should render the code block with a curl example", () => {
|
||||
renderWithProviders(<HowItWorks />);
|
||||
expect(screen.getByTestId("code-block")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("code-block").textContent).toContain("curl");
|
||||
expect(screen.getByTestId("code-block")).toHaveTextContent(/curl/);
|
||||
});
|
||||
|
||||
it("should show the response header names for discount verification", () => {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ describe("MultiExportDropdown", () => {
|
|||
|
||||
it("should not render anything when no entries have results", () => {
|
||||
const { container } = renderWithProviders(<MultiExportDropdown multiResult={makeMultiResult(false)} />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("should render the Export button when at least one entry has a result", () => {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ describe("ProviderMarginTable", () => {
|
|||
/>,
|
||||
);
|
||||
const logo = screen.getByRole("img", { name: `${Providers.OpenAI} logo` });
|
||||
expect(logo.getAttribute("src")).toBe(providerLogoMap[Providers.OpenAI]);
|
||||
expect(logo).toHaveAttribute("src", providerLogoMap[Providers.OpenAI]);
|
||||
});
|
||||
|
||||
it("should fall back to a letter avatar for a provider with no bundled logo", () => {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ describe("GuardrailsMonitorView", () => {
|
|||
|
||||
render(<GuardrailsMonitorView accessToken="test-token" />, { wrapper });
|
||||
|
||||
expect(await screen.findByRole("heading", { name: /Guardrails Monitor/i })).toBeDefined();
|
||||
expect(await screen.findByRole("heading", { name: /Guardrails Monitor/i })).toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(mockGetGuardrailsUsageOverview).toHaveBeenCalled();
|
||||
});
|
||||
|
|
@ -40,6 +40,6 @@ describe("GuardrailsMonitorView", () => {
|
|||
|
||||
it("should render without crashing when accessToken is null", async () => {
|
||||
render(<GuardrailsMonitorView accessToken={null} />, { wrapper });
|
||||
expect(await screen.findByRole("heading", { name: /Guardrails Monitor/i })).toBeDefined();
|
||||
expect(await screen.findByRole("heading", { name: /Guardrails Monitor/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -53,6 +53,6 @@ describe("AddGuardrailForm provider options", () => {
|
|||
fireEvent.mouseDown(screen.getByLabelText("Guardrail Provider"));
|
||||
|
||||
const logo = await screen.findByAltText("Presidio PII logo");
|
||||
expect(logo.getAttribute("src")).toContain("microsoft_azure.svg");
|
||||
expect(logo).toHaveAttribute("src", expect.stringContaining("microsoft_azure.svg"));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ describe("ContentFilterManager", () => {
|
|||
|
||||
expect(screen.queryByTestId("content-filter-config")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("content-filter-display")).not.toBeInTheDocument();
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("should render read-only display when isEditing is false", async () => {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ describe("GuardrailCard", () => {
|
|||
it("should render the logo through the shared Logo component with the card src", () => {
|
||||
render(<GuardrailCard card={baseCard} onClick={vi.fn()} />);
|
||||
const img = screen.getByAltText("Test Guardrail logo");
|
||||
expect(img.getAttribute("src")).toContain("/logos/test.svg");
|
||||
expect(img).toHaveAttribute("src", expect.stringContaining("/logos/test.svg"));
|
||||
});
|
||||
|
||||
it("should pass a bundled static-import src through unchanged", () => {
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ describe("Guardrail Info", () => {
|
|||
);
|
||||
|
||||
const logo = await findByAltText("Presidio PII logo");
|
||||
expect(logo.getAttribute("src")).toContain("microsoft_azure.svg");
|
||||
expect(logo).toHaveAttribute("src", expect.stringContaining("microsoft_azure.svg"));
|
||||
});
|
||||
|
||||
it("should not render the edit button for config guardrails", async () => {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ describe("GuardrailTable", () => {
|
|||
it("renders the provider logo from the bundled guardrail logo map", () => {
|
||||
render(<GuardrailTable guardrailsList={[makeGuardrail()]} {...baseProps} />);
|
||||
const logo = screen.getByAltText("Presidio PII logo");
|
||||
expect(logo.getAttribute("src")).toContain("microsoft_azure.svg");
|
||||
expect(logo).toHaveAttribute("src", expect.stringContaining("microsoft_azure.svg"));
|
||||
});
|
||||
|
||||
it("falls back to a letter avatar for an unknown provider slug", () => {
|
||||
|
|
|
|||
|
|
@ -82,15 +82,15 @@ describe("(dashboard) Layout", () => {
|
|||
</AuthProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(screen.getByTestId("loading-screen")).toBeTruthy());
|
||||
expect(screen.queryByTestId("page-content")).toBeNull();
|
||||
expect(screen.queryByTestId("dashboard-header")).toBeNull();
|
||||
expect(await screen.findByTestId("loading-screen")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("page-content")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("dashboard-header")).not.toBeInTheDocument();
|
||||
|
||||
pendingUiConfig.resolve();
|
||||
|
||||
await waitFor(() => expect(screen.getByTestId("page-content")).toBeTruthy());
|
||||
expect(screen.getByTestId("dashboard-header")).toBeTruthy();
|
||||
expect(screen.queryByTestId("loading-screen")).toBeNull();
|
||||
expect(await screen.findByTestId("page-content")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("dashboard-header")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("loading-screen")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("redirects an invitation link to the onboarding route instead of rendering the dashboard shell", async () => {
|
||||
|
|
@ -109,8 +109,8 @@ describe("(dashboard) Layout", () => {
|
|||
await waitFor(() =>
|
||||
expect(replaceMock).toHaveBeenCalledWith(expect.stringContaining("/onboarding?invitation_id=abc123")),
|
||||
);
|
||||
expect(screen.queryByTestId("page-content")).toBeNull();
|
||||
expect(screen.queryByTestId("dashboard-header")).toBeNull();
|
||||
expect(screen.queryByTestId("sidebar")).toBeNull();
|
||||
expect(screen.queryByTestId("page-content")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("dashboard-header")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("sidebar")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -676,7 +676,7 @@ describe("CreateMCPServer", () => {
|
|||
it("clears the DCR ref and the upstream warning when the modal closes so nothing leaks to the next session", async () => {
|
||||
const { rerender } = render(<CreateMCPServer {...defaultProps} />);
|
||||
await selectAntOption("Transport Type", "Streamable HTTP");
|
||||
await waitFor(() => expect(screen.getByPlaceholderText("https://your-mcp-server.com")).toBeInTheDocument());
|
||||
expect(await screen.findByPlaceholderText("https://your-mcp-server.com")).toBeInTheDocument();
|
||||
const user = userEvent.setup({ delay: null });
|
||||
await user.type(getServerNameInput(), "Leak_Server");
|
||||
await user.type(screen.getByPlaceholderText("https://your-mcp-server.com"), "https://example.com/mcp");
|
||||
|
|
|
|||
|
|
@ -69,6 +69,6 @@ describe("MCPLogoSelector", () => {
|
|||
|
||||
it("should preview a custom external URL untouched", () => {
|
||||
render(<MCPLogoSelector value="https://cdn.example.com/logo.png" />);
|
||||
expect(screen.getByAltText("Selected logo").getAttribute("src")).toBe("https://cdn.example.com/logo.png");
|
||||
expect(screen.getByAltText("Selected logo")).toHaveAttribute("src", "https://cdn.example.com/logo.png");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ describe("MCPPermissionManagement", () => {
|
|||
// The first switch in the component is for allow_all_keys
|
||||
const switches = screen.getAllByRole("switch");
|
||||
const toggle = switches[0];
|
||||
expect(toggle).toHaveAttribute("aria-checked", "false");
|
||||
expect(toggle).not.toBeChecked();
|
||||
});
|
||||
|
||||
const renderWithInitialValues = (initialValues: Record<string, unknown>, props = {}) => {
|
||||
|
|
@ -122,9 +122,9 @@ describe("MCPPermissionManagement", () => {
|
|||
// The first switch in the component is for allow_all_keys
|
||||
const switches = screen.getAllByRole("switch");
|
||||
const toggle = switches[0];
|
||||
expect(toggle).toHaveAttribute("aria-checked", "true");
|
||||
expect(toggle).toBeChecked();
|
||||
|
||||
await user.click(toggle);
|
||||
expect(toggle).toHaveAttribute("aria-checked", "false");
|
||||
expect(toggle).not.toBeChecked();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -52,13 +52,13 @@ describe("MCPServerCard logo", () => {
|
|||
|
||||
it("passes an external logo_url through untouched", () => {
|
||||
renderCard({ mcp_info: { server_name: "demo_server", logo_url: "https://cdn.example.com/logo.png" } });
|
||||
expect(screen.getByAltText("demo_server logo").getAttribute("src")).toBe("https://cdn.example.com/logo.png");
|
||||
expect(screen.getByAltText("demo_server logo")).toHaveAttribute("src", "https://cdn.example.com/logo.png");
|
||||
});
|
||||
|
||||
it("prefixes a stored asset path with the server root path under a non-root mount", () => {
|
||||
setServerRootPath("/litellm");
|
||||
renderCard({ mcp_info: { server_name: "demo_server", logo_url: "/ui/assets/logos/github.svg" } });
|
||||
expect(screen.getByAltText("demo_server logo").getAttribute("src")).toBe("/litellm/ui/assets/logos/github.svg");
|
||||
expect(screen.getByAltText("demo_server logo")).toHaveAttribute("src", "/litellm/ui/assets/logos/github.svg");
|
||||
});
|
||||
|
||||
it("renders a letter avatar when no logo_url is set", () => {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ describe("MCPConnectionStatus", () => {
|
|||
|
||||
it("should render nothing when canFetchTools is false and no URL is set", () => {
|
||||
const { container } = render(<MCPConnectionStatus {...defaultProps} formValues={{}} />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("should show 'Complete required fields' message when URL is set but canFetchTools is false", () => {
|
||||
|
|
|
|||
|
|
@ -1214,8 +1214,9 @@ describe("MCPServerEdit (tool list fetch)", () => {
|
|||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("mcp-tool-config").getAttribute("data-external-error")).toContain(
|
||||
"Authorize with the upstream (browser-only",
|
||||
expect(screen.getByTestId("mcp-tool-config")).toHaveAttribute(
|
||||
"data-external-error",
|
||||
expect.stringContaining("Authorize with the upstream (browser-only"),
|
||||
);
|
||||
});
|
||||
expect(networking.listMCPTools).not.toHaveBeenCalled();
|
||||
|
|
@ -1242,8 +1243,9 @@ describe("MCPServerEdit (tool list fetch)", () => {
|
|||
const { rerender } = render(<MCPServerEdit {...props} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("mcp-tool-config").getAttribute("data-external-error")).toContain(
|
||||
"Authenticate with this server in the Tools tab",
|
||||
expect(screen.getByTestId("mcp-tool-config")).toHaveAttribute(
|
||||
"data-external-error",
|
||||
expect.stringContaining("Authenticate with this server in the Tools tab"),
|
||||
);
|
||||
});
|
||||
expect(networking.listMCPTools).not.toHaveBeenCalled();
|
||||
|
|
@ -1277,8 +1279,9 @@ describe("MCPServerEdit (tool list fetch)", () => {
|
|||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("mcp-tool-config").getAttribute("data-external-error")).toContain(
|
||||
"Authenticate with this server in the Tools tab",
|
||||
expect(screen.getByTestId("mcp-tool-config")).toHaveAttribute(
|
||||
"data-external-error",
|
||||
expect.stringContaining("Authenticate with this server in the Tools tab"),
|
||||
);
|
||||
});
|
||||
expect(networking.listMCPTools).not.toHaveBeenCalled();
|
||||
|
|
@ -1657,9 +1660,7 @@ describe("MCPServerEdit (OAuth token persistence on save)", () => {
|
|||
|
||||
// Check "remove saved app" on server A.
|
||||
fireEvent.click(screen.getByRole("checkbox", { name: /Remove the saved OAuth app on save/ }));
|
||||
expect(
|
||||
(screen.getByRole("checkbox", { name: /Remove the saved OAuth app on save/ }) as HTMLInputElement).checked,
|
||||
).toBe(true);
|
||||
expect(screen.getByRole("checkbox", { name: /Remove the saved OAuth app on save/ })).toBeChecked();
|
||||
|
||||
// Switch the panel to server B without unmounting.
|
||||
rerender(
|
||||
|
|
@ -1674,9 +1675,7 @@ describe("MCPServerEdit (OAuth token persistence on save)", () => {
|
|||
);
|
||||
|
||||
// The checkbox must have reset, so saving server B does not send the explicit-null delete write.
|
||||
expect(
|
||||
(screen.getByRole("checkbox", { name: /Remove the saved OAuth app on save/ }) as HTMLInputElement).checked,
|
||||
).toBe(false);
|
||||
expect(screen.getByRole("checkbox", { name: /Remove the saved OAuth app on save/ })).not.toBeChecked();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getAllByRole("button", { name: "Save Changes" })[0]);
|
||||
|
|
|
|||
|
|
@ -76,14 +76,14 @@ describe("ModelsAndEndpointsPage", () => {
|
|||
const { getByRole, getByTestId, queryByTestId } = renderPage();
|
||||
await user.click(getByRole("tab", { name: "Health Status" }));
|
||||
expect(getByTestId("panel-health")).toBeInTheDocument();
|
||||
expect(queryByTestId("panel-all-models")).toBeNull();
|
||||
expect(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" })).toBeNull();
|
||||
expect(queryByRole("tab", { name: "All Models" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the team detail overlay from the ?team drill-in", () => {
|
||||
|
|
@ -95,8 +95,8 @@ describe("ModelsAndEndpointsPage", () => {
|
|||
it("hides admin-only tabs for a non-admin user", () => {
|
||||
mockUseAuthorized.mockReturnValue(NON_ADMIN);
|
||||
const { queryByRole } = renderPage();
|
||||
expect(queryByRole("tab", { name: "LLM Credentials" })).toBeNull();
|
||||
expect(queryByRole("tab", { name: "Health Status" })).toBeNull();
|
||||
expect(queryByRole("tab", { name: "LLM Credentials" })).not.toBeInTheDocument();
|
||||
expect(queryByRole("tab", { name: "Health Status" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Auto-routers are excluded from the All Models table, so this tab is their home: the only
|
||||
|
|
@ -125,7 +125,7 @@ describe("ModelsAndEndpointsPage", () => {
|
|||
mockUseAuthorized.mockReturnValue(NON_ADMIN);
|
||||
const { queryByRole } = renderPage();
|
||||
|
||||
expect(queryByRole("tab", { name: /Auto-Routers/ })).toBeNull();
|
||||
expect(queryByRole("tab", { name: /Auto-Routers/ })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -39,13 +39,13 @@ describe("AdditionalModelSettings", () => {
|
|||
await waitFor(() => {
|
||||
const sliders = screen.getAllByRole("slider");
|
||||
expect(sliders.length).toBeGreaterThan(0);
|
||||
expect(sliders[0]).not.toBeDisabled();
|
||||
expect(sliders[0]).toBeEnabled();
|
||||
});
|
||||
|
||||
const temperatureSlider = screen.getAllByRole("slider")[0];
|
||||
const maxTokensSlider = screen.getAllByRole("slider")[1];
|
||||
expect(temperatureSlider).not.toBeDisabled();
|
||||
expect(maxTokensSlider).not.toBeDisabled();
|
||||
expect(temperatureSlider).toBeEnabled();
|
||||
expect(maxTokensSlider).toBeEnabled();
|
||||
});
|
||||
|
||||
it("should not show Stream responses when onStreamingChange is not provided", () => {
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ describe("AgentBuilderView", () => {
|
|||
|
||||
expect(screen.getByRole("button", { name: "research-agent litellm_agent" })).toBeInTheDocument();
|
||||
expect(screen.getByText("Agent Builder")).toBeInTheDocument();
|
||||
await waitFor(() => expect(screen.getByDisplayValue("support-agent")).toBeInTheDocument());
|
||||
expect(await screen.findByDisplayValue("support-agent")).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue("Be helpful.")).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue("0.3")).toBeInTheDocument();
|
||||
});
|
||||
|
|
@ -144,7 +144,7 @@ describe("AgentBuilderView", () => {
|
|||
|
||||
await user.click(screen.getByRole("button", { name: "research-agent litellm_agent" }));
|
||||
|
||||
await waitFor(() => expect(screen.getByDisplayValue("research-agent")).toBeInTheDocument());
|
||||
expect(await screen.findByDisplayValue("research-agent")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("offers a blank draft and a save control for a new agent", async () => {
|
||||
|
|
|
|||
|
|
@ -109,6 +109,6 @@ describe("ChatComposer", () => {
|
|||
|
||||
fireEvent.click(addonOf(container));
|
||||
|
||||
expect(document.activeElement).toBe(screen.getByTestId("chat-composer-input"));
|
||||
expect(screen.getByTestId("chat-composer-input")).toHaveFocus();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -148,10 +148,10 @@ describe("ChatUI", () => {
|
|||
await waitFor(() => {
|
||||
expect(screen.getAllByText("ChatModel").length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText("NoModeModel").length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText("SpeechModel")).toBeNull();
|
||||
expect(screen.queryByText("ImageModel")).toBeNull();
|
||||
expect(screen.queryByText("ResponsesModel")).toBeNull();
|
||||
expect(screen.queryByText("RealtimeModel")).toBeNull();
|
||||
expect(screen.queryByText("SpeechModel")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("ImageModel")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("ResponsesModel")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("RealtimeModel")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -182,7 +182,7 @@ describe("ChatUI", () => {
|
|||
await waitFor(() => {
|
||||
expect(screen.getAllByText("RealtimeModel").length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText("NoModeModel").length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText("ChatModel")).toBeNull();
|
||||
expect(screen.queryByText("ChatModel")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ describe("ChatUI", () => {
|
|||
await selectComboboxOption("Select an endpoint", "/v1/chat/completions");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mcpInput()).not.toBeDisabled();
|
||||
expect(mcpInput()).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -432,7 +432,7 @@ describe("ChatUI", () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText("Fill")).toBeNull();
|
||||
expect(screen.queryByText("Fill")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
const customProxyInput = screen.getByPlaceholderText(
|
||||
|
|
@ -461,7 +461,7 @@ describe("ChatUI", () => {
|
|||
|
||||
const mcpInput = screen.getByLabelText("Select MCP servers");
|
||||
expect(mcpInput).toBeInTheDocument();
|
||||
expect(mcpInput).not.toBeDisabled();
|
||||
expect(mcpInput).toBeEnabled();
|
||||
|
||||
await user.click(mcpInput);
|
||||
|
||||
|
|
@ -521,7 +521,7 @@ describe("ChatUI", () => {
|
|||
await waitFor(() => {
|
||||
expect(screen.getAllByText("ChatModel").length).toBeGreaterThan(0);
|
||||
});
|
||||
expect(screen.queryByText("SpeechModel")).toBeNull();
|
||||
expect(screen.queryByText("SpeechModel")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should attach an audio file dropped on the transcription upload area", async () => {
|
||||
|
|
@ -611,7 +611,7 @@ describe("ChatUI", () => {
|
|||
await user.clear(keyField);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText("Select a Model")).not.toBeDisabled();
|
||||
expect(screen.getByPlaceholderText("Select a Model")).toBeEnabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ describe("CodeInterpreterOutput", () => {
|
|||
it("should return null when no code and no annotations", () => {
|
||||
const { container } = render(<CodeInterpreterOutput accessToken="test-token" />);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("should handle multiple image formats", async () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { act, render, screen, waitFor } from "@testing-library/react";
|
||||
import { act, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import RealtimePlayground from "./RealtimePlayground";
|
||||
|
|
@ -261,7 +261,7 @@ describe("RealtimePlayground", () => {
|
|||
await user.click(screen.getByRole("button", { name: /Disconnect/i }));
|
||||
|
||||
expect(socket.close).toHaveBeenCalled();
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: /Connect/i })).toBeInTheDocument());
|
||||
expect(await screen.findByRole("button", { name: /Connect/i })).toBeInTheDocument();
|
||||
expect(screen.queryByPlaceholderText("Type a message or use the mic...")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -138,14 +138,14 @@ describe("CompareUI", () => {
|
|||
}
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByTestId("has-attachment")).toBeInTheDocument();
|
||||
expect(getByTestId("has-attachment")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const textarea = getByTestId("message-textarea");
|
||||
await user.type(textarea, "Describe this image");
|
||||
|
||||
const sendButton = getByTestId("send-button");
|
||||
expect(sendButton).not.toBeDisabled();
|
||||
expect(sendButton).toBeEnabled();
|
||||
await user.click(sendButton);
|
||||
|
||||
await waitFor(() => {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ describe("MessageInput", () => {
|
|||
render(<MessageInput value="hello" onChange={vi.fn()} onSend={onSend} />);
|
||||
|
||||
const send = screen.getByRole("button");
|
||||
expect(send).not.toBeDisabled();
|
||||
expect(send).toBeEnabled();
|
||||
|
||||
await user.click(send);
|
||||
expect(onSend).toHaveBeenCalledTimes(1);
|
||||
|
|
@ -63,7 +63,7 @@ describe("MessageInput", () => {
|
|||
expect(screen.getByTestId("upload-component")).toBeInTheDocument();
|
||||
|
||||
const send = screen.getByRole("button");
|
||||
expect(send).not.toBeDisabled();
|
||||
expect(send).toBeEnabled();
|
||||
|
||||
await user.click(send);
|
||||
expect(onSend).toHaveBeenCalledTimes(1);
|
||||
|
|
|
|||
|
|
@ -135,9 +135,9 @@ describe("AttachmentTable", () => {
|
|||
const attachment = makeAttachment({ attachment_id: "att-abcdef1234567" });
|
||||
renderWithProviders(<AttachmentTable {...defaultProps} attachments={[attachment]} />);
|
||||
const idElement = screen.getByText("att-abcdef1234567");
|
||||
expect(idElement.className).toContain("font-mono");
|
||||
expect(idElement.className).toContain("truncate");
|
||||
expect(idElement.className).not.toContain("bg-blue-50");
|
||||
expect(idElement).toHaveClass("font-mono");
|
||||
expect(idElement).toHaveClass("truncate");
|
||||
expect(idElement).not.toHaveClass("bg-blue-50");
|
||||
});
|
||||
|
||||
it("should render model chips when the attachment has models", () => {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ describe("AiSuggestionModal", () => {
|
|||
expect(screen.getByRole("button", { name: "Suggest Policies" })).toBeDisabled();
|
||||
|
||||
await pickModel(user);
|
||||
expect(screen.getByRole("button", { name: "Suggest Policies" })).not.toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: "Suggest Policies" })).toBeEnabled();
|
||||
});
|
||||
|
||||
it("sends the examples, description and model to the suggest API", async () => {
|
||||
|
|
|
|||
|
|
@ -156,8 +156,8 @@ describe("PoliciesPanel attachment delete", () => {
|
|||
|
||||
await waitFor(() => {
|
||||
expect(networkingMocks.deletePolicyAttachmentCall).toHaveBeenCalledTimes(1);
|
||||
expect(networkingMocks.deletePolicyAttachmentCall).toHaveBeenCalledWith("test-token", EXPECTED_ATTACHMENT_ID);
|
||||
});
|
||||
expect(networkingMocks.deletePolicyAttachmentCall).toHaveBeenCalledWith("test-token", EXPECTED_ATTACHMENT_ID);
|
||||
});
|
||||
|
||||
it("should show mutation pending state while attachment delete is in flight", async () => {
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ describe("TemplateParameterModal", () => {
|
|||
|
||||
await user.type(screen.getByPlaceholderText("e.g. Contoso"), "Contoso");
|
||||
|
||||
expect(screen.getByRole("button", { name: "Continue" })).not.toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: "Continue" })).toBeEnabled();
|
||||
});
|
||||
|
||||
it("hands the entered parameters back to the caller", async () => {
|
||||
|
|
@ -181,7 +181,7 @@ describe("TemplateParameterModal", () => {
|
|||
expect(await screen.findByText("Northwind")).toBeInTheDocument();
|
||||
expect(screen.getByText("Fabrikam")).toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: "Continue" })).not.toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: "Continue" })).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -12,17 +12,20 @@ describe("SearchProviderLabel", () => {
|
|||
it("renders the exa_ai logo file for the exa_ai slug", () => {
|
||||
render(<SearchProviderLabel providerName="exa_ai" displayName="Exa AI" />);
|
||||
const img = screen.getByRole("img", { name: "Exa AI logo" });
|
||||
expect(img.getAttribute("src")).toContain("exa_ai.png");
|
||||
expect(img).toHaveAttribute("src", expect.stringContaining("exa_ai.png"));
|
||||
});
|
||||
|
||||
it("renders the google_pse logo file for the google_pse slug", () => {
|
||||
render(<SearchProviderLabel providerName="google_pse" displayName="Google PSE" />);
|
||||
expect(screen.getByRole("img", { name: "Google PSE logo" }).getAttribute("src")).toContain("google_pse.png");
|
||||
expect(screen.getByRole("img", { name: "Google PSE logo" })).toHaveAttribute(
|
||||
"src",
|
||||
expect.stringContaining("google_pse.png"),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to a letter avatar for a provider with no bundled logo", () => {
|
||||
render(<SearchProviderLabel providerName="brave" displayName="Brave Search" />);
|
||||
expect(screen.queryByRole("img")).toBeNull();
|
||||
expect(screen.queryByRole("img")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("B")).toBeInTheDocument();
|
||||
expect(screen.getByText("Brave Search")).toBeInTheDocument();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ describe("SearchToolTester", () => {
|
|||
const input = screen.getByPlaceholderText("Enter your search query...");
|
||||
await user.type(input, "test query");
|
||||
const searchButton = screen.getByRole("button", { name: /search/i });
|
||||
expect(searchButton).not.toBeDisabled();
|
||||
expect(searchButton).toBeEnabled();
|
||||
});
|
||||
|
||||
it("should call searchToolQueryCall when search button is clicked", async () => {
|
||||
|
|
@ -416,11 +416,9 @@ describe("SearchToolTester", () => {
|
|||
await user.type(input, "test query");
|
||||
const searchButton = screen.getByRole("button", { name: /search/i });
|
||||
await user.click(searchButton);
|
||||
await waitFor(() => {
|
||||
const link = screen.getByRole("link", { name: "Test Result 1" });
|
||||
expect(link).toHaveAttribute("href", "https://example.com/result1");
|
||||
expect(link).toHaveAttribute("target", "_blank");
|
||||
expect(link).toHaveAttribute("rel", "noopener noreferrer");
|
||||
});
|
||||
const link = await screen.findByRole("link", { name: "Test Result 1" });
|
||||
expect(link).toHaveAttribute("href", "https://example.com/result1");
|
||||
expect(link).toHaveAttribute("rel", "noopener noreferrer");
|
||||
expect(link).toHaveAttribute("target", "_blank");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ describe("AddPluginForm", () => {
|
|||
await waitFor(() => {
|
||||
expect(screen.getByText(/Git repo/)).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByPlaceholderText(SUBPATH_PLACEHOLDER)).not.toBeDisabled();
|
||||
expect(screen.getByPlaceholderText(SUBPATH_PLACEHOLDER)).toBeEnabled();
|
||||
});
|
||||
|
||||
it("combines a repo URL with a subfolder into a git-subdir preview", async () => {
|
||||
|
|
|
|||
|
|
@ -76,10 +76,10 @@ describe("TransformRequestPanel", () => {
|
|||
});
|
||||
|
||||
const output = await screen.findByText(/api\.anthropic\.com\/v1\/messages/);
|
||||
expect(output.textContent).toContain("curl -X POST");
|
||||
expect(output.textContent).toContain("-H 'x-api-key: redacted'");
|
||||
expect(output.textContent).toContain('"model": "claude-opus-4-8"');
|
||||
expect(output.textContent).toContain('"max_tokens": 42');
|
||||
expect(output).toHaveTextContent(/curl \-X POST/);
|
||||
expect(output).toHaveTextContent(/\-H 'x\-api\-key: redacted'/);
|
||||
expect(output).toHaveTextContent(/"model": "claude\-opus\-4\-8"/);
|
||||
expect(output).toHaveTextContent(/"max_tokens": 42/);
|
||||
expect(notify.success).toHaveBeenCalledWith("Request transformed successfully");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ describe("EndpointUsageLineChart", () => {
|
|||
|
||||
const legend = container.querySelector(".recharts-legend-wrapper");
|
||||
expect(legend).not.toBeNull();
|
||||
expect(legend!.textContent).toContain("/chat/completions");
|
||||
expect(legend!.textContent).toContain("/embeddings");
|
||||
expect(legend!).toHaveTextContent(/\/chat\/completions/);
|
||||
expect(legend!).toHaveTextContent(/\/embeddings/);
|
||||
});
|
||||
|
||||
it("orders formatted dates oldest to newest on the x axis", () => {
|
||||
|
|
|
|||
|
|
@ -838,7 +838,7 @@ describe("EntityUsage", () => {
|
|||
|
||||
const sectors = container.querySelectorAll(".recharts-pie-sector path");
|
||||
expect(sectors).toHaveLength(1);
|
||||
expect(sectors[0].getAttribute("fill")).toBe("var(--color-cyan-500, #06b6d4)");
|
||||
expect(sectors[0]).toHaveAttribute("fill", "var(--color-cyan-500, #06b6d4)");
|
||||
|
||||
const centerLabels = Array.from(container.querySelectorAll("text.fill-foreground")).map((text) => text.textContent);
|
||||
expect(centerLabels).toContain("$100.50");
|
||||
|
|
@ -883,7 +883,7 @@ describe("EntityUsage", () => {
|
|||
render(<EntityUsage {...defaultProps} />);
|
||||
|
||||
const logo = await screen.findByAltText("openai logo");
|
||||
expect(logo.getAttribute("src")).toContain("openai_small");
|
||||
expect(logo).toHaveAttribute("src", expect.stringContaining("openai_small"));
|
||||
});
|
||||
|
||||
describe("capability gating", () => {
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ describe("UserEditView", () => {
|
|||
|
||||
await waitFor(() => {
|
||||
const modelsSelect = screen.getByRole("combobox", { name: /select models/i });
|
||||
expect(modelsSelect).not.toBeDisabled();
|
||||
expect(modelsSelect).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -300,7 +300,7 @@ describe("UserEditView", () => {
|
|||
|
||||
await waitFor(() => {
|
||||
const budgetInput = screen.getByRole("spinbutton", { name: /max budget/i });
|
||||
expect(budgetInput).not.toBeDisabled();
|
||||
expect(budgetInput).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ describe("ViewUserDashboard", () => {
|
|||
|
||||
await user.click(screen.getByTestId("datatable-select-row-user-2"));
|
||||
expect(screen.getByTestId("bulk-edit-users")).toHaveTextContent("Bulk Edit (1 selected)");
|
||||
expect(screen.getByTestId("bulk-edit-users")).not.toBeDisabled();
|
||||
expect(screen.getByTestId("bulk-edit-users")).toBeEnabled();
|
||||
|
||||
await user.click(screen.getByTestId("datatable-select-all"));
|
||||
expect(screen.getByTestId("bulk-edit-users")).toHaveTextContent("Bulk Edit (2 selected)");
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ describe("UsersTable", () => {
|
|||
"Created At",
|
||||
"Updated At",
|
||||
].forEach((header) => {
|
||||
expect(headerRow.textContent).toContain(header);
|
||||
expect(headerRow).toHaveTextContent(header);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -251,7 +251,7 @@ describe("UsersTable", () => {
|
|||
|
||||
await user.click(screen.getByTestId("datatable-select-row-user-1"));
|
||||
|
||||
expect(screen.getByTestId("datatable-select-all")).toHaveAttribute("aria-checked", "mixed");
|
||||
expect(screen.getByTestId("datatable-select-all")).toBePartiallyChecked();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -226,9 +226,9 @@ describe("CreateVectorStore", () => {
|
|||
|
||||
// Check if S3-specific fields are displayed
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText("Vector Bucket Name")).toBeInTheDocument();
|
||||
expect(screen.queryByText("AWS Region")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Embedding Model")).toBeInTheDocument();
|
||||
expect(screen.getByText("Vector Bucket Name")).toBeInTheDocument();
|
||||
expect(screen.getByText("AWS Region")).toBeInTheDocument();
|
||||
expect(screen.getByText("Embedding Model")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ describe("IndexesTable", () => {
|
|||
it("should link created_by to the user detail deep link", () => {
|
||||
render(<IndexesTable data={[newerIndex]} resolveVectorStoreId={noResolve} onViewVectorStore={vi.fn()} />);
|
||||
const link = screen.getByRole("link", { name: "admin@example.com" });
|
||||
expect(link.getAttribute("href")).toMatch(/\/users\?user=admin%40example\.com$/);
|
||||
expect(link).toHaveAttribute("href", expect.stringMatching(/\/users\?user=admin%40example\.com$/));
|
||||
});
|
||||
|
||||
it("should keep the dash fallback and render no link for a null created_by", () => {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,6 @@ describe("VectorStoreForm", () => {
|
|||
renderForm();
|
||||
|
||||
const logo = screen.getByRole("img", { name: `${VectorStoreProviders.Bedrock} logo` });
|
||||
expect(logo.getAttribute("src")).toBe(providerLogoMap[Providers.Bedrock]);
|
||||
expect(logo).toHaveAttribute("src", providerLogoMap[Providers.Bedrock]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ const ON_TOTAL_LATENCY_INDEX = 24;
|
|||
|
||||
async function sendOneMessage(): Promise<void> {
|
||||
render(<ChatConversationPage />);
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: /gpt-5\.4-mini/ })).toBeInTheDocument());
|
||||
expect(await screen.findByRole("button", { name: /gpt-5\.4-mini/ })).toBeInTheDocument();
|
||||
fireEvent.change(screen.getByPlaceholderText("How can I help you today?"), {
|
||||
target: { value: "How much did this cost?" },
|
||||
});
|
||||
|
|
@ -98,7 +98,7 @@ describe("/ui/chat request metrics", () => {
|
|||
|
||||
await sendOneMessage();
|
||||
|
||||
await waitFor(() => expect(screen.getByLabelText("Total: 20")).toBeInTheDocument());
|
||||
expect(await screen.findByLabelText("Total: 20")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("TTFT: 0.25s")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("Total Latency: 1.20s")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("In: 12")).toBeInTheDocument();
|
||||
|
|
@ -126,7 +126,7 @@ describe("/ui/chat request metrics", () => {
|
|||
|
||||
await sendOneMessage();
|
||||
|
||||
await waitFor(() => expect(screen.getByText("No usage here.")).toBeInTheDocument());
|
||||
expect(await screen.findByText("No usage here.")).toBeInTheDocument();
|
||||
expect(document.querySelector(".response-metrics")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ describe("UsefulLinksManagement", () => {
|
|||
|
||||
render(<UsefulLinksManagement accessToken="token" userRole="Admin" />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText("First Link")).toBeInTheDocument());
|
||||
expect(await screen.findByText("First Link")).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /rearrange order/i }));
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ describe("UsefulLinksManagement", () => {
|
|||
|
||||
render(<UsefulLinksManagement accessToken="token" userRole="Admin" />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText("Test Link")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Test Link")).toBeInTheDocument();
|
||||
|
||||
// Click edit button
|
||||
const editButton = screen.getByTestId("edit-link-0-Test Link");
|
||||
|
|
@ -147,7 +147,7 @@ describe("UsefulLinksManagement", () => {
|
|||
|
||||
render(<UsefulLinksManagement accessToken="token" userRole="Admin" />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText("Test Link")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Test Link")).toBeInTheDocument();
|
||||
|
||||
// Click edit button
|
||||
const editButton = screen.getByTestId("edit-link-0-Test Link");
|
||||
|
|
@ -183,7 +183,7 @@ describe("UsefulLinksManagement", () => {
|
|||
|
||||
render(<UsefulLinksManagement accessToken="token" userRole="Admin" />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText("Test Link")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Test Link")).toBeInTheDocument();
|
||||
|
||||
// Click edit button
|
||||
const editButton = screen.getByTestId("edit-link-0-Test Link");
|
||||
|
|
@ -216,7 +216,7 @@ describe("UsefulLinksManagement", () => {
|
|||
|
||||
render(<UsefulLinksManagement accessToken="token" userRole="Admin" />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText("First Link")).toBeInTheDocument());
|
||||
expect(await screen.findByText("First Link")).toBeInTheDocument();
|
||||
|
||||
// Enter rearrange mode
|
||||
await user.click(screen.getByRole("button", { name: /rearrange order/i }));
|
||||
|
|
@ -235,7 +235,7 @@ describe("UsefulLinksManagement", () => {
|
|||
const user = userEvent.setup();
|
||||
render(<UsefulLinksManagement accessToken="token" userRole="Admin" />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText("Link Management")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Link Management")).toBeInTheDocument();
|
||||
|
||||
// Initially expanded
|
||||
expect(screen.getByText("Manage Existing Links")).toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ describe("MakeAgentPublicForm", () => {
|
|||
|
||||
// Check that the Next button is enabled (agents are preselected)
|
||||
const nextButton = screen.getByRole("button", { name: "Next" });
|
||||
expect(nextButton).not.toBeDisabled();
|
||||
expect(nextButton).toBeEnabled();
|
||||
});
|
||||
|
||||
it("should handle agent selection and navigation", async () => {
|
||||
|
|
@ -91,7 +91,7 @@ describe("MakeAgentPublicForm", () => {
|
|||
|
||||
// Verify Next button is enabled
|
||||
const nextButton = screen.getByRole("button", { name: "Next" });
|
||||
expect(nextButton).not.toBeDisabled();
|
||||
expect(nextButton).toBeEnabled();
|
||||
|
||||
// Click Next
|
||||
await act(async () => {
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ describe("MakeMCPPublicForm", () => {
|
|||
|
||||
// Check that the Next button is enabled (servers are preselected)
|
||||
const nextButton = screen.getByRole("button", { name: "Next" });
|
||||
expect(nextButton).not.toBeDisabled();
|
||||
expect(nextButton).toBeEnabled();
|
||||
});
|
||||
|
||||
it("should handle server selection and navigation", async () => {
|
||||
|
|
@ -114,7 +114,7 @@ describe("MakeMCPPublicForm", () => {
|
|||
|
||||
// Verify Next button is enabled
|
||||
const nextButton = screen.getByRole("button", { name: "Next" });
|
||||
expect(nextButton).not.toBeDisabled();
|
||||
expect(nextButton).toBeEnabled();
|
||||
|
||||
// Click Next
|
||||
await act(async () => {
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ describe("MakeModelPublicForm", () => {
|
|||
|
||||
// Check that the Next button is enabled (models are preselected)
|
||||
const nextButton = screen.getByRole("button", { name: "Next" });
|
||||
expect(nextButton).not.toBeDisabled();
|
||||
expect(nextButton).toBeEnabled();
|
||||
});
|
||||
|
||||
it("should handle model selection and navigation", async () => {
|
||||
|
|
@ -138,7 +138,7 @@ describe("MakeModelPublicForm", () => {
|
|||
|
||||
// Verify Next button is enabled
|
||||
const nextButton = screen.getByRole("button", { name: "Next" });
|
||||
expect(nextButton).not.toBeDisabled();
|
||||
expect(nextButton).toBeEnabled();
|
||||
|
||||
// Click Next
|
||||
await act(async () => {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ describe("BetaBadge", () => {
|
|||
|
||||
const { container } = render(<BetaBadge />);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("should render badge with dot instead of text when dot prop is true", () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { act, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { DashboardHeader } from "./DashboardHeader";
|
||||
|
||||
const { mockUsePluginMode, mockUseUISettings, state } = vi.hoisted(() => {
|
||||
|
|
@ -44,7 +44,7 @@ describe("DashboardHeader breadcrumb", () => {
|
|||
act(() => {
|
||||
fireEvent.click(selector);
|
||||
});
|
||||
await waitFor(() => expect(screen.getByText("Chat")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Chat")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the AI Gateway selector at the root even when there is nothing to switch to (discovery)", () => {
|
||||
|
|
|
|||
|
|
@ -197,12 +197,10 @@ describe("BlogDropdown", () => {
|
|||
|
||||
await openDropdown();
|
||||
|
||||
await waitFor(() => {
|
||||
const link = screen.getByRole("link", { name: /post one/i });
|
||||
expect(link).toHaveAttribute("href", "https://example.com/1");
|
||||
expect(link).toHaveAttribute("target", "_blank");
|
||||
expect(link).toHaveAttribute("rel", "noopener noreferrer");
|
||||
});
|
||||
const link = await screen.findByRole("link", { name: /post one/i });
|
||||
expect(link).toHaveAttribute("href", "https://example.com/1");
|
||||
expect(link).toHaveAttribute("rel", "noopener noreferrer");
|
||||
expect(link).toHaveAttribute("target", "_blank");
|
||||
});
|
||||
|
||||
it("should render formatted post dates", async () => {
|
||||
|
|
@ -226,12 +224,10 @@ describe("BlogDropdown", () => {
|
|||
|
||||
await openDropdown();
|
||||
|
||||
await waitFor(() => {
|
||||
const viewAllLink = screen.getByRole("link", { name: /view all posts/i });
|
||||
expect(viewAllLink).toHaveAttribute("href", "https://docs.litellm.ai/blog");
|
||||
expect(viewAllLink).toHaveAttribute("target", "_blank");
|
||||
expect(viewAllLink).toHaveAttribute("rel", "noopener noreferrer");
|
||||
});
|
||||
const viewAllLink = await screen.findByRole("link", { name: /view all posts/i });
|
||||
expect(viewAllLink).toHaveAttribute("href", "https://docs.litellm.ai/blog");
|
||||
expect(viewAllLink).toHaveAttribute("rel", "noopener noreferrer");
|
||||
expect(viewAllLink).toHaveAttribute("target", "_blank");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { act, fireEvent, render, screen } from "@testing-library/react";
|
||||
import ViewSwitcher from "./ViewSwitcher";
|
||||
|
||||
const { mockUsePluginMode, mockUseUISettings, mockUsePathname, state } = vi.hoisted(() => {
|
||||
|
|
@ -58,7 +58,7 @@ describe("ViewSwitcher", () => {
|
|||
act(() => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
await waitFor(() => expect(screen.getByText("Chat")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Chat")).toBeInTheDocument();
|
||||
expect(screen.getByText(/Admins can enable in Settings/i)).toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
|
|
@ -81,7 +81,7 @@ describe("ViewSwitcher", () => {
|
|||
act(() => {
|
||||
fireEvent.click(screen.getByRole("button"));
|
||||
});
|
||||
await waitFor(() => expect(screen.getByText("AI Gateway")).toBeInTheDocument());
|
||||
expect(await screen.findByText("AI Gateway")).toBeInTheDocument();
|
||||
expect(screen.getByText("Observability")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ describe("ViewSwitcher", () => {
|
|||
act(() => {
|
||||
fireEvent.click(screen.getByRole("button"));
|
||||
});
|
||||
await waitFor(() => expect(screen.getByText("Chat UI")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Chat UI")).toBeInTheDocument();
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByText("Chat UI"));
|
||||
});
|
||||
|
|
@ -108,7 +108,7 @@ describe("ViewSwitcher", () => {
|
|||
act(() => {
|
||||
fireEvent.click(screen.getByRole("button"));
|
||||
});
|
||||
await waitFor(() => expect(screen.getByText("Chat")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Chat")).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Admins can enable in Settings/i)).not.toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
|
|
@ -127,7 +127,7 @@ describe("ViewSwitcher", () => {
|
|||
act(() => {
|
||||
fireEvent.click(screen.getByRole("button"));
|
||||
});
|
||||
await waitFor(() => expect(screen.getByText("AI Gateway")).toBeInTheDocument());
|
||||
expect(await screen.findByText("AI Gateway")).toBeInTheDocument();
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByText("AI Gateway"));
|
||||
});
|
||||
|
|
@ -143,7 +143,7 @@ describe("ViewSwitcher", () => {
|
|||
act(() => {
|
||||
fireEvent.click(screen.getByRole("button"));
|
||||
});
|
||||
await waitFor(() => expect(screen.getByText("Observability")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Observability")).toBeInTheDocument();
|
||||
expect(screen.getByText("Chat")).toBeInTheDocument();
|
||||
expect(screen.getByText(/Admins can enable in Settings/i)).toBeInTheDocument();
|
||||
|
||||
|
|
|
|||
|
|
@ -67,10 +67,8 @@ describe("SSOModals", () => {
|
|||
const ssoProviderSelect = screen.getByLabelText("SSO Provider");
|
||||
fireEvent.mouseDown(ssoProviderSelect);
|
||||
// Wait for dropdown and select Google
|
||||
await waitFor(() => {
|
||||
const googleOption = screen.getByText("Google SSO");
|
||||
fireEvent.click(googleOption);
|
||||
});
|
||||
const googleOption = await screen.findByText("Google SSO");
|
||||
fireEvent.click(googleOption);
|
||||
|
||||
// Fill in the email field
|
||||
const emailInput = screen.getByLabelText("Proxy Admin Email");
|
||||
|
|
@ -119,10 +117,8 @@ describe("SSOModals", () => {
|
|||
const ssoProviderSelect = screen.getByLabelText("SSO Provider");
|
||||
fireEvent.mouseDown(ssoProviderSelect);
|
||||
// Wait for dropdown and select Google
|
||||
await waitFor(() => {
|
||||
const googleOption = screen.getByText("Google SSO");
|
||||
fireEvent.click(googleOption);
|
||||
});
|
||||
const googleOption = await screen.findByText("Google SSO");
|
||||
fireEvent.click(googleOption);
|
||||
|
||||
// Fill in the email field
|
||||
const emailInput = screen.getByLabelText("Proxy Admin Email");
|
||||
|
|
@ -216,10 +212,8 @@ describe("SSOModals", () => {
|
|||
const ssoProviderSelect = screen.getByLabelText("SSO Provider");
|
||||
fireEvent.mouseDown(ssoProviderSelect);
|
||||
// Wait for dropdown and select Google
|
||||
await waitFor(() => {
|
||||
const googleOption = screen.getByText("Google SSO");
|
||||
fireEvent.click(googleOption);
|
||||
});
|
||||
const googleOption = await screen.findByText("Google SSO");
|
||||
fireEvent.click(googleOption);
|
||||
|
||||
// Fill in the email field
|
||||
const emailInput = screen.getByLabelText("Proxy Admin Email");
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ describe("MCPSemanticFilterSettings", () => {
|
|||
|
||||
await user.click(screen.getByRole("switch"));
|
||||
|
||||
expect(screen.getByRole("button", { name: /save settings/i })).not.toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: /save settings/i })).toBeEnabled();
|
||||
});
|
||||
|
||||
it("should show an error alert when the mutation fails", async () => {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ describe("MCPSemanticFilterTestPanel", () => {
|
|||
|
||||
it("should enable the Test Filter button when testQuery is set and filter is enabled", () => {
|
||||
render(<MCPSemanticFilterTestPanel {...buildProps({ testQuery: "search query" })} />);
|
||||
expect(screen.getByRole("button", { name: /test filter/i })).not.toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: /test filter/i })).toBeEnabled();
|
||||
});
|
||||
|
||||
it("should call onTest when the Test Filter button is clicked", async () => {
|
||||
|
|
|
|||
|
|
@ -39,10 +39,8 @@ describe("BaseSSOSettingsForm", () => {
|
|||
fireEvent.mouseDown(providerSelect);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const googleOption = screen.getByText(/google sso/i);
|
||||
fireEvent.click(googleOption);
|
||||
});
|
||||
const googleOption = await screen.findByText(/google sso/i);
|
||||
fireEvent.click(googleOption);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Google Client ID")).toBeInTheDocument();
|
||||
|
|
@ -65,10 +63,8 @@ describe("BaseSSOSettingsForm", () => {
|
|||
fireEvent.mouseDown(providerSelect);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const oktaOption = screen.getByText(/okta/i);
|
||||
fireEvent.click(oktaOption);
|
||||
});
|
||||
const oktaOption = await screen.findByText(/okta/i);
|
||||
fireEvent.click(oktaOption);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Use Role Mappings")).toBeInTheDocument();
|
||||
|
|
@ -132,10 +128,8 @@ describe("BaseSSOSettingsForm", () => {
|
|||
fireEvent.mouseDown(providerSelect);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const genericOption = screen.getByText(/generic sso/i);
|
||||
fireEvent.click(genericOption);
|
||||
});
|
||||
const genericOption = await screen.findByText(/generic sso/i);
|
||||
fireEvent.click(genericOption);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Use Role Mappings")).toBeInTheDocument();
|
||||
|
|
@ -167,10 +161,8 @@ describe("BaseSSOSettingsForm", () => {
|
|||
fireEvent.mouseDown(providerSelect);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const oktaOption = screen.getByText(/okta/i);
|
||||
fireEvent.click(oktaOption);
|
||||
});
|
||||
const oktaOption = await screen.findByText(/okta/i);
|
||||
fireEvent.click(oktaOption);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Use Team Mappings")).toBeInTheDocument();
|
||||
|
|
@ -192,10 +184,8 @@ describe("BaseSSOSettingsForm", () => {
|
|||
fireEvent.mouseDown(providerSelect);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const genericOption = screen.getByText(/generic sso/i);
|
||||
fireEvent.click(genericOption);
|
||||
});
|
||||
const genericOption = await screen.findByText(/generic sso/i);
|
||||
fireEvent.click(genericOption);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Use Team Mappings")).toBeInTheDocument();
|
||||
|
|
@ -217,10 +207,8 @@ describe("BaseSSOSettingsForm", () => {
|
|||
fireEvent.mouseDown(providerSelect);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const oktaOption = screen.getByText(/okta/i);
|
||||
fireEvent.click(oktaOption);
|
||||
});
|
||||
const oktaOption = await screen.findByText(/okta/i);
|
||||
fireEvent.click(oktaOption);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Use Team Mappings")).toBeInTheDocument();
|
||||
|
|
@ -251,10 +239,8 @@ describe("BaseSSOSettingsForm", () => {
|
|||
fireEvent.mouseDown(providerSelect);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const googleOption = screen.getByText(/google sso/i);
|
||||
fireEvent.click(googleOption);
|
||||
});
|
||||
const googleOption = await screen.findByText(/google sso/i);
|
||||
fireEvent.click(googleOption);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Google Client ID")).toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ describe("RoleMappings", () => {
|
|||
it("should return null when roleMappings is undefined", () => {
|
||||
const { container } = renderWithProviders(<RoleMappings roleMappings={undefined} />);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("should display Group Claim and Default Role with correct values and display names", () => {
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ describe("AddFallbacks", () => {
|
|||
});
|
||||
|
||||
const saveButton = screen.getByRole("button", { name: /save all configurations/i });
|
||||
expect(saveButton).not.toBeDisabled();
|
||||
expect(saveButton).toBeEnabled();
|
||||
await user.click(saveButton);
|
||||
|
||||
await waitFor(() => {
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ describe("Fallbacks", () => {
|
|||
|
||||
it("should not render when accessToken is null", () => {
|
||||
const { container } = renderWithQueryClient(<Fallbacks {...defaultProps} accessToken={null} />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("should fetch router settings on mount", async () => {
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ describe("TeamSSOSettings", () => {
|
|||
await userEvent.click(screen.getByRole("button", { name: /Edit Settings/i }));
|
||||
const permissionComboboxes = screen.getAllByRole("combobox");
|
||||
const permissionCombobox = permissionComboboxes[permissionComboboxes.length - 1];
|
||||
expect(permissionCombobox).toBeDefined();
|
||||
expect(permissionCombobox).toBeInTheDocument();
|
||||
await userEvent.click(permissionCombobox!);
|
||||
const deletePermissionOptions = await screen.findAllByText("/key/delete");
|
||||
await userEvent.click(deletePermissionOptions[deletePermissionOptions.length - 1]);
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ describe("sort contract – only backend-sortable columns are sortable", () => {
|
|||
|
||||
it("does not make Spend / Budget sortable (the backend rejects sort_by=spend)", () => {
|
||||
renderTable();
|
||||
expect(screen.getByText("Spend / Budget").closest("button")).toBeNull();
|
||||
expect(screen.queryByText("Spend / Budget").closest("button")).toBeNull();
|
||||
// Team and Created are the only sortable headers.
|
||||
expect(screen.getByText("Team").closest("button")).not.toBeNull();
|
||||
expect(screen.getByText("Created").closest("button")).not.toBeNull();
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ describe("TopKeyView", () => {
|
|||
|
||||
const bars = container.querySelectorAll("path.recharts-rectangle");
|
||||
expect(bars).toHaveLength(1);
|
||||
expect(bars[0].getAttribute("fill")).toBe("var(--color-cyan-500, #06b6d4)");
|
||||
expect(bars[0]).toHaveAttribute("fill", "var(--color-cyan-500, #06b6d4)");
|
||||
expect(screen.getAllByText("A Very Lon...").length).toBeGreaterThan(0);
|
||||
|
||||
fireEvent.click(bars[0]);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ describe("KeyModelUsageView", () => {
|
|||
|
||||
it("should return null when topModels is empty", () => {
|
||||
const { container } = render(<KeyModelUsageView topModels={[]} />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("should display Model Usage title", () => {
|
||||
|
|
|
|||
|
|
@ -339,13 +339,9 @@ it("emphasizes the active field in the Spend / Budget header so the sorted colum
|
|||
await user.click(await screen.findByText("Budget descending"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Budget", { selector: "[data-sort-field='max_budget']" }).className).toContain(
|
||||
"font-semibold",
|
||||
);
|
||||
expect(screen.getByText("Budget", { selector: "[data-sort-field='max_budget']" })).toHaveClass("font-semibold");
|
||||
});
|
||||
expect(screen.getByText("Spend", { selector: "[data-sort-field='spend']" }).className).toContain(
|
||||
"text-muted-foreground",
|
||||
);
|
||||
expect(screen.getByText("Spend", { selector: "[data-sort-field='spend']" })).toHaveClass("text-muted-foreground");
|
||||
});
|
||||
|
||||
it("sorts by spend ascending when 'Spend ascending' is chosen from the Spend / Budget menu", async () => {
|
||||
|
|
@ -537,7 +533,7 @@ describe("refresh button", () => {
|
|||
|
||||
const refresh = screen.getByTestId("datatable-refresh");
|
||||
expect(refresh).toBeInTheDocument();
|
||||
expect(refresh).not.toBeDisabled();
|
||||
expect(refresh).toBeEnabled();
|
||||
});
|
||||
|
||||
it("disables the refresh control while a fetch is in flight but keeps data visible", () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { renderWithProviders, screen, waitFor } from "../../../tests/test-utils";
|
||||
import { renderWithProviders, screen } from "../../../tests/test-utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { vi } from "vitest";
|
||||
import ClassifierPromptEditor from "./ClassifierPromptEditor";
|
||||
|
|
@ -45,7 +45,7 @@ const openEditor = async ({
|
|||
/>,
|
||||
);
|
||||
await userEvent.click(screen.getByRole("button", { name: /prompt/i }));
|
||||
await waitFor(() => expect(screen.getByLabelText("Classifier system prompt")).toBeInTheDocument());
|
||||
expect(await screen.findByLabelText("Classifier system prompt")).toBeInTheDocument();
|
||||
return onChange;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -455,7 +455,7 @@ describe("AddAutoRouterTab", () => {
|
|||
|
||||
const anthropicOption = optionByLabel("Anthropic Family")!;
|
||||
expect(isOptionDisabled(anthropicOption)).toBe(true);
|
||||
expect(anthropicOption.textContent).toContain("Checking model availability");
|
||||
expect(anthropicOption).toHaveTextContent(/Checking model availability/);
|
||||
|
||||
// The dropdown is already open from above; polling re-reads its options in place rather than
|
||||
// reopening (openTemplateDropdown toggles, so a second call here would close it instead).
|
||||
|
|
@ -474,7 +474,7 @@ describe("AddAutoRouterTab", () => {
|
|||
openTemplateDropdown();
|
||||
const anthropicOption = optionByLabel("Anthropic Family")!;
|
||||
expect(isOptionDisabled(anthropicOption)).toBe(true);
|
||||
expect(anthropicOption.textContent).toContain("Cannot verify these models are available");
|
||||
expect(anthropicOption).toHaveTextContent(/Cannot verify these models are available/);
|
||||
});
|
||||
|
||||
it("keeps group-name presets selectable when only the deployment fetch fails", async () => {
|
||||
|
|
@ -496,7 +496,7 @@ describe("AddAutoRouterTab", () => {
|
|||
openTemplateDropdown();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(optionByLabel("Anthropic Family")!.textContent).toContain(`Missing: ${ANTHROPIC_ONLY_MODEL}`);
|
||||
expect(optionByLabel("Anthropic Family")!).toHaveTextContent(new RegExp(`Missing: ${ANTHROPIC_ONLY_MODEL}`));
|
||||
});
|
||||
expect(isOptionDisabled(optionByLabel("Anthropic Family")!)).toBe(true);
|
||||
});
|
||||
|
|
@ -695,7 +695,7 @@ describe("AddAutoRouterTab", () => {
|
|||
await waitFor(() => {
|
||||
expect(isOptionDisabled(optionByLabel("Anthropic Family")!)).toBe(false);
|
||||
});
|
||||
expect(optionByLabel("Anthropic Family")!.textContent).toContain("Matches your deployments");
|
||||
expect(optionByLabel("Anthropic Family")!).toHaveTextContent(/Matches your deployments/);
|
||||
});
|
||||
|
||||
it("keeps detailed configuration open and prefills the admin's group names on apply", async () => {
|
||||
|
|
@ -755,7 +755,7 @@ describe("AddAutoRouterTab", () => {
|
|||
openTemplateDropdown();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(optionByLabel("OpenAI Family")!.textContent).toContain("Missing:");
|
||||
expect(optionByLabel("OpenAI Family")!).toHaveTextContent(/Missing:/);
|
||||
});
|
||||
expect(isOptionDisabled(optionByLabel("OpenAI Family")!)).toBe(true);
|
||||
});
|
||||
|
|
@ -784,7 +784,7 @@ describe("AddAutoRouterTab", () => {
|
|||
await waitFor(() => {
|
||||
expect(isOptionDisabled(optionByLabel("Anthropic Family")!)).toBe(false);
|
||||
});
|
||||
expect(optionByLabel("Anthropic Family")!.textContent).toContain("Matches your deployments");
|
||||
expect(optionByLabel("Anthropic Family")!).toHaveTextContent(/Matches your deployments/);
|
||||
});
|
||||
|
||||
it("prefills the expanded group names and submits them", async () => {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ describe("AutoRouterConnectionTest", () => {
|
|||
renderWithProviders(<AutoRouterConnectionTest accessToken="sk-test" targets={targets} />);
|
||||
|
||||
await waitFor(() => expect(screen.getAllByTestId("test-status-success")).toHaveLength(3));
|
||||
expect(screen.queryByTestId("test-status-error")).toBeNull();
|
||||
expect(screen.queryByTestId("test-status-error")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("MEDIUM, COMPLEX")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ describe("AutoRouterConnectionTest", () => {
|
|||
|
||||
renderWithProviders(<AutoRouterConnectionTest accessToken="sk-test" targets={targets} />);
|
||||
|
||||
await waitFor(() => expect(screen.getByTestId("test-error-message")).toBeInTheDocument());
|
||||
expect(await screen.findByTestId("test-error-message")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("test-error-message")).toHaveTextContent("invalid api key");
|
||||
expect(screen.getByTestId("test-error-message")).not.toHaveTextContent("litellm.AuthenticationError");
|
||||
expect(screen.getAllByTestId("test-status-success")).toHaveLength(2);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,6 @@ describe("LitellmModelNameField", () => {
|
|||
</Form>,
|
||||
);
|
||||
expect(getByPlaceholderText("my-deployment")).toBeInTheDocument();
|
||||
expect(queryByPlaceholderText("gpt-3.5-turbo")).toBeNull();
|
||||
expect(queryByPlaceholderText("gpt-3.5-turbo")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -150,17 +150,15 @@ describe("ProviderSpecificFields", () => {
|
|||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const apiKeyLabel = screen.getByLabelText("OpenAI API Key");
|
||||
expect(apiKeyLabel).toBeInTheDocument();
|
||||
const apiKeyLabel = await screen.findByLabelText("OpenAI API Key");
|
||||
expect(apiKeyLabel).toBeInTheDocument();
|
||||
|
||||
const apiBaseInput = screen.getByPlaceholderText("https://api.openai.com/v1");
|
||||
expect(apiBaseInput).toBeInTheDocument();
|
||||
expect(apiBaseInput).toHaveAttribute("type", "text");
|
||||
const apiBaseInput = screen.getByPlaceholderText("https://api.openai.com/v1");
|
||||
expect(apiBaseInput).toBeInTheDocument();
|
||||
expect(apiBaseInput).toHaveAttribute("type", "text");
|
||||
|
||||
const orgInput = screen.getByPlaceholderText("[OPTIONAL] my-unique-org");
|
||||
expect(orgInput).toBeInTheDocument();
|
||||
});
|
||||
const orgInput = screen.getByPlaceholderText("[OPTIONAL] my-unique-org");
|
||||
expect(orgInput).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the provider specific fields for vLLM", async () => {
|
||||
|
|
@ -173,14 +171,12 @@ describe("ProviderSpecificFields", () => {
|
|||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const apiKeyLabel = screen.getByLabelText("vLLM API Key");
|
||||
expect(apiKeyLabel).toBeInTheDocument();
|
||||
const apiKeyLabel = await screen.findByLabelText("vLLM API Key");
|
||||
expect(apiKeyLabel).toBeInTheDocument();
|
||||
|
||||
const apiBaseInput = screen.getByPlaceholderText("https://...");
|
||||
expect(apiBaseInput).toBeInTheDocument();
|
||||
expect(apiBaseInput).toHaveAttribute("type", "text");
|
||||
});
|
||||
const apiBaseInput = screen.getByPlaceholderText("https://...");
|
||||
expect(apiBaseInput).toBeInTheDocument();
|
||||
expect(apiBaseInput).toHaveAttribute("type", "text");
|
||||
});
|
||||
|
||||
it("should render the provider specific fields for Azure", async () => {
|
||||
|
|
@ -193,27 +189,25 @@ describe("ProviderSpecificFields", () => {
|
|||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const apiKeyInput = screen.getByLabelText("Azure API Key");
|
||||
expect(apiKeyInput).toBeInTheDocument();
|
||||
expect(apiKeyInput).toHaveAttribute("type", "password");
|
||||
expect(apiKeyInput).toHaveAttribute("placeholder", "Enter your Azure API Key");
|
||||
const apiKeyInput = await screen.findByLabelText("Azure API Key");
|
||||
expect(apiKeyInput).toBeInTheDocument();
|
||||
expect(apiKeyInput).toHaveAttribute("type", "password");
|
||||
expect(apiKeyInput).toHaveAttribute("placeholder", "Enter your Azure API Key");
|
||||
|
||||
const azureAdTokenInput = screen.getByLabelText("Azure AD Token");
|
||||
expect(azureAdTokenInput).toBeInTheDocument();
|
||||
expect(azureAdTokenInput).toHaveAttribute("type", "password");
|
||||
expect(azureAdTokenInput).toHaveAttribute("placeholder", "Enter your Azure AD Token");
|
||||
const azureAdTokenInput = screen.getByLabelText("Azure AD Token");
|
||||
expect(azureAdTokenInput).toBeInTheDocument();
|
||||
expect(azureAdTokenInput).toHaveAttribute("type", "password");
|
||||
expect(azureAdTokenInput).toHaveAttribute("placeholder", "Enter your Azure AD Token");
|
||||
|
||||
const apiBaseInput = screen.getByPlaceholderText("https://...");
|
||||
expect(apiBaseInput).toBeInTheDocument();
|
||||
expect(apiBaseInput).toHaveAttribute("type", "text");
|
||||
const apiBaseInput = screen.getByPlaceholderText("https://...");
|
||||
expect(apiBaseInput).toBeInTheDocument();
|
||||
expect(apiBaseInput).toHaveAttribute("type", "text");
|
||||
|
||||
const apiVersionInput = screen.getByPlaceholderText("2023-07-01-preview");
|
||||
expect(apiVersionInput).toBeInTheDocument();
|
||||
const apiVersionInput = screen.getByPlaceholderText("2023-07-01-preview");
|
||||
expect(apiVersionInput).toBeInTheDocument();
|
||||
|
||||
const baseModelInput = screen.getByPlaceholderText("azure/gpt-3.5-turbo");
|
||||
expect(baseModelInput).toBeInTheDocument();
|
||||
});
|
||||
const baseModelInput = screen.getByPlaceholderText("azure/gpt-3.5-turbo");
|
||||
expect(baseModelInput).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("sets Azure API version from the API base query parameter", async () => {
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ describe("ConnectFlowBanner", () => {
|
|||
const { container } = render(<ConnectFlowBanner flowHandle="flow-handle-123" clientOrigin="https://claude.ai" />);
|
||||
|
||||
const form = container.querySelector("form")!;
|
||||
expect(form.getAttribute("method")).toBe("POST");
|
||||
expect(form.getAttribute("action")).toBe("https://gateway.example.com/authorize/complete");
|
||||
expect(form).toHaveAttribute("method", "POST");
|
||||
expect(form).toHaveAttribute("action", "https://gateway.example.com/authorize/complete");
|
||||
|
||||
const hidden = form.querySelector('input[name="flow"]') as HTMLInputElement;
|
||||
expect(hidden.value).toBe("flow-handle-123");
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ describe("MCPAppsPanel logos", () => {
|
|||
renderPanel();
|
||||
|
||||
expect(await screen.findByText("external_logo")).toBeInTheDocument();
|
||||
expect(screen.getByAltText("external_logo logo").getAttribute("src")).toBe("https://cdn.example.com/ext.png");
|
||||
expect(screen.getByAltText("local_logo logo").getAttribute("src")).toBe("/litellm/ui/assets/logos/github.svg");
|
||||
expect(screen.getByAltText("external_logo logo")).toHaveAttribute("src", "https://cdn.example.com/ext.png");
|
||||
expect(screen.getByAltText("local_logo logo")).toHaveAttribute("src", "/litellm/ui/assets/logos/github.svg");
|
||||
});
|
||||
|
||||
it("renders a colored letter avatar for servers without logo_url", async () => {
|
||||
|
|
@ -83,7 +83,7 @@ describe("MCPAppsPanel logos", () => {
|
|||
fireEvent.click(await screen.findByText("local_logo"));
|
||||
|
||||
expect(await screen.findByRole("heading", { name: "local_logo" })).toBeInTheDocument();
|
||||
expect(screen.getByAltText("local_logo logo").getAttribute("src")).toBe("/litellm/ui/assets/logos/github.svg");
|
||||
expect(screen.getByAltText("local_logo logo")).toHaveAttribute("src", "/litellm/ui/assets/logos/github.svg");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -239,7 +239,7 @@ describe("MCPAppsPanel connected-app reachability (LIT-4861)", () => {
|
|||
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
expect(screen.queryByText("revoked_srv")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Connected", { exact: false }).textContent).toBe("Connected");
|
||||
expect(screen.getByText("Connected", { exact: false })).toHaveTextContent("Connected");
|
||||
});
|
||||
|
||||
it("does not select a server when Connect resolves in the same tick the refetch drops it", async () => {
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ describe("MCPConnectPicker logos", () => {
|
|||
render(<MCPConnectPicker accessToken="tok" selectedServers={[]} onChange={vi.fn()} />);
|
||||
|
||||
expect(await screen.findByText("external_logo")).toBeInTheDocument();
|
||||
expect(screen.getByAltText("external_logo logo").getAttribute("src")).toBe("https://cdn.example.com/ext.png");
|
||||
expect(screen.getByAltText("local_logo logo").getAttribute("src")).toBe("/litellm/ui/assets/logos/github.svg");
|
||||
expect(screen.getByAltText("external_logo logo")).toHaveAttribute("src", "https://cdn.example.com/ext.png");
|
||||
expect(screen.getByAltText("local_logo logo")).toHaveAttribute("src", "/litellm/ui/assets/logos/github.svg");
|
||||
});
|
||||
|
||||
it("renders no logo at all for servers without logo_url", async () => {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,6 @@ describe("DefaultProxyAdminTag", () => {
|
|||
it("should render empty text when userId is null", () => {
|
||||
const { container } = render(<DefaultProxyAdminTag userId={null} />);
|
||||
expect(screen.queryByText("Default Proxy Admin")).not.toBeInTheDocument();
|
||||
expect(container.textContent).toBe("");
|
||||
expect(container).toHaveTextContent("");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ describe("DeleteResourceModal", () => {
|
|||
const input = screen.getByPlaceholderText("DELETE");
|
||||
await user.type(input, "DELETE");
|
||||
const deleteButton = screen.getByRole("button", { name: /delete/i });
|
||||
expect(deleteButton).not.toBeDisabled();
|
||||
expect(deleteButton).toBeEnabled();
|
||||
});
|
||||
|
||||
it("should reset requiredConfirmation input when modal opens", async () => {
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ describe("KeyLifecycleSettings", () => {
|
|||
expect(screen.queryByText("Rotation Interval")).not.toBeInTheDocument();
|
||||
await user.click(screen.getByRole("switch"));
|
||||
|
||||
await waitFor(() => expect(screen.getByText("Rotation Interval")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Rotation Interval")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("propagates a selected predefined interval", async () => {
|
||||
|
|
@ -152,7 +152,7 @@ describe("KeyLifecycleSettings", () => {
|
|||
renderWithProviders(<Harness />);
|
||||
|
||||
await user.click(screen.getByRole("switch"));
|
||||
await waitFor(() => expect(screen.getByText("Rotation Interval")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Rotation Interval")).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("combobox"));
|
||||
await user.click(await screen.findByText("90 days"));
|
||||
|
|
@ -166,7 +166,7 @@ describe("KeyLifecycleSettings", () => {
|
|||
renderWithProviders(<Harness />);
|
||||
|
||||
await user.click(screen.getByRole("switch"));
|
||||
await waitFor(() => expect(screen.getByText("Rotation Interval")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Rotation Interval")).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("combobox"));
|
||||
await user.click(await screen.findByText("Custom interval"));
|
||||
|
|
@ -181,7 +181,7 @@ describe("KeyLifecycleSettings", () => {
|
|||
renderWithProviders(<Harness />);
|
||||
|
||||
await user.click(screen.getByRole("switch"));
|
||||
await waitFor(() => expect(screen.getByText("Rotation Interval")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Rotation Interval")).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("combobox"));
|
||||
await user.click(await screen.findByText("Custom interval"));
|
||||
|
|
@ -198,7 +198,7 @@ describe("KeyLifecycleSettings", () => {
|
|||
renderWithProviders(<Harness />);
|
||||
|
||||
await user.click(screen.getByRole("switch"));
|
||||
await waitFor(() => expect(screen.getByText("Rotation Interval")).toBeInTheDocument());
|
||||
expect(await screen.findByText("Rotation Interval")).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("combobox"));
|
||||
await user.click(await screen.findByText("Custom interval"));
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ describe("NewBadge", () => {
|
|||
|
||||
const { container } = render(<NewBadge />);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("should render badge with dot when dot prop is true", () => {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ describe("CustomTooltip", () => {
|
|||
label: "2024-01-15",
|
||||
};
|
||||
const { container } = render(<CustomTooltip {...props} />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("should return null when payload is empty", () => {
|
||||
|
|
@ -56,7 +56,7 @@ describe("CustomTooltip", () => {
|
|||
label: "2024-01-15",
|
||||
};
|
||||
const { container } = render(<CustomTooltip {...props} />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("should display formatted category names", () => {
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ describe("key router settings wiring, /key/info payload through to rendered outp
|
|||
it("should not leak a field the accordion has no control for into the editor", async () => {
|
||||
render(renderAccordion(KEY_INFO_ROUTER_SETTINGS));
|
||||
|
||||
await waitFor(() => expect(screen.getByTestId("loadbalancing")).toBeInTheDocument());
|
||||
expect(screen.getByTestId("loadbalancing").textContent).not.toContain("tag_routing_prefix");
|
||||
expect(await screen.findByTestId("loadbalancing")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("loadbalancing")).not.toHaveTextContent(/tag_routing_prefix/);
|
||||
});
|
||||
|
||||
it("should render an empty editor for a key holding only fields it cannot show", async () => {
|
||||
|
|
|
|||
|
|
@ -519,6 +519,7 @@ describe("EditAutoRouterModal custom classifier prompt and fallback", () => {
|
|||
|
||||
await user.click(await screen.findByText("Advanced: Classification Method"));
|
||||
expect(await screen.findByRole("button", { name: "Edit custom prompt" })).toBeInTheDocument();
|
||||
// eslint-disable-next-line jest-dom/prefer-checked -- antd sets the checked attribute without the DOM property, so toBeChecked reads false
|
||||
expect(screen.getByRole("radio", { name: /Route to the default model/ })).toHaveAttribute("checked");
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /save changes/i }));
|
||||
|
|
|
|||
|
|
@ -101,13 +101,13 @@ describe("EmailSettings", () => {
|
|||
renderWithProviders(<EmailSettings accessToken="sk-test" premiumUser={false} alerts={alerts} />);
|
||||
|
||||
expect(inputNamed("EMAIL_LOGO_URL")).toBeDisabled();
|
||||
expect(inputNamed("SMTP_HOST")).not.toBeDisabled();
|
||||
expect(inputNamed("SMTP_HOST")).toBeEnabled();
|
||||
});
|
||||
|
||||
it("leaves the premium-only fields editable for premium users", () => {
|
||||
renderWithProviders(<EmailSettings accessToken="sk-test" premiumUser alerts={alerts} />);
|
||||
|
||||
expect(inputNamed("EMAIL_LOGO_URL")).not.toBeDisabled();
|
||||
expect(inputNamed("EMAIL_LOGO_URL")).toBeEnabled();
|
||||
});
|
||||
|
||||
it("triggers a live email health check", async () => {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ describe("BudgetFallbacksEditor", () => {
|
|||
it("renders empty state with add button", () => {
|
||||
const onChange = vi.fn();
|
||||
render(<BudgetFallbacksEditor value={{}} onChange={onChange} availableModels={MODELS} />);
|
||||
expect(screen.getByText("Add Budget Fallback")).toBeTruthy();
|
||||
expect(screen.getByText(/reroute to fallback models/)).toBeTruthy();
|
||||
expect(screen.getByText("Add Budget Fallback")).toBeInTheDocument();
|
||||
expect(screen.getByText(/reroute to fallback models/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders existing entries from value prop", () => {
|
||||
|
|
@ -22,9 +22,9 @@ describe("BudgetFallbacksEditor", () => {
|
|||
availableModels={MODELS}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("IF BUDGET EXCEEDED, TRY")).toBeTruthy();
|
||||
expect(screen.getByText("Primary Model")).toBeTruthy();
|
||||
expect(screen.getByText("Fallback Models")).toBeTruthy();
|
||||
expect(screen.getByText("IF BUDGET EXCEEDED, TRY")).toBeInTheDocument();
|
||||
expect(screen.getByText("Primary Model")).toBeInTheDocument();
|
||||
expect(screen.getByText("Fallback Models")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("adds a new empty entry when clicking add button", async () => {
|
||||
|
|
@ -33,7 +33,7 @@ describe("BudgetFallbacksEditor", () => {
|
|||
render(<BudgetFallbacksEditor value={{}} onChange={onChange} availableModels={MODELS} />);
|
||||
|
||||
await user.click(screen.getByText("Add Budget Fallback"));
|
||||
expect(screen.getByText("Primary Model")).toBeTruthy();
|
||||
expect(screen.getByText("Primary Model")).toBeInTheDocument();
|
||||
expect(onChange).toHaveBeenCalledWith({});
|
||||
});
|
||||
|
||||
|
|
@ -81,8 +81,8 @@ describe("BudgetFallbacksEditor", () => {
|
|||
expect(screen.getAllByText("Primary Model").length).toBe(1);
|
||||
|
||||
rerender(<BudgetFallbacksEditor key={2} value={{}} onChange={onChange} availableModels={MODELS} />);
|
||||
expect(screen.queryByText("Primary Model")).toBeNull();
|
||||
expect(screen.getByText("Add Budget Fallback")).toBeTruthy();
|
||||
expect(screen.queryByText("Primary Model")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Add Budget Fallback")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows ordering hint when multiple fallback models are configured", () => {
|
||||
|
|
@ -94,6 +94,6 @@ describe("BudgetFallbacksEditor", () => {
|
|||
availableModels={MODELS}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText(/first model still within its own budget/)).toBeTruthy();
|
||||
expect(screen.getByText(/first model still within its own budget/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -456,8 +456,8 @@ describe("Sidebar (leftnav)", () => {
|
|||
|
||||
const costOptimization = container.querySelector('a[href*="cost-optimization"]');
|
||||
expect(costOptimization).not.toBeNull();
|
||||
expect(costOptimization!.textContent).toContain("Cost Optimization");
|
||||
expect(costOptimization!.textContent).toContain("Beta");
|
||||
expect(costOptimization!).toHaveTextContent(/Cost Optimization/);
|
||||
expect(costOptimization!).toHaveTextContent(/Beta/);
|
||||
|
||||
expect(container.querySelector('a[href*="projects"]')).toBeNull();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ describe("LoggingSettingsView logos", () => {
|
|||
it("renders a letter avatar for the custom callback API, which has no bundled logo", () => {
|
||||
render(<LoggingSettingsView disabledCallbacks={["custom_callback_api"]} />);
|
||||
|
||||
expect(screen.queryByAltText("Custom Callback API logo")).toBeNull();
|
||||
expect(screen.queryByAltText("Custom Callback API logo")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("C")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ describe("CredentialModal", () => {
|
|||
expect(screen.getByText("Add Credential")).toBeInTheDocument();
|
||||
const nameInput = screen.getByLabelText("Credential Name:") as HTMLInputElement;
|
||||
expect(nameInput.value).toBe("");
|
||||
expect(nameInput.disabled).toBe(false);
|
||||
expect(nameInput).toBeEnabled();
|
||||
});
|
||||
|
||||
it("shows provider-specific fields for the selected provider", async () => {
|
||||
|
|
@ -124,7 +124,7 @@ describe("CredentialModal", () => {
|
|||
await waitFor(() => {
|
||||
const nameInput = screen.getByLabelText("Credential Name:") as HTMLInputElement;
|
||||
expect(nameInput.value).toBe("test-credential");
|
||||
expect(nameInput.disabled).toBe(true);
|
||||
expect(nameInput).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ describe("CredentialModal", () => {
|
|||
existingCredential: { ...mockCredential, credential_name: "" },
|
||||
});
|
||||
|
||||
expect((screen.getByLabelText("Credential Name:") as HTMLInputElement).disabled).toBe(true);
|
||||
expect(screen.getByLabelText("Credential Name:")).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ describe("HealthCheckComponent", () => {
|
|||
await user.click(screen.getByTestId("datatable-select-row-id-alpha"));
|
||||
await user.click(screen.getByTestId("clear-health-selection"));
|
||||
|
||||
expect(screen.getByTestId("datatable-select-row-id-alpha")).toHaveAttribute("aria-checked", "false");
|
||||
expect(screen.getByTestId("datatable-select-row-id-alpha")).not.toBeChecked();
|
||||
expect(screen.getByTestId("run-health-checks")).toHaveTextContent("Run All Checks");
|
||||
});
|
||||
|
||||
|
|
@ -195,7 +195,7 @@ describe("HealthCheckComponent", () => {
|
|||
await user.click(screen.getByTestId("pagination-next"));
|
||||
|
||||
expect(screen.queryByTestId("clear-health-selection")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("datatable-select-row-id-alpha")).toHaveAttribute("aria-checked", "false");
|
||||
expect(screen.getByTestId("datatable-select-row-id-alpha")).not.toBeChecked();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -955,8 +955,8 @@ describe("ModelInfoView", () => {
|
|||
|
||||
await waitFor(() => {
|
||||
expect(mockTestModelGroupConnection).toHaveBeenCalledWith("test-token", "gpt-4o-mini", "chat");
|
||||
expect(mockTestModelGroupConnection).toHaveBeenCalledWith("test-token", "gpt-4o", "chat");
|
||||
});
|
||||
expect(mockTestModelGroupConnection).toHaveBeenCalledWith("test-token", "gpt-4o", "chat");
|
||||
expect(mockTestConnectionRequest).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
@ -988,8 +988,8 @@ describe("ModelInfoView", () => {
|
|||
|
||||
await waitFor(() => {
|
||||
expect(mockTestModelGroupConnection).toHaveBeenCalledWith("test-token", "gpt-4o-mini", "chat");
|
||||
expect(mockTestModelGroupConnection).toHaveBeenCalledWith("test-token", "gpt-4o", "chat");
|
||||
});
|
||||
expect(mockTestModelGroupConnection).toHaveBeenCalledWith("test-token", "gpt-4o", "chat");
|
||||
});
|
||||
|
||||
it("does not duplicate the default model as a test target when it is already covered by a configured tier", async () => {
|
||||
|
|
@ -1128,7 +1128,7 @@ describe("ModelInfoView", () => {
|
|||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
|
||||
const logo = await screen.findByAltText("openai logo");
|
||||
expect(logo.getAttribute("src")).toContain("openai_small");
|
||||
expect(logo).toHaveAttribute("src", expect.stringContaining("openai_small"));
|
||||
});
|
||||
|
||||
it("renders a letter avatar instead of an img for an unknown provider slug", async () => {
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ describe("CostOptimizationFeedbackBanner", () => {
|
|||
});
|
||||
|
||||
it("hides itself and persists the dismissal when the dismiss button is clicked", () => {
|
||||
const { queryByText, getByLabelText } = render(<CostOptimizationFeedbackBanner />);
|
||||
expect(queryByText("Help shape cost optimization")).toBeInTheDocument();
|
||||
const { getByText, queryByText, getByLabelText } = render(<CostOptimizationFeedbackBanner />);
|
||||
expect(getByText("Help shape cost optimization")).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(getByLabelText("Dismiss banner"));
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ describe("Logo", () => {
|
|||
it("renders the bundled logo untouched by the server root path for a known provider", () => {
|
||||
render(<Logo provider="openai" />);
|
||||
const img = screen.getByRole("img", { name: "openai logo" });
|
||||
expect(img.getAttribute("src")).toBe(providerLogoMap[Providers.OpenAI]);
|
||||
expect(img.getAttribute("src")).toContain("openai_small");
|
||||
expect(img).toHaveAttribute("src", providerLogoMap[Providers.OpenAI]);
|
||||
expect(img).toHaveAttribute("src", expect.stringContaining("openai_small"));
|
||||
});
|
||||
|
||||
it("renders a letter avatar and no img for an unknown provider", () => {
|
||||
|
|
@ -29,12 +29,12 @@ describe("Logo", () => {
|
|||
it("resolves a backend asset path through the server root path in src mode", () => {
|
||||
render(<Logo src="/ui/assets/logos/github.svg" label="GitHub" />);
|
||||
const img = screen.getByRole("img", { name: "GitHub logo" });
|
||||
expect(img.getAttribute("src")).toBe("/litellm/ui/assets/logos/github.svg");
|
||||
expect(img).toHaveAttribute("src", "/litellm/ui/assets/logos/github.svg");
|
||||
});
|
||||
|
||||
it("passes an external https URL through untouched in src mode", () => {
|
||||
render(<Logo src="https://cdn.example.com/logo.png" label="Ext" />);
|
||||
expect(screen.getByRole("img").getAttribute("src")).toBe("https://cdn.example.com/logo.png");
|
||||
expect(screen.getByRole("img")).toHaveAttribute("src", "https://cdn.example.com/logo.png");
|
||||
});
|
||||
|
||||
it("swaps to the letter avatar and warns with the failing URL on image error", () => {
|
||||
|
|
@ -63,7 +63,7 @@ describe("Logo", () => {
|
|||
|
||||
rerender(<Logo src="/ui/assets/logos/github.svg" label="Agent" />);
|
||||
const img = screen.getByRole("img", { name: "Agent logo" });
|
||||
expect(img.getAttribute("src")).toBe("/litellm/ui/assets/logos/github.svg");
|
||||
expect(img).toHaveAttribute("src", "/litellm/ui/assets/logos/github.svg");
|
||||
|
||||
rerender(<Logo src="/ui/assets/logos/broken.svg" label="Agent" />);
|
||||
expect(screen.queryByRole("img")).not.toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ vi.mock("./Navbar/CommunityEngagementButtons/CommunityEngagementButtons", () =>
|
|||
let mockUseThemeImpl = () => ({ logoUrl: null as string | null });
|
||||
let mockUseHealthReadinessDetailsImpl = () => ({ data: null as any });
|
||||
let mockGetLocalStorageItemImpl = (key: string) => null as string | null;
|
||||
let mockUseAuthorizedImpl = () => ({
|
||||
const mockUseAuthorizedImpl = () => ({
|
||||
userId: "test-user",
|
||||
userEmail: "test@example.com",
|
||||
userRole: "Admin",
|
||||
|
|
|
|||
|
|
@ -611,7 +611,7 @@ describe("CreateKey", () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("org-dropdown")).not.toBeDisabled();
|
||||
expect(screen.getByTestId("org-dropdown")).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -124,9 +124,8 @@ test("renders organization view after loading data", async () => {
|
|||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(findAllByText("Acme Corp")).toBeTruthy();
|
||||
});
|
||||
const [orgName] = await findAllByText("Acme Corp");
|
||||
expect(orgName).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("should display empty state when organization has no members", async () => {
|
||||
|
|
|
|||
|
|
@ -117,8 +117,8 @@ describe("PerUserUsage", () => {
|
|||
const xPositions = new Set(rectangles.map((rect) => rect.getAttribute("d")?.match(/^M\s*([\d.]+)/)?.[1]));
|
||||
expect(xPositions.size).toBe(3);
|
||||
|
||||
expect(chart.textContent).toContain("curl/8.0");
|
||||
expect(chart.textContent).toContain("Unknown");
|
||||
expect(chart).toHaveTextContent("curl/8.0");
|
||||
expect(chart).toHaveTextContent("Unknown");
|
||||
for (const bucket of [
|
||||
"1-9 requests",
|
||||
"10-99 requests",
|
||||
|
|
@ -127,7 +127,7 @@ describe("PerUserUsage", () => {
|
|||
"10K-99.9K requests",
|
||||
"100K+ requests",
|
||||
]) {
|
||||
expect(chart.textContent).toContain(bucket);
|
||||
expect(chart).toHaveTextContent(bucket);
|
||||
}
|
||||
|
||||
const tickTexts = Array.from(chart.querySelectorAll(".recharts-cartesian-axis-tick-value")).map(
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue