Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,7 @@ export const MaximumTaskAdornments: Story = {
label: 'Longest running required task',
icon: <VerificationIcon />,
isRequired: true,
isMocked: true,
hasEntryCondition: true,
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface StageTaskItem {
isRequired?: boolean;
isAdhoc?: boolean;
isPlaceholder?: boolean;
isMocked?: boolean;
taskGroupType?: 'sequential' | 'event-driven' | 'adhoc';
hasEntryCondition?: boolean;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -63,6 +64,7 @@ const AdhocTaskItemComponent = ({
paused={taskExecution?.status === 'Paused'}
onToggle={onToggleBreakpoint}
/>
<TaskMockedChip taskId={task.id} mocked={!!task.isMocked} />
<TaskContent task={task} taskExecution={taskExecution} onTaskPlay={onTaskPlay} />
{getContextMenuItems && (
<TaskMenu
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,4 +461,26 @@ describe('DraggableTask', () => {
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(<DraggableTask {...defaultProps} task={{ ...defaultProps.task, isMocked: true }} />);

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(<DraggableTask {...defaultProps} task={{ ...defaultProps.task, isMocked: true }} />);

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(<DraggableTask {...defaultProps} />);

expect(screen.queryByTestId('stage-task-mocked-task-1')).not.toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -100,6 +101,7 @@ const DraggableTaskComponent = ({
paused={taskExecution?.status === 'Paused'}
onToggle={onToggleBreakpoint}
/>
<TaskMockedChip taskId={task.id} mocked={!!task.isMocked} />
<TaskContent
task={task}
taskExecution={taskExecution}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 EventDrivenTaskItemProps {
task: StageTaskItem;
Expand Down Expand Up @@ -61,6 +62,7 @@ const EventDrivenTaskItemComponent = ({
paused={taskExecution?.status === 'Paused'}
onToggle={onToggleBreakpoint}
/>
<TaskMockedChip taskId={task.id} mocked={!!task.isMocked} />
<TaskContent task={task} taskExecution={taskExecution} />

{getContextMenuItems && (
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}) => (
Comment on lines +11 to +14
<span
data-testid="canvas-tooltip"
data-tooltip-content={typeof content === 'string' ? content : ''}
>
{children}
</span>
),
}));

describe('TaskMockedChip', () => {
it('renders nothing when the task is not mocked', () => {
render(<TaskMockedChip taskId="t1" mocked={false} />);

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(<TaskMockedChip taskId="t1" mocked={true} />);

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(<TaskMockedChip taskId="t1" mocked={true} />);

expect(screen.getByTestId('stage-task-mocked-t1')).toHaveClass(
'absolute',
'-top-1.5',
'-right-1.5'
);
});

it('labels the marker for screen readers', () => {
render(<TaskMockedChip taskId="t1" mocked={true} />);

expect(screen.getByRole('img', { name: "This task's outputs are mocked" })).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -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 (
<CanvasTooltip content={label} placement="top">
<Badge
variant="warning"
role="img"
aria-label={label}
className="absolute -top-1.5 -right-1.5 z-10 h-4 w-4 justify-center rounded-full border-warning bg-chip-warning-background p-0 hover:bg-chip-warning-background [&>svg]:size-2.5"
data-testid={`stage-task-mocked-${taskId}`}
>
<FlaskConical aria-hidden />
</Badge>
</CanvasTooltip>
);
}

export const TaskMockedChip = memo(TaskMockedChipInner);
1 change: 1 addition & 0 deletions packages/apollo-react/src/canvas/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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})",
Expand Down
Loading