Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: field handle isValidating flag #26

Merged
merged 4 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 122 additions & 34 deletions src/core/builders/create-form-validator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ describe("createFormValidator", () => {
]);
const getValue = () => "" as any;

const validation = await validate([Schema.string], getValue);
const validation = await validate({ fields: [Schema.string], getValue });

expect(validation).toEqual([{ field: Schema.string, error: "REQUIRED" }]);
});
Expand All @@ -88,7 +88,7 @@ describe("createFormValidator", () => {
]);
const getValue = () => "defined string" as any;

const validation = await validate([Schema.string], getValue);
const validation = await validate({ fields: [Schema.string], getValue });

expect(validation).toEqual([{ field: Schema.string, error: null }]);
});
Expand All @@ -106,7 +106,7 @@ describe("createFormValidator", () => {
]);
const getValue = () => "" as any;

const validation = await validate([Schema.string], getValue);
const validation = await validate({ fields: [Schema.string], getValue });

expect(validation).toEqual([{ field: Schema.string, error: "REQUIRED" }]);
});
Expand All @@ -124,7 +124,7 @@ describe("createFormValidator", () => {
]);
const getValue = () => "ab" as any;

const validation = await validate([Schema.string], getValue);
const validation = await validate({ fields: [Schema.string], getValue });

expect(validation).toEqual([{ field: Schema.string, error: "TOO_SHORT" }]);
});
Expand All @@ -142,7 +142,7 @@ describe("createFormValidator", () => {
]);
const getValue = () => "abcd" as any;

const validation = await validate([Schema.string], getValue);
const validation = await validate({ fields: [Schema.string], getValue });

expect(validation).toEqual([{ field: Schema.string, error: null }]);
});
Expand All @@ -156,7 +156,7 @@ describe("createFormValidator", () => {
]);
const getValue = () => "A" as any;

const validation = await validate([Schema.choice], getValue);
const validation = await validate({ fields: [Schema.choice], getValue });

expect(validation).toEqual([
{ field: Schema.choice, error: "INVALID_VALUE" },
Expand All @@ -172,7 +172,7 @@ describe("createFormValidator", () => {
]);
const getValue = () => "C" as any;

const validation = await validate([Schema.choice], getValue);
const validation = await validate({ fields: [Schema.choice], getValue });

expect(validation).toEqual([{ field: Schema.choice, error: null }]);
});
Expand All @@ -186,7 +186,7 @@ describe("createFormValidator", () => {
]);
const getValue = () => null as any;

const validation = await validate([Schema.instance], getValue);
const validation = await validate({ fields: [Schema.instance], getValue });

expect(validation).toEqual([{ field: Schema.instance, error: "REQUIRED" }]);
});
Expand All @@ -203,7 +203,10 @@ describe("createFormValidator", () => {
]);
const getValue = () => ["ok", "very-ok", "invalid", "still-ok"] as any;

const validation = await validate([Schema.arrayString], getValue);
const validation = await validate({
fields: [Schema.arrayString],
getValue,
});

