mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 09:58:04 +08:00
Merge remote-tracking branch 'origin/main' into feat/support-agent-sandbox
# Conflicts: # web/app/components/workflow-app/hooks/__tests__/use-nodes-sync-draft.spec.ts # web/app/components/workflow-app/hooks/__tests__/use-workflow-refresh-draft.spec.ts # web/app/components/workflow-app/hooks/use-workflow-run.ts # web/app/components/workflow-app/index.tsx # web/app/components/workflow/panel/chat-variable-panel/components/use-variable-modal-state.ts # web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.helpers.ts
This commit is contained in:
@ -90,6 +90,22 @@ describe('useVariableModalState', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('should keep valid object rows when switching to json mode from form mode', () => {
|
||||
const { result } = renderHook(() => useVariableModalState(createOptions()))
|
||||
|
||||
act(() => {
|
||||
result.current.handleTypeChange(ChatVarType.Object)
|
||||
result.current.setObjectValue([
|
||||
{ key: '', type: ChatVarType.String, value: undefined },
|
||||
{ key: 'timeout', type: ChatVarType.Number, value: 30 },
|
||||
])
|
||||
result.current.handleEditorChange(true)
|
||||
})
|
||||
|
||||
expect(result.current.editInJSON).toBe(true)
|
||||
expect(result.current.value).toEqual({ timeout: 30 })
|
||||
expect(result.current.editorContent).toBe(JSON.stringify({ timeout: 30 }))
|
||||
})
|
||||
it('should reset object form values when leaving empty json mode', () => {
|
||||
const { result } = renderHook(() => useVariableModalState(createOptions({
|
||||
chatVar: {
|
||||
@ -141,6 +157,19 @@ 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()
|
||||
|
||||
@ -33,6 +33,10 @@ describe('variable-modal helpers', () => {
|
||||
{ key: '', type: ChatVarType.Number, value: 1 },
|
||||
])).toEqual({ apiKey: 'secret' })
|
||||
|
||||
expect(formatObjectValueFromList([
|
||||
{ key: 'count', type: ChatVarType.Number, value: 0 },
|
||||
{ key: 'label', type: ChatVarType.String, value: '' },
|
||||
])).toEqual({ count: 0, label: null })
|
||||
expect(formatChatVariableValue({
|
||||
editInJSON: false,
|
||||
objectValue: [{ key: 'enabled', type: ChatVarType.String, value: 'true' }],
|
||||
@ -54,6 +58,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: [],
|
||||
@ -94,6 +105,10 @@ 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,
|
||||
|
||||
@ -80,7 +80,7 @@ describe('variable-modal', () => {
|
||||
await user.type(screen.getByPlaceholderText('workflow.chatVariable.modal.namePlaceholder'), 'existing_name')
|
||||
await user.click(screen.getByText('common.operation.save'))
|
||||
|
||||
expect(mockToastError.mock.calls.at(-1)?.[0]).toBe('name is existed')
|
||||
expect(mockToastError.mock.calls.at(-1)?.[0]).toBe('appDebug.varKeyError.keyAlreadyExists:{"key":"workflow.chatVariable.modal.name"}')
|
||||
expect(onSave).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@ -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',
|
||||
})
|
||||
@ -195,4 +197,22 @@ describe('variable-modal', () => {
|
||||
description: '',
|
||||
})
|
||||
})
|
||||
|
||||
it('should keep the number input empty while editing after the user clears it', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderVariableModal({
|
||||
chatVar: {
|
||||
id: 'var-4',
|
||||
name: 'timeout',
|
||||
description: '',
|
||||
value_type: ChatVarType.Number,
|
||||
value: 3,
|
||||
},
|
||||
})
|
||||
|
||||
const input = screen.getByDisplayValue('3') as HTMLInputElement
|
||||
await user.clear(input)
|
||||
|
||||
expect(input.value).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
@ -108,7 +108,7 @@ export const useVariableModalState = ({
|
||||
|
||||
if (prev.type === ChatVarType.Object) {
|
||||
if (nextEditInJSON) {
|
||||
const nextValue = !prev.objectValue[0].key ? undefined : formatObjectValueFromList(prev.objectValue)
|
||||
const nextValue = prev.objectValue.some(item => item.key) ? formatObjectValueFromList(prev.objectValue) : undefined
|
||||
nextState.value = nextValue
|
||||
nextState.editorContent = JSON.stringify(nextValue)
|
||||
return nextState
|
||||
@ -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)
|
||||
@ -188,11 +191,8 @@ export const useVariableModalState = ({
|
||||
return
|
||||
}
|
||||
|
||||
if (state.type === ChatVarType.Object && state.objectValue.some(item => !item.key && !!item.value)) {
|
||||
notify({
|
||||
type: 'error',
|
||||
message: t('chatVariable.modal.objectKeyRequired', { ns: 'workflow' }),
|
||||
})
|
||||
if (state.type === ChatVarType.Object && state.objectValue.some(item => !item.key && item.value !== undefined && item.value !== '')) {
|
||||
notify({ type: 'error', message: t('chatVariable.modal.objectKeyRequired', { ns: 'workflow' }) })
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -117,6 +117,8 @@ export const formatChatVariableValue = ({
|
||||
type: ChatVarType
|
||||
value: unknown
|
||||
}): JsonValue => {
|
||||
const compactArrayValue = (items: unknown[]) =>
|
||||
items.filter((item): item is JsonValue => item !== null && item !== undefined && item !== '')
|
||||
switch (type) {
|
||||
case ChatVarTypeEnum.String:
|
||||
return typeof value === 'string' ? value : ''
|
||||
@ -129,7 +131,7 @@ export const formatChatVariableValue = ({
|
||||
case ChatVarTypeEnum.ArrayString:
|
||||
case ChatVarTypeEnum.ArrayNumber:
|
||||
case ChatVarTypeEnum.ArrayObject:
|
||||
return Array.isArray(value) ? value.filter((item): item is JsonValue => item !== undefined) : []
|
||||
return Array.isArray(value) ? compactArrayValue(value) : []
|
||||
case ChatVarTypeEnum.ArrayBoolean:
|
||||
return Array.isArray(value) ? value.filter((item): item is JsonValue => item !== undefined) : []
|
||||
}
|
||||
@ -186,6 +188,8 @@ export const parseEditorContent = ({
|
||||
if (!Array.isArray(parsed))
|
||||
throw new Error('Invalid JSON array')
|
||||
|
||||
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)
|
||||
|
||||
@ -138,7 +138,10 @@ export const ValueSection = ({
|
||||
<Input
|
||||
placeholder={t('chatVariable.modal.valuePlaceholder', { ns: 'workflow' }) || ''}
|
||||
value={value as number | undefined}
|
||||
onChange={e => onArrayChange([Number(e.target.value)])}
|
||||
onChange={(e) => {
|
||||
const rawValue = e.target.value
|
||||
onArrayChange([rawValue === '' ? undefined : Number(rawValue)])
|
||||
}}
|
||||
type="number"
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user