Skip to content

Commit d21d08b

Browse files
author
Larry Botha
committed
fix(error initialisation): ensure array values in initialValues are represented in errors
re #79
1 parent 1a7be0f commit d21d08b

File tree

3 files changed

+70
-43
lines changed

3 files changed

+70
-43
lines changed

lib/create-form.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const createForm = (config) => {
1919
values: () => util.cloneDeep(initialValues),
2020
errors: () =>
2121
validationSchema
22-
? util.getErrorsFromSchema(validationSchema.fields)
22+
? util.getErrorsFromSchema(initialValues, validationSchema.fields)
2323
: util.assignDeep(initialValues, NO_ERROR),
2424
touched: () => util.assignDeep(initialValues, !IS_TOUCHED),
2525
};

lib/util.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,31 @@ function getValues(object) {
3535
return result;
3636
}
3737

38-
function getErrorsFromSchema(schema, errors = {}) {
38+
// TODO: refactor this so as not to rely directly on yup's API
39+
// This should use dependency injection, with a default callback which may assume
40+
// yup as the validation schema
41+
function getErrorsFromSchema(initialValues, schema, errors = {}) {
3942
for (const key in schema) {
4043
switch (true) {
4144
case schema[key].type === 'object' && !isEmpty(schema[key].fields): {
4245
errors[key] = getErrorsFromSchema(
46+
initialValues[key],
4347
schema[key].fields,
4448
Object.assign({}, errors[key]),
4549
);
4650
break;
4751
}
4852

4953
case schema[key].type === 'array': {
50-
errors[key] = [];
54+
const values =
55+
initialValues && initialValues[key] ? initialValues[key] : [];
56+
errors[key] = values.map((value) =>
57+
getErrorsFromSchema(
58+
value,
59+
schema[key].innerType.fields,
60+
Object.assign({}, errors[key]),
61+
),
62+
);
5163
break;
5264
}
5365

test/library.spec.js

Lines changed: 55 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -69,53 +69,68 @@ describe('createForm', () => {
6969
expect(instance.errors.subscribe).toBeDefined();
7070
});
7171

72-
it('should match the shape of validationSchema', (done) => {
73-
const initialValues = {
74-
objectWithPrimitives: {foo: '', bar: '', baz: ''},
75-
arrayOfPrimitives: [],
76-
arrayOfObjects: [],
77-
objectWithMixedTypes: {
78-
foo: '',
79-
arrayOfPrimitives: [],
80-
},
81-
};
82-
const validationSchema = yup.object().shape({
83-
primitive: yup.string().required(),
84-
arrayOfPrimitives: yup.array().of(yup.string()).required(),
85-
objectWithMixedTypes: yup.object().shape({
86-
foo: yup.string().required(),
87-
arrayOfPrimitives: yup.array().of(yup.string()).required(),
88-
}),
89-
objectWithPrimitives: yup.object().shape({
72+
describe('using validationSchema', () => {
73+
it('initialises primitive properties to empty strings', async () => {
74+
const initialValues = {foo: 'bar', baz: 'quux', nested: {foo: 'bar'}};
75+
const validationSchema = yup.object().shape({
9076
foo: yup.string().required(),
91-
bar: yup.string().required(),
92-
}),
93-
arrayOfObjects: yup.array().of(
94-
yup.object({
95-
foo: yup.string().required(),
96-
bar: yup.string().required(),
97-
}),
98-
),
99-
});
100-
instance = getInstance({
101-
initialValues,
102-
validationSchema,
77+
baz: yup.string().required(),
78+
nested: yup.object().shape({foo: yup.string().required()}),
79+
});
80+
const instance = getInstance({initialValues, validationSchema});
81+
const $errors = await subscribeOnce(instance.errors);
82+
83+
expect($errors.foo).toBe('');
84+
expect($errors.baz).toBe('');
85+
expect($errors.nested.foo).toBe('');
10386
});
10487

105-
subscribeOnce(instance.errors).then((errors) => {
106-
expect(errors.primitive).toBe('');
107-
expect(errors.arrayOfPrimitives).toEqual([]);
88+
it('does not initialise initialValues not defined in schema', async () => {
89+
const initialValues = {notInSchema: ''};
90+
const validationSchema = yup
91+
.object()
92+
.shape({foo: yup.string().required()});
93+
const instance = getInstance({initialValues, validationSchema});
94+
const $errors = await subscribeOnce(instance.errors);
95+
96+
expect($errors.notInSchema).toBeUndefined();
97+
});
10898

109-
expect(errors.objectWithPrimitives.foo).toBe('');
110-
expect(errors.objectWithPrimitives.bar).toBe('');
111-
expect(errors.objectWithPrimitives.baz).not.toBeDefined();
99+
it('contains an empty array when initialValues property is empty', async () => {
100+
const initialValues = {foo: []};
101+
const validationSchema = yup.object().shape({
102+
stringArray: yup.array().of(yup.string().required()),
103+
objectArray: yup
104+
.array()
105+
.of(
106+
yup
107+
.object()
108+
.shape({foo: yup.array().of(yup.string().required())}),
109+
),
110+
nested: yup
111+
.object()
112+
.shape({foo: yup.array().of(yup.string().required())}),
113+
});
114+
const instance = getInstance({initialValues, validationSchema});
115+
const $errors = await subscribeOnce(instance.errors);
112116

113-
expect(errors.objectWithMixedTypes.foo).toBe('');
114-
expect(errors.objectWithMixedTypes.arrayOfPrimitives).toEqual([]);
117+
expect($errors.stringArray).toEqual([]);
118+
expect($errors.objectArray).toEqual([]);
119+
expect($errors.nested.foo).toEqual([]);
120+
});
115121

116-
expect(errors.arrayOfObjects).toEqual([]);
122+
it('preserves number of initial values for array properties', async () => {
123+
const initialValues = {foo: [{name: 'foo'}, {name: 'bar'}]};
124+
const validationSchema = yup.object().shape({
125+
foo: yup
126+
.array()
127+
.of(yup.object().shape({name: yup.string().required()})),
128+
});
129+
const instance = getInstance({initialValues, validationSchema});
130+
const $errors = await subscribeOnce(instance.errors);
131+
const $form = await subscribeOnce(instance.form);
117132

118-
done();
133+
expect($errors.foo.length).toEqual($form.foo.length);
119134
});
120135
});
121136
});

0 commit comments

Comments
 (0)