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:
Novice
2026-03-23 14:20:06 +08:00
1671 changed files with 124822 additions and 22302 deletions

View File

@ -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')
})
})

View File

@ -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()
})
})
})

View File

@ -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')
})
})
})