Skip to content
Permalink
Browse files

feat: support nested object validation

  • Loading branch information...
jeffhandley committed Dec 18, 2017
1 parent 5a48d78 commit 789f9e2170c3fa72870f4129bdf8ae992166b9a0
Showing with 339 additions and 11 deletions.
  1. +28 −11 packages/strickland/src/strickland.js
  2. +311 −0 packages/strickland/test/strickland.spec.js
@@ -11,6 +11,18 @@ export function isValid(result) {
}

function isValidObjectResult(result) {
if (typeof result.results === 'object') {
const props = Object.keys(result.results);

for (let i = 0; i < props.length; i++) {
if(!isValid(result.results[props[i]])) {
return false;
}
}

return true;
}

return !!result.isValid;
}

@@ -21,7 +33,7 @@ export default function validate(rules, value) {
result = rules(value);
} else if (Array.isArray(rules)) {
result = validateRulesArray(rules, value);
} else if (typeof rules === 'object') {
} else if (typeof rules === 'object' && rules) {
result = validateRulesObject(rules, value);
} else {
throw 'unrecognized validation rules: ' + (typeof rules)
@@ -50,18 +62,23 @@ function validateRulesArray(rules, value) {
}

function validateRulesObject(rules, value) {
const props = Object.keys(rules);
let results = {};
if (typeof value === 'object' && value) {
const props = Object.keys(rules);
let results = {};
let isValid = true;

for (let i = 0; i < props.length; i++) {
results = {
...results,
[props[i]]: validate(rules[props[i]], value[props[i]])
};
}

for (let i = 0; i < props.length; i++) {
results = {
...results,
[props[i]]: validate(rules[props[i]], value[props[i]])
};
// Wrap the results in an object to be nested in the outer result
return {results};
}

// Wrap the results in an object to be nested in the outer result
return {results};
return true;
}

function convertResult(result, value) {
@@ -96,7 +113,7 @@ function convertStringResult(result) {
function convertObjectResult(result, value) {
return {
...result,
isValid: !!result.isValid,
isValid: isValid(result),
value
};
}
@@ -2,6 +2,7 @@ import expect from 'expect';
import validate, {isValid} from '../src/strickland';
import required from '../src/required';
import minLength from '../src/minLength';
import rangeLength from '../src/rangeLength';

describe('validate', () => {
describe('throws', () => {
@@ -281,6 +282,316 @@ describe('validate', () => {
}
});
});

it('returns valid results', () => {
const result = validate(rules, value);
expect(result.isValid).toBe(true);
});

it('returns invalid results', () => {
const invalidValue = {
firstName: 'First',
lastName: 'L'
};

const result = validate(rules, invalidValue);
expect(result.isValid).toBe(false);
});
});

describe('with nested rules objects', () => {
const rules = {
name: required(),
homeAddress: {
street: required(),
city: required(),
state: [required(), rangeLength(2, 2)]
},
workAddress: {
street: {
number: required(),
name: required()
},
city: required(),
state: [required(), rangeLength(2, 2)]
}
};

it('returns results in the shape of the rules', () => {
const value = {
name: 'Name',
homeAddress: {
street: '9303 Lyon Dr.',
city: 'Hill Valley',
state: 'CA'
},
workAddress: {
street: {
number: 456,
name: 'Front St.'
},
city: 'City',
state: 'ST'
}
};

const result = validate(rules, value);

expect(result.results).toMatchObject({
name: {isValid: true},
homeAddress: {
isValid: true,
results: {
street: {isValid: true},
city: {isValid: true},
state: {isValid: true}
}
},
workAddress: {
isValid: true,
results: {
street: {
isValid: true,
results: {
number: {isValid: true},
name: {isValid: true}
}
},
city: {isValid: true},
state: {isValid: true}
}
}
});
});

it('parses values', () => {
const value = {
name: ' Name ',
homeAddress: {
street: ' 9303 Lyon Dr. ',
city: ' Hill Valley ',
state: ' CA '
},
workAddress: {
street: {
number: 456,
name: ' Front St. '
},
city: ' City ',
state: ' ST '
}
};

const result = validate(rules, value);

expect(result.results).toMatchObject({
name: {
parsedValue: 'Name'
},
homeAddress: {
results: {
street: {
parsedValue: '9303 Lyon Dr.'
},
city: {
parsedValue: 'Hill Valley'
},
state: {
parsedValue: 'CA'
}
}
},
workAddress: {
results: {
street: {
results: {
number: {
parsedValue: '456'
},
name: {
parsedValue: 'Front St.'
}
}
},
city: {
parsedValue: 'City'
},
state: {
parsedValue: 'ST'
}
}
}
});
});

it('returns valid results', () => {
const value = {
name: 'Name',
homeAddress: {
street: ' 9303 Lyon Dr. ',
city: ' Hill Valley ',
state: ' CA '
},
workAddress: {
street: {
number: 456,
name: ' Front St. '
},
city: ' City ',
state: ' ST '
}
};

const result = validate(rules, value);

expect(result).toMatchObject({
isValid: true,
results: {
homeAddress: {isValid: true},
workAddress: {
isValid: true,
results: {
street: {isValid: true}
}
}
}
});
});

it('returns invalid results (validating all properties)', () => {
const value = {
name: '',
homeAddress: {
street: '9303 Lyon Dr.',
city: 'Hill Valley',
state: ''
},
workAddress: {
street: {
number: '',
name: ''
},
city: '',
state: 'ST'
}
};

const result = validate(rules, value);

expect(result).toMatchObject({
isValid: false,
results: {
homeAddress: {
isValid: false
},
workAddress: {
isValid: false,
results: {
street: {
isValid: false,
results: {
name: {isValid: false}
}
}
}
}
}
});
});
});

describe('when properties are missing from the value', () => {
const rules = {
name: required(),
homeAddress: {
street: required(),
city: required(),
state: [required(), rangeLength(2, 2)]
},
workAddress: {
street: {
number: required(),
name: required()
},
city: required(),
state: [required(), rangeLength(2, 2)]
}
};

it('validates missing scalar values', () => {
const value = {
homeAddress: {},
workAddress: {
street: {}
}
};

const result = validate(rules, value);

expect(result).toMatchObject({
isValid: false,
results: {
name: {isValid: false},
homeAddress: {
isValid: false,
results: {
street: {isValid: false},
city: {isValid: false},
state: {isValid: false}
}
},
workAddress: {
isValid: false,
results: {
street: {
isValid: false,
results: {
number: {isValid: false},
name: {isValid: false}
}
},
city: {isValid: false},
state: {isValid: false}
}
}
}
});
});

it('sets missing top-level object properties to valid', () => {
const value = {
workAddress: {
street: {}
}
};

const result = validate(rules, value);
expect(result.results.homeAddress.isValid).toBe(true);
});

it('does not create results for missing top-level object properties', () => {
const value = {};
const result = validate(rules, value);

expect(result.results).not.toHaveProperty('homeAddress.results');
});

it('sets missing nested object properties to valid', () => {
const value = {
workAddress: {}
};

const result = validate(rules, value);

expect(result.results.workAddress.results.street.isValid).toBe(true);
});

it('does not create results for missing nested object properties', () => {
const value = {};
const result = validate(rules, value);

expect(result.results).not.toHaveProperty('workAddress.results.street.results');
});
});
});
});

0 comments on commit 789f9e2

Please sign in to comment.
You can’t perform that action at this time.