Merge pull request #20596 from BerriAI/litellm_ui_yj_cov_01

[Infra] UI - Testing: Adding Unit Testing Coverage
This commit is contained in:
yuneng-jiang 2026-02-06 12:33:40 -08:00 • committed by GitHub
commit 49eab29335
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1168 additions and 0 deletions

View file

@ -0,0 +1,180 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { renderHook, waitFor } from "@testing-library/react";
import { useDisableShowNewBadge } from "./useDisableShowNewBadge";
import { LOCAL_STORAGE_EVENT } from "@/utils/localStorageUtils";
describe("useDisableShowNewBadge", () => {
const STORAGE_KEY = "disableShowNewBadge";
beforeEach(() => {
localStorage.clear();
vi.clearAllMocks();
});
afterEach(() => {
localStorage.clear();
});
it("should return false when localStorage is empty", () => {
const { result } = renderHook(() => useDisableShowNewBadge());
expect(result.current).toBe(false);
});
it("should return false when localStorage value is not 'true'", () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowNewBadge());
expect(result.current).toBe(false);
});
it("should return true when localStorage value is 'true'", () => {
localStorage.setItem(STORAGE_KEY, "true");
const { result } = renderHook(() => useDisableShowNewBadge());
expect(result.current).toBe(true);
});
it("should return false when localStorage value is an empty string", () => {
localStorage.setItem(STORAGE_KEY, "");
const { result } = renderHook(() => useDisableShowNewBadge());
expect(result.current).toBe(false);
});
it("should update when storage event fires for the correct key", async () => {
const { result } = renderHook(() => useDisableShowNewBadge());
expect(result.current).toBe(false);
localStorage.setItem(STORAGE_KEY, "true");
const storageEvent = new StorageEvent("storage", {
key: STORAGE_KEY,
newValue: "true",
});
window.dispatchEvent(storageEvent);
await waitFor(() => {
expect(result.current).toBe(true);
});
});
it("should not update when storage event fires for a different key", () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowNewBadge());
expect(result.current).toBe(false);
const storageEvent = new StorageEvent("storage", {
key: "otherKey",
newValue: "true",
});
window.dispatchEvent(storageEvent);
expect(result.current).toBe(false);
});
it("should update when custom LOCAL_STORAGE_EVENT fires for the correct key", async () => {
const { result } = renderHook(() => useDisableShowNewBadge());
expect(result.current).toBe(false);
localStorage.setItem(STORAGE_KEY, "true");
const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: STORAGE_KEY },
});
window.dispatchEvent(customEvent);
await waitFor(() => {
expect(result.current).toBe(true);
});
});
it("should not update when custom LOCAL_STORAGE_EVENT fires for a different key", () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowNewBadge());
expect(result.current).toBe(false);
const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: "otherKey" },
});
window.dispatchEvent(customEvent);
expect(result.current).toBe(false);
});
it("should update when localStorage changes from false to true via custom event", async () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowNewBadge());
expect(result.current).toBe(false);
localStorage.setItem(STORAGE_KEY, "true");
const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: STORAGE_KEY },
});
window.dispatchEvent(customEvent);
await waitFor(() => {
expect(result.current).toBe(true);
});
});
it("should update when localStorage changes from true to false via storage event", async () => {
localStorage.setItem(STORAGE_KEY, "true");
const { result } = renderHook(() => useDisableShowNewBadge());
expect(result.current).toBe(true);
localStorage.setItem(STORAGE_KEY, "false");
const storageEvent = new StorageEvent("storage", {
key: STORAGE_KEY,
newValue: "false",
});
window.dispatchEvent(storageEvent);
await waitFor(() => {
expect(result.current).toBe(false);
});
});
it("should cleanup event listeners on unmount", () => {
const addEventListenerSpy = vi.spyOn(window, "addEventListener");
const removeEventListenerSpy = vi.spyOn(window, "removeEventListener");
const { unmount } = renderHook(() => useDisableShowNewBadge());
expect(addEventListenerSpy).toHaveBeenCalledTimes(2);
expect(addEventListenerSpy).toHaveBeenCalledWith("storage", expect.any(Function));
expect(addEventListenerSpy).toHaveBeenCalledWith(LOCAL_STORAGE_EVENT, expect.any(Function));
unmount();
expect(removeEventListenerSpy).toHaveBeenCalledTimes(2);
expect(removeEventListenerSpy).toHaveBeenCalledWith("storage", expect.any(Function));
expect(removeEventListenerSpy).toHaveBeenCalledWith(LOCAL_STORAGE_EVENT, expect.any(Function));
});
it("should handle multiple hooks independently", async () => {
const { result: result1 } = renderHook(() => useDisableShowNewBadge());
const { result: result2 } = renderHook(() => useDisableShowNewBadge());
expect(result1.current).toBe(false);
expect(result2.current).toBe(false);
localStorage.setItem(STORAGE_KEY, "true");
const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: STORAGE_KEY },
});
window.dispatchEvent(customEvent);
await waitFor(() => {
expect(result1.current).toBe(true);
expect(result2.current).toBe(true);
});
});
});

