From 9420ce770c759ef97a55b16d773f343d6afbcb96 Mon Sep 17 00:00:00 2001 From: Larry Botha Date: Sat, 1 May 2021 14:59:19 +0200 Subject: [PATCH 1/2] fix(isvalid store): fix isValid returning 'false' for valid nested arrays fix #115 --- lib/util.js | 9 ++++-- test/specs/library.spec.js | 60 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/lib/util.js b/lib/util.js index da2bbf0..8b37b36 100644 --- a/lib/util.js +++ b/lib/util.js @@ -26,11 +26,14 @@ function isEmpty(object) { } function getValues(object) { - let result = []; + let results = []; + for (const [, value] of Object.entries(object)) { - result = [...result, ...(typeof value === 'object' ? getValues(value) : [value])]; + const values = typeof value === 'object' ? getValues(value) : [value]; + results = [...results, ...values]; } - return result; + + return results; } // TODO: refactor this so as not to rely directly on yup's API diff --git a/test/specs/library.spec.js b/test/specs/library.spec.js index 46f2312..7a96170 100644 --- a/test/specs/library.spec.js +++ b/test/specs/library.spec.js @@ -255,6 +255,66 @@ describe('createForm', () => { }) .then(done); }); + + it('is false for invalid arrays', async done => { + const validationSchema = yup + .array() + .of(yup.object().shape({x: yup.string().required()}).required()); + const initialValues = [{x: ''}]; + const formInstance = getInstance({validationSchema, initialValues}); + + formInstance + .handleSubmit() + .then(() => subscribeOnce(formInstance.isValid)) + .then(isValid => expect(isValid).toBe(false)) + .then(done); + }); + + it('is true for valid arrays', async done => { + const validationSchema = yup + .array() + .of(yup.object().shape({x: yup.string().required()}).required()); + const initialValues = [{x: 'foo'}]; + const formInstance = getInstance({validationSchema, initialValues}); + + formInstance + .handleSubmit() + .then(() => subscribeOnce(formInstance.isValid)) + .then(isValid => expect(isValid).toBe(true)) + .then(done); + }); + + it('is false for invalid nested arrays', async done => { + const validationSchema = yup.object().shape({ + xs: yup + .array() + .of(yup.object().shape({x: yup.string().required()}).required()), + }); + const initialValues = {xs: [{x: ''}]}; + const formInstance = getInstance({validationSchema, initialValues}); + + formInstance + .handleSubmit() + .then(() => subscribeOnce(formInstance.isValid)) + .then(isValid => expect(isValid).toBe(false)) + .then(done); + }); + + it('is true for valid nested arrays', async done => { + const validationSchema = yup.object().shape({ + xs: yup + .array() + .of(yup.object().shape({x: yup.string().required()}).required()), + }); + const initialValues = {xs: [{x: 'bar'}]}; + const formInstance = getInstance({validationSchema, initialValues}); + + formInstance + .handleSubmit() + .then(() => subscribeOnce(formInstance.isValid)) + .then(isValid => expect(isValid).toBe(true)) + .then(done); + }); }); describe('handleReset', () => { From 03482d6acf3e89e221378799520a70dc127e61f2 Mon Sep 17 00:00:00 2001 From: Larry Botha Date: Sat, 1 May 2021 14:59:50 +0200 Subject: [PATCH 2/2] style(various files): prettier --- lib/util.js | 8 +- test/specs/library.spec.js | 176 ++++++++++++++++++------------------- 2 files changed, 92 insertions(+), 92 deletions(-) diff --git a/lib/util.js b/lib/util.js index 8b37b36..a4c515f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,13 +1,13 @@ import {dequal as isEqual} from 'dequal/lite'; function subscribeOnce(observable) { - return new Promise((resolve) => { + return new Promise(resolve => { observable.subscribe(resolve)(); // immediately invoke to unsubscribe }); } function update(object, path, value) { - object.update((o) => { + object.update(o => { set(o, path, value); return o; }); @@ -54,7 +54,7 @@ function getErrorsFromSchema(initialValues, schema, errors = {}) { case schema[key].type === 'array': { const values = initialValues && initialValues[key] ? initialValues[key] : []; - errors[key] = values.map((value) => + errors[key] = values.map(value => getErrorsFromSchema( value, schema[key].innerType.fields, @@ -77,7 +77,7 @@ const deepEqual = isEqual; function assignDeep(object, value) { if (Array.isArray(object)) { - return object.map((o) => assignDeep(o, value)); + return object.map(o => assignDeep(o, value)); } const copy = {}; for (const key in object) { diff --git a/test/specs/library.spec.js b/test/specs/library.spec.js index 7a96170..57e97a3 100644 --- a/test/specs/library.spec.js +++ b/test/specs/library.spec.js @@ -7,11 +7,11 @@ const {createForm} = require('../../lib'); const chance = new Chance(); function nonEmpty(array) { - return array.filter((string) => string !== ''); + return array.filter(string => string !== ''); } function subscribeOnce(observable) { - return new Promise((resolve) => { + return new Promise(resolve => { observable.subscribe(resolve)(); // immediately invoke to unsubscribe }); } @@ -57,7 +57,7 @@ describe('createForm', () => { }); it('contains the current values which are accessed by subscription', () => { - subscribeOnce(instance.form).then((values) => { + subscribeOnce(instance.form).then(values => { expect(values.name).toBe(initialValues.name); expect(values.email).toBe(initialValues.email); expect(values.country).toBe(initialValues.country); @@ -141,8 +141,8 @@ describe('createForm', () => { expect(instance.errors.subscribe).toBeDefined(); }); - it('contains the current values which are accessed by subscription', (done) => { - subscribeOnce(instance.touched).then((touched) => { + it('contains the current values which are accessed by subscription', done => { + subscribeOnce(instance.touched).then(touched => { expect(touched.name).toBe(false); expect(touched.email).toBe(false); expect(touched.country).toBe(false); @@ -230,17 +230,17 @@ describe('createForm', () => { expect(instance.isValid.subscribe).toBeDefined(); }); - it('returns true if form is valid', async (done) => { + it('returns true if form is valid', async done => { instance .handleSubmit() .then(() => subscribeOnce(instance.isValid)) - .then((isValid) => { + .then(isValid => { expect(isValid).toBe(true); }) .then(done); }); - it('returns false if form is invalid', async (done) => { + it('returns false if form is invalid', async done => { await instance.form.set({ name: '', email: '', @@ -250,7 +250,7 @@ describe('createForm', () => { instance .handleSubmit() .then(() => subscribeOnce(instance.isValid)) - .then((isValid) => { + .then(isValid => { expect(isValid).toBe(false); }) .then(done); @@ -258,105 +258,105 @@ describe('createForm', () => { it('is false for invalid arrays', async done => { const validationSchema = yup - .array() - .of(yup.object().shape({x: yup.string().required()}).required()); + .array() + .of(yup.object().shape({x: yup.string().required()}).required()); const initialValues = [{x: ''}]; const formInstance = getInstance({validationSchema, initialValues}); formInstance - .handleSubmit() - .then(() => subscribeOnce(formInstance.isValid)) - .then(isValid => expect(isValid).toBe(false)) - .then(done); + .handleSubmit() + .then(() => subscribeOnce(formInstance.isValid)) + .then(isValid => expect(isValid).toBe(false)) + .then(done); }); it('is true for valid arrays', async done => { const validationSchema = yup - .array() - .of(yup.object().shape({x: yup.string().required()}).required()); + .array() + .of(yup.object().shape({x: yup.string().required()}).required()); const initialValues = [{x: 'foo'}]; const formInstance = getInstance({validationSchema, initialValues}); formInstance - .handleSubmit() - .then(() => subscribeOnce(formInstance.isValid)) - .then(isValid => expect(isValid).toBe(true)) - .then(done); + .handleSubmit() + .then(() => subscribeOnce(formInstance.isValid)) + .then(isValid => expect(isValid).toBe(true)) + .then(done); }); it('is false for invalid nested arrays', async done => { const validationSchema = yup.object().shape({ - xs: yup - .array() - .of(yup.object().shape({x: yup.string().required()}).required()), + xs: yup + .array() + .of(yup.object().shape({x: yup.string().required()}).required()), }); const initialValues = {xs: [{x: ''}]}; const formInstance = getInstance({validationSchema, initialValues}); formInstance - .handleSubmit() - .then(() => subscribeOnce(formInstance.isValid)) - .then(isValid => expect(isValid).toBe(false)) - .then(done); + .handleSubmit() + .then(() => subscribeOnce(formInstance.isValid)) + .then(isValid => expect(isValid).toBe(false)) + .then(done); }); it('is true for valid nested arrays', async done => { const validationSchema = yup.object().shape({ - xs: yup - .array() - .of(yup.object().shape({x: yup.string().required()}).required()), + xs: yup + .array() + .of(yup.object().shape({x: yup.string().required()}).required()), }); const initialValues = {xs: [{x: 'bar'}]}; const formInstance = getInstance({validationSchema, initialValues}); formInstance - .handleSubmit() - .then(() => subscribeOnce(formInstance.isValid)) - .then(isValid => expect(isValid).toBe(true)) - .then(done); + .handleSubmit() + .then(() => subscribeOnce(formInstance.isValid)) + .then(isValid => expect(isValid).toBe(true)) + .then(done); }); }); describe('handleReset', () => { it('resets form to initial state', () => { instance.form.set({name: 'foo'}); - subscribeOnce(instance.form).then((values) => + subscribeOnce(instance.form).then(values => expect(values.name).toBe('foo'), ); instance.handleReset(); - subscribeOnce(instance.form).then((form) => + subscribeOnce(instance.form).then(form => expect(form.name).toBe(form.name), ); }); it('resets errors to initial state', () => { instance.errors.set({name: 'name is required'}); - subscribeOnce(instance.errors).then((errors) => + subscribeOnce(instance.errors).then(errors => expect(errors.name).toBe('name is required'), ); instance.handleReset(); - subscribeOnce(instance.errors).then((errors) => + subscribeOnce(instance.errors).then(errors => expect(errors.name).toBe(''), ); }); it('resets touched to initial state', () => { instance.touched.set({name: true}); - subscribeOnce(instance.touched).then((touched) => + subscribeOnce(instance.touched).then(touched => expect(touched.name).toBe(true), ); instance.handleReset(); - subscribeOnce(instance.touched).then((touched) => + subscribeOnce(instance.touched).then(touched => expect(touched.name).toBe(false), ); }); }); describe('handleChange', () => { - it('updates the form when connected to change handler of input', async (done) => { + it('updates the form when connected to change handler of input', async done => { const email = chance.email(); const event = { target: { @@ -365,8 +365,8 @@ describe('createForm', () => { }, }; - await new Promise((resolve) => { - subscribeOnce(instance.form).then((form) => { + await new Promise(resolve => { + subscribeOnce(instance.form).then(form => { expect(form.email).toBe(initialValues.email); resolve(); @@ -376,11 +376,11 @@ describe('createForm', () => { instance .handleChange(event) .then(() => subscribeOnce(instance.form)) - .then((form) => expect(form.email).toBe(email)) + .then(form => expect(form.email).toBe(email)) .then(done); }); - it('uses checked value for checkbox inputs', (done) => { + it('uses checked value for checkbox inputs', done => { instance = getInstance({ initialValues: { terms: false, @@ -392,18 +392,18 @@ describe('createForm', () => { const event = { target: { name: 'terms', - getAttribute: (type) => 'checkbox', + getAttribute: type => 'checkbox', checked: true, }, }; instance .handleChange(event) .then(() => subscribeOnce(instance.form)) - .then((form) => expect(form.terms).toBe(true)) + .then(form => expect(form.terms).toBe(true)) .then(done); }); - it('runs field validation when validateSchema is provided', (done) => { + it('runs field validation when validateSchema is provided', done => { const invalid = 'invalid.email'; const event = { target: { @@ -415,13 +415,13 @@ describe('createForm', () => { instance .handleChange(event) .then(() => subscribeOnce(instance.errors)) - .then((errors) => + .then(errors => expect(errors.email).toBe('email must be a valid email'), ) .then(done); }); - it('runs field validation when validateFn is provided', (done) => { + it('runs field validation when validateFn is provided', done => { const invalid = 'invalid.email'; const event = { target: { @@ -433,19 +433,19 @@ describe('createForm', () => { initialValues: { email: '', }, - validate: (values) => { + validate: values => { let errs = {}; if (values.email === 'invalid.email') { errs.email = 'this email is invalid'; } return errs; }, - onSubmit: (values) => console.log(values), + onSubmit: values => console.log(values), }); instance .handleChange(event) .then(() => subscribeOnce(instance.errors)) - .then((errors) => expect(errors.email).toBe('this email is invalid')) + .then(errors => expect(errors.email).toBe('this email is invalid')) .then(done); }); @@ -464,7 +464,7 @@ describe('createForm', () => { expect(() => instance.handleChange(event)).not.toThrow(); }); - it('assigns empty string to field if validateFn returns undefined', (done) => { + it('assigns empty string to field if validateFn returns undefined', done => { const value = 'email@email.com'; const event = { target: { @@ -476,20 +476,20 @@ describe('createForm', () => { initialValues: { email: '', }, - validate: (values) => undefined, - onSubmit: (values) => console.log(values), + validate: values => {}, + onSubmit: values => console.log(values), }); instance .handleChange(event) .then(() => subscribeOnce(instance.errors)) - .then((errors) => expect(errors.email).toBe('')) + .then(errors => expect(errors.email).toBe('')) .then(done); }); }); describe('handleSubmit', () => { - it('validates form on submit when validationSchema is provided', async (done) => { + it('validates form on submit when validationSchema is provided', async done => { instance = getInstance({ initialValues: { name: '', @@ -498,7 +498,7 @@ describe('createForm', () => { }, }); - subscribeOnce(instance.errors).then((errors) => { + subscribeOnce(instance.errors).then(errors => { const errorValues = nonEmpty(Object.values(errors)); expect(errorValues.length).toBe(0); }); @@ -506,8 +506,8 @@ describe('createForm', () => { await instance .handleSubmit() .then(() => subscribeOnce(instance.errors)) - .then((errors) => nonEmpty(Object.values(errors))) - .then((errors) => expect(errors.length).toBe(3)); + .then(errors => nonEmpty(Object.values(errors))) + .then(errors => expect(errors.length).toBe(3)); await instance.form.set({ name: chance.name(), @@ -518,17 +518,17 @@ describe('createForm', () => { instance .handleSubmit() .then(() => subscribeOnce(instance.errors)) - .then((errors) => nonEmpty(Object.values(errors))) - .then((errors) => expect(errors.length).toBe(2)) + .then(errors => nonEmpty(Object.values(errors))) + .then(errors => expect(errors.length).toBe(2)) .then(done); }); - it('calls onSubmit when form is valid', (done) => { + it('calls onSubmit when form is valid', done => { instance = getInstance(); instance.handleSubmit().then(expect(onSubmit).toBeCalled).then(done); }); - it('does not call onSubmit when form is invalid', (done) => { + it('does not call onSubmit when form is invalid', done => { const onSubmit = jest.fn(); // create invalid form instance = getInstance({ @@ -549,13 +549,13 @@ describe('createForm', () => { expect(formValue.email).toBe(initialValues.email); expect(formValue.country).toBe(initialValues.country); - subscribeOnce($form).then((form) => { + subscribeOnce($form).then(form => { expect(form.name).toBe(initialValues.name); expect(form.email).toBe(initialValues.email); expect(form.country).toBe(initialValues.country); }); - subscribeOnce($errors).then((errors) => { + subscribeOnce($errors).then(errors => { expect(errors.name).toBe(''); expect(errors.email).toBe(''); expect(errors.country).toBe(''); @@ -564,7 +564,7 @@ describe('createForm', () => { }); describe('validateField', () => { - it('validate a field only by name', (done) => { + it('validate a field only by name', done => { instance = getInstance({ initialValues: { name: '', @@ -573,7 +573,7 @@ describe('createForm', () => { }, }); - subscribeOnce(instance.errors).then((errors) => { + subscribeOnce(instance.errors).then(errors => { const errorValues = nonEmpty(Object.values(errors)); expect(errorValues.length).toBe(0); }); @@ -581,13 +581,13 @@ describe('createForm', () => { instance .validateField('email') .then(() => subscribeOnce(instance.errors)) - .then((errors) => nonEmpty(Object.values(errors))) - .then((errors) => expect(errors.length).toBe(1)) + .then(errors => nonEmpty(Object.values(errors))) + .then(errors => expect(errors.length).toBe(1)) .then(done); }); }); describe('updateValidateField', () => { - it('update and validate a single field', (done) => { + it('update and validate a single field', done => { instance = getInstance({ initialValues: { name: '', @@ -598,7 +598,7 @@ describe('createForm', () => { instance.errors.set({name: 'name is required'}); - subscribeOnce(instance.errors).then((errors) => { + subscribeOnce(instance.errors).then(errors => { const errorValues = nonEmpty(Object.values(errors)); expect(errorValues.length).toBe(1); }); @@ -606,8 +606,8 @@ describe('createForm', () => { instance .updateValidateField('name', 'name') .then(() => subscribeOnce(instance.errors)) - .then((errors) => nonEmpty(Object.values(errors))) - .then((errors) => expect(errors.length).toBe(0)) + .then(errors => nonEmpty(Object.values(errors))) + .then(errors => expect(errors.length).toBe(0)) .then(done); }); }); @@ -623,14 +623,14 @@ describe('createForm', () => { }); }); - it('when a is true, b is required', (done) => { + it('when a is true, b is required', done => { instance = getInstance({ initialValues: { wantsSomething: true, }, }); - subscribeOnce(instance.errors).then((errors) => { + subscribeOnce(instance.errors).then(errors => { const errorValues = nonEmpty(Object.values(errors)); expect(errorValues.length).toBe(0); }); @@ -638,7 +638,7 @@ describe('createForm', () => { instance .handleSubmit() .then(() => subscribeOnce(instance.errors)) - .then((errors) => { + .then(errors => { const errorValues = nonEmpty(Object.values(errors)); expect(errorValues.length).toBe(1); expect(errors.what).toBe('what is a required field'); @@ -646,14 +646,14 @@ describe('createForm', () => { .then(done); }); - it('when a is false, b is not required', (done) => { + it('when a is false, b is not required', done => { instance = getInstance({ initialValues: { wantsSomething: false, }, }); - subscribeOnce(instance.errors).then((errors) => { + subscribeOnce(instance.errors).then(errors => { const errorValues = nonEmpty(Object.values(errors)); expect(errorValues.length).toBe(0); }); @@ -661,7 +661,7 @@ describe('createForm', () => { instance .handleSubmit() .then(() => subscribeOnce(instance.errors)) - .then((errors) => { + .then(errors => { const errorValues = nonEmpty(Object.values(errors)); expect(errorValues.length).toBe(0); }) @@ -679,7 +679,7 @@ describe('createForm', () => { }); }); - it("is invalid when passwords don't match", (done) => { + it("is invalid when passwords don't match", done => { instance = getInstance({ initialValues: { password: 'a', @@ -687,7 +687,7 @@ describe('createForm', () => { }, }); - subscribeOnce(instance.errors).then((errors) => { + subscribeOnce(instance.errors).then(errors => { const errorValues = nonEmpty(Object.values(errors)); expect(errorValues.length).toBe(0); }); @@ -695,7 +695,7 @@ describe('createForm', () => { instance .handleSubmit() .then(() => subscribeOnce(instance.errors)) - .then((errors) => { + .then(errors => { const errorValues = nonEmpty(Object.values(errors)); expect(errorValues.length).toBe(1); expect(errors.passwordConfirmation).toBe("Passwords don't match!"); @@ -703,7 +703,7 @@ describe('createForm', () => { .then(done); }); - it('is valid when passwords match', (done) => { + it('is valid when passwords match', done => { instance = getInstance({ initialValues: { password: 'a', @@ -711,7 +711,7 @@ describe('createForm', () => { }, }); - subscribeOnce(instance.errors).then((errors) => { + subscribeOnce(instance.errors).then(errors => { const errorValues = nonEmpty(Object.values(errors)); expect(errorValues.length).toBe(0); }); @@ -719,7 +719,7 @@ describe('createForm', () => { instance .handleSubmit() .then(() => subscribeOnce(instance.errors)) - .then((errors) => { + .then(errors => { const errorValues = nonEmpty(Object.values(errors)); expect(errorValues.length).toBe(0); })