UNPKG

validation-algebra

Version:

Semigroup + Monad for doing validations in Javascript

180 lines (154 loc) 5.9 kB
var assert = require('chai').assert; var claire = require('claire'); var gens = claire.data; var K = function(x){return function(y){return x}}; var I = function(x){return x}; var B = function(f){return function(g){return function(x){return f(g(x))}}}; var Validation = require('../validation'); var Success = Validation.Success; var Failure = Validation.Failure; var val = claire.sized(K(10), gens.Array(gens.Str)); var successGen = claire.transform(Success, val); var failureGen = claire.transform(Failure, val); var validGen = claire.choice(successGen, failureGen); var Id = require('fantasy-land/id').of var Some = require('data.maybe').Just; var some = function(x){ return new Some(x); }; var none = new (require('data.maybe').Nothing); Array.of = Array.of || function(x){ return [x] }; describe('concat', function(){ it('is associative', claire.forAll(validGen, validGen, validGen).satisfy(function(v1, v2, v3){ assert.deepEqual(v1.concat(v2.concat(v3)), v1.concat(v2).concat(v3)); return true; }).asTest() ); it('chooses the last of two success values', claire.forAll(successGen, successGen).satisfy(function(s1, s2){ assert.deepEqual(s1.concat(s2), s2); return true; }).asTest() ); it('returns a failure over a success', claire.forAll(successGen, failureGen).satisfy(function(s, f){ assert.deepEqual(s.concat(f), f); assert.deepEqual(f.concat(s), f); return true; }).asTest() ); it('concats two failure values', claire.forAll(failureGen, failureGen).satisfy(function(f1, f2){ assert.deepEqual(f1.concat(f2).value, f1.value.concat(f2.value)); return true; }).asTest() ); }); describe('of', function(){ it('is Success', function(){ assert.equal(Validation.of, Success) }); }); var drop1 = function(a){ return a.length ? a.slice(1) : a; } var isEmpty = function(a){ return !a.length; } describe('map', function(){ it('obeys identity', claire.forAll(validGen).satisfy(function(v){ assert.deepEqual(v.map(I), v); return true; }).asTest()); it('obeys composition', claire.forAll(validGen).satisfy(function(v){ assert.deepEqual(v.map(B(isEmpty)(drop1)), v.map(drop1).map(isEmpty)); return true; }).asTest()); it('maps the inner value of a success', claire.forAll(successGen).satisfy(function(s){ assert.deepEqual(s.map(drop1).value, drop1(s.value)); return true; }).asTest() ); it('is no-op with a failure', claire.forAll(failureGen).satisfy(function(f){ assert.deepEqual(f.map(drop1).value, f.value); return true; }).asTest()); }); describe('ap', function(){ it('obeys homomorphism', claire.forAll(val).satisfy(function(x){ assert.deepEqual(Success(drop1).ap(Success(x)), Success(drop1(x))); return true; }).asTest()); it('obeys interchange', //wrap success and failure in arrays, otherwise claire treats them as //generators and invokes them with a size hint to get a value. claire.forAll(claire.choice([Success], [Failure]), val).satisfy(function(c, x){ var u = c[0](drop1); assert.deepEqual(u.ap(Success(x)), Success(function(f){return f(x)}).ap(u)); return true; }).asTest() ); }); describe('chain', function(){ var mDrop1 = B(Success)(drop1); var mEmpty = B(Success)(isEmpty); it('is associative', claire.forAll(validGen).satisfy(function(v){ assert.deepEqual(v.chain(mDrop1).chain(mEmpty), v.chain(function(x){return mDrop1(x).chain(mEmpty)}) ); return true; }).asTest()); it('has left identity', claire.forAll(val).satisfy(function(x){ assert.deepEqual(Success(x).chain(mDrop1), mDrop1(x)); return true; }).asTest()); it('has right identity', claire.forAll(validGen).satisfy(function(v){ assert.deepEqual(v.chain(Success), v); return true; }).asTest()); }); describe('traverse', function(){ function arrayToMaybe(a){return a.length ? some(a[0]) : none}; function maybeToArray(m){return m.isJust ? [m.value] : [] }; function idToArray(i){return [i.value]}; function idToMaybe(i){return some(i.value)}; function idToValid(i){return Success(i.value)}; function arrayToValid(a){return a.length ? Success(a[0]) : Failure(['empty'])} function maybeToValid(m){return m.isJust ? Success(m.value) : Failure(['empty']); }; var transformGen = claire.choice( {t: arrayToMaybe, i: Array.of, o: some}, {t: maybeToArray, i: some, o: Array.of}, {t: idToArray, i: Id, o: Array.of}, {t: idToMaybe, i: Id, o: some}, {t: idToValid, i: Id, o: Success}, {t: arrayToValid, i: Array.of, o: Success}, {t: maybeToValid, i: some, o: Success} ); //wrapping because of generator issue in interchange comment var fGen = claire.choice([drop1], [isEmpty], [arrayToMaybe], [function(a){return a.length;}]); it('obeys naturality', claire.forAll(transformGen, fGen, validGen).satisfy(function(t, f, v){ var apF = B(t.i)(f[0]); assert.deepEqual(t.t(v.traverse(apF, t.i)), v.traverse(B(t.t)(apF), t.o)); return true; }).asTest() ); it('obeys identity', claire.forAll(validGen).satisfy(function(v){ assert.deepEqual(v.traverse(Id, Id), Id(v)); return true; }).asTest()); var _ = require('lodash'); function ComposeF(nestedFs){ if(this instanceof ComposeF){ this.value = nestedFs; } else { return new ComposeF(nestedFs) } }; ComposeF.prototype.map = function(f){ return new ComposeF(this.value.map(function(x){return x.map(f)})); }; it('obeys composition', claire.forAll(validGen).satisfy(function(v){ var a = v.traverse(function(a){return ComposeF(drop1(a).map(some))}, function(x){return ComposeF(Array.of(some(x)))} ); var b = ComposeF(v.traverse(drop1, Array.of).map(function(x){return x.traverse(some, some)})); assert.deepEqual(a, b); return true; }).asTest()); });