View file

@ -0,0 +1,180 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { renderHook, waitFor } from "@testing-library/react";
import { useDisableShowPrompts } from "./useDisableShowPrompts";
import { LOCAL_STORAGE_EVENT } from "@/utils/localStorageUtils";
describe("useDisableShowPrompts", () => {
const STORAGE_KEY = "disableShowPrompts";
beforeEach(() => {
localStorage.clear();
vi.clearAllMocks();
});
afterEach(() => {
localStorage.clear();
});
it("should return false when localStorage is empty", () => {
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);
});
it("should return false when localStorage value is not 'true'", () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);
});
it("should return true when localStorage value is 'true'", () => {
localStorage.setItem(STORAGE_KEY, "true");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(true);
});
it("should return false when localStorage value is an empty string", () => {
localStorage.setItem(STORAGE_KEY, "");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);
});
it("should update when storage event fires for the correct key", async () => {
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);
localStorage.setItem(STORAGE_KEY, "true");
const storageEvent = new StorageEvent("storage", {
key: STORAGE_KEY,
newValue: "true",
});
window.dispatchEvent(storageEvent);
await waitFor(() => {
expect(result.current).toBe(true);
});
});
it("should not update when storage event fires for a different key", () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);
const storageEvent = new StorageEvent("storage", {
key: "otherKey",
newValue: "true",
});
window.dispatchEvent(storageEvent);
expect(result.current).toBe(false);
});
it("should update when custom LOCAL_STORAGE_EVENT fires for the correct key", async () => {
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);
localStorage.setItem(STORAGE_KEY, "true");
const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: STORAGE_KEY },
});
window.dispatchEvent(customEvent);
await waitFor(() => {
expect(result.current).toBe(true);
});
});
it("should not update when custom LOCAL_STORAGE_EVENT fires for a different key", () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);
const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: "otherKey" },
});
window.dispatchEvent(customEvent);
expect(result.current).toBe(false);
});
it("should update when localStorage changes from false to true via custom event", async () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);
localStorage.setItem(STORAGE_KEY, "true");
const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: STORAGE_KEY },
});
window.dispatchEvent(customEvent);
await waitFor(() => {
expect(result.current).toBe(true);
});
});
it("should update when localStorage changes from true to false via storage event", async () => {
localStorage.setItem(STORAGE_KEY, "true");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(true);
localStorage.setItem(STORAGE_KEY, "false");
const storageEvent = new StorageEvent("storage", {
key: STORAGE_KEY,
newValue: "false",
});
window.dispatchEvent(storageEvent);
await waitFor(() => {
expect(result.current).toBe(false);
});
});
it("should cleanup event listeners on unmount", () => {
const addEventListenerSpy = vi.spyOn(window, "addEventListener");
const removeEventListenerSpy = vi.spyOn(window, "removeEventListener");
const { unmount } = renderHook(() => useDisableShowPrompts());
expect(addEventListenerSpy).toHaveBeenCalledTimes(2);
expect(addEventListenerSpy).toHaveBeenCalledWith("storage", expect.any(Function));
expect(addEventListenerSpy).toHaveBeenCalledWith(LOCAL_STORAGE_EVENT, expect.any(Function));
unmount();
expect(removeEventListenerSpy).toHaveBeenCalledTimes(2);
expect(removeEventListenerSpy).toHaveBeenCalledWith("storage", expect.any(Function));
expect(removeEventListenerSpy).toHaveBeenCalledWith(LOCAL_STORAGE_EVENT, expect.any(Function));
});
it("should handle multiple hooks independently", async () => {
const { result: result1 } = renderHook(() => useDisableShowPrompts());
const { result: result2 } = renderHook(() => useDisableShowPrompts());
expect(result1.current).toBe(false);
expect(result2.current).toBe(false);
localStorage.setItem(STORAGE_KEY, "true");
const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: STORAGE_KEY },
});
window.dispatchEvent(customEvent);
await waitFor(() => {
expect(result1.current).toBe(true);
expect(result2.current).toBe(true);
});
});
});

