mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 08:58:09 +08:00
test(workflow): reorganize specs into __tests__ and align with shared test infrastructure (#33625)
Co-authored-by: CodingOnStar <hanxujiang@dify.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@ -0,0 +1,84 @@
|
||||
import type { NodeProps } from 'reactflow'
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import ReactFlow, { ReactFlowProvider } from 'reactflow'
|
||||
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||
import { BlockEnum, NodeRunningStatus } from '@/app/components/workflow/types'
|
||||
import ErrorHandleOnNode from '../error-handle-on-node'
|
||||
|
||||
const createNodeData = (overrides: Partial<CommonNodeType> = {}): CommonNodeType => ({
|
||||
type: BlockEnum.Code,
|
||||
title: 'Node',
|
||||
desc: '',
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const ErrorNode = ({ id, data }: NodeProps<CommonNodeType>) => (
|
||||
<div>
|
||||
<ErrorHandleOnNode id={id} data={data} />
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderErrorNode = (data: CommonNodeType) => {
|
||||
return render(
|
||||
<div style={{ width: 800, height: 600 }}>
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow
|
||||
fitView
|
||||
edges={[]}
|
||||
nodes={[
|
||||
{
|
||||
id: 'node-1',
|
||||
type: 'errorNode',
|
||||
position: { x: 0, y: 0 },
|
||||
data,
|
||||
},
|
||||
]}
|
||||
nodeTypes={{ errorNode: ErrorNode }}
|
||||
/>
|
||||
</ReactFlowProvider>
|
||||
</div>,
|
||||
)
|
||||
}
|
||||
|
||||
describe('ErrorHandleOnNode', () => {
|
||||
// Empty and default-value states.
|
||||
describe('Rendering', () => {
|
||||
it('should render nothing when the node has no error strategy', () => {
|
||||
const { container } = renderErrorNode(createNodeData())
|
||||
|
||||
expect(screen.queryByText('workflow.common.onFailure')).not.toBeInTheDocument()
|
||||
expect(container.querySelector('.react-flow__handle')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render the default-value label', async () => {
|
||||
renderErrorNode(createNodeData({ error_strategy: ErrorHandleTypeEnum.defaultValue }))
|
||||
|
||||
await waitFor(() => expect(screen.getByText('workflow.common.onFailure')).toBeInTheDocument())
|
||||
expect(screen.getByText('workflow.common.onFailure')).toBeInTheDocument()
|
||||
expect(screen.getByText('workflow.nodes.common.errorHandle.defaultValue.output')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Fail-branch behavior and warning styling.
|
||||
describe('Effects', () => {
|
||||
it('should render the fail-branch source handle', async () => {
|
||||
const { container } = renderErrorNode(createNodeData({ error_strategy: ErrorHandleTypeEnum.failBranch }))
|
||||
|
||||
await waitFor(() => expect(screen.getByText('workflow.nodes.common.errorHandle.failBranch.title')).toBeInTheDocument())
|
||||
expect(screen.getByText('workflow.nodes.common.errorHandle.failBranch.title')).toBeInTheDocument()
|
||||
expect(container.querySelector('.react-flow__handle')).toHaveAttribute('data-handleid', ErrorHandleTypeEnum.failBranch)
|
||||
})
|
||||
|
||||
it('should add warning styles when the node is in exception status', async () => {
|
||||
const { container } = renderErrorNode(createNodeData({
|
||||
error_strategy: ErrorHandleTypeEnum.defaultValue,
|
||||
_runningStatus: NodeRunningStatus.Exception,
|
||||
}))
|
||||
|
||||
await waitFor(() => expect(container.querySelector('.bg-state-warning-hover')).toBeInTheDocument())
|
||||
expect(container.querySelector('.bg-state-warning-hover')).toHaveClass('border-components-badge-status-light-warning-halo')
|
||||
expect(container.querySelector('.text-text-warning')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,130 @@
|
||||
import type { NodeProps } from 'reactflow'
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
import { render, waitFor } from '@testing-library/react'
|
||||
import ReactFlow, { ReactFlowProvider } from 'reactflow'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { NodeSourceHandle, NodeTargetHandle } from '../node-handle'
|
||||
|
||||
const createNodeData = (overrides: Partial<CommonNodeType> = {}): CommonNodeType => ({
|
||||
type: BlockEnum.Code,
|
||||
title: 'Node',
|
||||
desc: '',
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const TargetHandleNode = ({ id, data }: NodeProps<CommonNodeType>) => (
|
||||
<div>
|
||||
<NodeTargetHandle
|
||||
id={id}
|
||||
data={data}
|
||||
handleId="target-1"
|
||||
handleClassName="target-marker"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
const SourceHandleNode = ({ id, data }: NodeProps<CommonNodeType>) => (
|
||||
<div>
|
||||
<NodeSourceHandle
|
||||
id={id}
|
||||
data={data}
|
||||
handleId="source-1"
|
||||
handleClassName="source-marker"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderFlowNode = (type: 'targetNode' | 'sourceNode', data: CommonNodeType) => {
|
||||
return render(
|
||||
<div style={{ width: 800, height: 600 }}>
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow
|
||||
fitView
|
||||
edges={[]}
|
||||
nodes={[
|
||||
{
|
||||
id: 'node-1',
|
||||
type,
|
||||
position: { x: 0, y: 0 },
|
||||
data,
|
||||
},
|
||||
]}
|
||||
nodeTypes={{
|
||||
targetNode: TargetHandleNode,
|
||||
sourceNode: SourceHandleNode,
|
||||
}}
|
||||
/>
|
||||
</ReactFlowProvider>
|
||||
</div>,
|
||||
)
|
||||
}
|
||||
|
||||
describe('node-handle', () => {
|
||||
// Target handle states and visibility rules.
|
||||
describe('NodeTargetHandle', () => {
|
||||
it('should hide the connection indicator when the target handle is not connected', async () => {
|
||||
const { container } = renderFlowNode('targetNode', createNodeData())
|
||||
|
||||
await waitFor(() => expect(container.querySelector('.target-marker')).toBeInTheDocument())
|
||||
|
||||
const handle = container.querySelector('.target-marker')
|
||||
|
||||
expect(handle).toHaveAttribute('data-handleid', 'target-1')
|
||||
expect(handle).toHaveClass('after:opacity-0')
|
||||
})
|
||||
|
||||
it('should merge custom classes and hide start-like nodes completely', async () => {
|
||||
const { container } = render(
|
||||
<div style={{ width: 800, height: 600 }}>
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow
|
||||
fitView
|
||||
edges={[]}
|
||||
nodes={[
|
||||
{
|
||||
id: 'node-2',
|
||||
type: 'targetNode',
|
||||
position: { x: 0, y: 0 },
|
||||
data: createNodeData({ type: BlockEnum.Start }),
|
||||
},
|
||||
]}
|
||||
nodeTypes={{
|
||||
targetNode: ({ id, data }: NodeProps<CommonNodeType>) => (
|
||||
<div>
|
||||
<NodeTargetHandle
|
||||
id={id}
|
||||
data={data}
|
||||
handleId="target-2"
|
||||
handleClassName="custom-target"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</ReactFlowProvider>
|
||||
</div>,
|
||||
)
|
||||
|
||||
await waitFor(() => expect(container.querySelector('.custom-target')).toBeInTheDocument())
|
||||
|
||||
const handle = container.querySelector('.custom-target')
|
||||
|
||||
expect(handle).toHaveClass('opacity-0')
|
||||
expect(handle).toHaveClass('custom-target')
|
||||
})
|
||||
})
|
||||
|
||||
// Source handle connection state.
|
||||
describe('NodeSourceHandle', () => {
|
||||
it('should keep the source indicator visible when the handle is connected', async () => {
|
||||
const { container } = renderFlowNode('sourceNode', createNodeData({ _connectedSourceHandleIds: ['source-1'] }))
|
||||
|
||||
await waitFor(() => expect(container.querySelector('.source-marker')).toBeInTheDocument())
|
||||
|
||||
const handle = container.querySelector('.source-marker')
|
||||
|
||||
expect(handle).toHaveAttribute('data-handleid', 'source-1')
|
||||
expect(handle).not.toHaveClass('after:opacity-0')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user