mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-09-22 03:46:27 +00:00
fix(form): typeMatch 按内容比较对象选项值,避免 Proxy 误判
This commit is contained in:
parent
ecff8ceb76
commit
6b9791a309
@ -217,7 +217,7 @@ describe('editorTypeMatchRules', () => {
|
||||
expect(run('data-source-field-select', 'text-value', { name: 'f', fieldConfig: { type: 'text' } })).toBeUndefined();
|
||||
expect(run('data-source-field-select', 123, { name: 'f', fieldConfig: { type: 'text' } })).toBeUndefined();
|
||||
expect(firstLine(run('data-source-field-select', { a: 1 }, { name: 'f', fieldConfig: { type: 'text' } }))).toBe(
|
||||
'[object Object] 类型应为字符串',
|
||||
'{"a":1} 类型应为字符串',
|
||||
);
|
||||
expect(run('data-source-field-select', 123, { name: 'f', fieldConfig: { type: 'number' } })).toBeUndefined();
|
||||
// fieldConfig 未声明 type 时不做类型校验
|
||||
|
||||
@ -20,6 +20,7 @@ import { readonly } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
// dayjs 没有 exports 映射,原生 Node ESM 不会补扩展名,深路径必须写全 .js
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat.js';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import { appendValidateSuggestion } from '@tmagic/design/headless';
|
||||
import { getValueByKeyPath, toLine } from '@tmagic/utils';
|
||||
@ -167,6 +168,14 @@ export const stringifyExampleValue = (value: any): string => {
|
||||
return String(value);
|
||||
};
|
||||
|
||||
/** 错误主文案里的非法取值:对象/数组用 JSON,避免变成 `[object Object]`。 */
|
||||
const formatErrorValue = (value: any): string => {
|
||||
if (value !== null && typeof value === 'object') {
|
||||
return stringifyExampleValue(value);
|
||||
}
|
||||
return String(value);
|
||||
};
|
||||
|
||||
/** 参考建议中可选值全量展示上限;超出时仅举例 EXAMPLE_SUGGESTION_COUNT 个并标明总数。 */
|
||||
export const MAX_SUGGESTION_OPTIONS = 20;
|
||||
const EXAMPLE_SUGGESTION_COUNT = 5;
|
||||
@ -363,7 +372,20 @@ const resolveOptions = (mForm: FormState | undefined, props: any): any[] => {
|
||||
return resolved;
|
||||
};
|
||||
|
||||
const includesOptionValue = (optionValues: any[], value: any) => optionValues.some((item) => Object.is(item, value));
|
||||
/**
|
||||
* option.value 可能是对象,表单取值又常被 Vue reactive 包成 Proxy。
|
||||
* 引用比较会把内容相同的选项判成不在可选项中,因此按内容比较。
|
||||
* 配置了 `valueKey` 时只比该字段(与 Select 的对象值比对一致)。
|
||||
*/
|
||||
const isSameOptionValue = (left: any, right: any, valueKey?: string) => {
|
||||
if (valueKey && left !== null && right !== null && typeof left === 'object' && typeof right === 'object') {
|
||||
return isEqual(left[valueKey], right[valueKey]);
|
||||
}
|
||||
return isEqual(left, right);
|
||||
};
|
||||
|
||||
const includesOptionValue = (optionValues: any[], value: any, valueKey?: string) =>
|
||||
optionValues.some((item) => isSameOptionValue(item, value, valueKey));
|
||||
|
||||
const collectCascaderLeafValues = (options: CascaderOption[], result: any[] = []) => {
|
||||
options.forEach((option) => {
|
||||
@ -381,10 +403,11 @@ const isValidCascaderPath = (options: CascaderOption[], path: any[]): boolean =>
|
||||
|
||||
let currentOptions = options;
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const node = currentOptions.find((option) => Object.is(option.value, path[i]));
|
||||
if (!node) return false;
|
||||
// 同层可能有多份内容相同的 value,不能只走 find 到的第一项
|
||||
const matches = currentOptions.filter((option) => isSameOptionValue(option.value, path[i]));
|
||||
if (!matches.length) return false;
|
||||
if (i === path.length - 1) return true;
|
||||
currentOptions = node.children || [];
|
||||
currentOptions = matches.flatMap((option) => option.children || []);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -450,7 +473,7 @@ const validateSelectValue = (
|
||||
if (!Array.isArray(value)) {
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${value} 类型应为数组`,
|
||||
`${formatErrorValue(value)} 类型应为数组`,
|
||||
optionExampleSuggestion(optionValues, '["选项1", "选项2"]', true),
|
||||
);
|
||||
}
|
||||
@ -460,7 +483,7 @@ const validateSelectValue = (
|
||||
if (typeof value === 'object') {
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${value} 类型不合法`,
|
||||
`${formatErrorValue(value)} 类型不合法`,
|
||||
optionExampleSuggestion(optionValues, '"文本内容" 或 123', false),
|
||||
);
|
||||
}
|
||||
@ -471,18 +494,18 @@ const validateSelectValue = (
|
||||
if (!Array.isArray(value)) {
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${value} 类型应为数组`,
|
||||
`${formatErrorValue(value)} 类型应为数组`,
|
||||
optionExampleSuggestion(optionValues, '["选项1", "选项2"]', true),
|
||||
);
|
||||
}
|
||||
if (value.some((item) => !includesOptionValue(optionValues, item))) {
|
||||
return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
|
||||
if (value.some((item) => !includesOptionValue(optionValues, item, config.valueKey))) {
|
||||
return defaultMessage(message, `${formatErrorValue(value)} 不在可选项中`, optionSuggestion(optionValues));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!includesOptionValue(optionValues, value)) {
|
||||
return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
|
||||
if (!includesOptionValue(optionValues, value, config.valueKey)) {
|
||||
return defaultMessage(message, `${formatErrorValue(value)} 不在可选项中`, optionSuggestion(optionValues));
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@ -514,16 +537,16 @@ const validateCascaderValue = (
|
||||
if (typeof value !== 'string' && !Array.isArray(value)) {
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${value} 类型应为字符串或数组`,
|
||||
`${formatErrorValue(value)} 类型应为字符串或数组`,
|
||||
cascaderExample('"选项1,选项2" 或 ["选项1", "选项2"]'),
|
||||
);
|
||||
}
|
||||
} else if (multiple) {
|
||||
if (!Array.isArray(value)) {
|
||||
return defaultMessage(message, `${value} 类型应为数组`, cascaderExample('["选项1", "选项2"]'));
|
||||
return defaultMessage(message, `${formatErrorValue(value)} 类型应为数组`, cascaderExample('["选项1", "选项2"]'));
|
||||
}
|
||||
} else if (emitPath && !Array.isArray(value)) {
|
||||
return defaultMessage(message, `${value} 类型应为数组`, cascaderExample('["选项1", "选项2"]'));
|
||||
return defaultMessage(message, `${formatErrorValue(value)} 类型应为数组`, cascaderExample('["选项1", "选项2"]'));
|
||||
}
|
||||
|
||||
if (config.remote) {
|
||||
@ -538,7 +561,7 @@ const validateCascaderValue = (
|
||||
|
||||
if (multiple) {
|
||||
if (!Array.isArray(normalizedValue)) {
|
||||
return defaultMessage(message, `${value} 类型应为数组`, cascaderExample('["选项1", "选项2"]'));
|
||||
return defaultMessage(message, `${formatErrorValue(value)} 类型应为数组`, cascaderExample('["选项1", "选项2"]'));
|
||||
}
|
||||
|
||||
const invalid = normalizedValue.some((item) => {
|
||||
@ -549,20 +572,32 @@ const validateCascaderValue = (
|
||||
});
|
||||
|
||||
if (invalid) {
|
||||
return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(collectCascaderLeafValues(options)));
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${formatErrorValue(value)} 不在可选项中`,
|
||||
optionSuggestion(collectCascaderLeafValues(options)),
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (emitPath) {
|
||||
if (!Array.isArray(normalizedValue) || !isValidCascaderPath(options, normalizedValue)) {
|
||||
return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(collectCascaderLeafValues(options)));
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${formatErrorValue(value)} 不在可选项中`,
|
||||
optionSuggestion(collectCascaderLeafValues(options)),
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!includesOptionValue(collectCascaderLeafValues(options), normalizedValue)) {
|
||||
return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(collectCascaderLeafValues(options)));
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${formatErrorValue(value)} 不在可选项中`,
|
||||
optionSuggestion(collectCascaderLeafValues(options)),
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@ -585,7 +620,7 @@ const validateBuiltinTypeMatch = (
|
||||
if (typeof value !== 'number' || Number.isNaN(value)) {
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${value} 类型应为数字`,
|
||||
`${formatErrorValue(value)} 类型应为数字`,
|
||||
typeExampleSuggestion(mForm, props, '123', isNumberValue),
|
||||
);
|
||||
}
|
||||
@ -605,7 +640,7 @@ const validateBuiltinTypeMatch = (
|
||||
if (typeof value !== 'string') {
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${value} 类型应为字符串`,
|
||||
`${formatErrorValue(value)} 类型应为字符串`,
|
||||
typeExampleSuggestion(mForm, props, '"文本内容"', isStringValue),
|
||||
);
|
||||
}
|
||||
@ -624,7 +659,7 @@ const validateBuiltinTypeMatch = (
|
||||
if (typeof value !== 'number' || Number.isNaN(value)) {
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${value} 类型应为数字`,
|
||||
`${formatErrorValue(value)} 类型应为数字`,
|
||||
typeExampleSuggestion(mForm, props, '123', isNumberValue),
|
||||
);
|
||||
}
|
||||
@ -639,7 +674,7 @@ const validateBuiltinTypeMatch = (
|
||||
) {
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${value} 类型应为长度为 2 的数字数组`,
|
||||
`${formatErrorValue(value)} 类型应为长度为 2 的数字数组`,
|
||||
typeExampleSuggestion(mForm, props, '[0, 100]', isNumberRangeValue),
|
||||
);
|
||||
}
|
||||
@ -649,7 +684,11 @@ const validateBuiltinTypeMatch = (
|
||||
if (fieldType === 'switch' || fieldType === 'checkbox') {
|
||||
const { activeValue, inactiveValue } = resolveToggleValues(config);
|
||||
if (!Object.is(value, activeValue) && !Object.is(value, inactiveValue)) {
|
||||
return defaultMessage(message, `${value} 不在合法开关值中`, toggleSuggestion(activeValue, inactiveValue));
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${formatErrorValue(value)} 不在合法开关值中`,
|
||||
toggleSuggestion(activeValue, inactiveValue),
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@ -667,7 +706,7 @@ const validateBuiltinTypeMatch = (
|
||||
}
|
||||
|
||||
if (!includesOptionValue(optionValues, value)) {
|
||||
return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
|
||||
return defaultMessage(message, `${formatErrorValue(value)} 不在可选项中`, optionSuggestion(optionValues));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@ -682,12 +721,12 @@ const validateBuiltinTypeMatch = (
|
||||
if (!Array.isArray(value)) {
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${value} 类型应为数组`,
|
||||
`${formatErrorValue(value)} 类型应为数组`,
|
||||
optionExampleSuggestion(optionValues, '["选项1", "选项2"]', true),
|
||||
);
|
||||
}
|
||||
if (value.some((item) => !includesOptionValue(optionValues, item))) {
|
||||
return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
|
||||
return defaultMessage(message, `${formatErrorValue(value)} 不在可选项中`, optionSuggestion(optionValues));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@ -709,8 +748,8 @@ const validateBuiltinTypeMatch = (
|
||||
return defaultMessage(
|
||||
message,
|
||||
isTimestampValueFormat(valueFormat)
|
||||
? `${value} 类型应为长度为 2 的时间戳数字数组`
|
||||
: `${value} 格式应为长度为 2 的 ${valueFormat} 数组`,
|
||||
? `${formatErrorValue(value)} 类型应为长度为 2 的时间戳数字数组`
|
||||
: `${formatErrorValue(value)} 格式应为长度为 2 的 ${valueFormat} 数组`,
|
||||
dateRangeSuggestion(valueFormat),
|
||||
);
|
||||
}
|
||||
@ -721,7 +760,7 @@ const validateBuiltinTypeMatch = (
|
||||
if (!Array.isArray(value) || value.some((item) => !isObjectValue(item))) {
|
||||
return defaultMessage(
|
||||
message,
|
||||
`${value} 类型应为对象数组`,
|
||||
`${formatErrorValue(value)} 类型应为对象数组`,
|
||||
typeExampleSuggestion(mForm, props, '[{}]', isObjectArrayValue),
|
||||
);
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { reactive } from 'vue';
|
||||
|
||||
import { setDesignConfig } from '@tmagic/design';
|
||||
import { getDesignConfig } from '@tmagic/design/headless';
|
||||
@ -102,7 +103,7 @@ describe('validateTypeMatch', () => {
|
||||
'NaN 类型应为字符串\n\n请参考以下示例值:"文本内容"',
|
||||
);
|
||||
expect(validateTypeMatch({ a: 1 }, mForm, propsOf({ type: 'text' }))).toBe(
|
||||
'[object Object] 类型应为字符串\n\n请参考以下示例值:"文本内容"',
|
||||
'{"a":1} 类型应为字符串\n\n请参考以下示例值:"文本内容"',
|
||||
);
|
||||
expect(validateTypeMatch(true, mForm, propsOf({ type: 'text' }), '自定义错误')).toBe('自定义错误');
|
||||
});
|
||||
@ -199,6 +200,112 @@ describe('validateTypeMatch', () => {
|
||||
expect(validateTypeMatch(3, mForm, propsOf(config))).toBe('3 不在可选项中\n\n请使用以下某一个值:1;2');
|
||||
});
|
||||
|
||||
test('select 对象 option.value 按内容比较,兼容 Vue Proxy', () => {
|
||||
const share = { componentType: 'share', modType: 42, name: '分享' };
|
||||
const pop = { componentType: 'pop', name: '弹窗' };
|
||||
const config = {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ text: '弹窗', value: pop },
|
||||
{ text: '分享', value: share },
|
||||
],
|
||||
};
|
||||
|
||||
expect(validateTypeMatch({ ...share }, mForm, propsOf(config))).toBeUndefined();
|
||||
expect(validateTypeMatch(reactive({ ...share }), mForm, propsOf(config))).toBeUndefined();
|
||||
expect(
|
||||
validateTypeMatch(
|
||||
{ ...share },
|
||||
mForm,
|
||||
propsOf({ ...config, options: [{ text: '分享', value: reactive(share) }] }),
|
||||
),
|
||||
).toBeUndefined();
|
||||
expect(validateTypeMatch({ ...share, modType: 99 }, mForm, propsOf(config))).toContain(
|
||||
`${JSON.stringify({ ...share, modType: 99 })} 不在可选项中`,
|
||||
);
|
||||
expect(validateTypeMatch({ ...share, name: 'Share' }, mForm, propsOf(config))).toContain(
|
||||
`${JSON.stringify({ ...share, name: 'Share' })} 不在可选项中`,
|
||||
);
|
||||
|
||||
const byValueKey = { ...config, valueKey: 'modType' };
|
||||
expect(validateTypeMatch({ ...share, name: 'Share' }, mForm, propsOf(byValueKey))).toBeUndefined();
|
||||
expect(validateTypeMatch(reactive({ modType: 42 }), mForm, propsOf(byValueKey))).toBeUndefined();
|
||||
expect(validateTypeMatch({ ...share, modType: 99 }, mForm, propsOf(byValueKey))).toContain(
|
||||
`${JSON.stringify({ ...share, modType: 99 })} 不在可选项中`,
|
||||
);
|
||||
|
||||
const multipleConfig = { ...config, multiple: true };
|
||||
expect(validateTypeMatch([reactive({ ...share }), { ...pop }], mForm, propsOf(multipleConfig))).toBeUndefined();
|
||||
expect(validateTypeMatch([reactive({ ...share, modType: 99 })], mForm, propsOf(multipleConfig))).toMatch(
|
||||
/不在可选项中/,
|
||||
);
|
||||
expect(
|
||||
validateTypeMatch(
|
||||
[reactive({ componentType: 'share' }), { componentType: 'pop' }],
|
||||
mForm,
|
||||
propsOf({ ...multipleConfig, valueKey: 'componentType' }),
|
||||
),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
validateTypeMatch([{ componentType: 'copy' }], mForm, propsOf({ ...multipleConfig, valueKey: 'componentType' })),
|
||||
).toMatch(/不在可选项中/);
|
||||
|
||||
const checkboxGroup = {
|
||||
type: 'checkbox-group',
|
||||
options: [{ text: '分享', value: share }],
|
||||
};
|
||||
expect(validateTypeMatch([reactive({ ...share })], mForm, propsOf(checkboxGroup))).toBeUndefined();
|
||||
});
|
||||
|
||||
test('cascader 对象 value 按内容比较,兼容 Vue Proxy', () => {
|
||||
const options = [
|
||||
{
|
||||
value: { id: 'zhejiang' },
|
||||
label: 'Zhejiang',
|
||||
children: [{ value: { id: 'hangzhou' }, label: 'Hangzhou' }],
|
||||
},
|
||||
];
|
||||
expect(
|
||||
validateTypeMatch(
|
||||
[reactive({ id: 'zhejiang' }), { id: 'hangzhou' }],
|
||||
mForm,
|
||||
propsOf({ type: 'cascader', options }),
|
||||
),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
validateTypeMatch(
|
||||
[{ id: 'zhejiang' }, { id: 'hangzhou' }],
|
||||
mForm,
|
||||
propsOf({
|
||||
type: 'cascader',
|
||||
options: [
|
||||
{
|
||||
value: reactive({ id: 'zhejiang' }),
|
||||
label: 'Zhejiang',
|
||||
children: [{ value: reactive({ id: 'hangzhou' }), label: 'Hangzhou' }],
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
validateTypeMatch([{ id: 'zhejiang' }, { id: 'ningbo' }], mForm, propsOf({ type: 'cascader', options })),
|
||||
).toMatch(/不在可选项中/);
|
||||
expect(
|
||||
validateTypeMatch(reactive({ id: 'hangzhou' }), mForm, propsOf({ type: 'cascader', options, emitPath: false })),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
test('cascader 同层内容相同的 value 会尝试所有匹配节点', () => {
|
||||
const options = [
|
||||
{ value: { id: 1 }, children: [{ value: 'a' }] },
|
||||
{ value: { id: 1 }, children: [{ value: 'b' }] },
|
||||
];
|
||||
expect(validateTypeMatch([{ id: 1 }, 'a'], mForm, propsOf({ type: 'cascader', options }))).toBeUndefined();
|
||||
expect(validateTypeMatch([{ id: 1 }, 'b'], mForm, propsOf({ type: 'cascader', options }))).toBeUndefined();
|
||||
expect(validateTypeMatch([{ id: 1 }, 'c'], mForm, propsOf({ type: 'cascader', options }))).toMatch(/不在可选项中/);
|
||||
});
|
||||
|
||||
test('可选项超过 20 个时建议仅举例前 5 个并标明总数', () => {
|
||||
const values = Array.from({ length: 21 }, (_, i) => i + 1);
|
||||
const config = {
|
||||
@ -232,7 +339,7 @@ describe('validateTypeMatch', () => {
|
||||
};
|
||||
expect(validateTypeMatch(['a'], mForm, propsOf(config))).toBeUndefined();
|
||||
expect(validateTypeMatch(['a', 'c'], mForm, propsOf(config))).toBe(
|
||||
'a,c 不在可选项中\n\n请使用以下某一个值:"a";"b"',
|
||||
'["a","c"] 不在可选项中\n\n请使用以下某一个值:"a";"b"',
|
||||
);
|
||||
// multiple 类型不匹配时,示例值基于真实 options(前 2 个值组成的数组)
|
||||
expect(validateTypeMatch('a', mForm, propsOf(config))).toBe('a 类型应为数组\n\n请参考以下示例值:["a","b"]');
|
||||
@ -323,7 +430,7 @@ describe('validateTypeMatch', () => {
|
||||
validateTypeMatch(['a'], mForm, propsOf({ type: 'checkbox-group', options: checkboxOptions })),
|
||||
).toBeUndefined();
|
||||
expect(validateTypeMatch(['b'], mForm, propsOf({ type: 'checkbox-group', options: checkboxOptions }))).toBe(
|
||||
'b 不在可选项中\n\n请使用以下某一个值:"a"',
|
||||
'["b"] 不在可选项中\n\n请使用以下某一个值:"a"',
|
||||
);
|
||||
|
||||
const cascaderOptions = () => [
|
||||
@ -338,7 +445,7 @@ describe('validateTypeMatch', () => {
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
validateTypeMatch(['zhejiang', 'ningbo'], mForm, propsOf({ type: 'cascader', options: cascaderOptions })),
|
||||
).toBe('zhejiang,ningbo 不在可选项中\n\n请使用以下某一个值:"hangzhou"');
|
||||
).toBe('["zhejiang","ningbo"] 不在可选项中\n\n请使用以下某一个值:"hangzhou"');
|
||||
});
|
||||
|
||||
test('select allowCreate / remote 不做枚举', () => {
|
||||
@ -360,9 +467,7 @@ describe('validateTypeMatch', () => {
|
||||
{ text: 'B', value: 'b' },
|
||||
],
|
||||
};
|
||||
expect(validateTypeMatch({ a: 1 }, mForm, propsOf(config))).toBe(
|
||||
'[object Object] 类型不合法\n\n请参考以下示例值:"a"',
|
||||
);
|
||||
expect(validateTypeMatch({ a: 1 }, mForm, propsOf(config))).toBe('{"a":1} 类型不合法\n\n请参考以下示例值:"a"');
|
||||
// allowCreate + multiple + options:传非数组,示例取前 2 个真实 option 值组成数组
|
||||
expect(validateTypeMatch('a', mForm, propsOf({ ...config, multiple: true }))).toBe(
|
||||
'a 类型应为数组\n\n请参考以下示例值:["a","b"]',
|
||||
@ -389,7 +494,7 @@ describe('validateTypeMatch', () => {
|
||||
};
|
||||
expect(validateTypeMatch(['a', 'b'], mForm, propsOf(checkboxGroup))).toBeUndefined();
|
||||
expect(validateTypeMatch(['c'], mForm, propsOf(checkboxGroup))).toBe(
|
||||
'c 不在可选项中\n\n请使用以下某一个值:"a";"b"',
|
||||
'["c"] 不在可选项中\n\n请使用以下某一个值:"a";"b"',
|
||||
);
|
||||
|
||||
// type 为驼峰形式 radioGroup,应通过 toLine 归一化后按 radio-group 规则校验
|
||||
@ -422,7 +527,7 @@ describe('validateTypeMatch', () => {
|
||||
|
||||
expect(validateTypeMatch(['zhejiang', 'hangzhou'], mForm, propsOf({ type: 'cascader', options }))).toBeUndefined();
|
||||
expect(validateTypeMatch(['zhejiang', 'ningbo'], mForm, propsOf({ type: 'cascader', options }))).toBe(
|
||||
'zhejiang,ningbo 不在可选项中\n\n请使用以下某一个值:"hangzhou"',
|
||||
'["zhejiang","ningbo"] 不在可选项中\n\n请使用以下某一个值:"hangzhou"',
|
||||
);
|
||||
expect(
|
||||
validateTypeMatch('zhejiang/hangzhou', mForm, propsOf({ type: 'cascader', options, valueSeparator: '/' })),
|
||||
@ -435,17 +540,17 @@ describe('validateTypeMatch', () => {
|
||||
test('number-range / daterange / table', () => {
|
||||
expect(validateTypeMatch([1, 2], mForm, propsOf({ type: 'number-range' }))).toBeUndefined();
|
||||
expect(validateTypeMatch([1], mForm, propsOf({ type: 'number-range' }))).toBe(
|
||||
'1 类型应为长度为 2 的数字数组\n\n请参考以下示例值:[0, 100]',
|
||||
'[1] 类型应为长度为 2 的数字数组\n\n请参考以下示例值:[0, 100]',
|
||||
);
|
||||
|
||||
expect(
|
||||
validateTypeMatch(['2020/01/01 00:00:00', '2020/01/02 00:00:00'], mForm, propsOf({ type: 'daterange' })),
|
||||
).toBeUndefined();
|
||||
expect(validateTypeMatch(['2020-01-01', '2020-01-02'], mForm, propsOf({ type: 'daterange' }))).toMatch(
|
||||
/^2020-01-01,2020-01-02 格式应为长度为 2 的 YYYY\/MM\/DD HH:mm:ss 数组\n\n请参考以下示例值:\["\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}", "\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}"\]$/,
|
||||
/^\["2020-01-01","2020-01-02"\] 格式应为长度为 2 的 YYYY\/MM\/DD HH:mm:ss 数组\n\n请参考以下示例值:\["\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}", "\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}"\]$/,
|
||||
);
|
||||
expect(validateTypeMatch(['a'], mForm, propsOf({ type: 'daterange' }))).toMatch(
|
||||
/^a 格式应为长度为 2 的 YYYY\/MM\/DD HH:mm:ss 数组\n\n请参考以下示例值:\["\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}", "\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}"\]$/,
|
||||
/^\["a"\] 格式应为长度为 2 的 YYYY\/MM\/DD HH:mm:ss 数组\n\n请参考以下示例值:\["\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}", "\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}"\]$/,
|
||||
);
|
||||
expect(validateTypeMatch([1, 2], mForm, propsOf({ type: 'daterange', valueFormat: 'timestamp' }))).toBeUndefined();
|
||||
expect(validateTypeMatch('x', mForm, propsOf({ type: 'daterange', names: ['a', 'b'] }))).toBeUndefined();
|
||||
@ -453,18 +558,18 @@ describe('validateTypeMatch', () => {
|
||||
expect(validateTypeMatch([{ id: 1 }], mForm, propsOf({ type: 'table' }))).toBeUndefined();
|
||||
// table 无 options,类型不匹配示例回退到通用对象数组示例
|
||||
expect(validateTypeMatch({}, mForm, propsOf({ type: 'group-list' }))).toBe(
|
||||
'[object Object] 类型应为对象数组\n\n请参考以下示例值:[{}]',
|
||||
'{} 类型应为对象数组\n\n请参考以下示例值:[{}]',
|
||||
);
|
||||
// table / group-list 元素必须为对象,字符串数组不合法
|
||||
expect(validateTypeMatch(['a', 'b'], mForm, propsOf({ type: 'table' }))).toBe(
|
||||
'a,b 类型应为对象数组\n\n请参考以下示例值:[{}]',
|
||||
'["a","b"] 类型应为对象数组\n\n请参考以下示例值:[{}]',
|
||||
);
|
||||
expect(validateTypeMatch([1], mForm, propsOf({ type: 'group-list' }))).toBe(
|
||||
'1 类型应为对象数组\n\n请参考以下示例值:[{}]',
|
||||
'[1] 类型应为对象数组\n\n请参考以下示例值:[{}]',
|
||||
);
|
||||
// 数组中混入非对象元素也不合法
|
||||
expect(validateTypeMatch([{ id: 1 }, 'x'], mForm, propsOf({ type: 'grouplist' }))).toBe(
|
||||
'[object Object],x 类型应为对象数组\n\n请参考以下示例值:[{}]',
|
||||
'[{"id":1},"x"] 类型应为对象数组\n\n请参考以下示例值:[{}]',
|
||||
);
|
||||
});
|
||||
|
||||
@ -501,7 +606,7 @@ describe('validateTypeMatch', () => {
|
||||
test('timerange 按 valueFormat 校验', () => {
|
||||
expect(validateTypeMatch(['12:00:00', '13:00:00'], mForm, propsOf({ type: 'timerange' }))).toBeUndefined();
|
||||
expect(validateTypeMatch(['bad'], mForm, propsOf({ type: 'timerange' }))).toMatch(
|
||||
/^bad 格式应为长度为 2 的 HH:mm:ss 数组\n\n请参考以下示例值:\["\d{2}:\d{2}:\d{2}", "\d{2}:\d{2}:\d{2}"\]$/,
|
||||
/^\["bad"\] 格式应为长度为 2 的 HH:mm:ss 数组\n\n请参考以下示例值:\["\d{2}:\d{2}:\d{2}", "\d{2}:\d{2}:\d{2}"\]$/,
|
||||
);
|
||||
});
|
||||
|
||||
@ -534,7 +639,7 @@ describe('validateTypeMatch', () => {
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
validateTypeMatch(['ningbo'], mForm, propsOf({ type: 'cascader', options, multiple: true, emitPath: false })),
|
||||
).toBe('ningbo 不在可选项中\n\n请使用以下某一个值:"hangzhou"');
|
||||
).toBe('["ningbo"] 不在可选项中\n\n请使用以下某一个值:"hangzhou"');
|
||||
});
|
||||
|
||||
test('cascader valueSeparator 时数组值', () => {
|
||||
@ -686,7 +791,7 @@ describe('validateTypeMatch', () => {
|
||||
'1 类型应为数字\n\n请参考以下示例值:123',
|
||||
);
|
||||
expect(validateTypeMatch({ a: 1 }, mForm, propsOf({ type: 'text', defaultValue: async () => '示例' }))).toBe(
|
||||
'[object Object] 类型应为字符串\n\n请参考以下示例值:"文本内容"',
|
||||
'{"a":1} 类型应为字符串\n\n请参考以下示例值:"文本内容"',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user