From 0da1d4277f64ec627701c3d660ee9d16e22ace4e Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Thu, 10 Sep 2026 14:55:38 -0300 Subject: [PATCH] fix(dashboard): refresh the providers list after deleting a compatible provider node (#12298) --- .../12298-provider-node-delete-refresh.md | 1 + .../[id]/components/CompatibleNodeCard.tsx | 1 + ...le-node-card-delete-refresh-12298.test.tsx | 135 ++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 changelog.d/fixes/12298-provider-node-delete-refresh.md create mode 100644 tests/unit/ui/compatible-node-card-delete-refresh-12298.test.tsx diff --git a/changelog.d/fixes/12298-provider-node-delete-refresh.md b/changelog.d/fixes/12298-provider-node-delete-refresh.md new file mode 100644 index 0000000000..c902e6cc7b --- /dev/null +++ b/changelog.d/fixes/12298-provider-node-delete-refresh.md @@ -0,0 +1 @@ +- fix(dashboard): refresh the providers list after deleting a compatible provider node (#12298) diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/CompatibleNodeCard.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/CompatibleNodeCard.tsx index 5347c075a8..144023a4fa 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/CompatibleNodeCard.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/CompatibleNodeCard.tsx @@ -110,6 +110,7 @@ export default function CompatibleNodeCard({ }); if (res.ok) { router.push("/dashboard/providers"); + router.refresh(); } } catch (error) { console.error("Error deleting provider node:", error); diff --git a/tests/unit/ui/compatible-node-card-delete-refresh-12298.test.tsx b/tests/unit/ui/compatible-node-card-delete-refresh-12298.test.tsx new file mode 100644 index 0000000000..91eaf6f319 --- /dev/null +++ b/tests/unit/ui/compatible-node-card-delete-refresh-12298.test.tsx @@ -0,0 +1,135 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import CompatibleNodeCard from "../../../src/app/(dashboard)/dashboard/providers/[id]/components/CompatibleNodeCard"; + +const router = vi.hoisted(() => ({ + push: vi.fn(), + refresh: vi.fn(), +})); + +vi.mock("next/navigation", () => ({ + useRouter: () => router, +})); + +vi.mock("@/shared/components", () => ({ + Card: ({ children }: { children: React.ReactNode }) =>
{children}
, + Button: ({ + children, + onClick, + }: { + children: React.ReactNode; + onClick?: React.MouseEventHandler; + }) => , +})); + +vi.mock("@/shared/components/ProviderIcon", () => ({ + default: () => null, +})); + +function renderCard(container: HTMLDivElement) { + const root = createRoot(container); + return root; +} + +describe("CompatibleNodeCard provider deletion (#12298)", () => { + let container: HTMLDivElement; + let root: ReturnType; + + beforeEach(() => { + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; + router.push.mockClear(); + router.refresh.mockClear(); + vi.stubGlobal("confirm", vi.fn(() => true)); + + container = document.createElement("div"); + document.body.appendChild(container); + root = renderCard(container); + }); + + afterEach(() => { + act(() => root.unmount()); + container.remove(); + vi.unstubAllGlobals(); + }); + + async function clickDelete() { + await act(async () => { + root.render( + callback()} + openApiKeyAddFlow={vi.fn()} + onOpenEditNodeModal={vi.fn()} + t={(key) => key} + /> + ); + }); + + const deleteButton = Array.from(container.querySelectorAll("button")).find( + (button) => button.textContent === "delete" + ); + expect(deleteButton).toBeDefined(); + + await act(async () => { + deleteButton?.click(); + await Promise.resolve(); + await Promise.resolve(); + }); + + return deleteButton; + } + + it("invalidates the cached providers page after a successful delete and navigation", async () => { + vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true } as Response)); + + await clickDelete(); + + expect(fetch).toHaveBeenCalledWith("/api/provider-nodes/custom-node", { + method: "DELETE", + }); + expect(router.push).toHaveBeenCalledWith("/dashboard/providers"); + expect(router.refresh).toHaveBeenCalledTimes(1); + expect(router.push.mock.invocationCallOrder[0]).toBeLessThan( + router.refresh.mock.invocationCallOrder[0] + ); + }); + + it("does not navigate or refresh when the user cancels the confirm dialog", async () => { + vi.stubGlobal("confirm", vi.fn(() => false)); + vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true } as Response)); + + await clickDelete(); + + expect(fetch).not.toHaveBeenCalled(); + expect(router.push).not.toHaveBeenCalled(); + expect(router.refresh).not.toHaveBeenCalled(); + }); + + it("does not navigate or refresh when the DELETE response is not ok", async () => { + vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: false } as Response)); + + await clickDelete(); + + expect(router.push).not.toHaveBeenCalled(); + expect(router.refresh).not.toHaveBeenCalled(); + }); + + it("does not navigate or refresh when the DELETE request throws", async () => { + vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("network down"))); + vi.spyOn(console, "error").mockImplementation(() => {}); + + await clickDelete(); + + expect(router.push).not.toHaveBeenCalled(); + expect(router.refresh).not.toHaveBeenCalled(); + }); +});