Files
wehub-resource-sync 0232b4e2bb
CI / build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:09:51 +08:00

332 lines
8.6 KiB
TypeScript

/**
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
* SPDX-License-Identifier: MIT
*/
import * as React from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ValidateTrigger } from '@/types';
import { toField, toFieldState } from '@/core/to-field';
import { FormModel } from '@/core/form-model';
import { FieldModel } from '@/core/field-model';
describe('toField', () => {
let formModel: FormModel;
let fieldModel: FieldModel;
beforeEach(() => {
formModel = new FormModel();
formModel.init({});
fieldModel = formModel.createField('username') as FieldModel;
});
it('should convert FieldModel to Field', () => {
const field = toField(fieldModel);
expect(field).toBeDefined();
expect(field.name).toBe('username');
expect(field.value).toBeUndefined();
});
it('should expose name property from model', () => {
const field = toField(fieldModel);
expect(field.name).toBe(fieldModel.name);
});
it('should expose value property from model', () => {
fieldModel.value = 'John';
const field = toField(fieldModel);
expect(field.value).toBe('John');
expect(field.value).toBe(fieldModel.value);
});
describe('onChange', () => {
it('should update model value with plain value', () => {
const field = toField(fieldModel);
field.onChange('Alice');
expect(fieldModel.value).toBe('Alice');
expect(field.value).toBe('Alice');
});
it('should handle React change event for input', () => {
const field = toField(fieldModel);
const mockEvent = {
target: {
value: 'Bob',
},
} as React.ChangeEvent<HTMLInputElement>;
field.onChange(mockEvent);
expect(fieldModel.value).toBe('Bob');
});
it('should handle React change event for checkbox (checked)', () => {
const field = toField(fieldModel);
const mockEvent = {
target: {
type: 'checkbox',
checked: true,
value: 'on',
},
} as React.ChangeEvent<HTMLInputElement>;
field.onChange(mockEvent);
expect(fieldModel.value).toBe(true);
});
it('should handle React change event for checkbox (unchecked)', () => {
const field = toField(fieldModel);
const mockEvent = {
target: {
type: 'checkbox',
checked: false,
value: 'on',
},
} as React.ChangeEvent<HTMLInputElement>;
field.onChange(mockEvent);
expect(fieldModel.value).toBe(false);
});
it('should handle numeric value', () => {
const field = toField(fieldModel);
field.onChange(42);
expect(fieldModel.value).toBe(42);
});
it('should handle object value', () => {
const field = toField(fieldModel);
const objValue = { name: 'test', value: 123 };
field.onChange(objValue);
expect(fieldModel.value).toEqual(objValue);
});
it('should handle array value', () => {
const field = toField(fieldModel);
const arrValue = ['a', 'b', 'c'];
field.onChange(arrValue);
expect(fieldModel.value).toEqual(arrValue);
});
});
describe('onBlur', () => {
it('should call validate when validateTrigger is onBlur', () => {
formModel.dispose();
formModel = new FormModel();
formModel.init({ validateTrigger: ValidateTrigger.onBlur });
fieldModel = formModel.createField('username') as FieldModel;
const validateSpy = vi.spyOn(fieldModel, 'validate');
const field = toField(fieldModel);
field.onBlur?.();
expect(validateSpy).toHaveBeenCalled();
});
it('should not trigger validation when validateTrigger is not onBlur', () => {
formModel.dispose();
formModel = new FormModel();
formModel.init({ validateTrigger: ValidateTrigger.onChange });
fieldModel = formModel.createField('username') as FieldModel;
const validateSpy = vi.spyOn(fieldModel, 'validate');
const field = toField(fieldModel);
field.onBlur?.();
expect(validateSpy).not.toHaveBeenCalled();
});
it('should not trigger validation when validateTrigger is onSubmit', () => {
formModel.dispose();
formModel = new FormModel();
formModel.init({ validateTrigger: ValidateTrigger.onSubmit });
fieldModel = formModel.createField('username') as FieldModel;
const validateSpy = vi.spyOn(fieldModel, 'validate');
const field = toField(fieldModel);
field.onBlur?.();
expect(validateSpy).not.toHaveBeenCalled();
});
});
describe('onFocus', () => {
it('should set isTouched to true', () => {
const field = toField(fieldModel);
expect(fieldModel.state.isTouched).toBe(false);
field.onFocus?.();
expect(fieldModel.state.isTouched).toBe(true);
});
it('should set isTouched only once', () => {
const field = toField(fieldModel);
field.onFocus?.();
expect(fieldModel.state.isTouched).toBe(true);
field.onFocus?.();
expect(fieldModel.state.isTouched).toBe(true);
});
});
it('should expose key property (non-enumerable)', () => {
const field = toField(fieldModel);
expect((field as any).key).toBe(fieldModel.id);
expect(Object.keys(field)).not.toContain('key');
});
it('should hide _fieldModel property (non-enumerable)', () => {
const field = toField(fieldModel);
expect((field as any)._fieldModel).toBe(fieldModel);
expect(Object.keys(field)).not.toContain('_fieldModel');
});
it('should preserve reactivity through getters', () => {
const field = toField(fieldModel);
expect(field.name).toBe('username');
expect(field.value).toBeUndefined();
fieldModel.value = 'NewValue';
expect(field.value).toBe('NewValue');
});
});
describe('toFieldState', () => {
let formModel: FormModel;
let fieldModel: FieldModel;
beforeEach(() => {
formModel = new FormModel();
formModel.init({});
fieldModel = formModel.createField('username') as FieldModel;
});
it('should convert FieldModelState to FieldState', () => {
const fieldState = toFieldState(fieldModel.state);
expect(fieldState).toBeDefined();
expect(fieldState.isTouched).toBe(false);
expect(fieldState.isDirty).toBe(false);
expect(fieldState.invalid).toBe(false);
expect(fieldState.isValidating).toBe(false);
});
it('should reflect isTouched state', () => {
const fieldState = toFieldState(fieldModel.state);
expect(fieldState.isTouched).toBe(false);
fieldModel.state.isTouched = true;
expect(fieldState.isTouched).toBe(true);
});
it('should reflect isDirty state', () => {
const fieldState = toFieldState(fieldModel.state);
expect(fieldState.isDirty).toBe(false);
// Manually set dirty state
fieldModel.state.isDirty = true;
expect(fieldState.isDirty).toBe(true);
});
it('should reflect invalid state', () => {
const fieldState = toFieldState(fieldModel.state);
expect(fieldState.invalid).toBe(false);
fieldModel.state.invalid = true;
expect(fieldState.invalid).toBe(true);
});
it('should reflect isValidating state', () => {
const fieldState = toFieldState(fieldModel.state);
expect(fieldState.isValidating).toBe(false);
fieldModel.state.isValidating = true;
expect(fieldState.isValidating).toBe(true);
});
it('should return errors as flat array', () => {
const fieldState = toFieldState(fieldModel.state);
expect(fieldState.errors).toBeUndefined();
fieldModel.state.errors = {
validate1: ['Error 1', 'Error 2'],
validate2: ['Error 3'],
};
expect(fieldState.errors).toEqual(['Error 1', 'Error 2', 'Error 3']);
});
it('should return warnings as flat array', () => {
const fieldState = toFieldState(fieldModel.state);
expect(fieldState.warnings).toBeUndefined();
fieldModel.state.warnings = {
validate1: ['Warning 1', 'Warning 2'],
validate2: ['Warning 3'],
};
expect(fieldState.warnings).toEqual(['Warning 1', 'Warning 2', 'Warning 3']);
});
it('should handle empty errors object', () => {
const fieldState = toFieldState(fieldModel.state);
fieldModel.state.errors = {};
expect(fieldState.errors).toEqual([]);
});
it('should handle empty warnings object', () => {
const fieldState = toFieldState(fieldModel.state);
fieldModel.state.warnings = {};
expect(fieldState.warnings).toEqual([]);
});
it('should preserve reactivity through getters', () => {
const fieldState = toFieldState(fieldModel.state);
expect(fieldState.isTouched).toBe(false);
fieldModel.state.isTouched = true;
expect(fieldState.isTouched).toBe(true);
});
});