diff --git a/ui/litellm-dashboard/src/components/fallbacks.test.tsx b/ui/litellm-dashboard/src/components/fallbacks.test.tsx index 45aa06dbaf1..e6db11270cd 100644 --- a/ui/litellm-dashboard/src/components/fallbacks.test.tsx +++ b/ui/litellm-dashboard/src/components/fallbacks.test.tsx @@ -1,5 +1,5 @@ -import { render, waitFor } from "@testing-library/react"; -import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { render, screen, waitFor } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import Fallbacks from "./fallbacks"; import { getCallbacksCall, setCallbacksCall } from "./networking"; @@ -8,17 +8,6 @@ vi.mock("./networking", () => ({ setCallbacksCall: vi.fn(), })); -vi.mock("./molecules/notifications_manager", () => ({ - __esModule: true, - default: { - success: vi.fn(), - fromBackend: vi.fn(), - info: vi.fn(), - warning: vi.fn(), - clear: vi.fn(), - }, -})); - vi.mock("./add_fallbacks", () => ({ __esModule: true, default: () =>
Mock Add Fallbacks
, @@ -29,38 +18,15 @@ vi.mock("openai", () => ({ OpenAI: vi.fn().mockImplementation(() => ({ chat: { completions: { - create: vi.fn(), + create: vi.fn().mockResolvedValue({ + model: "test-model", + }), }, }, })), }, })); -// Polyfill ResizeObserver for components relying on it in tests -if (typeof window !== "undefined" && !window.ResizeObserver) { - window.ResizeObserver = class ResizeObserver { - observe() {} - unobserve() {} - disconnect() {} - }; -} - -beforeAll(() => { - Object.defineProperty(window, "matchMedia", { - writable: true, - value: vi.fn().mockImplementation((query: string) => ({ - matches: false, - media: query, - onchange: null, - addListener: vi.fn(), - removeListener: vi.fn(), - addEventListener: vi.fn(), - removeEventListener: vi.fn(), - dispatchEvent: vi.fn(), - })), - }); -}); - describe("Fallbacks", () => { const defaultProps = { accessToken: "token", @@ -78,15 +44,14 @@ describe("Fallbacks", () => { fallbacks: [], }, }); + mockSetCallbacksCall.mockResolvedValue({}); }); - it("should render an empty table with headers when access token is provided", async () => { - const { getByText } = render(); + it("should render", async () => { + render(); await waitFor(() => { - expect(getByText("Model Name")).toBeInTheDocument(); - expect(getByText("Fallbacks")).toBeInTheDocument(); - expect(getByText("Actions")).toBeInTheDocument(); + expect(screen.getByRole("columnheader", { name: "Model Name" })).toBeInTheDocument(); }); }); @@ -99,13 +64,13 @@ describe("Fallbacks", () => { mockGetCallbacksCall.mockResolvedValue(mockFallbackData); - const { getByText } = render(); + render(); await waitFor(() => { - expect(getByText("xai/grok-2")).toBeInTheDocument(); - expect(getByText("xai/grok-4, gpt-4")).toBeInTheDocument(); - expect(getByText("gpt-3.5-turbo")).toBeInTheDocument(); - expect(getByText("gpt-4")).toBeInTheDocument(); + expect(screen.getByText("xai/grok-2")).toBeInTheDocument(); + expect(screen.getByText("xai/grok-4, gpt-4")).toBeInTheDocument(); + expect(screen.getByText("gpt-3.5-turbo")).toBeInTheDocument(); + expect(screen.getByText("gpt-4")).toBeInTheDocument(); }); expect(mockGetCallbacksCall).toHaveBeenCalledWith( @@ -114,4 +79,17 @@ describe("Fallbacks", () => { defaultProps.userRole, ); }); + + it("should render AddFallbacks component", async () => { + render(); + + await waitFor(() => { + expect(screen.getByText("Mock Add Fallbacks")).toBeInTheDocument(); + }); + }); + + it("should not render when access token is not provided", () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); }); diff --git a/ui/litellm-dashboard/src/components/fallbacks.tsx b/ui/litellm-dashboard/src/components/fallbacks.tsx index cacf3869b13..dc90ae79136 100644 --- a/ui/litellm-dashboard/src/components/fallbacks.tsx +++ b/ui/litellm-dashboard/src/components/fallbacks.tsx @@ -1,9 +1,10 @@ import { PlayIcon, TrashIcon } from "@heroicons/react/outline"; import { Icon, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "@tremor/react"; -import { Modal, Tooltip } from "antd"; +import { Tooltip } from "antd"; import openai from "openai"; import React, { useEffect, useState } from "react"; import AddFallbacks from "./add_fallbacks"; +import DeleteResourceModal from "./common_components/DeleteResourceModal"; import NotificationsManager from "./molecules/notifications_manager"; import { getCallbacksCall, setCallbacksCall } from "./networking"; @@ -68,6 +69,7 @@ const Fallbacks: React.FC = ({ accessToken, userRole, userID, mo const [routerSettings, setRouterSettings] = useState<{ [key: string]: any }>({}); const [isDeleting, setIsDeleting] = useState(false); const [fallbackToDelete, setFallbackToDelete] = useState(null); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); useEffect(() => { if (!accessToken || !userRole || !userID) { @@ -85,6 +87,7 @@ const Fallbacks: React.FC = ({ accessToken, userRole, userID, mo const handleDeleteClick = (fallbackEntry: FallbackEntry) => { setFallbackToDelete(fallbackEntry); + setIsDeleteModalOpen(true); }; const handleDeleteConfirm = async () => { @@ -100,10 +103,11 @@ const Fallbacks: React.FC = ({ accessToken, userRole, userID, mo const updatedFallbacks = routerSettings["fallbacks"] .map((dict: FallbackEntry) => { - if (key in dict && Array.isArray(dict[key])) { - delete dict[key]; + const newDict = { ...dict }; + if (key in newDict && Array.isArray(newDict[key])) { + delete newDict[key]; } - return dict; + return newDict; }) .filter((dict: FallbackEntry) => Object.keys(dict).length > 0); @@ -124,11 +128,13 @@ const Fallbacks: React.FC = ({ accessToken, userRole, userID, mo NotificationsManager.fromBackend("Failed to update router settings: " + error); } finally { setIsDeleting(false); + setIsDeleteModalOpen(false); setFallbackToDelete(null); } }; const handleDeleteCancel = () => { + setIsDeleteModalOpen(false); setFallbackToDelete(null); }; @@ -183,20 +189,22 @@ const Fallbacks: React.FC = ({ accessToken, userRole, userID, mo )} - {fallbackToDelete && ( - -

Are you sure you want to delete fallback: {Object.keys(fallbackToDelete)[0]} ?

-

This action cannot be undone.

-
- )} + ); };