1. 排行榜悬浮窗关闭后小按钮消失问题 - 修复面板居中模式关闭时按钮位置错误 - 保存并恢复按钮的居中状态 2. 首页tag按钮按素材数量排序 - 添加按素材数量降序排序逻辑 3. 上传弹窗输入框浅色模式文字不可见 - 添加 text-slate-900 确保浅色模式下文字可见 - 添加 placeholder 样式确保占位符正确显示
333 lines
11 KiB
TypeScript
333 lines
11 KiB
TypeScript
/**
|
|
* 字段验证规则测试
|
|
*/
|
|
|
|
import {
|
|
parseValidationRules,
|
|
serializeValidationRules,
|
|
validateFieldValue,
|
|
validateValidationRules,
|
|
getSupportedValidations,
|
|
ValidationRule,
|
|
} from '../field-validation'
|
|
|
|
describe('field-validation', () => {
|
|
describe('parseValidationRules', () => {
|
|
it('should return null for empty string', () => {
|
|
expect(parseValidationRules('')).toBeNull()
|
|
expect(parseValidationRules(' ')).toBeNull()
|
|
})
|
|
|
|
it('should return null for null/undefined', () => {
|
|
expect(parseValidationRules(null)).toBeNull()
|
|
expect(parseValidationRules(undefined)).toBeNull()
|
|
})
|
|
|
|
it('should parse valid JSON', () => {
|
|
const rules = parseValidationRules('{"required":true,"maxLength":100}')
|
|
expect(rules).toEqual({ required: true, maxLength: 100 })
|
|
})
|
|
|
|
it('should return null for invalid JSON', () => {
|
|
expect(parseValidationRules('invalid json')).toBeNull()
|
|
expect(parseValidationRules('{broken')).toBeNull()
|
|
})
|
|
|
|
it('should return null for non-object JSON', () => {
|
|
expect(parseValidationRules('"string"')).toBeNull()
|
|
expect(parseValidationRules('123')).toBeNull()
|
|
expect(parseValidationRules('null')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('serializeValidationRules', () => {
|
|
it('should return empty string for null', () => {
|
|
expect(serializeValidationRules(null)).toBe('')
|
|
})
|
|
|
|
it('should return empty string for empty rules', () => {
|
|
expect(serializeValidationRules({})).toBe('')
|
|
expect(serializeValidationRules({ required: false })).toBe('')
|
|
})
|
|
|
|
it('should serialize required rule', () => {
|
|
const result = serializeValidationRules({ required: true })
|
|
expect(JSON.parse(result)).toEqual({ required: true })
|
|
})
|
|
|
|
it('should serialize length rules', () => {
|
|
const result = serializeValidationRules({ minLength: 5, maxLength: 100 })
|
|
expect(JSON.parse(result)).toEqual({ minLength: 5, maxLength: 100 })
|
|
})
|
|
|
|
it('should serialize number range rules', () => {
|
|
const result = serializeValidationRules({ min: 0, max: 100 })
|
|
expect(JSON.parse(result)).toEqual({ min: 0, max: 100 })
|
|
})
|
|
|
|
it('should serialize pattern rule', () => {
|
|
const result = serializeValidationRules({ pattern: '^[A-Z]+$' })
|
|
expect(JSON.parse(result)).toEqual({ pattern: '^[A-Z]+$' })
|
|
})
|
|
|
|
it('should include custom messages', () => {
|
|
const result = serializeValidationRules({
|
|
required: true,
|
|
requiredMessage: '请填写此字段',
|
|
})
|
|
expect(JSON.parse(result)).toEqual({
|
|
required: true,
|
|
requiredMessage: '请填写此字段',
|
|
})
|
|
})
|
|
|
|
it('should filter out zero/undefined values for length', () => {
|
|
const result = serializeValidationRules({ minLength: 0, maxLength: undefined })
|
|
expect(result).toBe('')
|
|
})
|
|
})
|
|
|
|
describe('validateFieldValue', () => {
|
|
describe('required validation', () => {
|
|
const rules: ValidationRule = { required: true }
|
|
|
|
it('should fail for empty value', () => {
|
|
const result = validateFieldValue('', 'text', rules, '字段')
|
|
expect(result.valid).toBe(false)
|
|
expect(result.errors).toContain('字段不能为空')
|
|
})
|
|
|
|
it('should fail for whitespace only', () => {
|
|
const result = validateFieldValue(' ', 'text', rules, '字段')
|
|
expect(result.valid).toBe(false)
|
|
})
|
|
|
|
it('should pass for non-empty value', () => {
|
|
const result = validateFieldValue('hello', 'text', rules, '字段')
|
|
expect(result.valid).toBe(true)
|
|
expect(result.errors).toHaveLength(0)
|
|
})
|
|
|
|
it('should use custom message', () => {
|
|
const rulesWithMessage: ValidationRule = {
|
|
required: true,
|
|
requiredMessage: '请输入商品型号',
|
|
}
|
|
const result = validateFieldValue('', 'text', rulesWithMessage)
|
|
expect(result.errors).toContain('请输入商品型号')
|
|
})
|
|
})
|
|
|
|
describe('length validation', () => {
|
|
it('should fail for value shorter than minLength', () => {
|
|
const rules: ValidationRule = { minLength: 5 }
|
|
const result = validateFieldValue('abc', 'text', rules, '字段')
|
|
expect(result.valid).toBe(false)
|
|
expect(result.errors[0]).toContain('不能少于5个字符')
|
|
})
|
|
|
|
it('should fail for value longer than maxLength', () => {
|
|
const rules: ValidationRule = { maxLength: 5 }
|
|
const result = validateFieldValue('abcdefgh', 'text', rules, '字段')
|
|
expect(result.valid).toBe(false)
|
|
expect(result.errors[0]).toContain('不能超过5个字符')
|
|
})
|
|
|
|
it('should pass for value within range', () => {
|
|
const rules: ValidationRule = { minLength: 2, maxLength: 10 }
|
|
const result = validateFieldValue('hello', 'text', rules)
|
|
expect(result.valid).toBe(true)
|
|
})
|
|
|
|
it('should skip validation for empty non-required field', () => {
|
|
const rules: ValidationRule = { minLength: 5 }
|
|
const result = validateFieldValue('', 'text', rules)
|
|
expect(result.valid).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('number range validation', () => {
|
|
it('should fail for value below min', () => {
|
|
const rules: ValidationRule = { min: 10 }
|
|
const result = validateFieldValue('5', 'number', rules, '数量')
|
|
expect(result.valid).toBe(false)
|
|
expect(result.errors[0]).toContain('不能小于10')
|
|
})
|
|
|
|
it('should fail for value above max', () => {
|
|
const rules: ValidationRule = { max: 100 }
|
|
const result = validateFieldValue('150', 'number', rules, '数量')
|
|
expect(result.valid).toBe(false)
|
|
expect(result.errors[0]).toContain('不能大于100')
|
|
})
|
|
|
|
it('should pass for value within range', () => {
|
|
const rules: ValidationRule = { min: 0, max: 100 }
|
|
const result = validateFieldValue('50', 'number', rules)
|
|
expect(result.valid).toBe(true)
|
|
})
|
|
|
|
it('should fail for non-numeric value', () => {
|
|
const rules: ValidationRule = { min: 0 }
|
|
const result = validateFieldValue('abc', 'number', rules, '数量')
|
|
expect(result.valid).toBe(false)
|
|
expect(result.errors[0]).toContain('必须是有效的数字')
|
|
})
|
|
|
|
it('should handle zero as valid min', () => {
|
|
const rules: ValidationRule = { min: 0 }
|
|
const result = validateFieldValue('0', 'number', rules)
|
|
expect(result.valid).toBe(true)
|
|
})
|
|
|
|
it('should handle negative numbers', () => {
|
|
const rules: ValidationRule = { min: -10, max: 10 }
|
|
const result = validateFieldValue('-5', 'number', rules)
|
|
expect(result.valid).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('pattern validation', () => {
|
|
it('should fail for value not matching pattern', () => {
|
|
const rules: ValidationRule = { pattern: '^[A-Z]{2}\\d{4}$' }
|
|
const result = validateFieldValue('abc123', 'text', rules, '编码')
|
|
expect(result.valid).toBe(false)
|
|
expect(result.errors[0]).toContain('格式不正确')
|
|
})
|
|
|
|
it('should pass for value matching pattern', () => {
|
|
const rules: ValidationRule = { pattern: '^[A-Z]{2}\\d{4}$' }
|
|
const result = validateFieldValue('AB1234', 'text', rules)
|
|
expect(result.valid).toBe(true)
|
|
})
|
|
|
|
it('should use custom pattern message', () => {
|
|
const rules: ValidationRule = {
|
|
pattern: '^[A-Z]+$',
|
|
patternMessage: '只能输入大写字母',
|
|
}
|
|
const result = validateFieldValue('abc', 'text', rules)
|
|
expect(result.errors).toContain('只能输入大写字母')
|
|
})
|
|
|
|
it('should handle invalid regex gracefully', () => {
|
|
const rules: ValidationRule = { pattern: '[invalid(' }
|
|
const result = validateFieldValue('test', 'text', rules)
|
|
// Should not throw, just skip validation
|
|
expect(result.valid).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('combined validations', () => {
|
|
it('should collect all errors', () => {
|
|
const rules: ValidationRule = {
|
|
required: true,
|
|
minLength: 10,
|
|
pattern: '^[A-Z]+$',
|
|
}
|
|
const result = validateFieldValue('abc', 'text', rules, '字段')
|
|
expect(result.valid).toBe(false)
|
|
expect(result.errors.length).toBeGreaterThanOrEqual(2)
|
|
})
|
|
|
|
it('should stop at required if empty', () => {
|
|
const rules: ValidationRule = {
|
|
required: true,
|
|
minLength: 10,
|
|
}
|
|
const result = validateFieldValue('', 'text', rules)
|
|
expect(result.errors).toHaveLength(1)
|
|
})
|
|
})
|
|
|
|
describe('null rules', () => {
|
|
it('should pass with null rules', () => {
|
|
const result = validateFieldValue('anything', 'text', null)
|
|
expect(result.valid).toBe(true)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('validateValidationRules', () => {
|
|
it('should pass for valid rules', () => {
|
|
const result = validateValidationRules({
|
|
required: true,
|
|
minLength: 5,
|
|
maxLength: 100,
|
|
})
|
|
expect(result.valid).toBe(true)
|
|
})
|
|
|
|
it('should fail when minLength > maxLength', () => {
|
|
const result = validateValidationRules({
|
|
minLength: 100,
|
|
maxLength: 5,
|
|
})
|
|
expect(result.valid).toBe(false)
|
|
expect(result.error).toContain('最小长度不能大于最大长度')
|
|
})
|
|
|
|
it('should fail when min > max', () => {
|
|
const result = validateValidationRules({
|
|
min: 100,
|
|
max: 5,
|
|
})
|
|
expect(result.valid).toBe(false)
|
|
expect(result.error).toContain('最小值不能大于最大值')
|
|
})
|
|
|
|
it('should fail for invalid regex', () => {
|
|
const result = validateValidationRules({
|
|
pattern: '[invalid(',
|
|
})
|
|
expect(result.valid).toBe(false)
|
|
expect(result.error).toContain('正则表达式格式无效')
|
|
})
|
|
|
|
it('should pass for valid regex', () => {
|
|
const result = validateValidationRules({
|
|
pattern: '^[A-Z]+$',
|
|
})
|
|
expect(result.valid).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('getSupportedValidations', () => {
|
|
it('should return correct validations for text', () => {
|
|
const supported = getSupportedValidations('text')
|
|
expect(supported).toContain('required')
|
|
expect(supported).toContain('minLength')
|
|
expect(supported).toContain('maxLength')
|
|
expect(supported).toContain('pattern')
|
|
expect(supported).not.toContain('min')
|
|
expect(supported).not.toContain('max')
|
|
})
|
|
|
|
it('should return correct validations for number', () => {
|
|
const supported = getSupportedValidations('number')
|
|
expect(supported).toContain('required')
|
|
expect(supported).toContain('min')
|
|
expect(supported).toContain('max')
|
|
expect(supported).not.toContain('minLength')
|
|
expect(supported).not.toContain('pattern')
|
|
})
|
|
|
|
it('should return only required for select', () => {
|
|
const supported = getSupportedValidations('select')
|
|
expect(supported).toContain('required')
|
|
expect(supported).toHaveLength(1)
|
|
})
|
|
|
|
it('should return only required for checkbox', () => {
|
|
const supported = getSupportedValidations('checkbox')
|
|
expect(supported).toContain('required')
|
|
expect(supported).toHaveLength(1)
|
|
})
|
|
|
|
it('should return common validations for unknown type', () => {
|
|
const supported = getSupportedValidations('unknown')
|
|
expect(supported).toContain('required')
|
|
})
|
|
})
|
|
})
|