From 219cd414f39993c85cbd66c31307758e1b8394f6 Mon Sep 17 00:00:00 2001 From: Chesars Date: Wed, 21 Jan 2026 14:54:27 -0300 Subject: [PATCH 1/6] Fix model management page UI improvements - Delete button now shows confirmation modal instead of redirecting to detail page - Entire table row is clickable to view model details - Fixed Last Refreshed text alignment and shortened time format - Added dismiss button (X) to Missing provider banner with localStorage persistence - Added compact Request Provider button in header when banner is dismissed --- .../ModelsAndEndpointsView.tsx | 116 ++++++++++++------ .../components/AllModelsTab.tsx | 65 +++++++++- .../src/components/model_dashboard/table.tsx | 8 +- .../components/molecules/models/columns.tsx | 18 ++- 4 files changed, 160 insertions(+), 47 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx index 6a4882a92a..9a96cb33a5 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx @@ -15,7 +15,7 @@ import { transformModelData } from "./utils/modelDataTransformer"; import { all_admin_roles, internalUserRoles, isProxyAdminRole, isUserTeamAdminForAnyTeam } from "@/utils/roles"; import { RefreshIcon } from "@heroicons/react/outline"; import { useQueryClient } from "@tanstack/react-query"; -import { Col, Grid, Icon, Tab, TabGroup, TabList, TabPanel, TabPanels, Text } from "@tremor/react"; +import { Col, Grid, Icon, Tab, TabGroup, TabList, TabPanel, TabPanels } from "@tremor/react"; import type { UploadProps } from "antd"; import { Form, Typography } from "antd"; import { PlusCircleOutlined } from "@ant-design/icons"; @@ -62,6 +62,12 @@ const ModelsAndEndpointsView: React.FC = ({ premiumUser, te const [selectedModelId, setSelectedModelId] = useState(null); const [selectedTeamId, setSelectedTeamId] = useState(null); const [selectedTabIndex, setSelectedTabIndex] = useState(0); + const [showMissingProviderBanner, setShowMissingProviderBanner] = useState(() => { + if (typeof window !== "undefined") { + return localStorage.getItem("hideMissingProviderBanner") !== "true"; + } + return true; + }); const queryClient = useQueryClient(); const { data: modelDataResponse, isLoading: isLoadingModels, refetch: refetchModels } = useModelsInfo(); @@ -153,7 +159,7 @@ const ModelsAndEndpointsView: React.FC = ({ premiumUser, te const handleRefreshClick = () => { const currentDate = new Date(); - setLastRefreshed(currentDate.toLocaleString()); + setLastRefreshed(currentDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })); queryClient.invalidateQueries({ queryKey: ["models", "list"] }); refetchModels(); }; @@ -275,43 +281,75 @@ const ModelsAndEndpointsView: React.FC = ({ premiumUser, te

Add and manage models for the proxy

)} + {!showMissingProviderBanner && ( + + + Request Provider + + )} {/* Missing Provider Banner */} -
-
- -
-
-

Missing a provider?

-

- The LiteLLM engineering team is constantly adding support for new LLM models, providers, endpoints. If - you don't see the one you need, let us know and we'll prioritize it. -

-
- - Request Provider - +
+ +
+
+

Missing a provider?

+

+ The LiteLLM engineering team is constantly adding support for new LLM models, providers, endpoints. If + you don't see the one you need, let us know and we'll prioritize it. +

+
+
- - - -
+ Request Provider + + + + + + + )} {selectedModelId && !isLoading ? ( = ({ premiumUser, te {all_admin_roles.includes(userRole) && Price Data Reload} -
- {lastRefreshed && Last Refreshed: {lastRefreshed}} +
+ {lastRefreshed && Last Refreshed: {lastRefreshed}}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx index a5553fa5f5..25ca5f4cf5 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx @@ -5,8 +5,12 @@ import { Team } from "@/components/key_team_helpers/key_list"; import { AllModelsDataTable } from "@/components/model_dashboard/all_models_table"; import { columns } from "@/components/molecules/models/columns"; import { getDisplayModelName } from "@/components/view_model/model_name_display"; +import DeleteResourceModal from "@/components/common_components/DeleteResourceModal"; +import NotificationsManager from "@/components/molecules/notifications_manager"; +import { modelDeleteCall } from "@/components/networking"; import { InfoCircleOutlined } from "@ant-design/icons"; import { PaginationState, SortingState } from "@tanstack/react-table"; +import { useQueryClient } from "@tanstack/react-query"; import { Grid, Select, SelectItem, TabPanel, Text } from "@tremor/react"; import { Skeleton, Spin } from "antd"; import debounce from "lodash/debounce"; @@ -33,8 +37,9 @@ const AllModelsTab = ({ setSelectedTeamId, }: AllModelsTabProps) => { const { data: modelCostMapData, isLoading: isLoadingModelCostMap } = useModelCostMap(); - const { userId, userRole, premiumUser } = useAuthorized(); + const { accessToken, userId, userRole, premiumUser } = useAuthorized(); const { data: teams, isLoading: isLoadingTeams } = useTeams(); + const queryClient = useQueryClient(); const [modelNameSearch, setModelNameSearch] = useState(""); const [debouncedSearch, setDebouncedSearch] = useState(""); @@ -92,7 +97,7 @@ const AllModelsTab = ({ return sort.desc ? "desc" : "asc"; }, [sorting]); - const { data: rawModelData, isLoading: isLoadingModelsInfo } = useModelsInfo( + const { data: rawModelData, isLoading: isLoadingModelsInfo, refetch: refetchModels } = useModelsInfo( currentPage, pageSize, debouncedSearch || undefined, @@ -117,6 +122,9 @@ const AllModelsTab = ({ return transformModelData(rawModelData, getProviderFromModel); }, [rawModelData, modelCostMapData]); + const [deleteModalModelId, setDeleteModalModelId] = useState(null); + const [deleteLoading, setDeleteLoading] = useState(false); + // Get pagination metadata from the response const paginationMeta = useMemo(() => { if (!rawModelData) { @@ -187,6 +195,28 @@ const AllModelsTab = ({ setSorting([]); }; + const modelToDelete = useMemo(() => { + if (!deleteModalModelId || !modelData?.data) return null; + return modelData.data.find((model: any) => model.model_info.id === deleteModalModelId); + }, [deleteModalModelId, modelData]); + + const handleDeleteModel = async () => { + if (!accessToken || !deleteModalModelId) return; + try { + setDeleteLoading(true); + await modelDeleteCall(accessToken, deleteModalModelId); + NotificationsManager.success("Model deleted successfully"); + queryClient.invalidateQueries({ queryKey: ["models", "list"] }); + refetchModels(); + } catch (error) { + console.error("Error deleting model:", error); + NotificationsManager.fromBackend("Failed to delete model"); + } finally { + setDeleteLoading(false); + setDeleteModalModelId(null); + } + }; + return ( @@ -481,6 +511,7 @@ const AllModelsTab = ({ () => { }, expandedRows, setExpandedRows, + setDeleteModalModelId, )} data={filteredData} isLoading={isLoadingModelsInfo} @@ -489,10 +520,40 @@ const AllModelsTab = ({ pagination={pagination} onPaginationChange={setPagination} enablePagination={true} + onRowClick={(model: any) => setSelectedModelId(model.model_info.id)} />
+ + setDeleteModalModelId(null)} + onOk={handleDeleteModel} + confirmLoading={deleteLoading} + /> ); }; diff --git a/ui/litellm-dashboard/src/components/model_dashboard/table.tsx b/ui/litellm-dashboard/src/components/model_dashboard/table.tsx index 79224edba4..b7c416a219 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard/table.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard/table.tsx @@ -30,6 +30,7 @@ interface ModelDataTableProps { pagination?: PaginationState; onPaginationChange?: OnChangeFn; enablePagination?: boolean; + onRowClick?: (row: TData) => void; } export function ModelDataTable({ @@ -40,6 +41,7 @@ export function ModelDataTable({ pagination, onPaginationChange, enablePagination = false, + onRowClick, }: ModelDataTableProps) { const [sorting, setSorting] = React.useState(defaultSorting); const [columnResizeMode] = React.useState("onChange"); @@ -157,7 +159,11 @@ export function ModelDataTable({ ) : tableInstance.getRowModel().rows.length > 0 ? ( tableInstance.getRowModel().rows.map((row) => ( - + onRowClick?.(row.original)} + className={onRowClick ? "cursor-pointer hover:bg-gray-50" : ""} + > {row.getVisibleCells().map((cell) => ( void, expandedRows: Set, setExpandedRows: (expandedRows: Set) => void, + onDeleteClick?: (modelId: string) => void, ): ColumnDef[] => [ { header: () => Model ID, @@ -27,7 +28,10 @@ export const columns = (
setSelectedModelId(model.model_info.id)} + onClick={(e) => { + e.stopPropagation(); + setSelectedModelId(model.model_info.id); + }} > {model.model_info.id}
@@ -195,7 +199,10 @@ export const columns = ( size="xs" variant="light" className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]" - onClick={() => setSelectedTeamId(model.model_info.team_id)} + onClick={(e: React.MouseEvent) => { + e.stopPropagation(); + setSelectedTeamId(model.model_info.team_id); + }} > {model.model_info.team_id.slice(0, 7)}... @@ -300,9 +307,10 @@ export const columns = ( { - if (canEditModel) { - setSelectedModelId(model.model_info.id); + onClick={(e) => { + e.stopPropagation(); + if (canEditModel && onDeleteClick) { + onDeleteClick(model.model_info.id); } }} className={!canEditModel ? "opacity-50 cursor-not-allowed" : "cursor-pointer hover:text-red-600"} From c005f1aec21dd5b043c8a89705ad89cdebdcd58f Mon Sep 17 00:00:00 2001 From: Chesars Date: Mon, 26 Jan 2026 12:17:13 -0300 Subject: [PATCH 2/6] test(ui): add tests for model management UI improvements - Add tests for dismissable Missing provider banner with localStorage persistence - Add tests for compact Request Provider button when banner is dismissed - Add tests for delete modal functionality and DB Model badge - Add tests for clickable Model ID that calls setSelectedModelId --- .../ModelsAndEndpointsView.test.tsx | 99 +++++++++++++- .../components/AllModelsTab.test.tsx | 121 +++++++++++++++++- 2 files changed, 218 insertions(+), 2 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx index 1e8eabaea2..f24de9f673 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx @@ -1,9 +1,27 @@ /* @vitest-environment jsdom */ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { render } from "@testing-library/react"; +import { fireEvent, render } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import ModelsAndEndpointsView from "./ModelsAndEndpointsView"; +// Mock localStorage +const localStorageMock = (() => { + let store: Record = {}; + return { + getItem: (key: string) => store[key] || null, + setItem: (key: string, value: string) => { + store[key] = value; + }, + removeItem: (key: string) => { + delete store[key]; + }, + clear: () => { + store = {}; + }, + }; +})(); +Object.defineProperty(window, "localStorage", { value: localStorageMock }); + // Minimal stubs to avoid Next.js router and network usage during render vi.mock("@/components/networking", () => ({ credentialListCall: vi.fn().mockResolvedValue({ credentials: [] }), @@ -104,4 +122,83 @@ describe("ModelsAndEndpointsView", () => { ); expect(await findByText("Model Management", {}, { timeout: 10000 })).toBeInTheDocument(); }, 15000); + + it("should show Missing provider banner by default", async () => { + localStorageMock.clear(); + const queryClient = createQueryClient(); + const { findByText } = render( + + {}} + premiumUser={false} + teams={[]} + /> + , + ); + expect(await findByText("Missing a provider?", {}, { timeout: 10000 })).toBeInTheDocument(); + }, 15000); + + it("should hide Missing provider banner when dismiss button is clicked and persist to localStorage", async () => { + localStorageMock.clear(); + const queryClient = createQueryClient(); + const { findByText, queryByText, container } = render( + + {}} + premiumUser={false} + teams={[]} + /> + , + ); + + // Wait for banner to appear + expect(await findByText("Missing a provider?", {}, { timeout: 10000 })).toBeInTheDocument(); + + // Find and click dismiss button (X button) + const dismissButton = container.querySelector('button[aria-label="Dismiss banner"]'); + if (dismissButton) { + fireEvent.click(dismissButton); + + // Banner should be hidden + expect(queryByText("Missing a provider?")).not.toBeInTheDocument(); + + // LocalStorage should be updated + expect(localStorageMock.getItem("hideMissingProviderBanner")).toBe("true"); + } + }, 15000); + + it("should show compact Request Provider button when banner is dismissed", async () => { + // Set localStorage to hide banner + localStorageMock.setItem("hideMissingProviderBanner", "true"); + const queryClient = createQueryClient(); + const { findByText, queryByText } = render( + + {}} + premiumUser={false} + teams={[]} + /> + , + ); + + // Wait for component to render + await findByText("Model Management", {}, { timeout: 10000 }); + + // Banner should not be visible + expect(queryByText("Missing a provider?")).not.toBeInTheDocument(); + + // Compact Request Provider button should be visible in header + const requestProviderLinks = document.querySelectorAll('a[href="https://models.litellm.ai/?request=true"]'); + // There should be a compact button when banner is hidden + expect(requestProviderLinks.length).toBeGreaterThan(0); + }, 15000); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx index 813a365d36..30c39bd6a2 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx @@ -1,8 +1,30 @@ import * as useAuthorizedModule from "@/app/(dashboard)/hooks/useAuthorized"; -import { render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import AllModelsTab from "./AllModelsTab"; +// Mock modelDeleteCall +const mockModelDeleteCall = vi.fn().mockResolvedValue({}); +vi.mock("@/components/networking", () => ({ + modelDeleteCall: (...args: any[]) => mockModelDeleteCall(...args), +})); + +// Mock NotificationsManager +vi.mock("@/components/molecules/notifications_manager", () => ({ + default: { + success: vi.fn(), + fromBackend: vi.fn(), + }, +})); + +// Mock react-query +const mockInvalidateQueries = vi.fn(); +vi.mock("@tanstack/react-query", () => ({ + useQueryClient: () => ({ + invalidateQueries: mockInvalidateQueries, + }), +})); + // Mock the useModelsInfo hook const mockUseModelsInfo = vi.fn(() => ({ data: { data: [], total_count: 0, current_page: 1, total_pages: 1, size: 50 }, @@ -493,4 +515,101 @@ describe("AllModelsTab", () => { const previousButton = screen.getByRole("button", { name: /previous/i }); expect(previousButton).toBeDisabled(); }); + + it("should pass setDeleteModalModelId to columns for delete functionality", async () => { + // This test verifies that the delete modal setter is passed to columns + // The actual modal rendering is handled by DeleteResourceModal component + mockUseTeams.mockReturnValue({ + data: [], + isLoading: false, + error: null, + refetch: vi.fn(), + }); + + mockUseModelCostMap.mockReturnValue( + createModelCostMapMock({ + "gpt-4-delete-test": { litellm_provider: "openai" }, + }), + ); + + const modelData = createPaginatedModelData([ + { + model_name: "gpt-4-delete-test", + litellm_model_name: "gpt-4-delete-test", + provider: "openai", + model_info: { + id: "model-to-delete", + db_model: true, + direct_access: true, + access_via_team_ids: [], + access_groups: [], + created_by: "user-123", + created_at: "2024-01-01", + updated_at: "2024-01-01", + }, + }, + ], 1, 1, 1, 50); + + mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null, refetch: vi.fn() }); + + render(); + + await waitFor(() => { + expect(screen.getByText("gpt-4-delete-test")).toBeInTheDocument(); + }); + + // Verify the DB Model badge is shown (indicating it can be deleted) + expect(screen.getByText("DB Model")).toBeInTheDocument(); + }); + + it("should render clickable model ID that calls setSelectedModelId", async () => { + mockUseTeams.mockReturnValue({ + data: [], + isLoading: false, + error: null, + refetch: vi.fn(), + }); + + mockUseModelCostMap.mockReturnValue( + createModelCostMapMock({ + "gpt-4-clickable": { litellm_provider: "openai" }, + }), + ); + + const modelData = createPaginatedModelData([ + { + model_name: "gpt-4-clickable", + litellm_model_name: "gpt-4-clickable", + provider: "openai", + model_info: { + id: "clickable-model-id", + db_model: true, + direct_access: true, + access_via_team_ids: [], + access_groups: [], + created_by: "user-123", + created_at: "2024-01-01", + updated_at: "2024-01-01", + }, + }, + ], 1, 1, 1, 50); + + mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null, refetch: vi.fn() }); + + render(); + + await waitFor(() => { + expect(screen.getByText("gpt-4-clickable")).toBeInTheDocument(); + }); + + // Click on the Model ID cell which should call setSelectedModelId + const modelIdCell = screen.getByText("clickable-model-id"); + expect(modelIdCell).toBeInTheDocument(); + + fireEvent.click(modelIdCell); + + await waitFor(() => { + expect(mockSetSelectedModelId).toHaveBeenCalledWith("clickable-model-id"); + }); + }); }); From 18af5815638a8270bf5e12cde9c58adb6fa9cddf Mon Sep 17 00:00:00 2001 From: Chesars Date: Wed, 4 Mar 2026 17:42:51 -0300 Subject: [PATCH 3/6] fix(ui): add missing onRowClick prop to AllModelsDataTable component The AllModelsTab component was passing onRowClick to AllModelsDataTable but the prop was not defined in the interface, causing the TypeScript build to fail. --- .../src/components/model_dashboard/all_models_table.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx b/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx index 964bb5658f..f1d4de7a11 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx @@ -30,6 +30,7 @@ interface AllModelsDataTableProps { pagination?: PaginationState; onPaginationChange?: OnChangeFn; enablePagination?: boolean; + onRowClick?: (row: TData) => void; } export function AllModelsDataTable({ @@ -41,6 +42,7 @@ export function AllModelsDataTable({ pagination, onPaginationChange, enablePagination = false, + onRowClick, }: AllModelsDataTableProps) { const [columnResizeMode] = React.useState("onChange"); const [columnSizing, setColumnSizing] = React.useState({}); @@ -174,7 +176,11 @@ export function AllModelsDataTable({
) : tableInstance.getRowModel().rows.length > 0 ? ( tableInstance.getRowModel().rows.map((row) => ( - + onRowClick?.(row.original)} + > {row.getVisibleCells().map((cell) => ( Date: Wed, 4 Mar 2026 17:50:55 -0300 Subject: [PATCH 4/6] Update ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../models-and-endpoints/components/AllModelsTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx index df4b340b7c..d7687def80 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx @@ -213,7 +213,7 @@ const AllModelsTab = ({ refetchModels(); } catch (error) { console.error("Error deleting model:", error); - NotificationsManager.fromBackend("Failed to delete model"); + NotificationsManager.fromBackend(error); } finally { setDeleteLoading(false); setDeleteModalModelId(null); From 68cccc830d912910a0e412c2359c2e8204d887c4 Mon Sep 17 00:00:00 2001 From: Chesars Date: Wed, 4 Mar 2026 17:51:32 -0300 Subject: [PATCH 5/6] style(ui): fix indentation on TableRow props in all_models_table --- .../src/components/model_dashboard/all_models_table.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx b/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx index f1d4de7a11..0585d96087 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx @@ -177,10 +177,10 @@ export function AllModelsDataTable({ ) : tableInstance.getRowModel().rows.length > 0 ? ( tableInstance.getRowModel().rows.map((row) => ( onRowClick?.(row.original)} - > + key={row.id} + className={onRowClick ? "cursor-pointer hover:bg-gray-50" : ""} + onClick={() => onRowClick?.(row.original)} + > {row.getVisibleCells().map((cell) => ( Date: Wed, 4 Mar 2026 18:01:28 -0300 Subject: [PATCH 6/6] Update ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../ModelsAndEndpointsView.test.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx index 74cfd45ac0..e1b3b35830 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx @@ -172,15 +172,14 @@ describe("ModelsAndEndpointsView", () => { // Find and click dismiss button (X button) const dismissButton = container.querySelector('button[aria-label="Dismiss banner"]'); - if (dismissButton) { - fireEvent.click(dismissButton); + expect(dismissButton).not.toBeNull(); + fireEvent.click(dismissButton!); - // Banner should be hidden - expect(queryByText("Missing a provider?")).not.toBeInTheDocument(); + // Banner should be hidden + expect(queryByText("Missing a provider?")).not.toBeInTheDocument(); - // LocalStorage should be updated - expect(localStorageMock.getItem("hideMissingProviderBanner")).toBe("true"); - } + // LocalStorage should be updated + expect(localStorageMock.getItem("hideMissingProviderBanner")).toBe("true"); }, 15000); it("should show compact Request Provider button when banner is dismissed", async () => {