diff --git a/packages/apollo-react/src/canvas/components/StageNode/StageNode.stories.tsx b/packages/apollo-react/src/canvas/components/StageNode/StageNode.stories.tsx
index 7985c2868..6bd102b0c 100644
--- a/packages/apollo-react/src/canvas/components/StageNode/StageNode.stories.tsx
+++ b/packages/apollo-react/src/canvas/components/StageNode/StageNode.stories.tsx
@@ -953,6 +953,7 @@ export const MaximumTaskAdornments: Story = {
label: 'Longest running required task',
icon: ,
isRequired: true,
+ isMocked: true,
hasEntryCondition: true,
},
],
diff --git a/packages/apollo-react/src/canvas/components/StageNode/StageNode.types.ts b/packages/apollo-react/src/canvas/components/StageNode/StageNode.types.ts
index 2cc312296..300b594d5 100644
--- a/packages/apollo-react/src/canvas/components/StageNode/StageNode.types.ts
+++ b/packages/apollo-react/src/canvas/components/StageNode/StageNode.types.ts
@@ -28,6 +28,7 @@ export interface StageTaskItem {
isRequired?: boolean;
isAdhoc?: boolean;
isPlaceholder?: boolean;
+ isMocked?: boolean;
taskGroupType?: 'sequential' | 'event-driven' | 'adhoc';
hasEntryCondition?: boolean;
}
diff --git a/packages/apollo-react/src/canvas/components/StageNode/tasks/AdhocTask.tsx b/packages/apollo-react/src/canvas/components/StageNode/tasks/AdhocTask.tsx
index bb9d31b84..fabee019d 100644
--- a/packages/apollo-react/src/canvas/components/StageNode/tasks/AdhocTask.tsx
+++ b/packages/apollo-react/src/canvas/components/StageNode/tasks/AdhocTask.tsx
@@ -5,6 +5,7 @@ import type { StageTaskExecution, StageTaskItem } from '../StageNode.types';
import { TaskBreakpointDot } from './TaskBreakpointDot';
import { TaskContent } from './TaskContent';
import { TaskMenu, type TaskMenuHandle } from './TaskMenu';
+import { TaskMockedChip } from './TaskMockedChip';
interface AdhocTaskItemProps {
task: StageTaskItem;
@@ -63,6 +64,7 @@ const AdhocTaskItemComponent = ({
paused={taskExecution?.status === 'Paused'}
onToggle={onToggleBreakpoint}
/>
+
{getContextMenuItems && (
{
expect(screen.getByTestId('stage-task-breakpoint-task-1')).toHaveClass('pointer-events-none');
});
});
+
+ describe('Mocked marker', () => {
+ it('renders the mocked marker on the task card when the task is mocked', () => {
+ render();
+
+ const chip = screen.getByTestId('stage-task-mocked-task-1');
+ expect(screen.getByTestId('stage-task-card-task-1')).toContainElement(chip);
+ });
+
+ it('keeps the mocked marker out of the trailing actions group', () => {
+ render();
+
+ const chip = screen.getByTestId('stage-task-mocked-task-1');
+ expect(screen.getByTestId('stage-task-actions-task-1')).not.toContainElement(chip);
+ });
+
+ it('does not render the mocked marker when the task is not mocked', () => {
+ render();
+
+ expect(screen.queryByTestId('stage-task-mocked-task-1')).not.toBeInTheDocument();
+ });
+ });
});
diff --git a/packages/apollo-react/src/canvas/components/StageNode/tasks/DraggableTask.tsx b/packages/apollo-react/src/canvas/components/StageNode/tasks/DraggableTask.tsx
index 7605d3e12..cdbb25949 100644
--- a/packages/apollo-react/src/canvas/components/StageNode/tasks/DraggableTask.tsx
+++ b/packages/apollo-react/src/canvas/components/StageNode/tasks/DraggableTask.tsx
@@ -13,6 +13,7 @@ import type { DraggableTaskProps } from './DraggableTask.types';
import { TaskBreakpointDot } from './TaskBreakpointDot';
import { TaskContent } from './TaskContent';
import { TaskMenu, type TaskMenuHandle } from './TaskMenu';
+import { TaskMockedChip } from './TaskMockedChip';
const DraggableTaskComponent = ({
task,
@@ -100,6 +101,7 @@ const DraggableTaskComponent = ({
paused={taskExecution?.status === 'Paused'}
onToggle={onToggleBreakpoint}
/>
+
+
{getContextMenuItems && (
diff --git a/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskMockedChip.test.tsx b/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskMockedChip.test.tsx
new file mode 100644
index 000000000..12d35324f
--- /dev/null
+++ b/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskMockedChip.test.tsx
@@ -0,0 +1,56 @@
+import { describe, expect, it, vi } from 'vitest';
+import { render, screen } from '../../../utils/testing';
+import { TaskMockedChip } from './TaskMockedChip';
+
+// Surface CanvasTooltip content into the DOM so assertions don't depend on
+// Radix's pointer-event-driven open/close logic (unreliable in jsdom).
+vi.mock('../../CanvasTooltip', () => ({
+ CanvasTooltip: ({
+ content,
+ children,
+ }: {
+ content: React.ReactNode;
+ children: React.ReactNode;
+ }) => (
+
+ {children}
+
+ ),
+}));
+
+describe('TaskMockedChip', () => {
+ it('renders nothing when the task is not mocked', () => {
+ render();
+
+ expect(screen.queryByTestId('stage-task-mocked-t1')).not.toBeInTheDocument();
+ });
+
+ it('renders a corner marker with an outputs-mocked tooltip when the task is mocked', () => {
+ render();
+
+ const chip = screen.getByTestId('stage-task-mocked-t1');
+ expect(chip.closest('[data-testid="canvas-tooltip"]')).toHaveAttribute(
+ 'data-tooltip-content',
+ "This task's outputs are mocked"
+ );
+ });
+
+ it('sits at the top-right corner of the task card, mirroring the breakpoint dot', () => {
+ render();
+
+ expect(screen.getByTestId('stage-task-mocked-t1')).toHaveClass(
+ 'absolute',
+ '-top-1.5',
+ '-right-1.5'
+ );
+ });
+
+ it('labels the marker for screen readers', () => {
+ render();
+
+ expect(screen.getByRole('img', { name: "This task's outputs are mocked" })).toBeInTheDocument();
+ });
+});
diff --git a/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskMockedChip.tsx b/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskMockedChip.tsx
new file mode 100644
index 000000000..fa24d4cb0
--- /dev/null
+++ b/packages/apollo-react/src/canvas/components/StageNode/tasks/TaskMockedChip.tsx
@@ -0,0 +1,38 @@
+import { Badge } from '@uipath/apollo-wind';
+import { FlaskConical } from 'lucide-react';
+import { memo } from 'react';
+import { useSafeLingui } from '../../../../i18n';
+import { CanvasTooltip } from '../../CanvasTooltip';
+
+export interface TaskMockedChipProps {
+ /** Whether this task's outputs are mocked. */
+ mocked: boolean;
+ /** Task id, used for a stable test id. */
+ taskId: string;
+}
+
+function TaskMockedChipInner({ mocked, taskId }: TaskMockedChipProps) {
+ const { _ } = useSafeLingui();
+
+ if (!mocked) {
+ return null;
+ }
+
+ const label = _({ id: 'stage-node.task-mocked', message: "This task's outputs are mocked" });
+
+ return (
+
+
+
+
+
+ );
+}
+
+export const TaskMockedChip = memo(TaskMockedChipInner);
diff --git a/packages/apollo-react/src/canvas/locales/en.json b/packages/apollo-react/src/canvas/locales/en.json
index e9a915ac0..7d764f5ef 100644
--- a/packages/apollo-react/src/canvas/locales/en.json
+++ b/packages/apollo-react/src/canvas/locales/en.json
@@ -34,6 +34,7 @@
"stage-node.status.early-exit": "Early exit",
"stage-node.task-badge.ran-n-times": "{count, plural, one {Ran # time} other {Ran # times}}",
"stage-node.task-badge.running-again": "Running again",
+ "stage-node.task-mocked": "This task's outputs are mocked",
"stage-node.ungroup-parallel-tasks": "Ungroup parallel tasks",
"stage-node.untitled-stage": "Untitled stage",
"sticky-note.formatting.bold": "Bold ({boldShortcut})",