Skip to content

Commit

Permalink
Implement explanation of collOf
Browse files Browse the repository at this point in the history
  • Loading branch information
Maurits Rijk committed May 24, 2017
1 parent 2c465f8 commit 7cc1ca9
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 23 deletions.
17 changes: 15 additions & 2 deletions lib/collOf.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {gen: tcg} = require('testcheck');

const {invalidString} = require('./conform');
const {spec: specize} = require('./def');
const explainData = require('./explainData');
const {gen} = require('./gen');
const {checkCount} = require('./util/helper');
const isValid = require('./isValid');
Expand All @@ -13,10 +14,10 @@ const describe = require('./util/describe');
function collOf(predicate, options = {}) {
const {kind = () => true, distinct = false, into} = options;
const uniq = value => !distinct || isUnique(value);
const spec = specize(predicate);

return {
conform: value => {
const spec = specize(predicate);
const result = kind(value) && uniq(value) && checkCount(value, options)
&& _.every(value, v => isValid(spec, v));
return result ? transform(value, into) : invalidString;
Expand All @@ -26,10 +27,22 @@ function collOf(predicate, options = {}) {
const count = options.count || 10;
return tcg.array(gen(predicate), {size: count});
}),
describe: () => [collOf.name, ...describe([predicate]), ...describe(_.flatten(_.toPairs(options)))]
describe: () => [collOf.name, ...describe([predicate]), ...describe(_.flatten(_.toPairs(options)))],
explain: function*(values, {via}) {
yield* explainInvalid(values, predicate, via);
}
};
}

function* explainInvalid([v, ...rest], predicate, via, index = 0) {
if (v) {
if (!isValid(predicate, v)) {
yield explainData(predicate, v, {via, in: [index]}).problems[0];
}
yield* explainInvalid(rest, predicate, via, ++index);
}
}

function isUnique(value) {
return _.uniq(value).length === value.length;
}
Expand Down
8 changes: 4 additions & 4 deletions lib/def.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ function createArraySpec(predicate) {
return {
conform: (value, unwrap = false) => conform(value, unwrap, _.partial(_.includes, predicate)),
describe: () => predicate,
explain: function*(value) {
explain: function*(value, options) {
if (!_.includes(predicate, value)) {
yield getExplanation(value);
yield getExplanation(value, options);
}
},
gen: () => tcg.null.then(() => tcg.return(_.sample(predicate)))
Expand All @@ -43,13 +43,13 @@ function createFunctionSpec(predicate, gen) {
};
}

function getExplanation(value, {path, pred = unknownString, via}) {
function getExplanation(value, {path, pred = unknownString, via, in:_in = []}) {
return {
path,
pred,
val: value,
via,
'in': []
'in': _in
};
}

Expand Down
4 changes: 2 additions & 2 deletions lib/explainData.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ const _ = require('lodash');
const {spec: specize} = require('./def');
const {isString} = require('./utils');

function explainData(spec, value, {path = [], via = []} = {}) {
function explainData(spec, value, {path = [], via = [], in:_in = []} = {}) {
const lookup = specize(spec);
if (lookup) {
const name = specName(spec);
const viaOut = _.isUndefined(name) ? via : [name, ...via];
const problems = [...lookup.explain(value, {path, pred: name, via: viaOut})];
const problems = [...lookup.explain(value, {path, pred: name, via: viaOut, in: _in})];
return _.isEmpty(problems) ? null : {problems};
} else {
throw new Error(`Unable to resolve spec ${spec}`);
Expand Down
53 changes: 38 additions & 15 deletions test/collOf.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,48 @@ describe('Test the collOf function', () => {
s.def('::vnum3', s.collOf(isNumber, {kind: isVector, count: 3, distinct: true}));
});

it('should return a spec for a collection', () => {
expect(s.isValid('::vnum3', [1, 2, 3])).to.be.true;
describe('should handle valid input', () => {
it('should return a spec for a collection', () => {
expect(s.isValid('::vnum3', [1, 2, 3])).to.be.true;
});

it('explainData should return null', () => {
expect(s.explainData('::vnum3', [1, 2, 3])).to.be.null;
});
});

it('should fail on invalid type (not a vector)', () => {
expect(s.isValid('::vnum3', {x: 1, y: 2, z: 3})).to.be.false;
describe('should reject invalid input', () => {
it('should fail on invalid type (not a vector)', () => {
expect(s.isValid('::vnum3', {x: 1, y: 2, z: 3})).to.be.false;
});

it('should fail on non-distinct data', () => {
expect(s.isValid('::vnum3', [1, 1, 1])).to.be.false;
});

it('should fail on invalid data', () => {
expect(s.isValid('::vnum3', [1, 2, 'foo'])).to.be.false;
});

it('explainData should report about wrong type', () => {
expect(s.explainData('::vnum3', [1, 2, 'foo'])).to.eql({
problems: [
{
path: [],
pred: 'isNumber',
val: 'foo',
via: ['::vnum3'],
'in': [2]
}
]
});
});

it('should fail on invalid length', () => {
expect(s.isValid('::vnum3', [1, 2, 3, 4])).to.be.false;
});
});

it('should fail on non-distinct data', () => {
expect(s.isValid('::vnum3', [1, 1, 1])).to.be.false;
});

it('should fail on invalid data', () => {
expect(s.isValid('::vnum3', [1, 2, 'foo'])).to.be.false;
});

it('should fail on invalid length', () => {
expect(s.isValid('::vnum3', [1, 2, 3, 4])).to.be.false;
});

it('should conform to a valid value', () => {
expect(s.conform(s.collOf(isNumber), [5, 10, 2])).to.eql([5, 10, 2]);
Expand Down
12 changes: 12 additions & 0 deletions test/explainData.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ describe('Test the explainData function', () => {
expect(s.explainData([1, 2], 2)).to.equal(null);
});

it('should report a problem when the value is not in the array', () => {
expect(s.explainData([1, 2], 3)).to.eql({
problems: [{
path: [],
pred: unknownString,
val: 3,
via: [],
'in': []
}]
});
});

it('should return an object explaining why the spec fails', () => {
expect(s.explainData(isString, 1)).to.eql({
problems: [{
Expand Down

0 comments on commit 7cc1ca9

Please sign in to comment.