diff --git a/packages/apollo-react/src/canvas/components/BaseNode/BaseNode.test.tsx b/packages/apollo-react/src/canvas/components/BaseNode/BaseNode.test.tsx
index a55b66f1f8..e9cbe4d8d0 100644
--- a/packages/apollo-react/src/canvas/components/BaseNode/BaseNode.test.tsx
+++ b/packages/apollo-react/src/canvas/components/BaseNode/BaseNode.test.tsx
@@ -1,4 +1,4 @@
-import { fireEvent, render, screen } from '@testing-library/react';
+import { act, fireEvent, render, screen } from '@testing-library/react';
import type { Node, NodeProps } from '@uipath/apollo-react/canvas/xyflow/react';
import { Position } from '@uipath/apollo-react/canvas/xyflow/react';
import { describe, expect, it, vi } from 'vitest';
@@ -12,6 +12,7 @@ const DEFAULT_MANIFEST = {
// Hoisted mocks — available inside vi.mock factories
const {
mockUpdateNode,
+ mockUpdateNodeData,
mockGetNode,
mockNodeState,
mockHandleConfigs,
@@ -26,6 +27,7 @@ const {
mockReadOnlyNodeIds,
} = vi.hoisted(() => ({
mockUpdateNode: vi.fn(),
+ mockUpdateNodeData: vi.fn(),
mockGetNode: vi.fn(),
// Simulates React Flow's stored node: updateNode writes height here, getNode reads it.
mockNodeState: { current: { height: undefined as number | undefined } },
@@ -64,7 +66,7 @@ vi.mock('@uipath/apollo-react/canvas/xyflow/react', async (importOriginal) => ({
useStore: () => mockIsConnecting.current,
useUpdateNodeInternals: () => vi.fn(),
useReactFlow: () => ({
- updateNodeData: vi.fn(),
+ updateNodeData: mockUpdateNodeData,
updateNode: mockUpdateNode,
getNode: mockGetNode,
}),
@@ -186,6 +188,7 @@ const makeHandles = (position: Position, count: number): HandleGroupManifest[] =
describe('BaseNode', () => {
afterEach(() => {
mockUpdateNode.mockClear();
+ mockUpdateNodeData.mockClear();
mockGetNode.mockClear();
mockNodeState.current = { height: undefined };
mockUseButtonHandles.mockClear();
@@ -736,4 +739,38 @@ describe('BaseNode', () => {
expect(opts.showAddButton).toBe(false);
});
});
+
+ // NodeLabel is module-mocked above, so the inline edit is driven through the
+ // onChange it receives, as useInlineLabelEditing would call it on commit.
+ describe('Inline label editing', () => {
+ const commitLabel = (values: { label: string; subLabel: string }) =>
+ act(() => mockNodeLabel.mock.calls.at(-1)?.[0]?.onChange(values));
+
+ it('stores an empty label when the name is cleared', () => {
+ render(
+
+ );
+ commitLabel({ label: '', subLabel: '' });
+ expect(mockUpdateNodeData).toHaveBeenCalledWith('script1', { display: { label: '' } });
+ });
+
+ it('drops an empty subLabel', () => {
+ render();
+ commitLabel({ label: 'A', subLabel: '' });
+ expect(mockUpdateNodeData).toHaveBeenCalledWith('test-node', { display: { label: 'A' } });
+ });
+
+ it('keeps the other display fields', () => {
+ render(
+
+ );
+ commitLabel({ label: 'B', subLabel: 'Details' });
+ expect(mockUpdateNodeData).toHaveBeenCalledWith('test-node', {
+ display: { label: 'B', subLabel: 'Details', icon: 'mail', color: 'red' },
+ });
+ });
+ });
});
diff --git a/packages/apollo-react/src/canvas/components/BaseNode/BaseNode.tsx b/packages/apollo-react/src/canvas/components/BaseNode/BaseNode.tsx
index ad455bcbe2..2cb6c153fc 100644
--- a/packages/apollo-react/src/canvas/components/BaseNode/BaseNode.tsx
+++ b/packages/apollo-react/src/canvas/components/BaseNode/BaseNode.tsx
@@ -439,15 +439,15 @@ const BaseNodeComponent = (props: NodeProps>) => {
const handleBlur = useCallback(() => setIsFocused(false), []);
const handleLabelChange = useCallback(
- (values: { label: string; subLabel: string }) => {
- const newDisplay = { ...data.display };
- // Ensure all label fields are updated or removed appropriately.
- for (const labelKey of Object.keys(values) as (keyof typeof values)[]) {
- if (values[labelKey]) {
- newDisplay[labelKey] = values[labelKey];
- } else {
- delete newDisplay[labelKey];
- }
+ ({ label, subLabel }: { label: string; subLabel: string }) => {
+ // A cleared label is stored as '' so the node stays unnamed; an unset
+ // label would fall back to the manifest label. An empty subLabel is
+ // dropped so a label-only edit doesn't add `subLabel: ''` to the node.
+ const newDisplay = { ...data.display, label };
+ if (subLabel) {
+ newDisplay.subLabel = subLabel;
+ } else {
+ delete newDisplay.subLabel;
}
updateNodeData(id, {
display: newDisplay,
diff --git a/packages/apollo-react/src/canvas/utils/manifest-resolver.test.ts b/packages/apollo-react/src/canvas/utils/manifest-resolver.test.ts
index 0d24b2d38d..bd8821a762 100644
--- a/packages/apollo-react/src/canvas/utils/manifest-resolver.test.ts
+++ b/packages/apollo-react/src/canvas/utils/manifest-resolver.test.ts
@@ -122,6 +122,13 @@ describe('resolveDisplay', () => {
);
expect(result.label).toBe('Notify Ops');
});
+
+ it('keeps an empty instance.label instead of falling back to the manifest', () => {
+ // A cleared name is stored as label: '', meaning "unnamed". Only an unset
+ // label falls back to the manifest.
+ const result = resolveDisplay({ label: 'Script', icon: 'code' }, { display: { label: '' } });
+ expect(result.label).toBe('');
+ });
});
// ============================================================================