diff --git a/packages/form/src/utils/typeMatch.ts b/packages/form/src/utils/typeMatch.ts index 8a2d3715..f0a37cd0 100644 --- a/packages/form/src/utils/typeMatch.ts +++ b/packages/form/src/utils/typeMatch.ts @@ -697,9 +697,13 @@ export const validateTypeMatch = ( return undefined; } + if (!props.config?.name) { + return undefined; + } + const rawFieldType = 'type' in (props.config || {}) ? props.config.type : ''; if (typeof rawFieldType !== 'string' || !rawFieldType) { - return; + return undefined; } // 统一将驼峰形式(如 radioGroup)归一化为连字符形式(radio-group),与内置规则的 key 保持一致 diff --git a/packages/form/tests/unit/utils/typeMatch.spec.ts b/packages/form/tests/unit/utils/typeMatch.spec.ts index da3ed25a..3a1f2b23 100644 --- a/packages/form/tests/unit/utils/typeMatch.spec.ts +++ b/packages/form/tests/unit/utils/typeMatch.spec.ts @@ -21,6 +21,7 @@ import type { FormState } from '@form/index'; import { getRules } from '@form/utils/form'; import { clearTypeMatchRules, + createTypeMatchValidator, deleteTypeMatchRule, getTypeMatchRule, registerTypeMatchRule, @@ -51,9 +52,12 @@ const mForm: FormState = { }; const propsOf = (config: Record, model: Record = {}) => ({ - config, + config: { + name: 'field', + ...config, + }, model, - prop: config.name || 'field', + prop: config.name || config.prop || 'field', }); describe('validateTypeMatch', () => { @@ -64,6 +68,19 @@ describe('validateTypeMatch', () => { expect(validateTypeMatch([], mForm, propsOf({ type: 'select', multiple: true }))).toBeUndefined(); }); + test('config 未配置 name 时跳过校验', () => { + const noNameProps = { config: { type: 'text' }, model: {} }; + expect(validateTypeMatch(123, mForm, noNameProps)).toBeUndefined(); + expect(validateTypeMatch({ a: 1 }, mForm, { config: { type: 'number' }, model: {} })).toBeUndefined(); + expect( + validateTypeMatch('bad', mForm, { + config: { type: 'select', options: [{ text: 'A', value: 'a' }] }, + model: {}, + }), + ).toBeUndefined(); + expect(validateTypeMatch(123, mForm, { config: undefined, model: {} })).toBeUndefined(); + }); + test('0 / false 不视为空值', () => { expect(validateTypeMatch(0, mForm, propsOf({ type: 'number' }))).toBeUndefined(); expect(validateTypeMatch(false, mForm, propsOf({ type: 'switch' }))).toBeUndefined(); @@ -386,6 +403,11 @@ describe('validateTypeMatch', () => { ).toBe('a 类型应为数组\n\n请参考以下示例值:["a"]'); }); + test('radio-group / checkbox-group 无 options 时跳过校验', () => { + expect(validateTypeMatch('a', mForm, propsOf({ type: 'radio-group' }))).toBeUndefined(); + expect(validateTypeMatch('a', mForm, propsOf({ type: 'checkbox-group' }))).toBeUndefined(); + }); + test('timerange 按 valueFormat 校验', () => { expect(validateTypeMatch(['12:00:00', '13:00:00'], mForm, propsOf({ type: 'timerange' }))).toBeUndefined(); expect(validateTypeMatch(['bad'], mForm, propsOf({ type: 'timerange' }))).toMatch( @@ -694,3 +716,30 @@ describe('plugin typeMatchRules', () => { expect(validateTypeMatch('bad', mForm, propsOf({ type: 'install-type' }))).toBe('install error'); }); }); + +describe('createTypeMatchValidator', () => { + beforeEach(() => { + clearTypeMatchRules(); + }); + + afterEach(() => { + clearTypeMatchRules(); + }); + + test('validateTypeMatch 抛异常时仍执行原始 validator', () => { + registerTypeMatchRule('throw', () => { + throw new Error('boom'); + }); + + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const originalValidator = vi.fn(); + const validator = createTypeMatchValidator(mForm, propsOf({ type: 'throw' }), { + validator: originalValidator, + } as any); + + validator({}, 'value', () => {}, {}, {}); + expect(originalValidator).toHaveBeenCalled(); + expect(errorSpy).toHaveBeenCalled(); + errorSpy.mockRestore(); + }); +});