mirror of
https://github.com/langgenius/dify.git
synced 2026-03-28 09:30:55 +08:00
219 lines
6.1 KiB
TypeScript
219 lines
6.1 KiB
TypeScript
import type { InputVar } from '../types'
|
|
import type { PromptVariable } from '@/models/debug'
|
|
import { screen } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { useNodes } from 'reactflow'
|
|
import Features from '../features'
|
|
import { InputVarType } from '../types'
|
|
import { createStartNode } from './fixtures'
|
|
import { renderWorkflowFlowComponent } from './workflow-test-env'
|
|
|
|
const mockHandleSyncWorkflowDraft = vi.fn()
|
|
const mockHandleAddVariable = vi.fn()
|
|
const mockUpdateFeatures = vi.hoisted(() => vi.fn())
|
|
|
|
let mockIsChatMode = true
|
|
let mockNodesReadOnly = false
|
|
|
|
vi.mock('../hooks', async () => {
|
|
const actual = await vi.importActual<typeof import('../hooks')>('../hooks')
|
|
return {
|
|
...actual,
|
|
useIsChatMode: () => mockIsChatMode,
|
|
useNodesReadOnly: () => ({
|
|
nodesReadOnly: mockNodesReadOnly,
|
|
}),
|
|
useNodesSyncDraft: () => ({
|
|
handleSyncWorkflowDraft: mockHandleSyncWorkflowDraft,
|
|
}),
|
|
}
|
|
})
|
|
|
|
vi.mock('../nodes/start/use-config', () => ({
|
|
default: () => ({
|
|
handleAddVariable: mockHandleAddVariable,
|
|
}),
|
|
}))
|
|
|
|
vi.mock('@/app/components/app/store', () => ({
|
|
useStore: (selector: (state: { appDetail?: { runtime_type?: string } }) => unknown) => selector({
|
|
appDetail: {
|
|
runtime_type: 'sandboxed',
|
|
},
|
|
}),
|
|
}))
|
|
|
|
vi.mock('@/service/workflow', () => ({
|
|
updateFeatures: mockUpdateFeatures,
|
|
}))
|
|
|
|
vi.mock('@/app/components/workflow/collaboration/core/websocket-manager', () => ({
|
|
webSocketClient: {
|
|
getSocket: () => null,
|
|
},
|
|
}))
|
|
|
|
vi.mock('@/app/components/base/features/new-feature-panel', () => ({
|
|
default: ({
|
|
show,
|
|
isChatMode,
|
|
disabled,
|
|
onChange,
|
|
onClose,
|
|
onAutoAddPromptVariable,
|
|
workflowVariables,
|
|
}: {
|
|
show: boolean
|
|
isChatMode: boolean
|
|
disabled: boolean
|
|
onChange: () => void
|
|
onClose: () => void
|
|
onAutoAddPromptVariable: (variables: PromptVariable[]) => void
|
|
workflowVariables: InputVar[]
|
|
}) => {
|
|
if (!show)
|
|
return null
|
|
|
|
return (
|
|
<section aria-label="new feature panel">
|
|
<div>{isChatMode ? 'chat mode' : 'completion mode'}</div>
|
|
<div>{disabled ? 'panel disabled' : 'panel enabled'}</div>
|
|
<ul aria-label="workflow variables">
|
|
{workflowVariables.map(variable => (
|
|
<li key={variable.variable}>
|
|
{`${variable.label}:${variable.variable}`}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<button type="button" onClick={onChange}>open features</button>
|
|
<button type="button" onClick={onClose}>close features</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => onAutoAddPromptVariable([{
|
|
key: 'opening_statement',
|
|
name: 'Opening Statement',
|
|
type: 'string',
|
|
max_length: 200,
|
|
required: true,
|
|
}])}
|
|
>
|
|
add required variable
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => onAutoAddPromptVariable([{
|
|
key: 'optional_statement',
|
|
name: 'Optional Statement',
|
|
type: 'string',
|
|
max_length: 120,
|
|
}])}
|
|
>
|
|
add optional variable
|
|
</button>
|
|
</section>
|
|
)
|
|
},
|
|
}))
|
|
|
|
const startNode = createStartNode({
|
|
id: 'start-node',
|
|
data: {
|
|
variables: [{ variable: 'existing_variable', label: 'Existing Variable' }],
|
|
},
|
|
})
|
|
|
|
const DelayedFeatures = () => {
|
|
const nodes = useNodes()
|
|
|
|
if (!nodes.length)
|
|
return null
|
|
|
|
return <Features />
|
|
}
|
|
|
|
const renderFeatures = (options?: Omit<NonNullable<Parameters<typeof renderWorkflowFlowComponent>[1]>, 'nodes' | 'edges'>) => {
|
|
const { initialStoreState, ...rest } = options ?? {}
|
|
|
|
return renderWorkflowFlowComponent(
|
|
<DelayedFeatures />,
|
|
{
|
|
nodes: [startNode],
|
|
edges: [],
|
|
initialStoreState: {
|
|
appId: 'app-1',
|
|
...initialStoreState,
|
|
},
|
|
...rest,
|
|
},
|
|
)
|
|
}
|
|
|
|
describe('Features', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mockIsChatMode = true
|
|
mockNodesReadOnly = false
|
|
mockUpdateFeatures.mockResolvedValue(undefined)
|
|
})
|
|
|
|
describe('Rendering', () => {
|
|
it('should pass workflow context to the feature panel', () => {
|
|
renderFeatures()
|
|
|
|
expect(screen.getByText('chat mode')).toBeInTheDocument()
|
|
expect(screen.getByText('panel enabled')).toBeInTheDocument()
|
|
expect(screen.getByRole('list', { name: 'workflow variables' })).toHaveTextContent('Existing Variable:existing_variable')
|
|
})
|
|
})
|
|
|
|
describe('User Interactions', () => {
|
|
it('should sync the draft and open the workflow feature panel when users change features', async () => {
|
|
const user = userEvent.setup()
|
|
const { store } = renderFeatures()
|
|
|
|
await user.click(screen.getByRole('button', { name: 'open features' }))
|
|
|
|
expect(mockHandleSyncWorkflowDraft).toHaveBeenCalledTimes(1)
|
|
expect(store.getState().showFeaturesPanel).toBe(true)
|
|
})
|
|
|
|
it('should close the workflow feature panel and transform required prompt variables', async () => {
|
|
const user = userEvent.setup()
|
|
const { store } = renderFeatures({
|
|
initialStoreState: {
|
|
showFeaturesPanel: true,
|
|
},
|
|
})
|
|
|
|
await user.click(screen.getByRole('button', { name: 'close features' }))
|
|
expect(store.getState().showFeaturesPanel).toBe(false)
|
|
|
|
await user.click(screen.getByRole('button', { name: 'add required variable' }))
|
|
expect(mockHandleAddVariable).toHaveBeenCalledWith({
|
|
variable: 'opening_statement',
|
|
label: 'Opening Statement',
|
|
type: InputVarType.textInput,
|
|
max_length: 200,
|
|
required: true,
|
|
options: [],
|
|
})
|
|
})
|
|
|
|
it('should default prompt variables to optional when required is omitted', async () => {
|
|
const user = userEvent.setup()
|
|
|
|
renderFeatures()
|
|
|
|
await user.click(screen.getByRole('button', { name: 'add optional variable' }))
|
|
expect(mockHandleAddVariable).toHaveBeenCalledWith({
|
|
variable: 'optional_statement',
|
|
label: 'Optional Statement',
|
|
type: InputVarType.textInput,
|
|
max_length: 120,
|
|
required: false,
|
|
options: [],
|
|
})
|
|
})
|
|
})
|
|
})
|