expect(validation).toEqual([
{ field: Schema.arrayString, error: "INVALID_VALUE" },
Expand All @@ -220,7 +223,10 @@ describe("createFormValidator", () => {
const getValue = () =>
[["ok"], ["very-ok"], ["invalid"], ["still-ok"]] as any;

const validation = await validate([Schema.arrayArrayString], getValue);
const validation = await validate({
fields: [Schema.arrayArrayString],
getValue,
});

expect(validation).toEqual([
{ field: Schema.arrayArrayString, error: null },
Expand All @@ -236,7 +242,7 @@ describe("createFormValidator", () => {
]);
const getValue = () => null as any;

const validation = await validate([Schema.object], getValue);
const validation = await validate({ fields: [Schema.object], getValue });

expect(validation).toEqual([
{ field: Schema.object, error: "INVALID_VALUE" },
Expand All @@ -252,7 +258,7 @@ describe("createFormValidator", () => {
]);
const getValue = () => null as any;

const validation = await validate([Schema.object], getValue);
const validation = await validate({ fields: [Schema.object], getValue });

expect(validation).toEqual([{ field: Schema.object, error: "REQUIRED" }]);
});
Expand All @@ -266,7 +272,7 @@ describe("createFormValidator", () => {
]);
const getValue = () => null as any;

const validation = await validate([Schema.object], getValue);
const validation = await validate({ fields: [Schema.object], getValue });

expect(validation).toEqual([{ field: Schema.object, error: null }]);
});
Expand Down Expand Up @@ -295,15 +301,15 @@ describe("createFormValidator", () => {
}
};

const validation = await validate(
[
const validation = await validate({
fields: [
Schema.arrayObjectString.nth(0),
Schema.arrayObjectString.nth(1),
Schema.arrayObjectString.nth(2),
Schema.arrayObjectString.nth(3),
],
getValue
);
getValue,
});

expect(validation).toEqual([
{ field: Schema.arrayObjectString.nth(0), error: "TOO_SHORT" },
Expand Down Expand Up @@ -338,15 +344,15 @@ describe("createFormValidator", () => {
}
};

const validation = await validate(
[
const validation = await validate({
fields: [
Schema.arrayChoice.nth(1),
Schema.arrayChoice.nth(0),
Schema.arrayObjectString.nth(0),
Schema.arrayObjectString.nth(1),
],
getValue
);
getValue,
});

expect(validation).toEqual([
{ field: Schema.arrayChoice.nth(1), error: null },
Expand Down Expand Up @@ -378,11 +384,11 @@ describe("createFormValidator", () => {
}
};

const validation = await validate(
[Schema.string, Schema.choice],
const validation = await validate({
fields: [Schema.string, Schema.choice],
getValue,
"change"
);
trigger: "change",
});

expect(validation).toEqual([{ field: Schema.choice, error: "REQUIRED" }]);
});
Expand Down Expand Up @@ -443,11 +449,11 @@ describe("createFormValidator", () => {
}
};

const validation = await validate(
[Schema.string, Schema.number, Schema.choice],
const validation = await validate({
fields: [Schema.string, Schema.number, Schema.choice],
getValue,
"change"
);
trigger: "change",
});

expect(validation).toEqual([
{ field: Schema.string, error: "TOO_SHORT" },
Expand Down Expand Up @@ -500,21 +506,103 @@ describe("createFormValidator", () => {
}
};

const validation = await validate(
[
const result = await validate({
fields: [
Schema.arrayString.nth(0),
Schema.arrayString.nth(1),
Schema.arrayString.nth(2),
Schema.string,
],
getValue
);
getValue,
});

expect(validation).toEqual([
expect(result).toEqual([
{ field: Schema.arrayString.nth(0), error: "INVALID_VALUE" },
{ field: Schema.arrayString.nth(1), error: null },
{ field: Schema.arrayString.nth(2), error: "TOO_SHORT" },
{ field: Schema.string, error: "TOO_SHORT" },
]);
});

it("calls callback functions to signal start and end of validation for every affected field", async () => {
const pass = <T>(_val: T) => wait(null);

const { validate } = createFormValidator(Schema, validate => [
validate({
field: Schema.string,
rules: () => [pass],
}),
validate({
field: Schema.string,
rules: () => [pass],
}),
validate.each({
field: Schema.arrayString,
rules: () => [pass],
}),
validate({
field: Schema.arrayString.nth(0),
rules: () => [pass],
}),
]);

const onFieldValidationStart = jest.fn();
const onFieldValidationEnd = jest.fn();

const fields = [
Schema.number,
Schema.arrayString.nth(0),
Schema.arrayString.nth(1),
Schema.arrayString.nth(2),
Schema.string,
];

await validate({
fields,
getValue: () => "foo" as any,
onFieldValidationStart,
onFieldValidationEnd,
});

const expectField = (desc: FieldDescriptor<any>) =>
expect.objectContaining({ __path: impl(desc).__path });

expect(onFieldValidationStart).toHaveBeenCalledWith(
expectField(Schema.arrayString.nth(0))
);
expect(onFieldValidationStart).toHaveBeenCalledWith(
expectField(Schema.arrayString.nth(1))
);
expect(onFieldValidationStart).toHaveBeenCalledWith(
expectField(Schema.arrayString.nth(2))
);
expect(onFieldValidationStart).toHaveBeenCalledWith(
expectField(Schema.string)
);
expect(onFieldValidationStart).not.toHaveBeenCalledWith(
expectField(Schema.number)
);
expect(onFieldValidationStart).not.toHaveBeenCalledWith(
expectField(Schema.arrayString)
);

expect(onFieldValidationEnd).toHaveBeenCalledWith(
expectField(Schema.arrayString.nth(0))
);
expect(onFieldValidationEnd).toHaveBeenCalledWith(
expectField(Schema.arrayString.nth(1))
);
expect(onFieldValidationEnd).toHaveBeenCalledWith(
expectField(Schema.arrayString.nth(2))
);
expect(onFieldValidationEnd).toHaveBeenCalledWith(
expectField(Schema.string)
);
expect(onFieldValidationEnd).not.toHaveBeenCalledWith(
expectField(Schema.number)
);
expect(onFieldValidationEnd).not.toHaveBeenCalledWith(
expectField(Schema.arrayString)
);
});
});
42 changes: 26 additions & 16 deletions src/core/builders/create-form-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ export const createFormValidator = <Values extends object, Err>(
};

const formValidator: FormValidator<Values, Err> = {
validate: (fields, getValue, trigger) => {
validate: ({
fields,
trigger,
getValue,
onFieldValidationStart,
onFieldValidationEnd,
}) => {
const fieldsToValidate = fields
.map(field => ({
field,
Expand All @@ -72,13 +78,16 @@ export const createFormValidator = <Values extends object, Err>(
.filter(x => x.validators.length > 0);

return Promise.all(
fieldsToValidate.map(async ({ field, validators }) => {
fieldsToValidate.map(({ field, validators }) => {
const value = getValue(field);
const error = await firstNonNullPromise(validators, x =>
runValidationForField(x, value)
);

return { field, error };
onFieldValidationStart?.(field);
return firstNonNullPromise(validators, v =>
runValidationForField(v, value)
).then(error => {
onFieldValidationEnd?.(field);
return { field, error };
});
})
);
},
Expand All @@ -103,28 +112,29 @@ validate.each = config => ({
dependencies: config.dependencies,
});

const runValidationForField = async <Value, Err>(
const runValidationForField = <Value, Err>(
validator: FieldValidator<Value, Err, unknown[]>,
value: Value
): Promise<Err | null> => {
const rules = validator
.validators([] as any)
.filter(x => !isFalsy(x)) as Validator<Value, Err>[];

return firstNonNullPromise(rules, async rule => await rule(value));
return firstNonNullPromise(rules, rule => Promise.resolve(rule(value)));
};

const firstNonNullPromise = async <T, V>(
const firstNonNullPromise = <T, V>(
list: T[],
mapper: (x: T) => Promise<V | null>
provider: (x: T) => Promise<V | null>
): Promise<V | null> => {
for (const x of list) {
const result = await mapper(x);
if (result != null) {
return result;
}
if (list.length === 0) {
return Promise.resolve(null);
}
return null;

const [el, ...rest] = list;
return provider(el).then(result =>
result != null ? result : firstNonNullPromise(rest, provider)
);
};

// TODO rethink
Expand Down
Loading