View file

@ -0,0 +1,426 @@
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { SearchToolTester } from "./SearchToolTester";
import * as networking from "../networking";
import NotificationsManager from "../molecules/notifications_manager";
import * as antd from "antd";
vi.mock("../networking", () => ({
searchToolQueryCall: vi.fn(),
}));
vi.mock("antd", async () => {
const actual = await vi.importActual("antd");
return {
...actual,
message: {
warning: vi.fn(),
success: vi.fn(),
error: vi.fn(),
},
};
});
const mockSearchResults = {
results: [
{
title: "Test Result 1",
url: "https://example.com/result1",
snippet: "This is a short snippet for the first result.",
},
{
title: "Test Result 2",
url: "https://example.com/result2",
snippet: "This is a longer snippet that exceeds two hundred characters and should be truncated when displayed in the results. It contains more detailed information about the search result that would normally be shown in a search engine result page.",
},
],
};
const defaultProps = {
searchToolName: "test-search-tool",
accessToken: "test-token",
};
describe("SearchToolTester", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(networking.searchToolQueryCall).mockResolvedValue(mockSearchResults);
vi.spyOn(Date, "now").mockReturnValue(1000000000000);
});
it("should render", () => {
render(<SearchToolTester {...defaultProps} />);
expect(screen.getByText("Test Search Tool")).toBeInTheDocument();
});
it("should display empty state when no search has been performed", () => {
render(<SearchToolTester {...defaultProps} />);
expect(screen.getByText("Test your search tool")).toBeInTheDocument();
expect(screen.getByText("Enter a query above to see search results")).toBeInTheDocument();
});
it("should display search input with placeholder", () => {
render(<SearchToolTester {...defaultProps} />);
expect(screen.getByPlaceholderText("Enter your search query...")).toBeInTheDocument();
});
it("should display search button", () => {
render(<SearchToolTester {...defaultProps} />);
expect(screen.getByRole("button", { name: /search/i })).toBeInTheDocument();
});
it("should disable search button when input is empty", () => {
render(<SearchToolTester {...defaultProps} />);
const searchButton = screen.getByRole("button", { name: /search/i });
expect(searchButton).toBeDisabled();
});
it("should enable search button when input has text", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
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();
});
it("should call searchToolQueryCall when search button is clicked", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
expect(networking.searchToolQueryCall).toHaveBeenCalledWith("test-token", "test-search-tool", "test query");
});
it("should call searchToolQueryCall when Enter is pressed in input", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query{Enter}");
expect(networking.searchToolQueryCall).toHaveBeenCalledWith("test-token", "test-search-tool", "test query");
});
it("should not call searchToolQueryCall when Shift+Enter is pressed", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
await user.keyboard("{Shift>}{Enter}{/Shift}");
expect(networking.searchToolQueryCall).not.toHaveBeenCalled();
});
it("should display loading state while searching", async () => {
vi.mocked(networking.searchToolQueryCall).mockImplementation(
() => new Promise((resolve) => setTimeout(() => resolve(mockSearchResults), 100)),
);
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
expect(screen.getByText("Searching...")).toBeInTheDocument();
});
it("should display search results after successful search", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("Test Result 1")).toBeInTheDocument();
});
expect(screen.getByText("Test Result 2")).toBeInTheDocument();
});
it("should display search query in results header", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("test query")).toBeInTheDocument();
});
});
it("should display result count in results header", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("2 results")).toBeInTheDocument();
});
});
it("should display singular result count when only one result", async () => {
const singleResult = {
results: [
{
title: "Single Result",
url: "https://example.com/single",
snippet: "Single result snippet",
},
],
};
vi.mocked(networking.searchToolQueryCall).mockResolvedValue(singleResult);
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("1 result")).toBeInTheDocument();
});
});
it("should display result URLs", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("https://example.com/result1")).toBeInTheDocument();
});
expect(screen.getByText("https://example.com/result2")).toBeInTheDocument();
});
it("should display result snippets", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("This is a short snippet for the first result.")).toBeInTheDocument();
});
});
it("should truncate long snippets and show expand button", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText(/Show more/i)).toBeInTheDocument();
});
});
it("should expand snippet when Show more is clicked", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText(/Show more/i)).toBeInTheDocument();
});
const expandButton = screen.getByText(/Show more/i);
await user.click(expandButton);
expect(screen.getByText(/Show less/i)).toBeInTheDocument();
});
it("should collapse snippet when Show less is clicked", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText(/Show more/i)).toBeInTheDocument();
});
const expandButton = screen.getByText(/Show more/i);
await user.click(expandButton);
const collapseButton = screen.getByText(/Show less/i);
await user.click(collapseButton);
expect(screen.getByText(/Show more/i)).toBeInTheDocument();
});
it("should display no results message when search returns empty results", async () => {
vi.mocked(networking.searchToolQueryCall).mockResolvedValue({ results: [] });
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("No results found")).toBeInTheDocument();
});
expect(screen.getByText("Try a different search query")).toBeInTheDocument();
});
it("should display no results message when search returns null results", async () => {
vi.mocked(networking.searchToolQueryCall).mockResolvedValue({ results: null });
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("No results found")).toBeInTheDocument();
});
});
it("should handle search errors and show notification", async () => {
const error = new Error("Search failed");
vi.mocked(networking.searchToolQueryCall).mockRejectedValue(error);
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => { });
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith("Failed to query search tool");
});
consoleSpy.mockRestore();
});
it("should maintain search history", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "first query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("first query")).toBeInTheDocument();
});
await user.clear(input);
await user.type(input, "second query");
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("second query")).toBeInTheDocument();
});
expect(screen.getByText("Previous Searches")).toBeInTheDocument();
expect(screen.getByText("first query")).toBeInTheDocument();
});
it("should allow clicking previous search to set query", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "first query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("first query")).toBeInTheDocument();
});
await user.clear(input);
await user.type(input, "second query");
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("second query")).toBeInTheDocument();
});
const historyItems = screen.getAllByText("first query");
const historyItem = historyItems.find((item) => item.closest('[class*="cursor-pointer"]'));
if (historyItem) {
await user.click(historyItem);
expect(input).toHaveValue("first query");
}
});
it("should clear history when Clear All is clicked", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "first query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("first query")).toBeInTheDocument();
});
await user.clear(input);
await user.type(input, "second query");
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("Previous Searches")).toBeInTheDocument();
});
const clearButton = screen.getByRole("button", { name: /clear all/i });
await user.click(clearButton);
expect(NotificationsManager.success).toHaveBeenCalledWith("Search history cleared");
expect(screen.queryByText("Previous Searches")).not.toBeInTheDocument();
});
it("should limit history display to 5 previous searches", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
for (let i = 1; i <= 7; i++) {
await user.clear(input);
await user.type(input, `query ${i}`);
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText(`query ${i}`)).toBeInTheDocument();
});
}
const historySection = screen.queryByText("Previous Searches");
if (historySection) {
const historyItems = historySection.parentElement?.querySelectorAll('[class*="cursor-pointer"]');
expect(historyItems?.length).toBeLessThanOrEqual(5);
}
});
it("should preserve query text after search", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(screen.getByText("test query")).toBeInTheDocument();
});
expect(input).toHaveValue("test query");
});
it("should disable input and button while loading", async () => {
vi.mocked(networking.searchToolQueryCall).mockImplementation(
() => new Promise((resolve) => setTimeout(() => resolve(mockSearchResults), 100)),
);
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
await user.type(input, "test query");
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
expect(input).toBeDisabled();
expect(searchButton).toBeDisabled();
});
it("should display result links that open in new tab", async () => {
const user = userEvent.setup();
render(<SearchToolTester {...defaultProps} />);
const input = screen.getByPlaceholderText("Enter your search query...");
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");
});
});
});

