guards
Version:
Data type & structure checking, runtime analog of types
287 lines (239 loc) • 9.69 kB
JavaScript
/* vim:set ts=2 sw=2 sts=2 expandtab */
/*jshint undef: true es5: true node: true devel: true
forin: true */
/*global define: true */
(typeof define !== "function" ? function($){ $(require, exports, module); } : define)(function(require, exports, module, undefined) {
"use strict";
var guards = require("../guards.js");
exports["test string guard"] = function(assert) {
var g1 = guards.String("default");
assert.equal(g1(), "default", "correct default value is assigned");
assert.equal(g1("foo"), "foo", "string guard accepts string values");
assert.throws(function() {
g1(12);
}, /String/g, "non-string passed to a string gaurd throws a TypeError");
assert.throws(function() {
g1(new String("foo bar"));
}, /String/, "String instance throws exception as well");
var g2 = guards.String();
assert.throws(function() {
g2();
}, /String/, "no default value is given");
var g3 = guards.String(undefined, "Boom -> {{value}}!");
assert.throws(function() {
var value = g3({});
}, /Boom \-\> \[object Object\]!/g, "Optional error temaplate can be used");
assert.throws(function() {
g3();
}, /Boom \-\>/, "no default value was given");
assert.equal(g3("baz"), "baz", "string value validates");
};
exports["test number guard"] = function(assert) {
var g1 = guards.Number();
assert.equal(g1(12), 12, "numbers work just fine");
assert.throws(function() {
g1('12');
}, /Number/, "non-number throws");
assert.throws(function() {
g1();
}, /Number/, "undefined throws without defaults attribute");
var g2 = guards.Number(0);
assert.equal(g2(), 0, "correct default value is assigned");
assert.equal(g2(17), 17, "string guard accepts string values");
assert.throws(function() {
g2("12");
}, /Number/g, "non-number throws a TypeError");
var g3 = guards.Number(0, "number expected not a {{type}}");
assert.throws(function() {
g3("boom!");
}, /number expected not a string/g, "Optional error temaplate can be used");
};
exports["test schema guard"] = function(assert) {
var Point = guards.Schema({
x: guards.Number(0),
y: guards.Number(0)
});
assert.deepEqual(Point(), { x: 0, y: 0 }, "default values are insterted");
assert.deepEqual(Point({ x: 17, z: 50 }), { x: 17, y: 0 },
"non-guarded values are stripped out");
assert.throws(function() {
Point({ x: "5" });
}, /Number/g, "Wrong type in caught by nested guard");
assert.throws(function() {
Point("{ y: 6 }");
}, /Object/g, "Scheme guard throws on non-object values");
var Segment = guards.Schema({
start: Point,
end: Point,
opacity: guards.Number(1)
});
assert.deepEqual(Segment(),
{ start: { x: 0, y: 0 }, end: { x: 0, y: 0 }, opacity: 1 },
"default values are used by all nested guards");
assert.deepEqual(Segment({ end: { x: 17, foo: 'bar' }, opacity: 0.5 }),
{ start: { x: 0, y: 0 }, end: { x: 17, y: 0 }, opacity: 0.5 },
"fallback to default values and strip out non-guarded ones");
assert.throws(function() {
Segment({ start: 17 })
}, /Object/g, "nested guard throw on wrong value types");
};
exports["test array guard"] = function(assert) {
var array = guards.Array();
assert.deepEqual(array([ 1, 2, 3 ]), [ 1, 2, 3 ], "array returned");
assert.throws(function() {
array(1, 2, 3);
}, /Array/, "throw because non-array passed");
var arraymsg = guards.Array(undefined, "Boom!");
assert.deepEqual(array([ 1, 2, 3 ]), [ 1, 2, 3 ], "array returned");
assert.throws(function() {
arraymsg(1, 2, 3);
}, /Boom/, "throw because non-array passed");
var Words = guards.Array(guards.String(''));
assert.deepEqual(Words([]), [], "Empty array passes through");
assert.deepEqual(Words([ "foo", "bar" ]), [ "foo", "bar" ],
"primitive type array works fine");
assert.throws(function() {
Words([ "foo", 9 ]);
}, /String/g, "throw because it got number instead of string");
var Point = guards.Schema({
x: guards.Number(0),
y: guards.Number(0)
});
var Points = guards.Array(Point);
assert.deepEqual(Points([{ foo: 'bar' }, { x: 2, y: 8 }]),
[ { x: 0, y: 0 }, { x: 2, y: 8 } ],
"Defualts used were needed and non-guradede stripped off");
assert.throws(function() {
Points({ x: 2, y: 8 });
}, /Array/g, "TypeError is thrown if value is not an array");
var Graph = guards.Array(Points);
assert.deepEqual(Graph([]), [], "empty array passes through");
assert.deepEqual(Graph([
[{ x: 17, foo: "bar" }, { x: 16 }],
[{ y: 4 }],
[]
]),
[
[ { x: 17, y: 0 }, { x: 16, y: 0 } ],
[ { x: 0, y: 4 } ],
[]
],
"arrray of arrays of points works fine");
};
exports["test tuple guards"] = function(assert) {
var guards = require("../guards.js");
var Point = guards.Schema({
x: guards.Number(0),
y: guards.Number(0)
});
var Segment = guards.Schema({
start: Point,
end: Point,
opacity: guards.Number(1)
});
var Triangle = guards.Tuple([ Segment, Segment, Segment ]);
assert.deepEqual(Triangle(),
[
{ start: { x: 0, y: 0 }, end: { x: 0, y: 0 }, opacity: 1 },
{ start: { x: 0, y: 0 }, end: { x: 0, y: 0 }, opacity: 1 },
{ start: { x: 0, y: 0 }, end: { x: 0, y: 0 }, opacity: 1 }
],
"if no argument passed then defaults are picked up");
assert.deepEqual(Triangle([
{ opacity: 0, foo: "bar" },
{ start: { x: 2 } }
]),
[
{ start: { x: 0, y: 0 }, end: { x: 0, y: 0 }, opacity: 0 },
{ start: { x: 2, y: 0 }, end: { x: 0, y: 0 }, opacity: 1 },
{ start: { x: 0, y: 0 }, end: { x: 0, y: 0 }, opacity: 1 }
],
"defualts picked up non-guarded stripped");
assert.throws(function() {
Triangle("foo");
}, /Array/g, "Tuple guard accepts only array");
assert.throws(function() {
Triangle([{ start: { x: "3" } } ]);
}, /Number/g, "Nested guard throws if wrong `value` is passed");
var Pointer = guards.Tuple([ Point, Segment ]);
assert.deepEqual(Pointer(),
[
{ x: 0, y: 0 },
{ start: { x: 0, y: 0 }, end: { x: 0, y: 0 }, opacity: 1 }
],
"Falls back to the default values");
assert.deepEqual(Pointer([{ x: 17 }, { opacity: 0 }]),
[
{ x: 17, y: 0 },
{ start: { x: 0, y: 0 }, end: { x: 0, y: 0 }, opacity: 0 }
],
"Defaults picked up, non-guarded stripped out");
assert.deepEqual(Pointer([{ foo: "bar" }, { baz: "bla" }, "foo"]),
[
{ x: 0, y: 0 },
{ start: { x: 0, y: 0 }, end: { x: 0, y: 0 }, opacity: 1 }
],
"Non-guarded elements and values ar stripped out");
};
exports["test custom guards"] = function(assert) {
var Point = guards.Schema({
x: guards.Number(0),
y: guards.Number(0)
});
function color(value) {
if (typeof value === "number" && value <= 255 && value >= 0)
return value
throw new TypeError("Color is a number between 0 and 255");
}
var RGB = guards.Tuple([ color, color, color ]);
var Segment = guards.Schema({
start: Point,
end: Point,
color: RGB,
});
assert.deepEqual(Segment({ end: { y: 23 }, color: [17, 255, 0] }),
{ start: { x: 0, y: 0 },
end: { x: 0, y: 23 },
color: [ 17, 255, 0 ]
},
"defaults values used for missing properties");
assert.deepEqual(Segment({ color: [ 1, 2, 3, 0.5 ] }).color,
[ 1, 2, 3 ],
"non guarded tuple elements are stripped out");
assert.throws(function() {
Segment({ colors: [] });
}, /Color is a number between 0 and 255/, "no defualt for colors");
assert.throws(function() {
Segment({ colors: [ 0, 255, 256 ] });
}, /Color is a/, "color out of the range");
};
exports["test AnyOf guards"] = function(assert) {
var guards = require("../guards.js");
var ObjectPoint = guards.Schema({
x: guards.Number(0),
y: guards.Number(0)
});
var ArrayPoint = guards.Tuple([ guards.Number(0), guards.Number(0) ]);
var Point = guards.AnyOf([ ObjectPoint, ArrayPoint ]);
assert.deepEqual(Point([ 1 ]), [ 1, 0 ], "ArrayPoint validates");
assert.deepEqual(Point({ y: 15 }), { x: 0, y: 15 }, "ObjectPoint validates");
assert.throws(function() {
Point(1, 2)
}, /invalid/, "Number does not validates");
};
exports["test function guards"] = function(assert) {
var guards = require("../guards.js");
var Callee = guards.Function();
var f1 = function () { return "hello world" }
assert.equal(Callee(Object), Object, "Object validates");
assert.equal(Callee(f1), f1, "function validates");
assert.throws(function() {
Callee(7);
}, /Function expected/, "Number does not validates as Function");
assert.throws(function() {
var Callee2 = guards.Function(undefined, "{{type}} is not a function.");
Callee2({});
}, /object is not a function/, "custom error message is thrown");
};
require("test").run(exports)
});