| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- /**
- * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
- * SPDX-License-Identifier: MIT
- */
- import { beforeEach, describe, expect, it, vi } from 'vitest';
- import { mapValues } from 'lodash-es';
- import { ValidateTrigger } from '@/types';
- import { FormModel } from '@/core/form-model';
- describe('FormModel', () => {
- let formModel = new FormModel();
- describe('validate trigger', () => {
- beforeEach(() => {
- formModel.dispose();
- formModel = new FormModel();
- });
- it('do not validate when value change if validateTrigger is onBlur', async () => {
- formModel.init({ validateTrigger: ValidateTrigger.onBlur });
- const field = formModel.createField('x');
- field.originalValidate = vi.fn();
- vi.spyOn(field, 'originalValidate');
- field.value = 'some value';
- expect(field.originalValidate).not.toHaveBeenCalledOnce();
- });
- describe('delete field', () => {
- beforeEach(() => {
- formModel.dispose();
- formModel = new FormModel();
- });
- it('validate onChange', async () => {
- formModel.init({ initialValues: { parent: { child1: 1 } } });
- formModel.createField('parent');
- formModel.createField('parent.child1');
- expect(formModel.values.parent?.child1).toBe(1);
- formModel.deleteField('parent');
- expect(formModel.values.parent?.child1).toBeUndefined();
- expect(formModel.getField('parent')).toBeUndefined();
- expect(formModel.getField('parent.child1')).toBeUndefined();
- });
- });
- });
- describe('FormModel.validate', () => {
- beforeEach(() => {
- formModel.dispose();
- formModel = new FormModel();
- });
- it('should run validate on all matched names', async () => {
- formModel.init({
- validate: {
- 'a.b.*': () => 'error',
- },
- });
- const bField = formModel.createField('a.b');
- const xField = formModel.createField('a.b.x');
- formModel.setValueIn('a.b', { x: 1, y: 2 });
- const results = await formModel.validate();
- // 1. assert validate has been executed correctly
- expect(results.length).toEqual(2);
- expect(results[0].message).toEqual('error');
- expect(results[0].name).toEqual('a.b.x');
- expect(results[1].message).toEqual('error');
- expect(results[1].name).toEqual('a.b.y');
- // 2. assert form state has been set correctly
- expect(formModel.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
- // 3. assert field state has been set correctly
- expect(xField.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
- // 4. assert field state has been bubbled to its parent
- expect(bField.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
- });
- it('should run validate if multiple patterns match', async () => {
- const mockValidate1 = vi.fn();
- const mockValidate2 = vi.fn();
- formModel.init({
- validate: {
- 'a.b.*': mockValidate1,
- 'a.b.x': mockValidate2,
- },
- });
- const bField = formModel.createField('a.b');
- const xField = formModel.createField('a.b.x');
- formModel.setValueIn('a.b', { x: 1, y: 2 });
- formModel.validate();
- expect(mockValidate1).toHaveBeenCalledTimes(2);
- expect(mockValidate2).toHaveBeenCalledTimes(1);
- });
- it('should run validate correctly if multiple patterns match but multiple layer empty value exist', async () => {
- const mockValidate1 = vi.fn();
- const mockValidate2 = vi.fn();
- formModel.init({
- validate: {
- 'a.*.x': mockValidate1,
- 'a.b.x': mockValidate2,
- },
- });
- const bField = formModel.createField('a.b');
- const xField = formModel.createField('a.b.x');
- formModel.setValueIn('a', {});
- formModel.validate();
- expect(mockValidate1).toHaveBeenCalledTimes(0);
- expect(mockValidate2).toHaveBeenCalledTimes(1);
- });
- it('should correctly set form errors state when field does not exist', async () => {
- formModel.init({
- validate: {
- 'a.b.*': () => 'error',
- },
- });
- formModel.setValueIn('a.b', { x: 1, y: 2 });
- await formModel.validate();
- expect(formModel.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
- });
- it('should set form and field state correctly when run validate twice', async () => {
- formModel.init({
- validate: {
- 'a.b.*': ({ value }) => (typeof value === 'string' ? undefined : 'error'),
- },
- });
- const bField = formModel.createField('a.b');
- const xField = formModel.createField('a.b.x');
- formModel.setValueIn('a.b', { x: 1, y: 2 });
- let results = await formModel.validate();
- // both x y is string, so 2 errors
- expect(results.length).toEqual(2);
- expect(formModel.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
- expect(formModel.state?.errors?.['a.b.y']?.[0].message).toEqual('error');
- expect(xField.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
- expect(bField.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
- formModel.setValueIn('a.b', { x: '1', y: '2' });
- results = await formModel.validate();
- expect(results.length).toEqual(0);
- expect(formModel.state?.errors?.['a.b.x']).toEqual([]);
- expect(formModel.state?.errors?.['a.b.y']).toEqual([]);
- });
- it('validate as dynamic function', async () => {
- formModel.init({
- initialValues: { a: 3, b: 'str' },
- validate: (v, ctx) => {
- expect(ctx).toEqual('context');
- return mapValues(v, (value) => {
- if (typeof value === 'string') {
- return () => 'string error';
- }
- return () => 'num error';
- });
- },
- context: 'context',
- });
- const fieldResult = await formModel.validateIn('a');
- expect(fieldResult).toEqual(['num error']);
- const results = await formModel.validate();
- expect(results).toEqual([
- { name: 'a', message: 'num error', level: 'error' },
- { name: 'b', message: 'string error', level: 'error' },
- ]);
- });
- });
- describe('FormModel set/get values', () => {
- beforeEach(() => {
- formModel.dispose();
- formModel = new FormModel();
- vi.spyOn(formModel.onFormValuesInitEmitter, 'fire');
- vi.spyOn(formModel.onFormValuesChangeEmitter, 'fire');
- vi.spyOn(formModel.onFormValuesUpdatedEmitter, 'fire');
- });
- it('should set value for root path', () => {
- formModel.init({
- initialValues: {
- a: 1,
- },
- });
- formModel.values = { a: 2 };
- expect(formModel.values).toEqual({ a: 2 });
- });
- it('should set initialValues and fire init and updated events', async () => {
- formModel.init({
- initialValues: {
- a: 1,
- },
- });
- expect(formModel.values).toEqual({ a: 1 });
- expect(formModel.onFormValuesInitEmitter.fire).toHaveBeenCalledWith({
- values: {
- a: 1,
- },
- name: '',
- });
- expect(formModel.onFormValuesUpdatedEmitter.fire).toHaveBeenCalledWith({
- values: {
- a: 1,
- },
- name: '',
- });
- });
- it('should set initialValues in certain path and fire change', async () => {
- formModel.init({
- initialValues: {
- a: 1,
- },
- });
- formModel.setInitValueIn('b', 2);
- expect(formModel.values).toEqual({ a: 1, b: 2 });
- expect(formModel.onFormValuesInitEmitter.fire).toHaveBeenCalledWith({
- values: {
- a: 1,
- b: 2,
- },
- prevValues: {
- a: 1,
- },
- name: 'b',
- });
- expect(formModel.onFormValuesUpdatedEmitter.fire).toHaveBeenCalledWith({
- values: {
- a: 1,
- b: 2,
- },
- prevValues: {
- a: 1,
- },
- name: 'b',
- });
- });
- it('should not set initialValues in certain path if value exists', async () => {
- formModel.init({
- initialValues: {
- a: 1,
- },
- });
- formModel.setInitValueIn('a', 2);
- expect(formModel.values).toEqual({ a: 1 });
- // 仅在初始化时调用一次,setInitValueIn 没有调用
- expect(formModel.onFormValuesInitEmitter.fire).toHaveBeenCalledTimes(1);
- expect(formModel.onFormValuesUpdatedEmitter.fire).toHaveBeenCalledTimes(1);
- });
- it('should set values in certain path and fire change and updated events', async () => {
- formModel.init({
- initialValues: {
- a: 1,
- },
- });
- formModel.setValueIn('a', 2);
- expect(formModel.values).toEqual({ a: 2 });
- // 仅在初始化时调用一次,setInitValueIn 没有调用
- expect(formModel.onFormValuesChangeEmitter.fire).toHaveBeenCalledTimes(1);
- // 初始化一次,变更值一次,所以是两次
- expect(formModel.onFormValuesUpdatedEmitter.fire).toHaveBeenCalledTimes(2);
- expect(formModel.onFormValuesChangeEmitter.fire).toHaveBeenCalledWith({
- values: {
- a: 2,
- },
- prevValues: {
- a: 1,
- },
- name: 'a',
- });
- expect(formModel.onFormValuesUpdatedEmitter.fire).toHaveBeenCalledWith({
- values: {
- a: 2,
- },
- prevValues: {
- a: 1,
- },
- name: 'a',
- });
- });
- });
- });
|