mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
test(ui): decouple access-groups, vector-stores and organizations tests from antd markup
Prepares the shadcn migration of these three routes by removing every assertion that depends on the current component library, so the same tests can gate the migration without being edited. FiltersButton and its OrganizationFilters consumer both asserted on the ".ant-badge" wrapper class; they now assert the active-filter indicator element itself, and FiltersButton additionally asserts that it is absent when there are no active filters. TestVectorStoreTab drove the antd Select with fireEvent.mouseDown and picked options by node; it now clicks through the combobox role and the option text, which works against any listbox implementation. The vector-stores index test relied on Tremor mounting every TabPanel at once, so it read the Manage tab's table without ever opening that tab. It now clicks the tab first, which is what a user does and what any tabs implementation supports. VectorStoreTester had no test at all, so this adds a characterisation suite covering the empty state, the blank-query guard, the search call and its rendered result, result expansion, Enter versus Shift+Enter, the failure path and clearing history. All of these pass against the current antd and Tremor components
This commit is contained in:
parent
64aad5877a
commit
f231d46375
5 changed files with 189 additions and 25 deletions
|
|
@ -106,7 +106,7 @@ describe("OrganizationFilters", () => {
|
|||
org_alias: "test org",
|
||||
};
|
||||
|
||||
render(
|
||||
const { container } = render(
|
||||
<OrganizationFilters
|
||||
filters={filtersWithActive}
|
||||
showFilters={false}
|
||||
|
|
@ -116,8 +116,7 @@ describe("OrganizationFilters", () => {
|
|||
/>,
|
||||
);
|
||||
|
||||
const filtersButton = screen.getByRole("button", { name: /^filters$/i });
|
||||
const badgeWrapper = filtersButton.closest(".ant-badge");
|
||||
expect(badgeWrapper).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /^filters$/i })).toBeInTheDocument();
|
||||
expect(container.querySelector("sup")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import TestVectorStoreTab from "./TestVectorStoreTab";
|
||||
import { VectorStore } from "@/components/vector_store_management/types";
|
||||
|
|
@ -60,31 +61,24 @@ describe("TestVectorStoreTab", () => {
|
|||
expect(screen.getByTestId("tester-access-token")).toHaveTextContent("test-token");
|
||||
});
|
||||
|
||||
it("should update VectorStoreTester when selecting different vector store", () => {
|
||||
it("should update VectorStoreTester when selecting different vector store", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TestVectorStoreTab accessToken="test-token" vectorStores={mockVectorStores} />);
|
||||
|
||||
// Find the select component
|
||||
const selectElement = screen.getByRole("combobox");
|
||||
await user.click(screen.getByRole("combobox"));
|
||||
await user.click(await screen.findByText("Test Store 2"));
|
||||
|
||||
// Change selection
|
||||
fireEvent.mouseDown(selectElement);
|
||||
|
||||
// Wait for options to appear and click the second one
|
||||
const option2 = screen.getByText("Test Store 2");
|
||||
fireEvent.click(option2);
|
||||
|
||||
// Verify the tester component updated
|
||||
expect(screen.getByTestId("tester-vector-store-id")).toHaveTextContent("vs_456");
|
||||
});
|
||||
|
||||
it("should display vector store names in select options", () => {
|
||||
it("should display vector store names in select options", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TestVectorStoreTab accessToken="test-token" vectorStores={mockVectorStores} />);
|
||||
|
||||
const selectElement = screen.getByRole("combobox");
|
||||
fireEvent.mouseDown(selectElement);
|
||||
await user.click(screen.getByRole("combobox"));
|
||||
|
||||
// Use getAllByText since the selected value also shows the name
|
||||
expect(screen.getAllByText("Test Store 1").length).toBeGreaterThan(0);
|
||||
// The selected store's name may also render in the trigger, so only require at least one match.
|
||||
expect((await screen.findAllByText("Test Store 1")).length).toBeGreaterThan(0);
|
||||
expect(screen.getByText("Test Store 2")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { vectorStoreSearchCall } from "@/components/networking";
|
||||
|
||||
import { VectorStoreTester } from "./VectorStoreTester";
|
||||
|
||||
vi.mock("@/components/networking", () => ({
|
||||
vectorStoreSearchCall: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockWarning = vi.fn();
|
||||
vi.mock("@/components/molecules/message_manager", () => ({
|
||||
__esModule: true,
|
||||
default: { warning: (...args: unknown[]) => mockWarning(...args) },
|
||||
}));
|
||||
|
||||
const mockFromBackend = vi.fn();
|
||||
const mockSuccess = vi.fn();
|
||||
vi.mock("@/components/molecules/notifications_manager", () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
fromBackend: (...args: unknown[]) => mockFromBackend(...args),
|
||||
success: (...args: unknown[]) => mockSuccess(...args),
|
||||
},
|
||||
}));
|
||||
|
||||
const mockSearch = vi.mocked(vectorStoreSearchCall);
|
||||
|
||||
const searchResponse = {
|
||||
object: "vector_store.search_results.page",
|
||||
search_query: "hello",
|
||||
data: [
|
||||
{
|
||||
score: 0.91234,
|
||||
content: [{ text: "the quick brown fox", type: "text" }],
|
||||
file_id: "file-1",
|
||||
filename: "notes.txt",
|
||||
attributes: { source: "manual" },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const EMPTY_STATE = "Test your vector store by entering a search query below";
|
||||
|
||||
const renderTester = () => render(<VectorStoreTester vectorStoreId="vs_123" accessToken="sk-test" />);
|
||||
|
||||
const queryInput = () => screen.getByPlaceholderText(/enter your search query/i);
|
||||
const searchButton = () => screen.getByRole("button", { name: /search/i });
|
||||
|
||||
describe("VectorStoreTester", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockSearch.mockResolvedValue(searchResponse);
|
||||
});
|
||||
|
||||
it("shows the empty state before any search has run", () => {
|
||||
renderTester();
|
||||
expect(screen.getByText(EMPTY_STATE)).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: /clear history/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not search until a non-blank query is entered", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderTester();
|
||||
|
||||
await user.click(searchButton());
|
||||
expect(mockSearch).not.toHaveBeenCalled();
|
||||
|
||||
await user.type(queryInput(), "hello");
|
||||
await user.click(searchButton());
|
||||
|
||||
await waitFor(() => expect(mockSearch).toHaveBeenCalledWith("sk-test", "vs_123", "hello"));
|
||||
});
|
||||
|
||||
it("renders the returned result and clears the query input", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderTester();
|
||||
|
||||
await user.type(queryInput(), "hello");
|
||||
await user.click(searchButton());
|
||||
|
||||
expect(await screen.findByText("Result 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("1 results")).toBeInTheDocument();
|
||||
expect(screen.getByText("Score: 0.9123")).toBeInTheDocument();
|
||||
expect(screen.queryByText(EMPTY_STATE)).not.toBeInTheDocument();
|
||||
await waitFor(() => expect(queryInput()).toHaveValue(""));
|
||||
});
|
||||
|
||||
it("expands a result to reveal its content and metadata", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderTester();
|
||||
|
||||
await user.type(queryInput(), "hello");
|
||||
await user.click(searchButton());
|
||||
|
||||
expect(await screen.findByText("Result 1")).toBeInTheDocument();
|
||||
expect(screen.queryByText("the quick brown fox")).not.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByText("Result 1"));
|
||||
|
||||
expect(screen.getByText("the quick brown fox")).toBeInTheDocument();
|
||||
expect(screen.getByText("File ID:").parentElement).toHaveTextContent("file-1");
|
||||
expect(screen.getByText("Filename:").parentElement).toHaveTextContent("notes.txt");
|
||||
});
|
||||
|
||||
it("warns instead of searching when the query is only whitespace", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderTester();
|
||||
|
||||
await user.type(queryInput(), " ");
|
||||
await user.type(queryInput(), "{Enter}");
|
||||
|
||||
expect(mockWarning).toHaveBeenCalledWith("Please enter a search query");
|
||||
expect(mockSearch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("submits on Enter but not on Shift+Enter", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderTester();
|
||||
|
||||
await user.type(queryInput(), "hello");
|
||||
await user.type(queryInput(), "{Shift>}{Enter}{/Shift}");
|
||||
expect(mockSearch).not.toHaveBeenCalled();
|
||||
|
||||
await user.type(queryInput(), "{Enter}");
|
||||
await waitFor(() => expect(mockSearch).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
|
||||
it("reports a failed search and keeps the history empty", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockSearch.mockRejectedValue(new Error("boom"));
|
||||
renderTester();
|
||||
|
||||
await user.type(queryInput(), "hello");
|
||||
await user.click(searchButton());
|
||||
|
||||
await waitFor(() => expect(mockFromBackend).toHaveBeenCalledWith("Failed to search vector store"));
|
||||
expect(screen.getByText(EMPTY_STATE)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("clears the search history", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderTester();
|
||||
|
||||
await user.type(queryInput(), "hello");
|
||||
await user.click(searchButton());
|
||||
expect(await screen.findByText("Result 1")).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /clear history/i }));
|
||||
|
||||
expect(screen.queryByText("Result 1")).not.toBeInTheDocument();
|
||||
expect(screen.getByText(EMPTY_STATE)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { vectorStoreListCall } from "@/components/networking";
|
||||
|
|
@ -25,18 +26,25 @@ vi.mock("./TestVectorStoreTab", () => ({ __esModule: true, default: () => null }
|
|||
|
||||
const mockVectorStoreListCall = vi.mocked(vectorStoreListCall);
|
||||
|
||||
const openManageTab = async (user: ReturnType<typeof userEvent.setup>) => {
|
||||
await user.click(screen.getByRole("tab", { name: "Manage Vector Stores" }));
|
||||
};
|
||||
|
||||
describe("VectorStoreManagement loading state", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should resolve the loading state when accessToken is null instead of showing the skeleton forever", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<VectorStoreManagement accessToken={null} userID={null} userRole={null} />);
|
||||
await openManageTab(user);
|
||||
expect(await screen.findByText("table-loaded")).toBeInTheDocument();
|
||||
expect(mockVectorStoreListCall).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should show the loading state until the vector store fetch settles", async () => {
|
||||
const user = userEvent.setup();
|
||||
let resolveFetch: (value: { data: never[] }) => void = () => {};
|
||||
mockVectorStoreListCall.mockReturnValue(
|
||||
new Promise((resolve) => {
|
||||
|
|
@ -44,6 +52,7 @@ describe("VectorStoreManagement loading state", () => {
|
|||
}),
|
||||
);
|
||||
render(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin" />);
|
||||
await openManageTab(user);
|
||||
expect(screen.getByText("table-loading")).toBeInTheDocument();
|
||||
|
||||
resolveFetch({ data: [] });
|
||||
|
|
|
|||
|
|
@ -21,12 +21,18 @@ describe("FiltersButton", () => {
|
|||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should show badge when hasActiveFilters is true", () => {
|
||||
it("should show the active-filter indicator when hasActiveFilters is true", () => {
|
||||
const onClick = vi.fn();
|
||||
const { container } = render(<FiltersButton onClick={onClick} active={false} hasActiveFilters={true} />);
|
||||
const button = screen.getByRole("button", { name: /filters/i });
|
||||
const badgeWrapper = button.closest(".ant-badge");
|
||||
expect(badgeWrapper).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /filters/i })).toBeInTheDocument();
|
||||
expect(container.querySelector("sup")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not show the active-filter indicator when hasActiveFilters is false", () => {
|
||||
const onClick = vi.fn();
|
||||
const { container } = render(<FiltersButton onClick={onClick} active={false} hasActiveFilters={false} />);
|
||||
expect(screen.getByRole("button", { name: /filters/i })).toBeInTheDocument();
|
||||
expect(container.querySelector("sup")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render custom label when provided", () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue