mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 00:48:04 +08:00
Merge main HEAD (segment 5) into sandboxed-agent-rebase
Resolve 83 conflicts: 10 backend, 62 frontend, 11 config/lock files. Preserve sandbox/agent/collaboration features while adopting main's UI refactorings (Dialog/AlertDialog/Popover), model provider updates, and enterprise features. Made-with: Cursor
This commit is contained in:
@ -0,0 +1,47 @@
|
||||
import { render, waitFor } from '@testing-library/react'
|
||||
import WorkflowPreview from '../index'
|
||||
|
||||
const defaultViewport = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
zoom: 1,
|
||||
}
|
||||
|
||||
describe('WorkflowPreview', () => {
|
||||
it('should render the preview container with the default left minimap placement', async () => {
|
||||
const { container } = render(
|
||||
<div style={{ width: 800, height: 600 }}>
|
||||
<WorkflowPreview
|
||||
nodes={[]}
|
||||
edges={[]}
|
||||
viewport={defaultViewport}
|
||||
className="preview-shell"
|
||||
/>
|
||||
</div>,
|
||||
)
|
||||
|
||||
await waitFor(() => expect(container.querySelector('.react-flow__minimap')).toBeInTheDocument())
|
||||
|
||||
expect(container.querySelector('#workflow-container')).toHaveClass('preview-shell')
|
||||
expect(container.querySelector('.react-flow__background')).toBeInTheDocument()
|
||||
expect(container.querySelector('.react-flow__minimap')).toHaveClass('!left-4')
|
||||
})
|
||||
|
||||
it('should move the minimap to the right when requested', async () => {
|
||||
const { container } = render(
|
||||
<div style={{ width: 800, height: 600 }}>
|
||||
<WorkflowPreview
|
||||
nodes={[]}
|
||||
edges={[]}
|
||||
viewport={defaultViewport}
|
||||
miniMapToRight
|
||||
/>
|
||||
</div>,
|
||||
)
|
||||
|
||||
await waitFor(() => expect(container.querySelector('.react-flow__minimap')).toBeInTheDocument())
|
||||
|
||||
expect(container.querySelector('.react-flow__minimap')).toHaveClass('!right-4')
|
||||
expect(container.querySelector('.react-flow__minimap')).not.toHaveClass('!left-4')
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,76 @@
|
||||
import type { NodeProps } from 'reactflow'
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import { createNode } from '@/app/components/workflow/__tests__/fixtures'
|
||||
import { renderWorkflowFlowComponent } from '@/app/components/workflow/__tests__/workflow-test-env'
|
||||
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) =>
|
||||
renderWorkflowFlowComponent(<div />, {
|
||||
nodes: [createNode({
|
||||
id: 'node-1',
|
||||
type: 'errorNode',
|
||||
data,
|
||||
})],
|
||||
edges: [],
|
||||
reactFlowProps: {
|
||||
nodeTypes: { errorNode: ErrorNode },
|
||||
},
|
||||
})
|
||||
|
||||
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,114 @@
|
||||
import type { NodeProps } from 'reactflow'
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
import { waitFor } from '@testing-library/react'
|
||||
import { createNode } from '@/app/components/workflow/__tests__/fixtures'
|
||||
import { renderWorkflowFlowComponent } from '@/app/components/workflow/__tests__/workflow-test-env'
|
||||
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) =>
|
||||
renderWorkflowFlowComponent(<div />, {
|
||||
nodes: [createNode({
|
||||
id: 'node-1',
|
||||
type,
|
||||
data,
|
||||
})],
|
||||
edges: [],
|
||||
reactFlowProps: {
|
||||
nodeTypes: {
|
||||
targetNode: TargetHandleNode,
|
||||
sourceNode: SourceHandleNode,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
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 } = renderWorkflowFlowComponent(<div />, {
|
||||
nodes: [createNode({
|
||||
id: 'node-2',
|
||||
type: 'targetNode',
|
||||
data: createNodeData({ type: BlockEnum.Start }),
|
||||
})],
|
||||
edges: [],
|
||||
reactFlowProps: {
|
||||
nodeTypes: {
|
||||
targetNode: ({ id, data }: NodeProps<CommonNodeType>) => (
|
||||
<div>
|
||||
<NodeTargetHandle
|
||||
id={id}
|
||||
data={data}
|
||||
handleId="target-2"
|
||||
handleClassName="custom-target"
|
||||
/>
|
||||
</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