diff --git a/src/Field.tsx b/src/Field.tsx index 69e680ce8..be5a13c2a 100644 --- a/src/Field.tsx +++ b/src/Field.tsx @@ -28,6 +28,7 @@ import { getNamePath, getValue, } from './utils/valueUtil'; +import delayFrame from './utils/delayUtil'; const EMPTY_ERRORS: any[] = []; const EMPTY_WARNINGS: any[] = []; @@ -99,8 +100,10 @@ export interface InternalFieldProps { fieldContext?: InternalFormInstance; } -export interface FieldProps - extends Omit, 'name' | 'fieldContext'> { +export interface FieldProps extends Omit< + InternalFieldProps, + 'name' | 'fieldContext' +> { name?: NamePath; } @@ -399,6 +402,10 @@ class Field extends React.Component implements F const { validateFirst = false, messageVariables, validateDebounce } = this.props; + // Should wait for the frame render, + // since developer may `useWatch` value in the rules. + await delayFrame(); + // Start validate let filteredRules = this.getRules(); if (triggerName) { diff --git a/src/utils/delayUtil.ts b/src/utils/delayUtil.ts new file mode 100644 index 000000000..b852ac35b --- /dev/null +++ b/src/utils/delayUtil.ts @@ -0,0 +1,9 @@ +import raf from '@rc-component/util/lib/raf'; + +export default async function delayFrame() { + return new Promise(resolve => { + raf(() => { + resolve(); + }); + }); +} diff --git a/tests/__mocks__/src/utils/delayUtil.ts b/tests/__mocks__/src/utils/delayUtil.ts new file mode 100644 index 000000000..b694c6bc7 --- /dev/null +++ b/tests/__mocks__/src/utils/delayUtil.ts @@ -0,0 +1,3 @@ +export default async function delayFrame() { + return Promise.resolve(); +} diff --git a/tests/common/timeout.ts b/tests/common/timeout.ts index 1b2e52cb0..ad44dcd49 100644 --- a/tests/common/timeout.ts +++ b/tests/common/timeout.ts @@ -7,6 +7,20 @@ export default async (timeout: number = 10) => { }; export async function waitFakeTime(timeout: number = 10) { + await act(async () => { + await new Promise(resolve => { + setTimeout(resolve, 11); + jest.advanceTimersByTime(11); + }); + }); + + await act(async () => { + await new Promise(resolve => { + setTimeout(resolve, 11); + jest.advanceTimersByTime(11); + }); + }); + await act(async () => { await Promise.resolve(); jest.advanceTimersByTime(timeout); diff --git a/tests/context.test.tsx b/tests/context.test.tsx index 4a587c9f3..2a68ab138 100644 --- a/tests/context.test.tsx +++ b/tests/context.test.tsx @@ -6,6 +6,8 @@ import InfoField from './common/InfoField'; import { changeValue, matchError, getInput } from './common'; import timeout from './common/timeout'; +jest.mock('../src/utils/delayUtil'); + describe('Form.Context', () => { it('validateMessages', async () => { const { container } = render( diff --git a/tests/dependencies.test.tsx b/tests/dependencies.test.tsx index deeb0fc74..9023bb282 100644 --- a/tests/dependencies.test.tsx +++ b/tests/dependencies.test.tsx @@ -1,12 +1,16 @@ import React from 'react'; import type { FormInstance } from '../src'; import Form, { Field } from '../src'; -import timeout, { waitFakeTime } from './common/timeout'; +import { waitFakeTime } from './common/timeout'; import InfoField, { Input } from './common/InfoField'; import { changeValue, matchError, getInput } from './common'; import { fireEvent, render } from '@testing-library/react'; describe('Form.Dependencies', () => { + afterEach(() => { + jest.useRealTimers(); + }); + it('touched', async () => { const form = React.createRef(); @@ -32,6 +36,8 @@ describe('Form.Dependencies', () => { describe('initialValue', () => { function test(name: string, formProps = {}, fieldProps = {}) { it(name, async () => { + jest.useFakeTimers(); + let validated = false; const { container } = render( @@ -56,6 +62,7 @@ describe('Form.Dependencies', () => { // Not trigger if not touched await changeValue(getInput(container, 0), 'bamboo'); + await waitFakeTime(); expect(validated).toBeTruthy(); }); } @@ -253,7 +260,7 @@ describe('Form.Dependencies', () => { false}> {() => { - console.log('render!'); + counter += 1; return null; }} @@ -268,4 +275,30 @@ describe('Form.Dependencies', () => { expect(container.querySelectorAll('input')).toHaveLength(1); expect(counter).toEqual(1); }); + + it('error should be cleared when dependency field changes and rule becomes false', async () => { + const Demo = () => { + const [form] = Form.useForm(); + const type = Form.useWatch('type', form); + + return ( +
+ + + + + + +
+ ); + }; + + const { container } = render(); + await changeValue(getInput(container, 1), ['bamboo', '']); + matchError(container, true); + + // Change type to make rule true + await changeValue(getInput(container), '1'); + matchError(container, false); + }); }); diff --git a/tests/index.test.tsx b/tests/index.test.tsx index 35cf0e232..8cafc91fb 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -6,6 +6,8 @@ import Form, { Field, useForm } from '../src'; import { changeValue, getInput, matchError } from './common'; import InfoField, { Input } from './common/InfoField'; import timeout, { waitFakeTime } from './common/timeout'; + +jest.mock('../src/utils/delayUtil'); import type { FormRef, Meta } from '@/interface'; describe('Form.Basic', () => { diff --git a/tests/initialValue.test.tsx b/tests/initialValue.test.tsx index bc5f0a105..6c06a9623 100644 --- a/tests/initialValue.test.tsx +++ b/tests/initialValue.test.tsx @@ -165,11 +165,11 @@ describe('Form.InitialValues', () => { fireEvent.click(container.querySelector('button')); expect(formValue.users[0].last).toEqual('bbb'); - console.log('Form Value:', refForm.getFieldsValue(true)); + fireEvent.click(container.querySelector('button')); expect(formValue.users[0].last).toEqual('bbb'); - console.log('Form Value:', refForm.getFieldsValue(true)); + fireEvent.click(container.querySelector('button')); diff --git a/tests/list.test.tsx b/tests/list.test.tsx index 9aac91b39..a491b83ae 100644 --- a/tests/list.test.tsx +++ b/tests/list.test.tsx @@ -10,6 +10,8 @@ import InfoField, { Input } from './common/InfoField'; import { changeValue, getInput } from './common'; import timeout from './common/timeout'; +jest.mock('../src/utils/delayUtil'); + describe('Form.List', () => { const form = React.createRef(); diff --git a/tests/useWatch.test.tsx b/tests/useWatch.test.tsx index ac6080d3a..b3d997ff7 100644 --- a/tests/useWatch.test.tsx +++ b/tests/useWatch.test.tsx @@ -471,6 +471,8 @@ describe('useWatch', () => { React.useEffect(() => { console.log(nameValuePreserve, nameValue); }, [nameValuePreserve, nameValue]); + + return (
diff --git a/tests/validate-warning.test.tsx b/tests/validate-warning.test.tsx index 84b0b6a8b..4a142c404 100644 --- a/tests/validate-warning.test.tsx +++ b/tests/validate-warning.test.tsx @@ -5,6 +5,8 @@ import { changeValue, getInput, matchError } from './common'; import type { FormInstance, Rule } from '../src/interface'; import { render } from '@testing-library/react'; +jest.mock('../src/utils/delayUtil'); + describe('Form.WarningValidate', () => { it('required', async () => { const form = React.createRef(); diff --git a/tests/validate.test.tsx b/tests/validate.test.tsx index 7fb1cdd99..a76e2dd8f 100644 --- a/tests/validate.test.tsx +++ b/tests/validate.test.tsx @@ -6,6 +6,8 @@ import { changeValue, getInput, matchError } from './common'; import InfoField, { Input } from './common/InfoField'; import timeout, { waitFakeTime } from './common/timeout'; +jest.mock('../src/utils/delayUtil'); + describe('Form.Validate', () => { it('required', async () => { const form = React.createRef();