Skip to content

Commit

Permalink
feat: add Parallel (#156)
Browse files Browse the repository at this point in the history
* support parallel

* add test case
  • Loading branch information
zombieJ committed Jun 30, 2020
1 parent 97c74a9 commit 608c219
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 63 deletions.
2 changes: 1 addition & 1 deletion src/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export interface InternalFieldProps {
shouldUpdate?: ShouldUpdate;
trigger?: string;
validateTrigger?: string | string[] | false;
validateFirst?: boolean;
validateFirst?: boolean | 'parallel';
valuePropName?: string;
getValueProps?: (value: StoreValue) => object;
messageVariables?: Record<string, string>;
Expand Down
46 changes: 33 additions & 13 deletions src/utils/validateUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export function validateRules(
value: StoreValue,
rules: RuleObject[],
options: ValidateOptions,
validateFirst: boolean,
validateFirst: boolean | 'parallel',
messageVariables?: Record<string, string>,
) {
const name = namePath.join('.');
Expand Down Expand Up @@ -188,20 +188,40 @@ export function validateRules(
};
});

const rulePromises = filledRules.map(rule =>
validateRule(name, value, rule, options, messageVariables),
);
let summaryPromise: Promise<string[]>;

const summaryPromise: Promise<string[]> = (validateFirst
? finishOnFirstFailed(rulePromises)
: finishOnAllFailed(rulePromises)
).then((errors: string[]): string[] | Promise<string[]> => {
if (!errors.length) {
return [];
}
if (validateFirst === true) {
// >>>>> Validate by serialization
summaryPromise = new Promise(async resolve => {
/* eslint-disable no-await-in-loop */
for (let i = 0; i < filledRules.length; i += 1) {
const errors = await validateRule(name, value, filledRules[i], options, messageVariables);
if (errors.length) {
resolve(errors);
return;
}
}
/* eslint-enable */

return Promise.reject<string[]>(errors);
});
resolve([]);
});
} else {
// >>>>> Validate by parallel
const rulePromises = filledRules.map(rule =>
validateRule(name, value, rule, options, messageVariables),
);

summaryPromise = (validateFirst
? finishOnFirstFailed(rulePromises)
: finishOnAllFailed(rulePromises)
).then((errors: string[]): string[] | Promise<string[]> => {
if (!errors.length) {
return [];
}

return Promise.reject<string[]>(errors);
});
}

// Internal catch error to avoid console error log.
summaryPromise.catch(e => e);
Expand Down
143 changes: 94 additions & 49 deletions tests/validate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,59 +408,104 @@ describe('Form.Validate', () => {
errorSpy.mockRestore();
});

it('validateFirst', async () => {
let form;
let canEnd = false;
const onFinish = jest.fn();
describe('validateFirst', () => {
it('work', async () => {
let form;
let canEnd = false;
const onFinish = jest.fn();

const wrapper = mount(
<div>
<Form
ref={instance => {
form = instance;
}}
onFinish={onFinish}
>
<InfoField
name="username"
validateFirst
rules={[
// Follow promise will never end
{ required: true },
{
validator: () =>
new Promise(resolve => {
if (canEnd) {
resolve();
}
}),
},
]}
/>
</Form>
</div>,
);
const wrapper = mount(
<div>
<Form
ref={instance => {
form = instance;
}}
onFinish={onFinish}
>
<InfoField
name="username"
validateFirst
rules={[
// Follow promise will never end
{ required: true },
{
validator: () =>
new Promise(resolve => {
if (canEnd) {
resolve();
}
}),
},
]}
/>
</Form>
</div>,
);

// Not pass
await changeValue(wrapper, '');
matchError(wrapper, true);
expect(form.getFieldError('username')).toEqual(["'username' is required"]);
expect(form.getFieldsError()).toEqual([
{
name: ['username'],
errors: ["'username' is required"],
},
]);
expect(onFinish).not.toHaveBeenCalled();
// Not pass
await changeValue(wrapper, '');
matchError(wrapper, true);
expect(form.getFieldError('username')).toEqual(["'username' is required"]);
expect(form.getFieldsError()).toEqual([
{
name: ['username'],
errors: ["'username' is required"],
},
]);
expect(onFinish).not.toHaveBeenCalled();

// Should pass
canEnd = true;
await changeValue(wrapper, 'test');
wrapper.find('form').simulate('submit');
await timeout();
// Should pass
canEnd = true;
await changeValue(wrapper, 'test');
wrapper.find('form').simulate('submit');
await timeout();

matchError(wrapper, false);
expect(onFinish).toHaveBeenCalledWith({ username: 'test' });
matchError(wrapper, false);
expect(onFinish).toHaveBeenCalledWith({ username: 'test' });
});

[
{ name: 'serialization', first: true, second: false, validateFirst: true },
{ name: 'parallel', first: true, second: true, validateFirst: 'parallel' },
].forEach(({ name, first, second, validateFirst }) => {
it(name, async () => {
let ruleFirst = false;
let ruleSecond = false;

const wrapper = mount(
<Form>
<InfoField
name="username"
validateFirst={validateFirst}
rules={[
{
validator: async () => {
ruleFirst = true;
await timeout();
throw new Error('failed first');
},
},
{
validator: async () => {
ruleSecond = true;
await timeout();
throw new Error('failed second');
},
},
]}
/>
</Form>,
);

await changeValue(wrapper, 'test');
await timeout();

matchError(wrapper, 'failed first');

expect(ruleFirst).toEqual(first);
expect(ruleSecond).toEqual(second);
});
});
});

it('switch to remove errors', async () => {
Expand Down

0 comments on commit 608c219

Please sign in to comment.