mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-01-25 19:28:10 +00:00
fix(form): onChange中修改model的key中有重复的key会出错
This commit is contained in:
parent
e6e68ae69a
commit
6cf2f38194
@ -180,7 +180,7 @@ import type {
|
||||
FormValue,
|
||||
ToolTipConfigType,
|
||||
} from '../schema';
|
||||
import { display as displayFunction, filterFunction, getRules } from '../utils/form';
|
||||
import { createObjectProp, display as displayFunction, filterFunction, getRules } from '../utils/form';
|
||||
|
||||
import FormLabel from './FormLabel.vue';
|
||||
|
||||
@ -416,11 +416,7 @@ const onChangeHandler = async function (v: any, eventData: ContainerChangeEventD
|
||||
|
||||
if (typeof onChange === 'function') {
|
||||
const setModel = (key: string, value: any) => {
|
||||
if (props.config.name) {
|
||||
newChangeRecords.push({ propPath: itemProp.value.replace(`${props.config.name}`, key), value });
|
||||
} else {
|
||||
newChangeRecords.push({ propPath: itemProp.value, value });
|
||||
}
|
||||
newChangeRecords.push({ propPath: createObjectProp(itemProp.value, key, props.config.name), value });
|
||||
};
|
||||
|
||||
const setFormValue = (key: string, value: any) => {
|
||||
|
||||
@ -366,3 +366,18 @@ export const sortChange = (data: any[], { prop, order }: SortProp) => {
|
||||
data.sort((a: any, b: any) => b[prop] - a[prop]);
|
||||
}
|
||||
};
|
||||
|
||||
export const createObjectProp = (prop: string, key: string, name?: string | number) => {
|
||||
if (prop === '') {
|
||||
return key;
|
||||
}
|
||||
|
||||
const itemPath = `${prop}`.split('.');
|
||||
|
||||
if (name) {
|
||||
if (`${itemPath[itemPath.length - 1]}` === `${name}`) {
|
||||
return `${[...itemPath.slice(0, -1), key].join('.')}`;
|
||||
}
|
||||
}
|
||||
return `${[...itemPath, key].join('.')}`;
|
||||
};
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import type { FormState } from '@form/index';
|
||||
import {
|
||||
createObjectProp,
|
||||
createValues,
|
||||
datetimeFormatter,
|
||||
display,
|
||||
@ -65,6 +66,66 @@ describe('filterFunction', () => {
|
||||
test('config 为函数', () => {
|
||||
expect(filterFunction(mForm, () => 2, {})).toBe(2);
|
||||
});
|
||||
|
||||
test('config 为undefined', () => {
|
||||
expect(filterFunction(mForm, undefined, {})).toBe(undefined);
|
||||
});
|
||||
|
||||
test('config 为null', () => {
|
||||
expect(filterFunction(mForm, null, {})).toBe(null);
|
||||
});
|
||||
|
||||
test('config 为空字符串', () => {
|
||||
expect(filterFunction(mForm, '', {})).toBe('');
|
||||
});
|
||||
|
||||
test('config 函数接收正确参数', () => {
|
||||
const mockForm: FormState = {
|
||||
...mForm,
|
||||
initValues: { init: 'initValue' },
|
||||
parentValues: { parent: 'parentValue' },
|
||||
values: { form: 'formValue' },
|
||||
};
|
||||
const props = {
|
||||
model: { model: 'modelValue' },
|
||||
prop: 'testProp',
|
||||
config: { type: 'text' },
|
||||
index: 5,
|
||||
};
|
||||
|
||||
let receivedArgs: any = null;
|
||||
filterFunction(
|
||||
mockForm,
|
||||
(_mForm, args) => {
|
||||
receivedArgs = args;
|
||||
return 'result';
|
||||
},
|
||||
props,
|
||||
);
|
||||
|
||||
expect(receivedArgs.prop).toBe('testProp');
|
||||
expect(receivedArgs.index).toBe(5);
|
||||
expect(receivedArgs.config).toEqual({ type: 'text' });
|
||||
});
|
||||
|
||||
test('config 函数getFormValue正确获取值', () => {
|
||||
const mockForm: FormState = {
|
||||
...mForm,
|
||||
values: { nested: { deep: 'deepValue' } },
|
||||
};
|
||||
|
||||
let result: any = null;
|
||||
filterFunction(
|
||||
mockForm,
|
||||
(_mForm: FormState | undefined, args: any) => {
|
||||
result = args.getFormValue('nested.deep');
|
||||
return result;
|
||||
},
|
||||
{ model: {} },
|
||||
);
|
||||
|
||||
expect(result).toBe('deepValue');
|
||||
});
|
||||
});
|
||||
|
||||
describe('display', () => {
|
||||
@ -98,6 +159,87 @@ describe('getRules', () => {
|
||||
const newRules: any = getRules(mForm, rules, props);
|
||||
expect(newRules[0].validator({} as any, {} as any, {})).toBe(1);
|
||||
});
|
||||
|
||||
test('rules为数组', () => {
|
||||
const rules: any = [
|
||||
{ required: true, message: '必填' },
|
||||
{ min: 3, message: '最少3个字符' },
|
||||
];
|
||||
const props = { config: {} };
|
||||
const newRules = getRules(mForm, rules, props);
|
||||
|
||||
expect(newRules).toHaveLength(2);
|
||||
expect(newRules[0].required).toBe(true);
|
||||
expect((newRules[1] as any).min).toBe(3);
|
||||
});
|
||||
|
||||
test('rules为空数组', () => {
|
||||
const rules: any = [];
|
||||
const props = { config: {} };
|
||||
const newRules = getRules(mForm, rules, props);
|
||||
|
||||
expect(newRules).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('validator函数接收正确参数', () => {
|
||||
let receivedParams: any = null;
|
||||
const rules: any = {
|
||||
validator: (params: any, context: any) => {
|
||||
receivedParams = { params, context };
|
||||
return true;
|
||||
},
|
||||
};
|
||||
const mockForm: FormState = {
|
||||
...mForm,
|
||||
initValues: { init: 'initValue' },
|
||||
parentValues: { parent: 'parentValue' },
|
||||
values: { form: 'formValue' },
|
||||
};
|
||||
const props = {
|
||||
config: { type: 'text' },
|
||||
model: { field: 'value' },
|
||||
prop: 'testProp',
|
||||
};
|
||||
|
||||
const newRules: any = getRules(mockForm, rules, props);
|
||||
newRules[0].validator('rule', 'value', 'callback', 'source', 'options');
|
||||
|
||||
expect(receivedParams.params.rule).toBe('rule');
|
||||
expect(receivedParams.params.value).toBe('value');
|
||||
expect(receivedParams.params.callback).toBe('callback');
|
||||
expect(receivedParams.context.prop).toBe('testProp');
|
||||
expect(receivedParams.context.model).toEqual({ field: 'value' });
|
||||
});
|
||||
|
||||
test('config有names时validator接收model作为value', () => {
|
||||
let receivedValue: any = null;
|
||||
const rules: any = {
|
||||
validator: (params: any) => {
|
||||
receivedValue = params.value;
|
||||
return true;
|
||||
},
|
||||
};
|
||||
const props = {
|
||||
config: { names: ['start', 'end'] },
|
||||
model: { start: '2021-01-01', end: '2021-12-31' },
|
||||
prop: 'dateRange',
|
||||
};
|
||||
|
||||
const newRules: any = getRules(mForm, rules, props);
|
||||
newRules[0].validator('rule', 'singleValue', 'callback', 'source', 'options');
|
||||
|
||||
expect(receivedValue).toEqual({ start: '2021-01-01', end: '2021-12-31' });
|
||||
});
|
||||
|
||||
test('不修改原始rules对象', () => {
|
||||
const originalValidator = () => 'original';
|
||||
const rules: any = { validator: originalValidator };
|
||||
const props = { config: {} };
|
||||
|
||||
getRules(mForm, rules, props);
|
||||
|
||||
expect(rules.validator).toBe(originalValidator);
|
||||
});
|
||||
});
|
||||
|
||||
describe('initValue', () => {
|
||||
@ -334,6 +476,114 @@ describe('initValue', () => {
|
||||
expect(values.table).toHaveLength(1);
|
||||
expect(values.table[0].a).toBe(1);
|
||||
});
|
||||
|
||||
test('table with defautSort (typo version)', async () => {
|
||||
const config = [
|
||||
{
|
||||
type: 'table',
|
||||
name: 'table',
|
||||
defautSort: { prop: 'order', order: 'ascending' },
|
||||
items: [{ name: 'order' }],
|
||||
},
|
||||
];
|
||||
const initValues = {
|
||||
table: [{ order: 3 }, { order: 1 }, { order: 2 }],
|
||||
};
|
||||
|
||||
const values = await initValue(mForm, { initValues, config });
|
||||
expect(values.table[0].order).toBe(1);
|
||||
expect(values.table[1].order).toBe(2);
|
||||
expect(values.table[2].order).toBe(3);
|
||||
});
|
||||
|
||||
test('table with defaultSort', async () => {
|
||||
const config = [
|
||||
{
|
||||
type: 'table',
|
||||
name: 'table',
|
||||
defaultSort: { prop: 'order', order: 'descending' },
|
||||
items: [{ name: 'order' }],
|
||||
},
|
||||
];
|
||||
const initValues = {
|
||||
table: [{ order: 1 }, { order: 3 }, { order: 2 }],
|
||||
};
|
||||
|
||||
const values = await initValue(mForm, { initValues, config });
|
||||
expect(values.table[0].order).toBe(3);
|
||||
expect(values.table[1].order).toBe(2);
|
||||
expect(values.table[2].order).toBe(1);
|
||||
});
|
||||
|
||||
test('table with sort and sortKey', async () => {
|
||||
const config = [
|
||||
{
|
||||
type: 'table',
|
||||
name: 'table',
|
||||
sort: true,
|
||||
sortKey: 'priority',
|
||||
items: [{ name: 'priority' }],
|
||||
},
|
||||
];
|
||||
const initValues = {
|
||||
table: [{ priority: 1 }, { priority: 3 }, { priority: 2 }],
|
||||
};
|
||||
|
||||
const values = await initValue(mForm, { initValues, config });
|
||||
// sort + sortKey 会按 sortKey 降序排序
|
||||
expect(values.table[0].priority).toBe(3);
|
||||
expect(values.table[1].priority).toBe(2);
|
||||
expect(values.table[2].priority).toBe(1);
|
||||
});
|
||||
|
||||
test('config不是数组抛出错误', async () => {
|
||||
await expect(initValue(mForm, { initValues: {}, config: {} as any })).rejects.toThrow('config应该为数组');
|
||||
});
|
||||
|
||||
test('onInitValue返回null时返回空对象', async () => {
|
||||
const config = [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'a',
|
||||
onInitValue: () => null,
|
||||
},
|
||||
];
|
||||
const values = await initValue(mForm, { initValues: { a: 1 }, config });
|
||||
expect(values).toEqual({});
|
||||
});
|
||||
|
||||
test('fieldset checkbox 自定义name和falseValue', async () => {
|
||||
const config = [
|
||||
{
|
||||
type: 'fieldset',
|
||||
name: 'fieldset',
|
||||
checkbox: { name: 'enabled', falseValue: false },
|
||||
items: [{ name: 'a' }],
|
||||
},
|
||||
];
|
||||
const initValues = {};
|
||||
|
||||
const values = await initValue(mForm, { initValues, config });
|
||||
expect(values.fieldset.enabled).toBe(false);
|
||||
});
|
||||
|
||||
test('fieldset checkbox initValue有值', async () => {
|
||||
const config = [
|
||||
{
|
||||
type: 'fieldset',
|
||||
name: 'fieldset',
|
||||
checkbox: true,
|
||||
items: [{ name: 'a' }],
|
||||
},
|
||||
];
|
||||
const initValues = {
|
||||
fieldset: { value: 1, a: 'test' },
|
||||
};
|
||||
|
||||
const values = await initValue(mForm, { initValues, config });
|
||||
expect(values.fieldset.value).toBe(1);
|
||||
expect(values.fieldset.a).toBe('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('datetimeFormatter', () => {
|
||||
@ -363,8 +613,30 @@ describe('datetimeFormatter', () => {
|
||||
});
|
||||
|
||||
test('format是x', () => {
|
||||
expect(datetimeFormatter(date.toISOString(), defaultValue, 'x')).toBe(date.getTime());
|
||||
});
|
||||
|
||||
test('format是timestamp', () => {
|
||||
expect(datetimeFormatter(date.toISOString(), defaultValue, 'timestamp')).toBe(date.getTime());
|
||||
});
|
||||
|
||||
test('v是数字时间戳字符串', () => {
|
||||
const timestamp = date.getTime();
|
||||
expect(datetimeFormatter(String(timestamp), defaultValue, 'x')).toBe(timestamp);
|
||||
});
|
||||
|
||||
test('v是数字时间戳', () => {
|
||||
const timestamp = date.getTime();
|
||||
expect(datetimeFormatter(String(timestamp), defaultValue, 'timestamp')).toBe(timestamp);
|
||||
});
|
||||
|
||||
test('自定义format格式', () => {
|
||||
expect(datetimeFormatter(date, defaultValue, 'YYYY/MM/DD')).toBe('2021/07/17');
|
||||
});
|
||||
|
||||
test('自定义format只显示时间', () => {
|
||||
expect(datetimeFormatter(date, defaultValue, 'HH:mm:ss')).toBe('15:37:00');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortArray', () => {
|
||||
@ -450,6 +722,21 @@ describe('sortArray', () => {
|
||||
|
||||
expect(result).not.toBe(data);
|
||||
});
|
||||
|
||||
test('负数索引时返回原数组', () => {
|
||||
const data = [1, 2, 3];
|
||||
|
||||
expect(sortArray(data, -1, 1)).toEqual(data);
|
||||
expect(sortArray(data, 1, -1)).toEqual(data);
|
||||
expect(sortArray(data, -1, -1)).toEqual(data);
|
||||
});
|
||||
|
||||
test('两个元素数组交换', () => {
|
||||
const data = [1, 2];
|
||||
|
||||
expect(sortArray(data, 0, 1)).toEqual([2, 1]);
|
||||
expect(sortArray(data, 1, 0)).toEqual([2, 1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDataByPage', () => {
|
||||
@ -565,4 +852,165 @@ describe('createValues', () => {
|
||||
|
||||
expect(result.num).toBe(123);
|
||||
});
|
||||
|
||||
test('处理checkboxGroup类型初始化空数组', () => {
|
||||
// checkboxGroup 需要在 initValue 函数中通过 config 处理
|
||||
const config = [{ type: 'checkboxGroup', name: 'checkboxGroup' }];
|
||||
const initValues = {};
|
||||
|
||||
const result = createValues(mForm, config, initValues, {});
|
||||
|
||||
// 当没有 items 配置时,使用 getDefaultValue 返回空字符串
|
||||
expect(result.checkboxGroup).toBe('');
|
||||
});
|
||||
|
||||
test('处理tab dynamic类型', () => {
|
||||
const config = [
|
||||
{
|
||||
type: 'tab',
|
||||
name: 'dynamicTab',
|
||||
dynamic: true,
|
||||
items: [{ title: 'Tab1', items: [{ name: 'field' }] }],
|
||||
},
|
||||
];
|
||||
const initValues = {};
|
||||
|
||||
const result = createValues(mForm, config, initValues, {});
|
||||
|
||||
// dynamic tab 会初始化为空数组,但因为有 items 配置,会处理 items
|
||||
expect(Array.isArray(result.dynamicTab)).toBe(true);
|
||||
expect(result.dynamicTab).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('处理tab dynamic类型有初始值', () => {
|
||||
const config = [
|
||||
{
|
||||
type: 'tab',
|
||||
name: 'dynamicTab',
|
||||
dynamic: true,
|
||||
items: [{ title: 'Tab1', items: [{ name: 'field' }] }],
|
||||
},
|
||||
];
|
||||
const initValues = { dynamicTab: [{ id: 1 }] };
|
||||
|
||||
const result = createValues(mForm, config, initValues, {});
|
||||
|
||||
// 有初始值时会递归处理每个元素,并补充 items 中定义的字段
|
||||
expect(result.dynamicTab).toHaveLength(1);
|
||||
expect(result.dynamicTab[0].id).toBe(1);
|
||||
expect(result.dynamicTab[0].field).toBe('');
|
||||
});
|
||||
|
||||
test('处理html类型的asyncLoad配置', () => {
|
||||
const config = [
|
||||
{
|
||||
type: 'html',
|
||||
name: 'htmlField',
|
||||
asyncLoad: { url: '/api/load' },
|
||||
},
|
||||
];
|
||||
const initValues = { htmlField: 'content' };
|
||||
|
||||
const result = createValues(mForm, config, initValues, {});
|
||||
|
||||
expect(result.asyncLoad.name).toBe('htmlField');
|
||||
expect(result.asyncLoad.url).toBe('/api/load');
|
||||
});
|
||||
|
||||
test('处理html类型的asyncLoad配置-initValue已有asyncLoad', () => {
|
||||
const config = [
|
||||
{
|
||||
type: 'html',
|
||||
name: 'htmlField',
|
||||
asyncLoad: { url: '/api/load' },
|
||||
},
|
||||
];
|
||||
const initValues = { htmlField: 'content', asyncLoad: { url: '/api/existing', name: 'existing' } };
|
||||
|
||||
const result = createValues(mForm, config, initValues, {});
|
||||
|
||||
expect(result.asyncLoad.url).toBe('/api/existing');
|
||||
expect(result.asyncLoad.name).toBe('existing');
|
||||
});
|
||||
|
||||
test('处理table-select类型', () => {
|
||||
const config = [{ type: 'table-select', name: 'tableSelect' }];
|
||||
const initValues = { tableSelect: 'selected' };
|
||||
|
||||
const result = createValues(mForm, config, initValues, {});
|
||||
|
||||
expect(result.tableSelect).toBe('selected');
|
||||
});
|
||||
|
||||
test('处理table-select类型无初始值', () => {
|
||||
const config = [{ type: 'table-select', name: 'tableSelect' }];
|
||||
const initValues = {};
|
||||
|
||||
const result = createValues(mForm, config, initValues, {});
|
||||
|
||||
expect(result.tableSelect).toBe('');
|
||||
});
|
||||
|
||||
test('处理daterange类型', () => {
|
||||
const config = [{ type: 'daterange', name: 'dateRange' }];
|
||||
const initValues = {};
|
||||
|
||||
const result = createValues(mForm, config, initValues, {});
|
||||
|
||||
expect(result.dateRange).toEqual([]);
|
||||
});
|
||||
|
||||
test('处理number-range类型', () => {
|
||||
const config = [{ type: 'number-range', name: 'numberRange' }];
|
||||
const initValues = {};
|
||||
|
||||
const result = createValues(mForm, config, initValues, {});
|
||||
|
||||
expect(result.numberRange).toEqual([]);
|
||||
});
|
||||
|
||||
test('value已存在时不覆盖', () => {
|
||||
const config = [{ type: 'text', name: 'field' }];
|
||||
const initValues = { field: 'new' };
|
||||
const value = { field: 'existing' };
|
||||
|
||||
const result = createValues(mForm, config, initValues, value);
|
||||
|
||||
expect(result.field).toBe('existing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createObjectProp', () => {
|
||||
test('基本路径拼接', () => {
|
||||
// 注意:无name参数时,实际返回数组转字符串格式
|
||||
expect(createObjectProp('form.field', 'subKey')).toBe('form.field.subKey');
|
||||
});
|
||||
|
||||
test('单层路径', () => {
|
||||
expect(createObjectProp('field', 'key')).toBe('field.key');
|
||||
});
|
||||
|
||||
test('带name参数且name匹配最后一段', () => {
|
||||
expect(createObjectProp('form.field.target', 'newKey', 'target')).toBe('form.field.newKey');
|
||||
});
|
||||
|
||||
test('带name参数但name不匹配最后一段', () => {
|
||||
expect(createObjectProp('form.field.other', 'newKey', 'target')).toBe('form.field.other.newKey');
|
||||
});
|
||||
|
||||
test('空字符串路径', () => {
|
||||
expect(createObjectProp('', 'key')).toBe('key');
|
||||
});
|
||||
|
||||
test('多层嵌套路径', () => {
|
||||
expect(createObjectProp('a.b.c.d', 'e')).toBe('a.b.c.d.e');
|
||||
});
|
||||
|
||||
test('带name参数且路径只有一段且匹配', () => {
|
||||
expect(createObjectProp('field', 'newKey', 'field')).toBe('newKey');
|
||||
});
|
||||
|
||||
test('带name参数且路径多段且最后一段匹配', () => {
|
||||
expect(createObjectProp('a.b.c', 'newKey', 'c')).toBe('a.b.newKey');
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user