Skip to content

Commit 6e6d774

Browse files
authored
test: update testcase (#616)
1 parent e918484 commit 6e6d774

File tree

2 files changed

+98
-12
lines changed

2 files changed

+98
-12
lines changed

src/Field.tsx

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
import toChildrenArray from 'rc-util/lib/Children/toArray';
2-
import warning from 'rc-util/lib/warning';
32
import isEqual from 'rc-util/lib/isEqual';
3+
import warning from 'rc-util/lib/warning';
44
import * as React from 'react';
5+
import FieldContext, { HOOK_MARK } from './FieldContext';
56
import type {
7+
EventArgs,
68
FieldEntity,
79
FormInstance,
10+
InternalFormInstance,
811
InternalNamePath,
12+
InternalValidateOptions,
913
Meta,
1014
NamePath,
1115
NotifyInfo,
1216
Rule,
13-
Store,
14-
InternalValidateOptions,
15-
InternalFormInstance,
17+
RuleError,
1618
RuleObject,
19+
Store,
1720
StoreValue,
18-
EventArgs,
19-
RuleError,
2021
} from './interface';
21-
import FieldContext, { HOOK_MARK } from './FieldContext';
2222
import ListContext from './ListContext';
2323
import { toArray } from './utils/typeUtil';
2424
import { validateRules } from './utils/validateUtil';
@@ -74,6 +74,10 @@ export interface InternalFieldProps<Values = any> {
7474
shouldUpdate?: ShouldUpdate<Values>;
7575
trigger?: string;
7676
validateTrigger?: string | string[] | false;
77+
/**
78+
* Trigger will after configured milliseconds.
79+
*/
80+
validateDebounce?: number;
7781
validateFirst?: boolean | 'parallel';
7882
valuePropName?: string;
7983
getValueProps?: (value: StoreValue) => Record<string, unknown>;
@@ -382,13 +386,14 @@ class Field extends React.Component<InternalFieldProps, FieldState> implements F
382386
const { triggerName, validateOnly = false } = options || {};
383387

384388
// Force change to async to avoid rule OOD under renderProps field
385-
const rootPromise = Promise.resolve().then(() => {
389+
const rootPromise = Promise.resolve().then(async (): Promise<any[]> => {
386390
if (!this.mounted) {
387391
return [];
388392
}
389393

390-
const { validateFirst = false, messageVariables } = this.props;
394+
const { validateFirst = false, messageVariables, validateDebounce } = this.props;
391395

396+
// Start validate
392397
let filteredRules = this.getRules();
393398
if (triggerName) {
394399
filteredRules = filteredRules
@@ -403,6 +408,18 @@ class Field extends React.Component<InternalFieldProps, FieldState> implements F
403408
});
404409
}
405410

411+
// Wait for debounce. Skip if no `triggerName` since its from `validateFields / submit`
412+
if (validateDebounce && triggerName) {
413+
await new Promise(resolve => {
414+
setTimeout(resolve, validateDebounce);
415+
});
416+
417+
// Skip since out of date
418+
if (this.validatePromise !== rootPromise) {
419+
return [];
420+
}
421+
}
422+
406423
const promise = validateRules(
407424
namePath,
408425
currentValue,

tests/validate.test.tsx

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import React, { useEffect } from 'react';
21
import { fireEvent, render } from '@testing-library/react';
2+
import React, { useEffect } from 'react';
33
import { act } from 'react-dom/test-utils';
44
import Form, { Field, useForm } from '../src';
5+
import type { FormInstance, ValidateMessages } from '../src/interface';
6+
import { changeValue, getInput, matchError } from './common';
57
import InfoField, { Input } from './common/InfoField';
6-
import { changeValue, matchError, getInput } from './common';
78
import timeout from './common/timeout';
8-
import type { FormInstance, ValidateMessages } from '../src/interface';
99

1010
describe('Form.Validate', () => {
1111
it('required', async () => {
@@ -964,4 +964,73 @@ describe('Form.Validate', () => {
964964
await timeout();
965965
expect(container.querySelector('.errors').textContent).toEqual(`'test' is required`);
966966
});
967+
968+
it('validateDebounce', async () => {
969+
jest.useFakeTimers();
970+
971+
const validator = jest.fn(
972+
() =>
973+
new Promise((_, reject) => {
974+
setTimeout(() => {
975+
reject(new Error('Not Correct'));
976+
}, 100);
977+
}),
978+
);
979+
980+
const formRef = React.createRef<FormInstance>();
981+
982+
const { container } = render(
983+
<Form ref={formRef}>
984+
<InfoField name="test" rules={[{ validator }]} validateDebounce={1000}>
985+
<Input />
986+
</InfoField>
987+
</Form>,
988+
);
989+
990+
fireEvent.change(container.querySelector('input'), {
991+
target: {
992+
value: 'light',
993+
},
994+
});
995+
996+
// Debounce should wait
997+
await act(async () => {
998+
await Promise.resolve();
999+
jest.advanceTimersByTime(900);
1000+
await Promise.resolve();
1001+
});
1002+
expect(validator).not.toHaveBeenCalled();
1003+
1004+
// Debounce should work
1005+
await act(async () => {
1006+
await Promise.resolve();
1007+
jest.advanceTimersByTime(1000);
1008+
await Promise.resolve();
1009+
});
1010+
expect(validator).toHaveBeenCalled();
1011+
1012+
// `validateFields` should ignore `validateDebounce`
1013+
validator.mockReset();
1014+
formRef.current.validateFields();
1015+
1016+
await act(async () => {
1017+
await Promise.resolve();
1018+
jest.advanceTimersByTime(200);
1019+
await Promise.resolve();
1020+
});
1021+
expect(validator).toHaveBeenCalled();
1022+
1023+
// `submit` should ignore `validateDebounce`
1024+
validator.mockReset();
1025+
fireEvent.submit(container.querySelector('form'));
1026+
1027+
await act(async () => {
1028+
await Promise.resolve();
1029+
jest.advanceTimersByTime(200);
1030+
await Promise.resolve();
1031+
});
1032+
expect(validator).toHaveBeenCalled();
1033+
1034+
jest.useRealTimers();
1035+
});
9671036
});

0 commit comments

Comments
 (0)