import type { ReactNode } from 'react' import { fireEvent, render, screen } from '@testing-library/react' 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' const mockUseAvailableBlocks = vi.hoisted(() => vi.fn()) const mockUseNodesInteractions = vi.hoisted(() => vi.fn()) const mockBlockSelector = 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', }, })) vi.mock('@/app/components/workflow/hooks', () => ({ useAvailableBlocks: (...args: unknown[]) => mockUseAvailableBlocks(...args), useNodesInteractions: () => mockUseNodesInteractions(), })) vi.mock('@/app/components/workflow/block-selector', () => ({ __esModule: true, default: (props: { open: boolean onOpenChange: (open: boolean) => void onSelect: (nodeType: string, pluginDefaultValue?: Record) => void availableBlocksTypes: string[] triggerClassName?: () => string }) => { mockBlockSelector(props) return ( ) }, })) 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 insert a node between the source and target', () => { render( , ) 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') expect(screen.getByTestId('block-selector')).toHaveTextContent('llm') expect(screen.getByTestId('block-selector').parentElement).toHaveStyle({ transform: 'translate(-50%, -50%) translate(24px, 48px)', opacity: '0.7', }) fireEvent.click(screen.getByTestId('block-selector')) expect(mockHandleNodeAdd).toHaveBeenCalledWith( { nodeType: 'llm', pluginDefaultValue: { provider: 'openai' }, }, { prevNodeId: 'source-node', prevNodeSourceHandle: 'source', nextNodeId: 'target-node', nextNodeTargetHandle: 'target', }, ) }) it('should prefer the running stroke color when the edge is selected', () => { render( , ) 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', () => { render( , ) 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', () => { render( , ) expect(screen.getByTestId('base-edge')).toHaveAttribute('data-stroke', 'var(--color-workflow-link-line-normal)') expect(screen.getByTestId('block-selector')).toHaveAttribute('data-trigger-class', 'hover:scale-150 transition-all') }) })