mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 06:28:05 +08:00
fix: enhance form validation for file inputs and improve handling of empty array values in variable modal
This commit is contained in:
@ -57,6 +57,16 @@ describe('before-run-form helpers', () => {
|
||||
values: createValues({ query: '' }),
|
||||
})], [{}], t)).toContain('errorMsg.fieldRequired')
|
||||
|
||||
expect(getFormErrorMessage([createForm({
|
||||
inputs: [createInput({ variable: 'file', label: 'File', type: InputVarType.singleFile, required: true })],
|
||||
values: createValues({ file: [] }),
|
||||
})], [{}], t)).toContain('errorMsg.fieldRequired')
|
||||
|
||||
expect(getFormErrorMessage([createForm({
|
||||
inputs: [createInput({ variable: 'files', label: 'Files', type: InputVarType.multiFiles, required: true })],
|
||||
values: createValues({ files: [] }),
|
||||
})], [{}], t)).toContain('errorMsg.fieldRequired')
|
||||
|
||||
expect(getFormErrorMessage([createForm({
|
||||
inputs: [createInput({ variable: 'file', label: 'File', type: InputVarType.singleFile })],
|
||||
values: createValues({ file: { transferMethod: TransferMethod.local_file } }),
|
||||
|
||||
@ -56,7 +56,16 @@ export const getFormErrorMessage = (
|
||||
const missingRequired = input.required
|
||||
&& input.type !== InputVarType.checkbox
|
||||
&& !(input.variable in existVarValuesInForm)
|
||||
&& (value === '' || value === undefined || value === null || (input.type === InputVarType.files && Array.isArray(value) && value.length === 0))
|
||||
&& (
|
||||
value === '' || value === undefined || value === null
|
||||
|| (
|
||||
(input.type === InputVarType.files
|
||||
|| input.type === InputVarType.multiFiles
|
||||
|| input.type === InputVarType.singleFile)
|
||||
&& Array.isArray(value)
|
||||
&& value.length === 0
|
||||
)
|
||||
)
|
||||
|
||||
if (!errMsg && missingRequired) {
|
||||
errMsg = t('errorMsg.fieldRequired', { ns: 'workflow', field: typeof input.label === 'object' ? input.label.variable : input.label })
|
||||
|
||||
@ -3,6 +3,40 @@ import userEvent from '@testing-library/user-event'
|
||||
import { useState } from 'react'
|
||||
import GenericTable from '../generic-table'
|
||||
|
||||
vi.mock('@/app/components/base/select', () => ({
|
||||
SimpleSelect: ({
|
||||
items,
|
||||
defaultValue,
|
||||
onSelect,
|
||||
disabled,
|
||||
placeholder,
|
||||
}: {
|
||||
items: Array<{ name: string, value: string }>
|
||||
defaultValue?: string
|
||||
onSelect: (item: { name: string, value: string }) => void
|
||||
disabled?: boolean
|
||||
placeholder?: string
|
||||
}) => (
|
||||
<select
|
||||
aria-label={placeholder ?? 'Select'}
|
||||
disabled={disabled}
|
||||
value={defaultValue ?? ''}
|
||||
onChange={(e) => {
|
||||
const item = items.find(item => item.value === e.target.value)
|
||||
if (item)
|
||||
onSelect(item)
|
||||
}}
|
||||
>
|
||||
<option value="">{placeholder ?? 'Select'}</option>
|
||||
{items.map(item => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
),
|
||||
}))
|
||||
|
||||
const columns = [
|
||||
{
|
||||
key: 'name',
|
||||
@ -144,12 +178,11 @@ describe('GenericTable', () => {
|
||||
<ControlledTable />,
|
||||
)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Choose method' }))
|
||||
await user.click(await screen.findByText('POST'))
|
||||
await user.selectOptions(screen.getAllByRole('combobox', { name: 'Choose method' })[0], 'post')
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toHaveBeenCalledWith([{ method: 'post', preview: '' }])
|
||||
expect(screen.getByRole('button', { name: 'POST' })).toBeInTheDocument()
|
||||
expect(screen.getAllByRole('combobox', { name: 'Choose method' })[0]).toHaveValue('post')
|
||||
})
|
||||
|
||||
onChange.mockClear()
|
||||
|
||||
@ -158,6 +158,20 @@ describe('useVariableModalState', () => {
|
||||
expect(result.current.editorContent).toBe(JSON.stringify(['True', 'False']))
|
||||
})
|
||||
|
||||
it('should preserve zero values when switching number arrays into json mode', () => {
|
||||
const { result } = renderHook(() => useVariableModalState(createOptions()))
|
||||
|
||||
act(() => {
|
||||
result.current.handleTypeChange(ChatVarType.ArrayNumber)
|
||||
result.current.setValue([0, 2, undefined])
|
||||
result.current.handleEditorChange(true)
|
||||
})
|
||||
|
||||
expect(result.current.editInJSON).toBe(true)
|
||||
expect(result.current.value).toEqual([0, 2])
|
||||
expect(result.current.editorContent).toBe(JSON.stringify([0, 2]))
|
||||
})
|
||||
|
||||
it('should notify and stop saving when object keys are invalid', () => {
|
||||
const notify = vi.fn()
|
||||
const onSave = vi.fn()
|
||||
|
||||
@ -59,6 +59,13 @@ describe('variable-modal helpers', () => {
|
||||
value: ['a', '', 'b'],
|
||||
})).toEqual(['a', 'b'])
|
||||
|
||||
expect(formatChatVariableValue({
|
||||
editInJSON: false,
|
||||
objectValue: [],
|
||||
type: ChatVarType.ArrayNumber,
|
||||
value: [0, 1, undefined, null, ''] as unknown as Array<number | undefined>,
|
||||
})).toEqual([0, 1])
|
||||
|
||||
expect(formatChatVariableValue({
|
||||
editInJSON: false,
|
||||
objectValue: [],
|
||||
@ -99,6 +106,11 @@ describe('variable-modal helpers', () => {
|
||||
type: ChatVarType.ArrayBoolean,
|
||||
})).toEqual([true, false, true, false])
|
||||
|
||||
expect(() => parseEditorContent({
|
||||
content: '{"enabled":true}',
|
||||
type: ChatVarType.ArrayBoolean,
|
||||
})).toThrow('JSON array')
|
||||
|
||||
expect(parseEditorContent({
|
||||
content: '{"enabled":true}',
|
||||
type: ChatVarType.Object,
|
||||
|
||||
@ -100,8 +100,10 @@ describe('variable-modal', () => {
|
||||
expect(screen.getByDisplayValue('secret')).toBeInTheDocument()
|
||||
expect(screen.getByDisplayValue('30')).toBeInTheDocument()
|
||||
|
||||
const timeoutInput = screen.getByDisplayValue('30') as HTMLInputElement
|
||||
await user.clear(screen.getByDisplayValue('secret'))
|
||||
await user.type(screen.getByDisplayValue('30'), '5')
|
||||
await user.clear(timeoutInput)
|
||||
await user.type(timeoutInput, '5')
|
||||
await user.click(screen.getByText('common.operation.save'))
|
||||
|
||||
expect(onSave).toHaveBeenCalledWith({
|
||||
@ -110,7 +112,7 @@ describe('variable-modal', () => {
|
||||
value_type: ChatVarType.Object,
|
||||
value: {
|
||||
apiKey: null,
|
||||
timeout: 305,
|
||||
timeout: 5,
|
||||
},
|
||||
description: 'settings',
|
||||
})
|
||||
|
||||
@ -133,8 +133,11 @@ export const useVariableModalState = ({
|
||||
|
||||
if (prev.type === ChatVarType.ArrayString || prev.type === ChatVarType.ArrayNumber) {
|
||||
if (nextEditInJSON) {
|
||||
const nextValue = (Array.isArray(prev.value) && prev.value.length && prev.value.filter(Boolean).length)
|
||||
? prev.value.filter(Boolean)
|
||||
const compactValues = Array.isArray(prev.value)
|
||||
? prev.value.filter(item => item !== null && item !== undefined && item !== '')
|
||||
: []
|
||||
const nextValue = compactValues.length
|
||||
? compactValues
|
||||
: undefined
|
||||
nextState.value = nextValue
|
||||
if (!prev.editorContent)
|
||||
|
||||
@ -88,6 +88,9 @@ export const formatChatVariableValue = ({
|
||||
type: ChatVarType
|
||||
value: unknown
|
||||
}) => {
|
||||
const compactArrayValue = (items: unknown[]) =>
|
||||
items.filter(item => item !== null && item !== undefined && item !== '')
|
||||
|
||||
switch (type) {
|
||||
case ChatVarTypeEnum.String:
|
||||
return value || ''
|
||||
@ -100,7 +103,7 @@ export const formatChatVariableValue = ({
|
||||
case ChatVarTypeEnum.ArrayString:
|
||||
case ChatVarTypeEnum.ArrayNumber:
|
||||
case ChatVarTypeEnum.ArrayObject:
|
||||
return Array.isArray(value) ? value.filter(Boolean) : []
|
||||
return Array.isArray(value) ? compactArrayValue(value) : []
|
||||
case ChatVarTypeEnum.ArrayBoolean:
|
||||
return value || []
|
||||
}
|
||||
@ -151,6 +154,9 @@ export const parseEditorContent = ({
|
||||
if (type !== ChatVarTypeEnum.ArrayBoolean)
|
||||
return parsed
|
||||
|
||||
if (!Array.isArray(parsed))
|
||||
throw new TypeError('ArrayBoolean editor content must be a JSON array')
|
||||
|
||||
return parsed
|
||||
.map((item: string | boolean) => {
|
||||
if (item === 'True' || item === 'true' || item === true)
|
||||
|
||||
Reference in New Issue
Block a user