diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/__tests__/NodeHeader.test.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/__tests__/NodeHeader.test.tsx
new file mode 100644
index 0000000000..dca3e87598
--- /dev/null
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/FlowEditor/nodes/CustomNode/components/__tests__/NodeHeader.test.tsx
@@ -0,0 +1,121 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { render, screen, fireEvent } from "@/tests/integrations/test-utils";
+import { NodeHeader } from "../NodeHeader";
+import { CustomNodeData } from "../../CustomNode";
+import { useNodeStore } from "@/app/(platform)/build/stores/nodeStore";
+
+vi.mock("../NodeCost", () => ({
+ NodeCost: () =>
,
+}));
+
+vi.mock("../NodeContextMenu", () => ({
+ NodeContextMenu: () => ,
+}));
+
+vi.mock("../NodeBadges", () => ({
+ NodeBadges: () => ,
+}));
+
+function makeData(overrides: Partial = {}): CustomNodeData {
+ return {
+ title: "AgentExecutorBlock",
+ description: "",
+ hardcodedValues: {},
+ inputSchema: {},
+ outputSchema: {},
+ uiType: "agent",
+ block_id: "block-1",
+ costs: [],
+ categories: [],
+ ...overrides,
+ } as CustomNodeData;
+}
+
+describe("NodeHeader", () => {
+ const mockUpdateNodeData = vi.fn();
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ useNodeStore.setState({ updateNodeData: mockUpdateNodeData } as any);
+ });
+
+ it("renders beautified generic block title", () => {
+ render();
+ expect(screen.getByText("Agent Executor")).toBeTruthy();
+ });
+
+ it("renders agent name with version from hardcodedValues", () => {
+ const data = makeData({
+ hardcodedValues: { agent_name: "Researcher", graph_version: 2 },
+ });
+ render();
+ expect(screen.getByText("Researcher v2")).toBeTruthy();
+ });
+
+ it("renders customized_name over agent name", () => {
+ const data = makeData({
+ metadata: { customized_name: "My Custom Node" } as any,
+ hardcodedValues: { agent_name: "Researcher", graph_version: 1 },
+ });
+ render();
+ expect(screen.getByText("My Custom Node")).toBeTruthy();
+ });
+
+ it("shows node ID prefix", () => {
+ render();
+ expect(screen.getByText("#abc")).toBeTruthy();
+ });
+
+ it("enters edit mode on double-click and saves on blur", () => {
+ render();
+ const titleEl = screen.getByText("Agent Executor");
+ fireEvent.doubleClick(titleEl);
+
+ const input = screen.getByDisplayValue("AgentExecutorBlock");
+ fireEvent.change(input, { target: { value: "New Name" } });
+ fireEvent.blur(input);
+
+ expect(mockUpdateNodeData).toHaveBeenCalledWith("node-1", {
+ metadata: { customized_name: "New Name" },
+ });
+ });
+
+ it("does not save when title is unchanged on blur", () => {
+ const data = makeData({
+ hardcodedValues: { agent_name: "Researcher", graph_version: 2 },
+ });
+ render();
+ const titleEl = screen.getByText("Researcher v2");
+ fireEvent.doubleClick(titleEl);
+
+ const input = screen.getByDisplayValue("Researcher v2");
+ fireEvent.blur(input);
+
+ expect(mockUpdateNodeData).not.toHaveBeenCalled();
+ });
+
+ it("saves on Enter key", () => {
+ render();
+ fireEvent.doubleClick(screen.getByText("Agent Executor"));
+
+ const input = screen.getByDisplayValue("AgentExecutorBlock");
+ fireEvent.change(input, { target: { value: "Renamed" } });
+ fireEvent.keyDown(input, { key: "Enter" });
+
+ expect(mockUpdateNodeData).toHaveBeenCalledWith("node-1", {
+ metadata: { customized_name: "Renamed" },
+ });
+ });
+
+ it("cancels edit on Escape key", () => {
+ render();
+ fireEvent.doubleClick(screen.getByText("Agent Executor"));
+
+ const input = screen.getByDisplayValue("AgentExecutorBlock");
+ fireEvent.change(input, { target: { value: "Changed" } });
+ fireEvent.keyDown(input, { key: "Escape" });
+
+ expect(mockUpdateNodeData).not.toHaveBeenCalled();
+ expect(screen.getByText("Agent Executor")).toBeTruthy();
+ });
+});