From 907ffccd55ffd65aed2a3933837f1dcbfa19be3e Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Fri, 11 Sep 2026 22:04:38 -0300 Subject: [PATCH] fix(dashboard): refresh the providers list after deleting a compatible provider node (#12298) (#13256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merged as part of the 39-PR owner batch of 2026-09-11, validated as a unit. Boarded into one consolidated worktree cut from `release/v3.8.51` with the other 38 — zero conflicts between them. - ESLint over every changed file: no errors (the only finding was one suppression entry the batch emptied, pruned on #13243) - `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, within baseline); `check:changelog-integrity` OK - complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437 — both under baseline - 256 assertions green: 246 under node:test and 10 under vitest, which is where `tests/unit/**/*.test.tsx` actually runs - `check-file-size`: `chatCore.ts` rebaselined 6144 → 6146 for #13278 and #13276, annotated and landed on #13243 ⚠️ base-red inherited: #12732 — the provider count (356 in the docs vs the 358 the modules define) and `open-sse/utils/stream.ts` at 3115 > frozen 3098 both reproduce on the pure tip with zero contribution from this batch. --- .../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(); + }); +});