dify
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
import { deepSanitizeFormValues, findMissingRequiredField, sanitizeFormValues } from '../form-helpers'
|
||||
|
||||
describe('Form Helpers', () => {
|
||||
describe('sanitizeFormValues', () => {
|
||||
it('should convert null values to empty strings', () => {
|
||||
const input = { field1: null, field2: 'value', field3: undefined }
|
||||
const result = sanitizeFormValues(input)
|
||||
|
||||
expect(result).toEqual({
|
||||
field1: '',
|
||||
field2: 'value',
|
||||
field3: '',
|
||||
})
|
||||
})
|
||||
|
||||
it('should convert undefined values to empty strings', () => {
|
||||
const input = { field1: undefined, field2: 'test' }
|
||||
const result = sanitizeFormValues(input)
|
||||
|
||||
expect(result).toEqual({
|
||||
field1: '',
|
||||
field2: 'test',
|
||||
})
|
||||
})
|
||||
|
||||
it('should convert non-string values to strings', () => {
|
||||
const input = { number: 123, boolean: true, string: 'test' }
|
||||
const result = sanitizeFormValues(input)
|
||||
|
||||
expect(result).toEqual({
|
||||
number: '123',
|
||||
boolean: 'true',
|
||||
string: 'test',
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle empty objects', () => {
|
||||
const result = sanitizeFormValues({})
|
||||
expect(result).toEqual({})
|
||||
})
|
||||
|
||||
it('should handle objects with mixed value types', () => {
|
||||
const input = {
|
||||
null_field: null,
|
||||
undefined_field: undefined,
|
||||
zero: 0,
|
||||
false_field: false,
|
||||
empty_string: '',
|
||||
valid_string: 'test',
|
||||
}
|
||||
const result = sanitizeFormValues(input)
|
||||
|
||||
expect(result).toEqual({
|
||||
null_field: '',
|
||||
undefined_field: '',
|
||||
zero: '0',
|
||||
false_field: 'false',
|
||||
empty_string: '',
|
||||
valid_string: 'test',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('deepSanitizeFormValues', () => {
|
||||
it('should handle nested objects', () => {
|
||||
const input = {
|
||||
level1: {
|
||||
field1: null,
|
||||
field2: 'value',
|
||||
level2: {
|
||||
field3: undefined,
|
||||
field4: 'nested',
|
||||
},
|
||||
},
|
||||
simple: 'test',
|
||||
}
|
||||
const result = deepSanitizeFormValues(input)
|
||||
|
||||
expect(result).toEqual({
|
||||
level1: {
|
||||
field1: '',
|
||||
field2: 'value',
|
||||
level2: {
|
||||
field3: '',
|
||||
field4: 'nested',
|
||||
},
|
||||
},
|
||||
simple: 'test',
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle arrays correctly', () => {
|
||||
const input = {
|
||||
array: [1, 2, 3],
|
||||
nested: {
|
||||
array: ['a', null, 'c'],
|
||||
},
|
||||
}
|
||||
const result = deepSanitizeFormValues(input)
|
||||
|
||||
expect(result).toEqual({
|
||||
array: [1, 2, 3],
|
||||
nested: {
|
||||
array: ['a', null, 'c'],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle null and undefined at root level', () => {
|
||||
const input = {
|
||||
null_field: null,
|
||||
undefined_field: undefined,
|
||||
nested: {
|
||||
null_nested: null,
|
||||
},
|
||||
}
|
||||
const result = deepSanitizeFormValues(input)
|
||||
|
||||
expect(result).toEqual({
|
||||
null_field: '',
|
||||
undefined_field: '',
|
||||
nested: {
|
||||
null_nested: '',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle deeply nested structures', () => {
|
||||
const input = {
|
||||
level1: {
|
||||
level2: {
|
||||
level3: {
|
||||
field: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
const result = deepSanitizeFormValues(input)
|
||||
|
||||
expect(result).toEqual({
|
||||
level1: {
|
||||
level2: {
|
||||
level3: {
|
||||
field: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should preserve non-null values in nested structures', () => {
|
||||
const input = {
|
||||
config: {
|
||||
client_id: 'valid_id',
|
||||
client_secret: null,
|
||||
options: {
|
||||
timeout: 5000,
|
||||
enabled: true,
|
||||
message: undefined,
|
||||
},
|
||||
},
|
||||
}
|
||||
const result = deepSanitizeFormValues(input)
|
||||
|
||||
expect(result).toEqual({
|
||||
config: {
|
||||
client_id: 'valid_id',
|
||||
client_secret: '',
|
||||
options: {
|
||||
timeout: 5000,
|
||||
enabled: true,
|
||||
message: '',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('findMissingRequiredField', () => {
|
||||
const requiredFields = [
|
||||
{ name: 'client_id', label: 'Client ID' },
|
||||
{ name: 'client_secret', label: 'Client Secret' },
|
||||
{ name: 'scope', label: 'Scope' },
|
||||
]
|
||||
|
||||
it('should return null when all required fields are present', () => {
|
||||
const formData = {
|
||||
client_id: 'test_id',
|
||||
client_secret: 'test_secret',
|
||||
scope: 'read',
|
||||
optional_field: 'optional',
|
||||
}
|
||||
|
||||
const result = findMissingRequiredField(formData, requiredFields)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should return the first missing field', () => {
|
||||
const formData = {
|
||||
client_id: 'test_id',
|
||||
scope: 'read',
|
||||
}
|
||||
|
||||
const result = findMissingRequiredField(formData, requiredFields)
|
||||
expect(result).toEqual({ name: 'client_secret', label: 'Client Secret' })
|
||||
})
|
||||
|
||||
it('should treat empty strings as missing fields', () => {
|
||||
const formData = {
|
||||
client_id: '',
|
||||
client_secret: 'test_secret',
|
||||
scope: 'read',
|
||||
}
|
||||
|
||||
const result = findMissingRequiredField(formData, requiredFields)
|
||||
expect(result).toEqual({ name: 'client_id', label: 'Client ID' })
|
||||
})
|
||||
|
||||
it('should treat null values as missing fields', () => {
|
||||
const formData = {
|
||||
client_id: 'test_id',
|
||||
client_secret: null,
|
||||
scope: 'read',
|
||||
}
|
||||
|
||||
const result = findMissingRequiredField(formData, requiredFields)
|
||||
expect(result).toEqual({ name: 'client_secret', label: 'Client Secret' })
|
||||
})
|
||||
|
||||
it('should treat undefined values as missing fields', () => {
|
||||
const formData = {
|
||||
client_id: 'test_id',
|
||||
client_secret: 'test_secret',
|
||||
scope: undefined,
|
||||
}
|
||||
|
||||
const result = findMissingRequiredField(formData, requiredFields)
|
||||
expect(result).toEqual({ name: 'scope', label: 'Scope' })
|
||||
})
|
||||
|
||||
it('should handle empty required fields array', () => {
|
||||
const formData = {
|
||||
client_id: 'test_id',
|
||||
}
|
||||
|
||||
const result = findMissingRequiredField(formData, [])
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('should handle empty form data', () => {
|
||||
const result = findMissingRequiredField({}, requiredFields)
|
||||
expect(result).toEqual({ name: 'client_id', label: 'Client ID' })
|
||||
})
|
||||
|
||||
it('should handle multilingual labels', () => {
|
||||
const multilingualFields = [
|
||||
{ name: 'field1', label: { en_US: 'Field 1 EN', zh_Hans: 'Field 1 CN' } },
|
||||
]
|
||||
const formData = {}
|
||||
|
||||
const result = findMissingRequiredField(formData, multilingualFields)
|
||||
expect(result).toEqual({
|
||||
name: 'field1',
|
||||
label: { en_US: 'Field 1 EN', zh_Hans: 'Field 1 CN' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should return null for form data with extra fields', () => {
|
||||
const formData = {
|
||||
client_id: 'test_id',
|
||||
client_secret: 'test_secret',
|
||||
scope: 'read',
|
||||
extra_field1: 'extra1',
|
||||
extra_field2: 'extra2',
|
||||
}
|
||||
|
||||
const result = findMissingRequiredField(formData, requiredFields)
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should handle objects with non-string keys', () => {
|
||||
const input = { [Symbol('test')]: 'value', regular: 'field' } as any
|
||||
const result = sanitizeFormValues(input)
|
||||
|
||||
expect(result.regular).toBe('field')
|
||||
})
|
||||
|
||||
it('should handle objects with getter properties', () => {
|
||||
const obj = {}
|
||||
Object.defineProperty(obj, 'getter', {
|
||||
get: () => 'computed_value',
|
||||
enumerable: true,
|
||||
})
|
||||
|
||||
const result = sanitizeFormValues(obj)
|
||||
expect(result.getter).toBe('computed_value')
|
||||
})
|
||||
|
||||
it('should handle circular references in deepSanitizeFormValues gracefully', () => {
|
||||
const obj: any = { field: 'value' }
|
||||
obj.circular = obj
|
||||
|
||||
expect(() => deepSanitizeFormValues(obj)).not.toThrow()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Utility functions for form data handling in trigger plugin components
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sanitizes form values by converting null/undefined to empty strings
|
||||
* This ensures React form inputs don't receive null values which can cause warnings
|
||||
*/
|
||||
export const sanitizeFormValues = (values: Record<string, any>): Record<string, string> => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(values).map(([key, value]) => [
|
||||
key,
|
||||
value === null || value === undefined ? '' : String(value),
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep sanitizes form values while preserving nested objects structure
|
||||
* Useful for complex form schemas with nested properties
|
||||
*/
|
||||
export const deepSanitizeFormValues = (values: Record<string, any>, visited = new WeakSet()): Record<string, any> => {
|
||||
if (visited.has(values))
|
||||
return {}
|
||||
|
||||
visited.add(values)
|
||||
|
||||
const result: Record<string, any> = {}
|
||||
|
||||
for (const [key, value] of Object.entries(values)) {
|
||||
if (value === null || value === undefined)
|
||||
result[key] = ''
|
||||
else if (typeof value === 'object' && !Array.isArray(value))
|
||||
result[key] = deepSanitizeFormValues(value, visited)
|
||||
else
|
||||
result[key] = value
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates required fields in form data
|
||||
* Returns the first missing required field or null if all are present
|
||||
*/
|
||||
export const findMissingRequiredField = (
|
||||
formData: Record<string, any>,
|
||||
requiredFields: Array<{ name: string; label: any }>,
|
||||
): { name: string; label: any } | null => {
|
||||
for (const field of requiredFields) {
|
||||
if (!formData[field.name] || formData[field.name] === '')
|
||||
return field
|
||||
}
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user