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.
-
- )}
+
>
);
};