Skip to content

Commit

Permalink
Switch to property-based testing
Browse files Browse the repository at this point in the history
  • Loading branch information
earldouglas committed Jan 15, 2015
1 parent 3585fc5 commit 36e42f3
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 165 deletions.
2 changes: 1 addition & 1 deletion lib/option.js
@@ -1,7 +1,7 @@
'use strict';

function option(value) {
if (value != null) {
if (value || value === false || value === 0 || value === '') {
return {
empty : false,
map : function(f) { return option(f(value)); },
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -33,7 +33,8 @@
"bluebird": "2.2.2",
"coveralls": "2.11.2",
"jscoverage": "0.5.9",
"nodeunit": "0.9.0"
"nodeunit": "0.9.0",
"jsverify": "0.4.3"
},
"keywords": [
"functional",
Expand Down
19 changes: 11 additions & 8 deletions test/array_test.js
Expand Up @@ -2,17 +2,20 @@

var teep = require('../teep.js');
var array = teep.array;
var jsc = require('jsverify');

exports['array'] = {
exports.array = {
'contains': function(test) {
test.expect(5);

test.equal(array.contains([1,2,3], 0), false);
test.equal(array.contains([1,2,3], 1), true);
test.equal(array.contains([1,2,3], 2), true);
test.equal(array.contains([1,2,3], 3), true);
test.equal(array.contains([1,2,3], 4), false);
jsc.assert(jsc.forall('array number', 'number', function (xs, x) {
var inXs = false;
xs.forEach(function (x0) { inXs = inXs || x === x0; });

var ys = xs.concat(x);
var inYs = false;
ys.forEach(function (y0) { inYs = inYs || x === y0; });
return array.contains(xs, x) === inXs &&
array.contains(ys, x);
}));
test.done();
},
};
104 changes: 36 additions & 68 deletions test/function_test.js
Expand Up @@ -4,26 +4,7 @@ if (!global.Promise) { global.Promise = require('bluebird'); }

var teep = require('../teep.js');
var fn = teep.fn;

function add(x, y) {
return x + y;
}

function mathemagic(x, y, z) {
return x * (y + z);
}

function pi(n) {
var sum = 0;
for (var k = 0; k < n; k++) {
sum = sum + Math.pow(-1, k) / (2 * k + 1);
}
return 4 * sum;
}

function round2(x) {
return Math.round(x * 100) / 100;
}
var jsc = require('jsverify');

function time(f) {
var start = (new Date()).getTime();
Expand All @@ -35,66 +16,53 @@ function time(f) {
};
}

exports['fn'] = {
'compose': function(test) {
test.expect(2);

function inc(x) {
return x + 1;
function slow(f) {
return function () {
var h = 0;
for (var i = 0; i < 1000000; i++) {
h = h + 1;
}
return f.apply(null, arguments);
};
}

function square(x) {
return x * x;
}

var nine = fn.compose(square, inc)(2);
test.equal(nine, 9);

var five = fn.compose(inc, square)(2);
test.equal(five, 5);

exports.fn = {
'compose': function(test) {
jsc.assert(jsc.forall(
'number -> number', 'number -> number', 'number',
function (f, g, x) {
return fn.compose(f, g)(x) === f(g(x));
}
));
test.done();
},
'curry': function(test) {
test.expect(2);

var add2 = fn.curry(add)(2);
var five = add2(3);
test.equal(five, 5);

var fortyTwo = fn.curry(mathemagic)(2)(20)(1);
test.equal(fortyTwo, 42);

jsc.assert(jsc.forall(
'number -> number -> number', 'number', 'number',
function (f, x, y) {
var uncurried = function(x, y) { return f(x, y); };
return fn.curry(uncurried)(x)(y) === f(x, y);
}
));
test.done();
},
'memoize': function(test) {
test.expect(3);

var piMemo = fn.memoize(pi);

var N = 1000000;
var est1 = time(function() { return piMemo(N); });
var est2 = time(function() { return piMemo(N); });

test.equal(round2(est1.result), 3.14);
test.equal(round2(est2.result), 3.14);
test.equal((est1.time > (est2.time + 1) * 10), true);

jsc.assert(jsc.forall( 'number -> number', 'number', function (f, x) {
var memoized = fn.memoize(slow(f));
var y1 = time(function() { return memoized(x); });
var y2 = time(function() { return memoized(x); });
return f(x) === y1.result && f(x) === y2.result && y1.time > y2.time;
}));
test.done();
},
'lazy': function(test) {
test.expect(3);

var N = 1000000;
var piLazy = fn.lazy(pi)(N);

var est1 = time(function() { return piLazy.get(); });
var est2 = time(function() { return piLazy.get(); });

test.equal(round2(est1.result), 3.14);
test.equal(round2(est2.result), 3.14);
test.equal((est1.time > (est2.time + 1) * 10), true);

jsc.assert(jsc.forall( 'number -> number', 'number', function (f, x) {
var lazied = fn.lazy(slow(f))(x);
var y1 = time(function() { return lazied.get(); });
var y2 = time(function() { return lazied.get(); });
return f(x) === y1.result && f(x) === y2.result && y1.time > y2.time;
}));
test.done();
},
};
28 changes: 2 additions & 26 deletions test/list_test.js
Expand Up @@ -2,33 +2,9 @@

var teep = require('../teep.js');
var list = teep.list;
var jsc = require('jsverify');

exports['list'] = {
'perf': function(test) {
test.expect(0);

var iterations = 1000000;

console.log('');

console.time('push');
var xs = [];
for (var i = 0; i < iterations; i++) {
xs.push(i);
}
console.timeEnd('push');

console.time('cons');
var ys = list();
for (var j = 0; j < iterations; j++) {
ys = list(i, ys);
}
console.timeEnd('cons');

console.log('');

test.done();
},
exports.list = {
'length': function(test) {
test.expect(3);

Expand Down
70 changes: 41 additions & 29 deletions test/option_test.js
Expand Up @@ -2,49 +2,61 @@

var teep = require('../teep.js');
var option = teep.option;
var jsc = require('jsverify');

function inc(x) { return 1 + x; }
function get(x) {
var y = null;
x.map(function (z) { y = z; });
return y;
}

exports['option'] = {
'option()': function(test) {
test.expect(2);
test.equal(option().empty, true, 'should be empty.');
test.equal(option().toString(), 'none()', 'should be none().');
exports.option = {
'option()': function (test) {
test.strictEqual(get(option()), null);
test.done();
},
'option(42)': function(test) {
test.expect(2);
test.equal(option(42).empty, false, 'should not be empty.');
test.equal(option(42).toString(), 'some(42)', 'should be some(42).');
'option(<falsy>)': function (test) {
jsc.assert(jsc.forall('falsy', function (x) {
var o = option(x);
var y = get(o);
return o.empty || y === false || y === 0 || y === '';
}));
test.done();
},
'option(false)': function(test) {
test.expect(1);
test.equal(option(false).toString(), 'some(false)', 'should not be empty.');
'option(<value>)': function (test) {
test.strictEqual(get(option()), null);
jsc.assert(jsc.forall('value', function (x) {
return get(option(x)) === x;
}));
test.done();
},
'option(null)': function(test) {
test.expect(1);
test.equal(option(null).toString(), 'none()', 'should be empty.');
'toString()': function (test) {
jsc.assert(jsc.forall('number', function (x) {
return option().toString() === 'none()' &&
option(x).toString() === 'some(' + x + ')';
}));
test.done();
},
'map': function(test) {
test.expect(2);
test.equal(option().map(inc).toString(), 'none()', 'should be empty.');
test.equal(option(41).map(inc).toString(), 'some(42)', 'should be 42.');
'map()': function (test) {
jsc.assert(jsc.forall('number -> number', 'number', function (f, x) {
return option().map(f).toString() === 'none()' &&
get(option(x).map(f)) === get(option(f(x)));
}));
test.done();
},
'flatMap': function(test) {
function incLift(x) { return option(1 + x); }
test.expect(2);
test.equal(option().flatMap(incLift).toString(), 'none()', 'should be empty.');
test.equal(option(41).flatMap(incLift).toString(), 'some(42)', 'should be 42.');
'flatMap()': function (test) {
function liftM0(f) { return function (x) { return option(f(x)); }; }
jsc.assert(jsc.forall('number -> number', 'number', function (f, x) {
return option().flatMap(liftM0(f)).toString() === 'none()' &&
get(option(x).flatMap(liftM0(f))) === get(liftM0(f)(x));
}));
test.done();
},
'ap': function(test) {
test.expect(2);
test.equal(option().ap(option(41)).toString(), 'none()', 'should be empty.');
test.equal(option(inc).ap(option(41)).toString(), 'some(42)', 'should be 42.');
'ap()': function (test) {
jsc.assert(jsc.forall('number -> number', 'number', function (f, x) {
return option().ap(option(x)).toString() === 'none()' &&
get(option(f).ap(option(x))) === f(x);
}));
test.done();
},
};
33 changes: 20 additions & 13 deletions test/promise_test.js
Expand Up @@ -4,19 +4,26 @@ if (!global.Promise) { global.Promise = require('bluebird'); }

var teep = require('../teep.js');
var promise = teep.promise;
var jsc = require('jsverify');

exports['promise'] = {
'promise.collect': function(test) {
test.expect(1);
var p1 = Promise.resolve(2);
var p2 = function () { return Promise.resolve(20); };
var p3 = Promise.resolve(1);
var f = function (x, y, z) { return x * (y + z); };
var p = promise.collect([p1, p2, p3], f);
p.then(function (x) {
test.equal(x, 42);
}).then(function () {
test.done();
exports.promise = {
'promise.collect': function (test) {
jsc.check(jsc.forall(
'number -> number -> number -> number', 'number', 'number', 'number',
function (f, x, y, z) {
var p1 = Promise.resolve(x);
var p2 = function () {
return Promise.resolve(y);
};
var p3 = Promise.resolve(z);
var p4 = promise.collect([p1, p2, p3], f);
return p4.then(function (w) {
return w === f(x)(y)(z);
});
}
)).then(function (res) {
test.ok(res === true);
test.done();
});
},
}
};
41 changes: 22 additions & 19 deletions test/validation_test.js
@@ -1,30 +1,33 @@
'use strict';

var teep = require('../teep.js');
var validation = teep.validation;
var valid = teep.validation.valid;
var invalid = teep.validation.invalid;
var jsc = require('jsverify');

function inc(x) { return 1 + x; }

exports['validation'] = {
'map': function(test) {
test.expect(2);
test.equal(validation.valid(41).map(inc).toString(), 'valid(42)', 'should be 42.');
test.equal(validation.invalid(['wat']).map(inc).toString(), 'invalid(wat)', 'should be invalid(wat).');
exports.validation = {
'map()': function (test) {
jsc.assert(jsc.forall('number -> number', 'number', function (f, x) {
return valid(x).map(f).toString() === valid(f(x)).toString() &&
invalid(['wat']).map(f).toString() === 'invalid(wat)';
}));
test.done();
},
'flatMap': function(test) {
function incLift(x) { return validation.valid(1 + x); }
test.expect(2);
test.equal(validation.valid(41).flatMap(incLift).toString(), 'valid(42)', 'should be 42.');
test.equal(validation.invalid(['wat']).flatMap(incLift).toString(), 'invalid(wat)', 'should be invalid(wat).');
'flatMap()': function (test) {
function liftM0(f) { return function (x) { return valid(f(x)); }; }
jsc.assert(jsc.forall('number -> number', 'number', function (f, x) {
return valid(x).flatMap(liftM0(f)).toString() === liftM0(f)(x).toString() &&
invalid(['wat']).flatMap(liftM0(f)).toString() === 'invalid(wat)';
}));
test.done();
},
'ap': function(test) {
test.expect(4);
test.equal(validation.valid(inc).ap(validation.valid(41)).toString(), 'valid(42)', 'should be 42.');
test.equal(validation.valid(inc).ap(validation.invalid(['wat'])).toString(), 'invalid(wat)', 'should be invalid(wat).');
test.equal(validation.invalid(['wat']).ap(validation.valid(41)).toString(), 'invalid(wat)', 'should be invalid(wat).');
test.equal(validation.invalid(['wat']).ap(validation.invalid(['nope'])).toString(), 'invalid(wat,nope)', 'should be invalid(wat).');
'ap()': function (test) {
jsc.assert(jsc.forall('number -> number', 'number', function (f, x) {
return valid(f).ap(valid(x)).toString() === valid(f(x)).toString() &&
valid(f).ap(invalid(['wat'])).toString() === 'invalid(wat)' &&
invalid(['wat']).ap(valid(x)).toString() === 'invalid(wat)' &&
invalid(['wat']).ap(invalid(['nope'])).toString() === 'invalid(wat,nope)';
}));
test.done();
},
};

0 comments on commit 36e42f3

Please sign in to comment.