import type { ReactNode } from 'react' import { screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { Position } from 'reactflow' import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' import CustomEdge from '../custom-edge' import { BlockEnum, NodeRunningStatus } from '../types' import { renderWorkflowComponent } from './workflow-test-env' const mockUseAvailableBlocks = vi.hoisted(() => vi.fn()) const mockUseNodesInteractions = vi.hoisted(() => vi.fn()) const mockGradientRender = vi.hoisted(() => vi.fn()) vi.mock('reactflow', () => ({ BaseEdge: (props: { id: string path: string style: { stroke: string strokeWidth: number opacity: number strokeDasharray?: string } }) => (
), EdgeLabelRenderer: ({ children }: { children?: ReactNode }) => (
{children}
), getBezierPath: () => ['M 0 0', 24, 48], Position: { Right: 'right', Left: 'left', }, useStoreApi: () => ({ getState: () => ({ getNodes: () => [] }), }), })) vi.mock('../hooks/use-available-blocks', async (importOriginal) => { const actual = await importOriginal() return { ...actual, useAvailableBlocks: (...args: unknown[]) => mockUseAvailableBlocks(...args), } }) vi.mock('../hooks/use-nodes-interactions', async (importOriginal) => { const actual = await importOriginal() return { ...actual, useNodesInteractions: () => mockUseNodesInteractions(), } }) vi.mock('@/app/components/workflow/custom-edge-linear-gradient-render', () => ({ __esModule: true, default: (props: { id: string; startColor: string; stopColor: string }) => { mockGradientRender(props) return
{props.id}
}, })) describe('CustomEdge', () => { const mockHandleNodeAdd = vi.fn() beforeEach(() => { vi.clearAllMocks() mockUseNodesInteractions.mockReturnValue({ handleNodeAdd: mockHandleNodeAdd, }) mockUseAvailableBlocks.mockImplementation((nodeType: BlockEnum) => { if (nodeType === BlockEnum.Code) return { availablePrevBlocks: ['code', 'llm'] } return { availableNextBlocks: ['llm', 'tool'] } }) }) it('should render a gradient edge and hide the start tab from its insert-node selector', async () => { const user = userEvent.setup() renderWorkflowComponent( , ) expect(screen.getByTestId('edge-gradient')).toHaveTextContent('edge-1') expect(mockGradientRender).toHaveBeenCalledWith( expect.objectContaining({ id: 'edge-1', startColor: 'var(--color-workflow-link-line-success-handle)', stopColor: 'var(--color-workflow-link-line-error-handle)', }), ) expect(screen.getByTestId('base-edge')).toHaveAttribute('data-stroke', 'url(#edge-1)') expect(screen.getByTestId('base-edge')).toHaveAttribute('data-opacity', '0.3') expect(screen.getByTestId('base-edge')).toHaveAttribute('data-dasharray', '8 8') const addBlockTrigger = screen.getByRole('button', { name: 'workflow.common.addBlock' }) expect(addBlockTrigger.parentElement).toHaveStyle({ transform: 'translate(-50%, -50%) translate(24px, 48px)', opacity: '0.7', zIndex: '1001', }) await user.click(addBlockTrigger) expect(screen.queryByRole('tab', { name: 'workflow.tabs.start' })).not.toBeInTheDocument() }) it('should prefer the running stroke color when the edge is selected', () => { renderWorkflowComponent( , ) expect(screen.getByTestId('base-edge')).toHaveAttribute( 'data-stroke', 'var(--color-workflow-link-line-handle)', ) }) it('should use the fail-branch running color while the connected node is hovering', () => { renderWorkflowComponent( , ) expect(screen.getByTestId('base-edge')).toHaveAttribute( 'data-stroke', 'var(--color-workflow-link-line-failure-handle)', ) }) it('should fall back to the default edge color when no highlight state is active', () => { renderWorkflowComponent( , ) expect(screen.getByTestId('base-edge')).toHaveAttribute( 'data-stroke', 'var(--color-workflow-link-line-normal)', ) expect( screen.getByRole('button', { name: 'workflow.common.addBlock' }).parentElement, ).toHaveStyle({ opacity: '0', pointerEvents: 'none', }) }) })