View file

@ -0,0 +1,382 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { CustomLegend, CustomTooltip } from "./chartUtils";
import type { CustomTooltipProps } from "@tremor/react";
import { SpendMetrics } from "../UsagePage/types";
describe("CustomTooltip", () => {
const mockPayload = [
{
dataKey: "metrics.total_tokens",
value: 1000,
color: "blue",
payload: {
date: "2024-01-15",
metrics: {
total_tokens: 1000,
prompt_tokens: 600,
completion_tokens: 400,
spend: 0.05,
api_requests: 10,
successful_requests: 9,
failed_requests: 1,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
} as SpendMetrics,
},
},
];
it("should render", () => {
const props: CustomTooltipProps = {
active: true,
payload: mockPayload,
label: "2024-01-15",
};
render(<CustomTooltip {...props} />);
expect(screen.getByText("2024-01-15")).toBeInTheDocument();
});
it("should return null when not active", () => {
const props: CustomTooltipProps = {
active: false,
payload: mockPayload,
label: "2024-01-15",
};
const { container } = render(<CustomTooltip {...props} />);
expect(container.firstChild).toBeNull();
});
it("should return null when payload is empty", () => {
const props: CustomTooltipProps = {
active: true,
payload: [],
label: "2024-01-15",
};
const { container } = render(<CustomTooltip {...props} />);
expect(container.firstChild).toBeNull();
});
it("should display formatted category names", () => {
const props: CustomTooltipProps = {
active: true,
payload: mockPayload,
label: "2024-01-15",
};
render(<CustomTooltip {...props} />);
expect(screen.getByText("Total Tokens")).toBeInTheDocument();
});
it("should format category names by removing metrics prefix and replacing underscores", () => {
const payloadWithUnderscores = [
{
dataKey: "metrics.prompt_tokens",
value: 600,
color: "green",
payload: {
date: "2024-01-15",
metrics: {
prompt_tokens: 600,
total_tokens: 1000,
completion_tokens: 400,
spend: 0.05,
api_requests: 10,
successful_requests: 9,
failed_requests: 1,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
} as SpendMetrics,
},
},
];
const props: CustomTooltipProps = {
active: true,
payload: payloadWithUnderscores,
label: "2024-01-15",
};
render(<CustomTooltip {...props} />);
expect(screen.getByText("Prompt Tokens")).toBeInTheDocument();
});
it("should format spend values with dollar sign and two decimal places", () => {
const spendPayload = [
{
dataKey: "metrics.spend",
value: 1234.567,
color: "red",
payload: {
date: "2024-01-15",
metrics: {
spend: 1234.567,
total_tokens: 1000,
prompt_tokens: 600,
completion_tokens: 400,
api_requests: 10,
successful_requests: 9,
failed_requests: 1,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
} as SpendMetrics,
},
},
];
const props: CustomTooltipProps = {
active: true,
payload: spendPayload,
label: "2024-01-15",
};
render(<CustomTooltip {...props} />);
expect(screen.getByText("$1,234.57")).toBeInTheDocument();
});
it("should format non-spend numeric values with locale string", () => {
const props: CustomTooltipProps = {
active: true,
payload: mockPayload,
label: "2024-01-15",
};
render(<CustomTooltip {...props} />);
expect(screen.getByText("1,000")).toBeInTheDocument();
});
it("should display N/A when value is undefined", () => {
const payloadWithUndefined = [
{
dataKey: "metrics.nonexistent",
value: undefined,
color: "blue",
payload: {
date: "2024-01-15",
metrics: {
total_tokens: 1000,
prompt_tokens: 600,
completion_tokens: 400,
spend: 0.05,
api_requests: 10,
successful_requests: 9,
failed_requests: 1,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
} as SpendMetrics,
},
},
];
const props: CustomTooltipProps = {
active: true,
payload: payloadWithUndefined,
label: "2024-01-15",
};
render(<CustomTooltip {...props} />);
expect(screen.getByText("N/A")).toBeInTheDocument();
});
it("should handle multiple payload items", () => {
const multiplePayload = [
{
dataKey: "metrics.total_tokens",
value: 1000,
color: "blue",
payload: {
date: "2024-01-15",
metrics: {
total_tokens: 1000,
prompt_tokens: 600,
completion_tokens: 400,
spend: 0.05,
api_requests: 10,
successful_requests: 9,
failed_requests: 1,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
} as SpendMetrics,
},
},
{
dataKey: "metrics.spend",
value: 0.05,
color: "green",
payload: {
date: "2024-01-15",
metrics: {
total_tokens: 1000,
prompt_tokens: 600,
completion_tokens: 400,
spend: 0.05,
api_requests: 10,
successful_requests: 9,
failed_requests: 1,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
} as SpendMetrics,
},
},
];
const props: CustomTooltipProps = {
active: true,
payload: multiplePayload,
label: "2024-01-15",
};
render(<CustomTooltip {...props} />);
expect(screen.getByText("Total Tokens")).toBeInTheDocument();
expect(screen.getByText("Spend")).toBeInTheDocument();
});
it("should convert color names to hex values", () => {
const props: CustomTooltipProps = {
active: true,
payload: mockPayload,
label: "2024-01-15",
};
const { container } = render(<CustomTooltip {...props} />);
const colorIndicator = container.querySelector('span[style*="background-color"]');
expect(colorIndicator).toHaveStyle({ backgroundColor: "#3b82f6" });
});
it("should use hex color directly when color is not a known color name", () => {
const payloadWithHexColor = [
{
dataKey: "metrics.total_tokens",
value: 1000,
color: "#ff0000",
payload: {
date: "2024-01-15",
metrics: {
total_tokens: 1000,
prompt_tokens: 600,
completion_tokens: 400,
spend: 0.05,
api_requests: 10,
successful_requests: 9,
failed_requests: 1,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
} as SpendMetrics,
},
},
];
const props: CustomTooltipProps = {
active: true,
payload: payloadWithHexColor,
label: "2024-01-15",
};
const { container } = render(<CustomTooltip {...props} />);
const colorIndicator = container.querySelector('span[style*="background-color"]');
expect(colorIndicator).toHaveStyle({ backgroundColor: "#ff0000" });
});
it("should skip items without dataKey", () => {
const payloadWithoutDataKey = [
{
dataKey: undefined,
value: 1000,
color: "blue",
payload: {
date: "2024-01-15",
metrics: {
total_tokens: 1000,
prompt_tokens: 600,
completion_tokens: 400,
spend: 0.05,
api_requests: 10,
successful_requests: 9,
failed_requests: 1,
cache_read_input_tokens: 0,
cache_creation_input_tokens: 0,
} as SpendMetrics,
},
},
];
const props: CustomTooltipProps = {
active: true,
payload: payloadWithoutDataKey as any,
label: "2024-01-15",
};
render(<CustomTooltip {...props} />);
expect(screen.queryByText("Total Tokens")).not.toBeInTheDocument();
});
it("should skip items without payload", () => {
const payloadWithoutPayload = [
{
dataKey: "metrics.total_tokens",
value: 1000,
color: "blue",
payload: undefined,
},
];
const props: CustomTooltipProps = {
active: true,
payload: payloadWithoutPayload as any,
label: "2024-01-15",
};
render(<CustomTooltip {...props} />);
expect(screen.queryByText("Total Tokens")).not.toBeInTheDocument();
});
});
describe("CustomLegend", () => {
it("should render", () => {
render(<CustomLegend categories={["metrics.total_tokens"]} colors={["blue"]} />);
expect(screen.getByText("Total Tokens")).toBeInTheDocument();
});
it("should display multiple categories", () => {
render(
<CustomLegend
categories={["metrics.total_tokens", "metrics.spend", "metrics.prompt_tokens"]}
colors={["blue", "green", "red"]}
/>,
);
expect(screen.getByText("Total Tokens")).toBeInTheDocument();
expect(screen.getByText("Spend")).toBeInTheDocument();
expect(screen.getByText("Prompt Tokens")).toBeInTheDocument();
});
it("should format category names by removing metrics prefix and replacing underscores", () => {
render(<CustomLegend categories={["metrics.api_requests"]} colors={["blue"]} />);
expect(screen.getByText("Api Requests")).toBeInTheDocument();
});
it("should capitalize first letter of each word", () => {
render(<CustomLegend categories={["metrics.successful_requests"]} colors={["green"]} />);
expect(screen.getByText("Successful Requests")).toBeInTheDocument();
});
it("should convert color names to hex values", () => {
const { container } = render(<CustomLegend categories={["metrics.total_tokens"]} colors={["cyan"]} />);
const colorIndicator = container.querySelector('span[style*="background-color"]');
expect(colorIndicator).toHaveStyle({ backgroundColor: "#06b6d4" });
});
it("should use hex color directly when color is not a known color name", () => {
const { container } = render(<CustomLegend categories={["metrics.total_tokens"]} colors={["#ff00ff"]} />);
const colorIndicator = container.querySelector('span[style*="background-color"]');
expect(colorIndicator).toHaveStyle({ backgroundColor: "#ff00ff" });
});
it("should handle all supported color names", () => {
const colors = ["blue", "cyan", "indigo", "green", "red", "purple", "emerald"];
const categories = colors.map((_, idx) => `metrics.category_${idx}`);
render(<CustomLegend categories={categories} colors={colors} />);
expect(screen.getByText("Category 0")).toBeInTheDocument();
});
it("should match categories and colors by index", () => {
render(
<CustomLegend
categories={["metrics.first", "metrics.second", "metrics.third"]}
colors={["blue", "green", "red"]}
/>,
);
const { container } = render(
<CustomLegend
categories={["metrics.first", "metrics.second", "metrics.third"]}
colors={["blue", "green", "red"]}
/>,
);
const colorIndicators = container.querySelectorAll('span[style*="background-color"]');
expect(colorIndicators[0]).toHaveStyle({ backgroundColor: "#3b82f6" });
expect(colorIndicators[1]).toHaveStyle({ backgroundColor: "#22c55e" });
expect(colorIndicators[2]).toHaveStyle({ backgroundColor: "#ef4444" });
});
});