fix(web): tests for component-ui & single-run-form

This commit is contained in:
JzoNg 2026-05-12 16:27:25 +08:00
parent 26c14fd58a
commit c86fd4cba0
2 changed files with 153 additions and 23 deletions

View File

@ -5,7 +5,8 @@ import type { ValueSelector } from '@/app/components/workflow/types'
import { LexicalComposer } from '@lexical/react/LexicalComposer'
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { BlockEnum, InputVarType } from '@/app/components/workflow/types'
import { BlockEnum, InputVarType, SupportUploadFileTypes } from '@/app/components/workflow/types'
import { TransferMethod } from '@/types/app'
import HITLInputComponentUI from '../component-ui'
import { HITLInputNode } from '../node'
@ -215,7 +216,7 @@ describe('HITLInputComponentUI', () => {
})
describe('Default formInput', () => {
it('should pass default payload to InputField when formInput is undefined', async () => {
it('should open an empty default editor when formInput is undefined', async () => {
const { findByRole } = renderComponent({
formInput: undefined,
})
@ -223,10 +224,10 @@ describe('HITLInputComponentUI', () => {
fireEvent.click(await screen.findByRole('button', { name: 'common.operation.edit' }))
const textbox = await findByRole('textbox')
const saveButton = await screen.findByRole('button', { name: 'common.operation.save' })
fireEvent.click(await screen.findByRole('button', { name: 'common.operation.save' }))
expect(textbox).toHaveValue('customer_name')
expect(textbox).toHaveValue('')
expect(saveButton).toBeDisabled()
})
it('should render variable selector when workflowNodesMap fallback is used', () => {

View File

@ -1,4 +1,5 @@
import type { ReactNode } from 'react'
import type { HumanInputFieldValue } from '@/app/components/base/chat/chat/answer/human-input-content/field-renderer'
import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types'
import type { HumanInputFormData } from '@/types/workflow'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
@ -12,25 +13,54 @@ vi.mock('react-i18next', () => ({
}),
}))
vi.mock('@langgenius/dify-ui/button', () => ({
Button: ({
children,
disabled,
onClick,
}: {
children?: ReactNode
disabled?: boolean
onClick?: () => void
}) => (
<button type="button" disabled={disabled} onClick={onClick}>
{children}
</button>
),
}))
vi.mock('@/app/components/base/chat/chat/answer/human-input-content/content-item', () => ({
__esModule: true,
default: ({ content }: { content: string }) => <div>{content}</div>,
default: ({
content,
formInputFields,
inputs,
onInputChange,
}: {
content: string
formInputFields: FormInputItem[]
inputs: Record<string, HumanInputFieldValue>
onInputChange: (name: string, value: HumanInputFieldValue) => void
}) => {
const fieldName = /\{\{#\$output\.([^#]+)#\}\}/.exec(content)?.[1]
if (!fieldName)
return <div>{content}</div>
const field = formInputFields.find(field => field.output_variable_name === fieldName)
if (!field)
return null
if (field.type === 'select') {
return (
<select
aria-label={fieldName}
value={typeof inputs[fieldName] === 'string' ? inputs[fieldName] : ''}
onChange={event => onInputChange(fieldName, event.target.value)}
>
<option value="">Select</option>
{field.option_source.value.map(option => (
<option key={option} value={option}>{option}</option>
))}
</select>
)
}
if (field.type === 'paragraph') {
return (
<textarea
aria-label={fieldName}
value={typeof inputs[fieldName] === 'string' ? inputs[fieldName] : ''}
onChange={event => onInputChange(fieldName, event.target.value)}
/>
)
}
return <div>{fieldName}</div>
},
}))
const createFormData = (overrides: Partial<HumanInputFormData> = {}): HumanInputFormData => ({
@ -60,6 +90,10 @@ const createFormData = (overrides: Partial<HumanInputFormData> = {}): HumanInput
})
describe('SingleRunForm', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('renders the back action as a named button and forwards clicks', async () => {
const user = userEvent.setup()
const handleBack = vi.fn()
@ -99,4 +133,99 @@ describe('SingleRunForm', () => {
})
})
})
it('submits updated paragraph input values', async () => {
const user = userEvent.setup()
const onSubmit = vi.fn().mockResolvedValue(undefined)
render(
<SingleRunForm
nodeName="Review"
data={createFormData()}
onSubmit={onSubmit}
/>,
)
await user.clear(screen.getByRole('textbox', { name: 'review' }))
await user.type(screen.getByRole('textbox', { name: 'review' }), 'updated review')
await user.click(screen.getByRole('button', { name: 'Approve' }))
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
inputs: { review: 'updated review' },
action: 'approve',
})
})
})
it('uses resolved default values for variable paragraph inputs', async () => {
const user = userEvent.setup()
const onSubmit = vi.fn().mockResolvedValue(undefined)
render(
<SingleRunForm
nodeName="Review"
data={createFormData({
inputs: [{
type: InputVarType.paragraph,
output_variable_name: 'review',
default: {
selector: ['source', 'answer'],
type: 'variable',
value: 'fallback review',
},
}],
resolved_default_values: {
review: 'resolved review',
},
})}
onSubmit={onSubmit}
/>,
)
await user.click(screen.getByRole('button', { name: 'Approve' }))
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
inputs: { review: 'resolved review' },
action: 'approve',
})
})
})
it('disables submit actions until a select input has a value', async () => {
const user = userEvent.setup()
const onSubmit = vi.fn().mockResolvedValue(undefined)
render(
<SingleRunForm
nodeName="Review"
data={createFormData({
form_content: 'Choose {{#$output.choice#}}',
inputs: [{
type: InputVarType.select,
output_variable_name: 'choice',
option_source: {
selector: [],
type: 'constant',
value: ['approve', 'reject'],
},
}],
})}
onSubmit={onSubmit}
/>,
)
expect(screen.getByRole('button', { name: 'Approve' })).toBeDisabled()
await user.selectOptions(screen.getByRole('combobox', { name: 'choice' }), 'approve')
await user.click(screen.getByRole('button', { name: 'Approve' }))
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
inputs: { choice: 'approve' },
action: 'approve',
})
})
})
})