diff --git a/tcomb/tcomb-tests.ts b/tcomb/tcomb-tests.ts
index aeecdcb310..19ca39b3aa 100644
--- a/tcomb/tcomb-tests.ts
+++ b/tcomb/tcomb-tests.ts
@@ -1,288 +1,1792 @@
// ReSharper disable InconsistentNaming
// ReSharper disable WrongExpressionStatement
+///
+///
+///
-import t = require("tcomb");
+// tests adapted from/for tcomb's test folder
-var Str = t.Str;
-var Num = t.Num;
+'use strict';
+import assert = require('assert');
+var t = require('../index');
+
+var Any = t.Any;
+var Nil = t.Nil;
var Bool = t.Bool;
+var Num = t.Num;
+var Str = t.Str;
var Arr = t.Arr;
var Obj = t.Obj;
var Func = t.Func;
var Err = t.Err;
var Re = t.Re;
var Dat = t.Dat;
-var Nil = t.Nil;
-var Any = t.Any;
-var Type = t.Type;
-
var struct = t.struct;
+var enums = t.enums;
+var union = t.union;
var tuple = t.tuple;
+var maybe = t.maybe;
+var subtype = t.subtype;
var list = t.list;
var dict = t.dict;
-var union = t.union;
-var maybe = t.maybe;
var func = t.func;
-var subtype = t.subtype;
+var getTypeName = t.getTypeName;
+var mixin = t.mixin;
+var format = t.format;
-Str.is("a string"); // => true
-Str.is(1); // => false
+//
+// setup
+//
-Num.is("a string"); // => true
-Num.is(1); // => false
+var ok = function (x:any) { assert.strictEqual(true, x); };
+var ko = function (x:any) { assert.strictEqual(false, x); };
+var eq = assert.deepEqual;
+var throwsWithMessage = function (f:any, message:any) {
+ assert.throws(f, function (err:any) {
+ ok(err instanceof Error);
+ eq(err.message, message);
+ return true;
+ });
+};
+var doesNotThrow = assert.doesNotThrow;
-Bool.is("a string"); // => true
-Bool.is(1); // => false
-
-Arr.is("a string"); // => true
-Arr.is(1); // => false
-
-Obj.is("a string"); // => true
-Obj.is(1); // => false
-
-Func.is("a string"); // => true
-Func.is(1); // => false
-
-Err.is("a string"); // => true
-Err.is(1); // => false
-
-Re.is("a string"); // => true
-Re.is(1); // => false
-
-Dat.is("a string"); // => true
-Dat.is(1); // => false
-
-Nil.is("a string"); // => true
-Nil.is(1); // => false
-
-Any.is("a string"); // => true
-Any.is(1); // => false
-
-Type.is("a string"); // => true
-Type.is(1); // => false
-
-var assert = t.assert;
-
-assert(t.Str.is("a string")); // => ok
-assert(t.Str.is(1)); // => fail!
-
-var x = -2;
-var min = 0;
-// throws "-2 should be greater then 0"
-assert(x > min, "%s should be greater then %s", x, min);
-
-Str("a string"); // => ok
-
-class Point1 {
- x: number;
- y: number;
- constructor(x: number, y: number) {
- this.x = Num(x);
- this.y = Num(y);
- }
-}
-
-var Foo = t.irreducible("Foo", x => {
- return t.Bool(x.hasOwnProperty("bar"));
-});
-
-Foo.is({ bar: "baz" }); // => true
-
-// defines a type representing positive numbers
-var Positive = t.subtype(t.Num, n => {
- return n >= 0;
-}, "Positive");
-
-Positive.is(1); // => true
-Positive.is(-1); // => false
-
-var Country = t.enums({
- IT: "Italy",
- US: "United States"
-}, "Country");
-
-Country.is("IT"); // => true
-Country.is("FR"); // => false
-
-// values will mirror the keys
-Country = t.enums.of("IT US", "Country");
-
-// same as
-
-Country = t.enums(["IT", "US"], "Country");
-
-// same as
-
-Country = t.enums({
- IT: "IT",
- US: "US"
-}, "Country");
-
-var Point = t.struct({
+var noop = function () {};
+var Point = struct({
x: Num,
y: Num
-}, "Point");
-
-// constructor usage, `p` is immutable, new is optional
-var p2 = new Point({ x: 1, y: 2 });
-
-Point.is(p2); // => true
-
-// now p is mutable
-new Point({ x: 1, y: 2 }, true);
-
-Point.extend({ z: Num }, "Point3D");
-
-// multiple inheritance
-var A = struct({});
-var B = struct({});
-var MixinC = {};
-var MixinD = {};
-A.extend([B, MixinC, MixinD]);
-
-var Rectangle = struct({
- width: Num,
- height: Num
});
-Rectangle.prototype.getArea = function() {
- return this.width * this.height;
-};
+describe('update', function () {
+
+ var update = t.update;
+ var Tuple = tuple([Str, Num]);
+ var List = list(Num);
+ var Dict = dict(Str, Num);
+
+ it('should handle $set command', function () {
+ var instance = 1;
+ var actual = update(instance, {$set: 2});
+ eq(actual, 2);
+ var instance2 = [1, 2, 3];
+ actual = update(instance2, {1: {'$set': 4}});
+ eq(instance2, [1, 2, 3]);
+ eq(actual, [1, 4, 3]);
+ });
+
+ it('$set and null value, fix #65', function () {
+ var NullStruct = struct({a: Num, b: maybe(Num)});
+ var instance = new NullStruct({a: 1});
+ var updated = update(instance, {b: {$set: 2}});
+ eq(instance, {a: 1, b: null});
+ eq(updated, {a: 1, b: 2});
+ });
+
+ it('should handle $apply command', function () {
+ var $apply = function (n:any) { return n + 1; };
+ var instance = 1;
+ var actual = update(instance, {$apply: $apply});
+ eq(actual, 2);
+ var instance2 = [1, 2, 3];
+ actual = update(instance2, {1: {'$apply': $apply}});
+ eq(instance2, [1, 2, 3]);
+ eq(actual, [1, 3, 3]);
+ });
+
+ it('should handle $unshift command', function () {
+ var actual = update([1, 2, 3], {'$unshift': [4]});
+ eq(actual, [4, 1, 2, 3]);
+ actual = update([1, 2, 3], {'$unshift': [4, 5]});
+ eq(actual, [4, 5, 1, 2, 3]);
+ actual = update([1, 2, 3], {'$unshift': [[4, 5]]});
+ eq(actual, [[4, 5], 1, 2, 3]);
+ });
+
+ it('should handle $push command', function () {
+ var actual = update([1, 2, 3], {'$push': [4]});
+ eq(actual, [1, 2, 3, 4]);
+ actual = update([1, 2, 3], {'$push': [4, 5]});
+ eq(actual, [1, 2, 3, 4, 5]);
+ actual = update([1, 2, 3], {'$push': [[4, 5]]});
+ eq(actual, [1, 2, 3, [4, 5]]);
+ });
+
+ it('should handle $splice command', function () {
+ var instance = [1, 2, {a: [12, 17, 15]}];
+ var actual = update(instance, {2: {a: {$splice: [[1, 1, 13, 14]]}}});
+ eq(instance, [1, 2, {a: [12, 17, 15]}]);
+ eq(actual, [1, 2, {a: [12, 13, 14, 15]}]);
+ });
+
+ it('should handle $remove command', function () {
+ var instance = {a: 1, b: 2};
+ var actual = update(instance, {'$remove': ['a']});
+ eq(instance, {a: 1, b: 2});
+ eq(actual, {b: 2});
+ });
+
+ it('should handle $swap command', function () {
+ var instance = [1, 2, 3, 4];
+ var actual = update(instance, {'$swap': {from: 1, to: 2}});
+ eq(instance, [1, 2, 3, 4]);
+ eq(actual, [1, 3, 2, 4]);
+ });
+
+ describe('structs', function () {
+
+ var instance = new Point({x: 0, y: 1});
+
+ it('should handle $set command', function () {
+ var updated = update(instance, {x: {$set: 1}});
+ eq(instance, {x: 0, y: 1});
+ eq(updated, {x: 1, y: 1});
+ });
+
+ it('should handle $apply command', function () {
+ var updated = update(instance, {x: {$apply: function (x:any) {
+ return x + 2;
+ }}});
+ eq(instance, {x: 0, y: 1});
+ eq(updated, {x: 2, y: 1});
+ });
+
+ it('should handle $merge command', function () {
+ var updated = update(instance, {'$merge': {x: 2, y: 2}});
+ eq(instance, {x: 0, y: 1});
+ eq(updated, {x: 2, y: 2});
+ var Nested = struct({
+ a: Num,
+ b: struct({
+ c: Num,
+ d: Num,
+ e: Num
+ })
+ });
+ instance = new Nested({a: 1, b: {c: 2, d: 3, e: 4}});
+ updated = update(instance, {b: {'$merge': {c: 5, e: 6}}});
+ eq(instance, {a: 1, b: {c: 2, d: 3, e: 4}});
+ eq(updated, {a: 1, b: {c: 5, d: 3, e: 6}});
+ });
+
+ });
+
+ describe('tuples', function () {
+
+ var instance = Tuple(['a', 1]);
+
+ it('should handle $set command', function () {
+ var updated = update(instance, {0: {$set: 'b'}});
+ eq(updated, ['b', 1]);
+ });
+
+ });
+
+ describe('lists', function () {
+
+ var instance = List([1, 2, 3, 4]);
+
+ it('should handle $set command', function () {
+ var updated = update(instance, {2: {$set: 5}});
+ eq(updated, [1, 2, 5, 4]);
+ });
+
+ it('should handle $splice command', function () {
+ var updated = update(instance, {$splice: [[1, 2, 5, 6]]});
+ eq(updated, [1, 5, 6, 4]);
+ });
+
+ it('should handle $concat command', function () {
+ var updated = update(instance, {$push: [5]});
+ eq(updated, [1, 2, 3, 4, 5]);
+ updated = update(instance, {$push: [5, 6]});
+ eq(updated, [1, 2, 3, 4, 5, 6]);
+ });
+
+ it('should handle $prepend command', function () {
+ var updated = update(instance, {$unshift: [5]});
+ eq(updated, [5, 1, 2, 3, 4]);
+ updated = update(instance, {$unshift: [5, 6]});
+ eq(updated, [5, 6, 1, 2, 3, 4]);
+ });
+
+ it('should handle $swap command', function () {
+ var updated = update(instance, {$swap: {from: 1, to: 2}});
+ eq(updated, [1, 3, 2, 4]);
+ });
+
+ });
+
+ describe('dicts', function () {
+
+ var instance = Dict({a: 1, b: 2});
+
+ it('should handle $set command', function () {
+ var updated = update(instance, {a: {$set: 2}});
+ eq(updated, {a: 2, b: 2});
+ });
+
+ it('should handle $remove command', function () {
+ var updated = update(instance, {$remove: ['a']});
+ eq(updated, {b: 2});
+ });
+
+ });
+
+ describe('memory saving', function () {
+
+ it('should reuse members that are not updated', function () {
+ var Struct = struct({
+ a: Num,
+ b: Str,
+ c: tuple([Num, Num]),
+ });
+ var List = list(Struct);
+ var instance = List([{
+ a: 1,
+ b: 'one',
+ c: [1000, 1000000]
+ },{
+ a: 2,
+ b: 'two',
+ c: [2000, 2000000]
+ }]);
+
+ var updated = update(instance, {
+ 1: {
+ a: {$set: 119}
+ }
+ });
+
+ assert.strictEqual(updated[0], instance[0]);
+ assert.notStrictEqual(updated[1], instance[1]);
+ assert.strictEqual(updated[1].c, instance[1].c);
+ });
+ });
+
+ describe('all together now', function () {
+
+ it('should handle mixed commands', function () {
+ var Struct = struct({
+ a: Num,
+ b: Tuple,
+ c: List,
+ d: Dict
+ });
+ var instance = new Struct({
+ a: 1,
+ b: ['a', 1],
+ c: [1, 2, 3, 4],
+ d: {a: 1, b: 2}
+ });
+ var updated = update(instance, {
+ a: {$set: 1},
+ b: {0: {$set: 'b'}},
+ c: {2: {$set: 5}},
+ d: {$remove: ['a']}
+ });
+ eq(updated, {
+ a: 1,
+ b: ['b', 1],
+ c: [1, 2, 5, 4],
+ d: {b: 2}
+ });
+ });
+
+ it('should handle nested structures', function () {
+ var Struct = struct({
+ a: struct({
+ b: tuple([
+ Str,
+ list(Num)
+ ])
+ })
+ });
+ var instance = new Struct({
+ a: {
+ b: ['a', [1, 2, 3]]
+ }
+ });
+ var updated = update(instance, {
+ a: {b: {1: {2: {$set: 4}}}}
+ });
+ eq(updated, {
+ a: {
+ b: ['a', [1, 2, 4]]
+ }
+ });
+ });
+
+ });
-var Cube = Rectangle.extend({
- thickness: Num
});
-// typeof Cube.prototype.getArea === 'function'
-Cube.prototype.getVolume = function() {
- return this.getArea() * this.thickness;
-};
+//
+// assert
+//
-var Area = tuple([Num, Num]);
+describe('assert', function () {
-// constructor usage, `area` is immutable
-Area([1, 2]);
+ var assert = t.assert;
-var Path = list(Point);
+ it('should nor throw when guard is true', function () {
+ assert(true);
+ });
-// costructor usage, `path` is immutable
-Path([
- { x: 0, y: 0 }, // tcomb hydrates automatically using the `Point` constructor
- { x: 1, y: 1 }
-]);
+ it('should throw a default message', function () {
+ throwsWithMessage(function () {
+ assert(1 === 2);
+ }, 'assert failed');
+ });
-var Tel = dict(Str, Num);
+ it('should throw the specified message', function () {
+ throwsWithMessage(function () {
+ assert(1 === 2, 'my message');
+ }, 'my message');
+ });
-// costructor usage, `tel` is immutable
-Tel({ jack: 4098, sape: 4139 });
+ it('should format the specified message', function () {
+ throwsWithMessage(function () {
+ assert(1 === 2, '%s !== %s', 1, 2);
+ }, '1 !== 2');
+ });
-var ReactKey = union([Str, Num]);
+ it('should handle custom fail behaviour', function () {
+ var fail = t.fail;
+ t.fail = function (message) {
+ try {
+ throw new Error(message);
+ } catch (e) {
+ eq(e.message, 'report error');
+ }
+ };
+ doesNotThrow(function () {
+ assert(1 === 2, 'report error');
+ });
+ t.fail = fail;
+ });
-ReactKey.is("a"); // => true
-ReactKey.is(1); // => true
-ReactKey.is(true); // => false
+});
-ReactKey.dispatch = x => {
- if (Str.is(x)) return Str;
- if (Num.is(x)) return Num;
- return Any;
-};
+//
+// utils
+//
-// now you can do this without a fail
-ReactKey("a");
+describe('format(str, [...])', function () {
-// the value of a radio input where null = no selection
-var Radio = maybe(Str);
+ it('should format strings', function () {
+ eq(format('%s', 'a'), 'a');
+ eq(format('%s', 2), '2');
+ eq(format('%s === %s', 1, 1), '1 === 1');
+ });
-Radio.is("a"); // => true
-Radio.is(null); // => true
-Radio.is(1); // => false
+ it('should format JSON', function () {
+ eq(format('%j', {a: 1}), '{"a":1}');
+ });
-// add takes two `Num`s and returns a `Num`
-var add = func([Num, Num], Num)
- .of((x: number, y: number) => { return x + y; });
+ it('should handle undefined formatters', function () {
+ eq(format('%o', 'a'), '%o a');
+ });
-add("Hello", 2); // Raises error: Invalid `Hello` supplied to `Num`
-add("Hello"); // Raises error: Invalid `Hello` supplied to `Num`
+ it('should handle escaping %', function () {
+ eq(format('%%s'), '%s');
+ });
-add(1, 2); // Returns: 3
-add(1)(2); // Returns: 3
+ it('should not consume an argument with a single %', function () {
+ eq(format('%s%', '100'), '100%');
+ });
-// An `A` takes a `Str` and returns an `Num`
-func(Str, Num);
+ it('should handle less arguments than placeholders', function () {
+ eq(format('%s %s', 'a'), 'a %s');
+ });
-// A `B` takes a `Func` (which takes a `Str` and returns a `Num`) and returns a `Str`.
-func(func(Str, Num), Str);
+ it('should handle more arguments than placeholders', function () {
+ eq(format('%s', 'a', 'b', 'c'), 'a b c');
+ });
-// An `ExcitedStr` is a `Str` containing an exclamation mark
-var ExcitedStr = subtype(Str, s => { return s.indexOf("!") !== -1; }, "ExcitedStr");
+ it('should be extensible', function () {
+ (format).formatters.l = function (x:any) { return x.length; };
+ eq(format('%l', ['a', 'b', 'c']), '3');
+ });
-// An `Exciter` takes a `Str` and returns an `ExcitedStr`
-var Exciter = func(Str, ExcitedStr);
+});
-// A `C` takes an `A`, a `B` and a `Str` and returns a `Num`
-func([A, B, Str], Num);
+describe('mixin(x, y, [overwrite])', function () {
-func(A, B).of(() => {});
+ it('should mix two objects', function () {
+ var o1 = {a: 1};
+ var o2 = {b: 2};
+ var o3 = mixin(o1, o2);
+ ok(o3 === o1);
+ eq(o3.a, 1);
+ eq(o3.b, 2);
+ });
-var simpleQuestionator = Exciter.of((s: string) => { return s + "?"; });
-var simpleExciter = Exciter.of((s: string) => { return s + "!"; });
+ it('should throw if a property already exists', function () {
+ throwsWithMessage(function () {
+ var o1 = {a: 1};
+ var o2 = {a: 2, b: 2};
+ mixin(o1, o2);
+ }, 'Cannot overwrite property a');
+ });
-// Raises error:
-// Invalid `Hello?` supplied to `ExcitedStr`, insert a valid value for the subtype
-simpleQuestionator("Hello");
+ it('should not throw if a property already exists but overwrite = true', function () {
+ var o1 = {a: 1};
+ var o2 = {a: 2, b: 2};
+ var o3 = mixin(o1, o2, true);
+ eq(o3.a, 2);
+ eq(o3.b, 2);
+ });
-// Raises error: Invalid `1` supplied to `Str`
-simpleExciter(1);
+ it('should not mix prototype properties', function () {
+ function F() {}
+ F.prototype.method = noop;
+ var source = new (F)();
+ var target = {};
+ mixin(target, source);
+ eq((target).method, undefined);
+ });
-// Returns: "Hello!"
-simpleExciter("Hello");
+});
-// We can reasonably suggest that add has the following type signature
-// add : Num -> Num -> Num
-add = func([Num, Num], Num)
- .of((x: number, y: number) => { return x + y });
+describe('getFunctionName(f, [defaultName])', function () {
-add("Hello"); // As this raises: "Error: Invalid `Hello` supplied to `Num`"
+ var getFunctionName = t.getFunctionName;
-var add2 = add(2);
-add2(1); // And this returns: 3
+ it('should return the name of a named function', function () {
+ eq(getFunctionName(function myfunc(){}), 'myfunc');
+ });
-func(A, B).is(x);
+ it('should return the value of `displayName` if specified', function () {
+ var f = function myfunc(){};
+ (f).displayName = 'mydisplayname';
+ eq(getFunctionName(f), 'mydisplayname');
+ });
-Exciter.is(simpleExciter); // Returns: true
-Exciter.is(simpleQuestionator); // Returns: true
+ it('should fallback on function arity if nothing is specified', function () {
+ eq(getFunctionName(function (a:any, b:any, c:any) { return a + b + c; }), '');
+ });
-var id = (x: number) => { return x; };
+});
-func([Num, Num], Num).is(func([Num, Num], Num).of(id)); // Returns: true
-func([Num, Num], Num).is(func(Num, Num).of(id)); // Returns: false
+describe('getTypeName(type)', function () {
-var p4 = new Point({x: 1, y: 2});
+ var UnnamedStruct = struct({});
+ var NamedStruct = struct({}, 'NamedStruct');
+ var UnnamedUnion = union([Str, Num]);
+ var NamedUnion = union([Str, Num], 'NamedUnion');
+ var UnnamedMaybe = maybe(Str);
+ var NamedMaybe = maybe(Str, 'NamedMaybe');
+ var UnnamedEnums = enums({a: 'A', b: 'B'});
+ var NamedEnums = enums({}, 'NamedEnums');
+ var UnnamedTuple = tuple([Str, Num]);
+ var NamedTuple = tuple([Str, Num], 'NamedTuple');
+ var UnnamedSubtype = subtype(Str, function notEmpty(x) { return x !== ''; });
+ var NamedSubtype = subtype(Str, function (x) { return x !== ''; }, 'NamedSubtype');
+ var UnnamedList = list(Str);
+ var NamedList = list(Str, 'NamedList');
+ var UnnamedDict = dict(Str, Str);
+ var NamedDict = dict(Str, Str, 'NamedDict');
+ var UnnamedFunc = func(Str, Str);
+ var NamedFunc = func(Str, Str, 'NamedFunc');
-p4 = Point.update(p4, { x: { "$set": 3 } }); // => {x: 3, y: 2}
+ it('should return the name of a named type', function () {
+ eq(getTypeName(NamedStruct), 'NamedStruct');
+ eq(getTypeName(NamedUnion), 'NamedUnion');
+ eq(getTypeName(NamedMaybe), 'NamedMaybe');
+ eq(getTypeName(NamedEnums), 'NamedEnums');
+ eq(getTypeName(NamedTuple), 'NamedTuple');
+ eq(getTypeName(NamedSubtype), 'NamedSubtype');
+ eq(getTypeName(NamedList), 'NamedList');
+ eq(getTypeName(NamedDict), 'NamedDict');
+ eq(getTypeName(NamedFunc), 'NamedFunc');
+ });
-var Type2 = dict(Str, Num);
-var instance = Type2({ a: 1, b: 2 });
-Type2.update(instance, { $remove: ["a"] }); // => {b: 2}
+ it('should return a meaningful name of a unnamed type', function () {
+ eq(getTypeName(UnnamedStruct), '{}');
+ eq(getTypeName(UnnamedUnion), 'Str | Num');
+ eq(getTypeName(UnnamedMaybe), '?Str');
+ eq(getTypeName(UnnamedEnums), '"a" | "b"');
+ eq(getTypeName(UnnamedTuple), '[Str, Num]');
+ eq(getTypeName(UnnamedSubtype), '{Str | notEmpty}');
+ eq(getTypeName(UnnamedList), 'Array');
+ eq(getTypeName(UnnamedDict), '{[key:Str]: Str}');
+ eq(getTypeName(UnnamedFunc), '(Str) => Str');
+ });
-var Type3 = list(Num);
-var instance2 = Type3([1, 2, 3, 4]);
-Type3.update(instance2, { "$swap": { from: 1, to: 2 } }); // => [1, 3, 2, 4]
+});
-t.options.onFail = message => {
- return message;
-};
+//
+// Any
+//
-t.format("Invalid argument `name` = `%s` supplied to `%s`", 1, "MyType");
+describe('Any', function () {
-t.getKind(Str); // => 'irreducible'
-t.getKind(list(Str)); // => 'list'
+ var T = Any;
-t.getFunctionName(t.getKind); // => 'getKind'
-t.getFunctionName(() => { }); // => ''
+ describe('constructor', function () {
-t.getTypeName(Str);
+ it('should behave like identity', function () {
+ eq(Any('a'), 'a');
+ });
-t.mixin({ a: 1 }, { b: 2 }); // => {a: 1, b: 2}
-t.mixin({ a: 1 }, { a: 2 }); // => fail!
+ it('should throw if used with new', function () {
+ throwsWithMessage(function () {
+ /* jshint ignore:start */
+ var x = new (T)();
+ /* jshint ignore:end */
+ }, 'Operator `new` is forbidden for type `Any`');
+ });
+
+ });
+
+ describe('#is(x)', function () {
+
+ it('should always return true', function () {
+ ok(T.is(null));
+ ok(T.is(undefined));
+ ok(T.is(0));
+ ok(T.is(true));
+ ok(T.is(''));
+ ok(T.is([]));
+ ok(T.is({}));
+ ok(T.is(noop));
+ ok(T.is(/a/));
+ ok(T.is(new RegExp('a')));
+ ok(T.is(new Error()));
+ });
+
+ });
+
+});
+
+//
+// irreducible types
+//
+
+describe('irreducible types constructors', function () {
+
+ [
+ {T: Nil, x: null},
+ {T: Str, x: 'a'},
+ {T: Num, x: 1},
+ {T: Bool, x: true},
+ {T: Arr, x: []},
+ {T: Obj, x: {}},
+ {T: Func, x: noop},
+ {T: Err, x: new Error()},
+ {T: Re, x: /a/},
+ {T: Dat, x: new Date()}
+ ].forEach(function (o) {
+
+ var T = o.T;
+ var x = o.x;
+
+ it('should accept only valid values', function () {
+ eq(T(x), x);
+ });
+
+ it('should throw if used with new', function () {
+ throwsWithMessage(function () {
+ /* jshint ignore:start */
+ var x = new (T) ();
+ /* jshint ignore:end */
+ }, 'Operator `new` is forbidden for type `' + getTypeName(T) + '`');
+ });
+
+ });
+
+});
+
+describe('Nil', function () {
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is null or undefined', function () {
+ ok(Nil.is(null));
+ ok(Nil.is(undefined));
+ });
+
+ it('should return false when x is neither null nor undefined', function () {
+ ko(Nil.is(0));
+ ko(Nil.is(true));
+ ko(Nil.is(''));
+ ko(Nil.is([]));
+ ko(Nil.is({}));
+ ko(Nil.is(noop));
+ ko(Nil.is(new Error()));
+ ko(Nil.is(new Date()));
+ ko(Nil.is(/a/));
+ ko(Nil.is(new RegExp('a')));
+ });
+
+ });
+
+});
+
+describe('Bool', function () {
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is true or false', function () {
+ ok(Bool.is(true));
+ ok(Bool.is(false));
+ });
+
+ it('should return false when x is neither true nor false', function () {
+ ko(Bool.is(null));
+ ko(Bool.is(undefined));
+ ko(Bool.is(0));
+ ko(Bool.is(''));
+ ko(Bool.is([]));
+ ko(Bool.is({}));
+ ko(Bool.is(noop));
+ ko(Bool.is(/a/));
+ ko(Bool.is(new RegExp('a')));
+ ko(Bool.is(new Error()));
+ ko(Bool.is(new Date()));
+ });
+
+ });
+
+});
+
+describe('Num', function () {
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is a number', function () {
+ ok(Num.is(0));
+ ok(Num.is(1));
+ /* jshint ignore:start */
+ ko(Num.is(new Number(1)));
+ /* jshint ignore:end */
+ });
+
+ it('should return false when x is not a number', function () {
+ ko(Num.is(NaN));
+ ko(Num.is(Infinity));
+ ko(Num.is(-Infinity));
+ ko(Num.is(null));
+ ko(Num.is(undefined));
+ ko(Num.is(true));
+ ko(Num.is(''));
+ ko(Num.is([]));
+ ko(Num.is({}));
+ ko(Num.is(noop));
+ ko(Num.is(/a/));
+ ko(Num.is(new RegExp('a')));
+ ko(Num.is(new Error()));
+ ko(Num.is(new Date()));
+ });
+
+ });
+
+});
+
+describe('Str', function () {
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is a string', function () {
+ ok(Str.is(''));
+ ok(Str.is('a'));
+ /* jshint ignore:start */
+ ko(Str.is(new String('a')));
+ /* jshint ignore:end */
+ });
+
+ it('should return false when x is not a string', function () {
+ ko(Str.is(NaN));
+ ko(Str.is(Infinity));
+ ko(Str.is(-Infinity));
+ ko(Str.is(null));
+ ko(Str.is(undefined));
+ ko(Str.is(true));
+ ko(Str.is(1));
+ ko(Str.is([]));
+ ko(Str.is({}));
+ ko(Str.is(noop));
+ ko(Str.is(/a/));
+ ko(Str.is(new RegExp('a')));
+ ko(Str.is(new Error()));
+ ko(Str.is(new Date()));
+ });
+
+ });
+
+});
+
+describe('Arr', function () {
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is an array', function () {
+ ok(Arr.is([]));
+ });
+
+ it('should return false when x is not an array', function () {
+ ko(Arr.is(NaN));
+ ko(Arr.is(Infinity));
+ ko(Arr.is(-Infinity));
+ ko(Arr.is(null));
+ ko(Arr.is(undefined));
+ ko(Arr.is(true));
+ ko(Arr.is(1));
+ ko(Arr.is('a'));
+ ko(Arr.is({}));
+ ko(Arr.is(noop));
+ ko(Arr.is(/a/));
+ ko(Arr.is(new RegExp('a')));
+ ko(Arr.is(new Error()));
+ ko(Arr.is(new Date()));
+ });
+
+ });
+
+});
+
+describe('Obj', function () {
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is an object', function () {
+ function A() {}
+ ok(Obj.is({}));
+ ok(Obj.is(new (A)()));
+ });
+
+ it('should return false when x is not an object', function () {
+ ko(Obj.is(null));
+ ko(Obj.is(undefined));
+ ko(Obj.is(0));
+ ko(Obj.is(''));
+ ko(Obj.is([]));
+ ko(Obj.is(noop));
+ });
+
+ });
+
+});
+
+describe('Func', function () {
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is a function', function () {
+ ok(Func.is(noop));
+ /* jshint ignore:start */
+ ok(Func.is(new Function()));
+ /* jshint ignore:end */
+ });
+
+ it('should return false when x is not a function', function () {
+ ko(Func.is(null));
+ ko(Func.is(undefined));
+ ko(Func.is(0));
+ ko(Func.is(''));
+ ko(Func.is([]));
+ /* jshint ignore:start */
+ ko(Func.is(new String('1')));
+ ko(Func.is(new Number(1)));
+ ko(Func.is(new Boolean()));
+ /* jshint ignore:end */
+ ko(Func.is(/a/));
+ ko(Func.is(new RegExp('a')));
+ ko(Func.is(new Error()));
+ ko(Func.is(new Date()));
+ });
+
+ });
+
+});
+
+describe('Err', function () {
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is an error', function () {
+ ok(Err.is(new Error()));
+ });
+
+ it('should return false when x is not an error', function () {
+ ko(Err.is(null));
+ ko(Err.is(undefined));
+ ko(Err.is(0));
+ ko(Err.is(''));
+ ko(Err.is([]));
+ /* jshint ignore:start */
+ ko(Err.is(new String('1')));
+ ko(Err.is(new Number(1)));
+ ko(Err.is(new Boolean()));
+ /* jshint ignore:end */
+ ko(Err.is(/a/));
+ ko(Err.is(new RegExp('a')));
+ ko(Err.is(new Date()));
+ });
+
+ });
+
+});
+
+describe('Re', function () {
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is a regexp', function () {
+ ok(Re.is(/a/));
+ ok(Re.is(new RegExp('a')));
+ });
+
+ it('should return false when x is not a regexp', function () {
+ ko(Re.is(null));
+ ko(Re.is(undefined));
+ ko(Re.is(0));
+ ko(Re.is(''));
+ ko(Re.is([]));
+ /* jshint ignore:start */
+ ko(Re.is(new String('1')));
+ ko(Re.is(new Number(1)));
+ ko(Re.is(new Boolean()));
+ /* jshint ignore:end */
+ ko(Re.is(new Error()));
+ ko(Re.is(new Date()));
+ });
+
+ });
+
+});
+
+describe('Dat', function () {
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is a Dat', function () {
+ ok(Dat.is(new Date()));
+ });
+
+ it('should return false when x is not a Dat', function () {
+ ko(Dat.is(null));
+ ko(Dat.is(undefined));
+ ko(Dat.is(0));
+ ko(Dat.is(''));
+ ko(Dat.is([]));
+ /* jshint ignore:start */
+ ko(Dat.is(new String('1')));
+ ko(Dat.is(new Number(1)));
+ ko(Dat.is(new Boolean()));
+ /* jshint ignore:end */
+ ko(Dat.is(new Error()));
+ ko(Dat.is(/a/));
+ ko(Dat.is(new RegExp('a')));
+ });
+
+ });
+
+});
+
+//
+// struct
+//
+
+describe('struct', function () {
+
+ describe('combinator', function () {
+
+ it('should throw if used with wrong arguments', function () {
+
+ throwsWithMessage(function () {
+ (struct)();
+ }, 'Invalid argument `props` = `undefined` supplied to `struct` combinator');
+
+ throwsWithMessage(function () {
+ struct({a: null});
+ }, 'Invalid argument `props` = `[object Object]` supplied to `struct` combinator');
+
+ throwsWithMessage(function () {
+ (struct)({}, 1);
+ }, 'Invalid argument `name` = `1` supplied to `struct` combinator');
+
+ });
+
+ });
+ describe('constructor', function () {
+
+ it('should be idempotent', function () {
+ var T = Point;
+ var p1 = T({x: 0, y: 0});
+ var p2 = T(p1);
+ eq(Object.isFrozen(p1), true);
+ eq(Object.isFrozen(p2), true);
+ eq(p2 === p1, true);
+ });
+
+ it('should accept only valid values', function () {
+ throwsWithMessage(function () {
+ Point(1);
+ }, 'Invalid argument `value` = `1` supplied to struct type `{x: Num, y: Num}`');
+ });
+
+ });
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is an instance of the struct', function () {
+ var p = new Point({ x: 1, y: 2 });
+ ok(Point.is(p));
+ });
+
+ });
+
+ describe('#update()', function () {
+
+ var Type = struct({name: Str});
+ var instance = new Type({name: 'Giulio'});
+
+ it('should return a new instance', function () {
+ var newInstance = Type.update(instance, {name: {$set: 'Canti'}});
+ ok(Type.is(newInstance));
+ eq( ( instance).name, 'Giulio');
+ eq(( newInstance).name, 'Canti');
+ });
+
+ });
+
+ describe('#extend(props, [name])', function () {
+
+ it('should extend an existing struct', function () {
+ var Point = struct({
+ x: Num,
+ y: Num
+ }, 'Point');
+ var Point3D = Point.extend({z: Num}, 'Point3D');
+ eq(getTypeName(Point3D), 'Point3D');
+ eq((Point3D).meta.props.x, Num);
+ eq((Point3D).meta.props.y, Num);
+ eq((Point3D).meta.props.z, Num);
+ });
+
+ it('should handle an array as argument', function () {
+ var Type = struct({a: Str}, 'Type');
+ var Mixin = [{b: Num, c: Bool}];
+ var NewType = Type.extend(Mixin, 'NewType');
+ eq(getTypeName(NewType), 'NewType');
+ eq((NewType).meta.props.a, Str);
+ eq((NewType).meta.props.b, Num);
+ eq((NewType).meta.props.c, Bool);
+ });
+
+ it('should handle a struct (or list of structs) as argument', function () {
+ var A = struct({a: Str}, 'A');
+ var B = struct({b: Str}, 'B');
+ var C = struct({c: Str}, 'C');
+ var MixinD = {d: Str};
+ var E = A.extend([B, C, MixinD]);
+ eq(E.meta.props, {
+ a: Str,
+ b: Str,
+ c: Str,
+ d: Str
+ });
+ });
+
+ it('should support prototypal inheritance', function () {
+ var Rectangle = struct({
+ w: Num,
+ h: Num
+ }, 'Rectangle');
+ Rectangle.prototype.area = function () {
+ return this.w * this.h;
+ };
+ var Cube = Rectangle.extend({
+ l: Num
+ });
+ Cube.prototype.volume = function () {
+ return this.area() * this.l;
+ };
+
+ assert('function' === typeof Rectangle.prototype.area);
+ assert('function' === typeof Cube.prototype.area);
+ assert(undefined === Rectangle.prototype.volume);
+ assert('function' === typeof Cube.prototype.volume);
+ assert(Cube.prototype.constructor === Cube);
+
+ var c = new Cube({w:2, h:2, l:2});
+ eq((c).volume(), 8);
+ });
+
+ });
+
+});
+
+//
+// enums
+//
+
+describe('enums', function () {
+
+ describe('combinator', function () {
+
+ it('should throw if used with wrong arguments', function () {
+
+ throwsWithMessage(function () {
+ (enums)();
+ }, 'Invalid argument `map` = `undefined` supplied to `enums` combinator');
+
+ throwsWithMessage(function () {
+ (enums)({}, 1);
+ }, 'Invalid argument `name` = `1` supplied to `enums` combinator');
+
+ });
+
+ });
+
+ describe('constructor', function () {
+
+ var T = enums({a: 0}, 'T');
+
+ it('should throw if used with new', function () {
+ throwsWithMessage(function () {
+ /* jshint ignore:start */
+ var x = new (T)('a');
+ /* jshint ignore:end */
+ }, 'Operator `new` is forbidden for type `T`');
+ });
+
+ it('should accept only valid values', function () {
+ eq((T)('a'), 'a');
+ throwsWithMessage(function () {
+ (T)('b');
+ }, 'Invalid argument `value` = `b` supplied to enums type `T`, expected one of ["a"]');
+ });
+
+ });
+
+ describe('#is(x)', function () {
+
+ var Direction = enums({
+ North: 0,
+ East: 1,
+ South: 2,
+ West: 3,
+ 1: 'North-East',
+ 2.5: 'South-East'
+ });
+
+ it('should return true when x is an instance of the enum', function () {
+ ok(Direction.is('North'));
+ ok(Direction.is(1));
+ ok(Direction.is('1'));
+ ok(Direction.is(2.5));
+ });
+
+ it('should return false when x is not an instance of the enum', function () {
+ ko(Direction.is('North-East'));
+ ko(Direction.is(2));
+ });
+
+ });
+
+ describe('#of(keys)', function () {
+
+ it('should return an enum', function () {
+ var Size = (enums).of(['large', 'small', 1, 10.9]); ///!!!
+ ok(Size.meta.map.large === 'large');
+ ok(Size.meta.map.small === 'small');
+ ok(Size.meta.map['1'] === 1);
+ ok(Size.meta.map[10.9] === 10.9);
+ });
+
+ it('should handle a string', function () {
+ var Size = (enums).of('large small 10');
+ ok(Size.meta.map.large === 'large');
+ ok(Size.meta.map.small === 'small');
+ ok(Size.meta.map['10'] === '10');
+ ok(Size.meta.map[10] === '10');
+ });
+
+ });
+
+});
+
+//
+// union
+//
+
+describe('union', function () {
+
+ var Circle = struct({
+ center: Point,
+ radius: Num
+ }, 'Circle');
+
+ var Rectangle = struct({
+ a: Point,
+ b: Point
+ });
+
+ var Shape = union([Circle, Rectangle], 'Shape');
+
+ Shape.dispatch = function (values) {
+ assert(Obj.is(values));
+ return values.hasOwnProperty('center') ?
+ Circle :
+ Rectangle;
+ };
+
+ describe('combinator', function () {
+
+ it('should throw if used with wrong arguments', function () {
+
+ throwsWithMessage(function () {
+ (union)();
+ }, 'Invalid argument `types` = `undefined` supplied to `union` combinator');
+
+ throwsWithMessage(function () {
+ union([]);
+ }, 'Invalid argument `types` = `` supplied to `union` combinator, provide at least two types');
+
+ throwsWithMessage(function () {
+ union([Circle]);
+ }, 'Invalid argument `types` = `Circle` supplied to `union` combinator, provide at least two types');
+
+ throwsWithMessage(function () {
+ (union)([Circle, Point], 1);
+ }, 'Invalid argument `name` = `1` supplied to `union` combinator');
+
+ });
+
+ });
+
+ describe('constructor', function () {
+
+ it('should throw when dispatch() is not implemented', function () {
+ throwsWithMessage(function () {
+ var T = union([Str, Num], 'T');
+ T.dispatch = null;
+ T(1);
+ }, 'Unimplemented `dispatch()` function for union type `T`');
+ });
+
+ it('should have a default dispatch() implementation', function () {
+ var T = union([Str, Num], 'T');
+ eq(T(1), 1);
+ });
+
+ it('should throw when dispatch() does not return a type', function () {
+ throwsWithMessage(function () {
+ var T = union([Str, Num], 'T');
+ T(true);
+ }, 'The `dispatch()` function of union type `T` returns no type constructor');
+ });
+
+ it('should build instances when dispatch() is implemented', function () {
+ var circle = Shape({center: {x: 0, y: 0}, radius: 10});
+ ok(Circle.is(circle));
+ });
+
+ it('should throw if used with new and union types are not instantiables with new', function () {
+ throwsWithMessage(function () {
+ var T = union([Str, Num], 'T');
+ T.dispatch = function () { return Str; };
+ /* jshint ignore:start */
+ var x = new T('a');
+ /* jshint ignore:end */
+ }, 'Operator `new` is forbidden for type `T`');
+ });
+
+ it('should not throw if used with new and union types are instantiables with new', function () {
+ doesNotThrow(function () {
+ Shape({center: {x: 0, y: 0}, radius: 10});
+ });
+ });
+
+ it('should be idempotent', function () {
+ var p1 = Shape({center: {x: 0, y: 0}, radius: 10});
+ var p2 = Shape(p1);
+ eq(Object.isFrozen(p1), true);
+ eq(Object.isFrozen(p2), true);
+ eq(p2 === p1, true);
+ });
+
+ });
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is an instance of the union', function () {
+ var p = new Circle({center: { x: 0, y: 0 }, radius: 10});
+ ok(Shape.is(p));
+ });
+
+ });
+
+});
+
+//
+// maybe
+//
+
+describe('maybe', function () {
+
+ describe('combinator', function () {
+
+ it('should throw if used with wrong arguments', function () {
+
+ throwsWithMessage(function () {
+ (maybe)();
+ }, 'Invalid argument `type` = `undefined` supplied to `maybe` combinator');
+
+ throwsWithMessage(function () {
+ (maybe)(Point, 1);
+ }, 'Invalid argument `name` = `1` supplied to `maybe` combinator');
+
+ });
+
+ it('should be idempotent', function () {
+ var MaybeStr = maybe(Str);
+ ok(maybe(MaybeStr) === MaybeStr);
+ });
+
+ it('should be noop with Any', function () {
+ ok(maybe(Any) === Any);
+ });
+
+ it('should be noop with Nil', function () {
+ ok((maybe)(Nil) === Nil);
+ });
+
+ });
+
+ describe('constructor', function () {
+
+ it('should throw if used with new', function () {
+ throwsWithMessage(function () {
+ /* jshint ignore:start */
+ var T = maybe(Str, 'T');
+ var x = new (T)();
+ /* jshint ignore:end */
+ }, 'Operator `new` is forbidden for type `T`');
+ });
+
+ it('should coerce values', function () {
+ var T = maybe(Point);
+ eq(T(null), null);
+ eq(T(undefined), null);
+ ok(Point.is(T({x: 0, y: 0})));
+ });
+
+ it('should be idempotent', function () {
+ var T = maybe(Point);
+ var p1 = T({x: 0, y: 0});
+ var p2 = T(p1);
+ eq(Object.isFrozen(p1), true);
+ eq(Object.isFrozen(p2), true);
+ eq(p2 === p1, true);
+ });
+
+ });
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is an instance of the maybe', function () {
+ var Radio = maybe(Str);
+ ok(Radio.is('a'));
+ ok(Radio.is(null));
+ ok(Radio.is(undefined));
+ });
+
+ });
+
+});
+
+//
+// tuple
+//
+
+describe('tuple', function () {
+
+ var Area = tuple([Num, Num], 'Area');
+
+ describe('combinator', function () {
+
+ it('should throw if used with wrong arguments', function () {
+
+ throwsWithMessage(function () {
+ (tuple)();
+ }, 'Invalid argument `types` = `undefined` supplied to `tuple` combinator');
+
+ throwsWithMessage(function () {
+ (tuple)([Point, Point], 1);
+ }, 'Invalid argument `name` = `1` supplied to `tuple` combinator');
+
+ });
+
+ });
+
+ describe('constructor', function () {
+
+ var S = struct({}, 'S');
+ var T = tuple([S, S], 'T');
+
+ it('should coerce values', function () {
+ var t = T([{}, {}]);
+ ok(S.is(t[0]));
+ ok(S.is(t[1]));
+ });
+
+ it('should accept only valid values', function () {
+
+ throwsWithMessage(function () {
+ T(1);
+ }, 'Invalid argument `value` = `1` supplied to tuple type `T`, expected an `Arr` of length `2`');
+
+ throwsWithMessage(function () {
+ T([1, 1]);
+ }, 'Invalid argument `value` = `1` supplied to struct type `S`');
+
+ });
+
+ it('should be idempotent', function () {
+ var T = tuple([Str, Num]);
+ var p1 = T(['a', 1]);
+ var p2 = T(p1);
+ eq(Object.isFrozen(p1), true);
+ eq(Object.isFrozen(p2), true);
+ eq(p2 === p1, true);
+ });
+
+ });
+
+ describe('#is(x)', function () {
+
+ it('should return true when x is an instance of the tuple', function () {
+ ok(Area.is([1, 2]));
+ });
+
+ it('should return false when x is not an instance of the tuple', function () {
+ ko(Area.is([1]));
+ ko(Area.is([1, 2, 3]));
+ ko(Area.is([1, 'a']));
+ });
+
+ it('should not depend on `this`', function () {
+ ok([[1, 2]].every(Area.is));
+ });
+
+ });
+
+ describe('#update()', function () {
+
+ var Type = tuple([Str, Num]);
+ var instance = Type(['a', 1]);
+
+ it('should return a new instance', function () {
+ var newInstance = Type.update(instance, {0: {$set: 'b'}});
+ assert(Type.is(newInstance));
+ assert(instance[0] === 'a');
+ assert(newInstance[0] === 'b');
+ });
+
+ });
+
+});
+
+//
+// list
+//
+
+describe('list', function () {
+
+ describe('combinator', function () {
+
+ it('should throw if used with wrong arguments', function () {
+
+ throwsWithMessage(function () {
+ (list)();
+ }, 'Invalid argument `type` = `undefined` supplied to `list` combinator');
+
+ throwsWithMessage(function () {
+ (list)(Point, 1);
+ }, 'Invalid argument `name` = `1` supplied to `list` combinator');
+
+ });
+
+ });
+
+ describe('constructor', function () {
+
+ var S = struct({}, 'S');
+ var T = list(S, 'T');
+
+ it('should coerce values', function () {
+ var t = T([{}]);
+ ok(S.is(t[0]));
+ });
+
+ it('should accept only valid values', function () {
+
+ throwsWithMessage(function () {
+ T(1);
+ }, 'Invalid argument `value` = `1` supplied to list type `T`');
+
+ throwsWithMessage(function () {
+ T([1]);
+ }, 'Invalid argument `value` = `1` supplied to struct type `S`');
+
+ });
+
+ it('should be idempotent', function () {
+ var T = list(Num);
+ var p1 = T([1, 2]);
+ var p2 = T(p1);
+ eq(Object.isFrozen(p1), true);
+ eq(Object.isFrozen(p2), true);
+ eq(p2 === p1, true);
+ });
+
+ });
+
+ describe('#is(x)', function () {
+
+ var Path = list(Point);
+ var p1 = new Point({x: 0, y: 0});
+ var p2 = new Point({x: 1, y: 1});
+
+ it('should return true when x is a list', function () {
+ ok(Path.is([p1, p2]));
+ });
+
+ it('should not depend on `this`', function () {
+ ok([[p1, p2]].every(Path.is));
+ });
+
+ });
+
+ describe('#update()', function () {
+
+ var Type = list(Str);
+ var instance = Type(['a', 'b']);
+
+ it('should return a new instance', function () {
+ var newInstance = Type.update(instance, {'$push': ['c']});
+ assert(Type.is(newInstance));
+ assert((instance).length === 2);
+ assert((newInstance).length === 3);
+ });
+
+ });
+
+});
+
+//
+// subtype
+//
+
+describe('subtype', function () {
+
+ var True = function () { return true; };
+
+ describe('combinator', function () {
+
+ it('should throw if used with wrong arguments', function () {
+
+ throwsWithMessage(function () {
+ (subtype)();
+ }, 'Invalid argument `type` = `undefined` supplied to `subtype` combinator');
+
+ throwsWithMessage(function () {
+ subtype(Point, null);
+ }, 'Invalid argument `predicate` = `null` supplied to `subtype` combinator');
+
+ throwsWithMessage(function () {
+ (subtype)(Point, True, 1);
+ }, 'Invalid argument `name` = `1` supplied to `subtype` combinator');
+
+ });
+
+ });
+
+ describe('constructor', function () {
+
+ it('should throw if used with new and a type that is not instantiable with new', function () {
+ throwsWithMessage(function () {
+ /* jshint ignore:start */
+ var T = subtype(Str, function () { return true; }, 'T');
+ var x = new( T)();
+ /* jshint ignore:end */
+ }, 'Operator `new` is forbidden for type `T`');
+ });
+
+ it('should coerce values', function () {
+ var T = subtype(Point, function () { return true; });
+ var p = T({x: 0, y: 0});
+ ok(Point.is(p));
+ });
+
+ it('should accept only valid values', function () {
+ var predicate = function (p:any) { return p.x > 0; };
+ var T = subtype(Point, predicate, 'T');
+ throwsWithMessage(function () {
+ T({x: 0, y: 0});
+ }, 'Invalid argument `value` = `[object Object]` supplied to subtype type `T`');
+ });
+
+ });
+
+ describe('#is(x)', function () {
+
+ var Positive = subtype(Num, function (n) {
+ return n >= 0;
+ });
+
+ it('should return true when x is a subtype', function () {
+ ok(Positive.is(1));
+ });
+
+ it('should return false when x is not a subtype', function () {
+ ko(Positive.is(-1));
+ });
+
+ });
+
+ describe('#update()', function () {
+
+ var Type = subtype(Str, function (s) { return s.length > 2; });
+ var instance = Type('abc');
+
+ it('should return a new instance', function () {
+ var newInstance = Type.update(instance, {'$set': 'bca'});
+ assert(Type.is(newInstance));
+ eq(newInstance, 'bca');
+ });
+
+ });
+
+});
+
+//
+// dict
+//
+
+describe('dict', function () {
+
+ describe('combinator', function () {
+
+ it('should throw if used with wrong arguments', function () {
+
+ throwsWithMessage(function () {
+ (dict)();
+ }, 'Invalid argument `domain` = `undefined` supplied to `dict` combinator');
+
+ throwsWithMessage(function () {
+ (dict)(Str);
+ }, 'Invalid argument `codomain` = `undefined` supplied to `dict` combinator');
+
+ throwsWithMessage(function () {
+ (dict)(Str, Point, 1);
+ }, 'Invalid argument `name` = `1` supplied to `dict` combinator');
+
+ });
+
+ });
+
+ describe('constructor', function () {
+
+ var S = struct({}, 'S');
+ var Domain = subtype(Str, function (x) {
+ return x !== 'forbidden';
+ }, 'Domain');
+ var T = dict(Domain, S, 'T');
+
+ it('should coerce values', function () {
+ var t = T({a: {}});
+ ok(S.is((t).a));
+ });
+
+ it('should accept only valid values', function () {
+
+ throwsWithMessage(function () {
+ T(1);
+ }, 'Invalid argument `value` = `1` supplied to dict type `T`');
+
+ throwsWithMessage(function () {
+ T({a: 1});
+ }, 'Invalid argument `value` = `1` supplied to struct type `S`');
+
+ throwsWithMessage(function () {
+ T({forbidden: {}});
+ }, 'Invalid argument `value` = `forbidden` supplied to subtype type `Domain`');
+
+ });
+
+ it('should be idempotent', function () {
+ var T = dict(Str, Str);
+ var p1 = T({a: 'a', b: 'b'});
+ var p2 = T(p1);
+ eq(Object.isFrozen(p1), true);
+ eq(Object.isFrozen(p2), true);
+ eq(p2 === p1, true);
+ });
+
+ });
+
+ describe('#is(x)', function () {
+
+ var T = dict(Str, Point);
+ var p1 = new Point({x: 0, y: 0});
+ var p2 = new Point({x: 1, y: 1});
+
+ it('should return true when x is a list', function () {
+ ok(T.is({a: p1, b: p2}));
+ });
+
+ it('should not depend on `this`', function () {
+ ok([{a: p1, b: p2}].every(T.is));
+ });
+
+ });
+
+ describe('#update()', function () {
+
+ var Type = dict(Str, Str);
+ var instance = Type({p1: 'a', p2: 'b'});
+
+ it('should return a new instance', function () {
+ var newInstance = Type.update(instance, {p2: {$set: 'c'}});
+ ok(Type.is(newInstance));
+ eq((instance).p2, 'b');
+ eq((newInstance).p2, 'c');
+ });
+
+ });
+
+});
+
+//
+// func
+//
+
+describe('func', function () {
+
+ it('should handle a no types', function () {
+ var T = func([], Str);
+ eq(T.meta.domain.length, 0);
+ var getGreeting = T.of(function () { return 'Hi'; });
+ eq(getGreeting(), 'Hi');
+ });
+
+ it('should handle a single type', function () {
+ var T = func(Num, Num);
+ eq(T.meta.domain.length, 1);
+ ok(T.meta.domain[0] === Num);
+ });
+
+ it('should automatically instrument a function', function () {
+ var T = func(Num, Num);
+ var f = function () { return 'hi'; };
+ ok(T.is(T(f)));
+ });
+
+ describe('of', function () {
+
+ it('should check the arguments', function () {
+
+ var T = func([Num, Num], Num);
+ var sum = T.of(function (a:any, b:any) {
+ return a + b;
+ });
+ eq(sum(1, 2), 3);
+
+ throwsWithMessage(function () {
+ sum(1, 2, 3);
+ }, 'Invalid argument `value` = `1,2,3` supplied to tuple type `[Num, Num]`, expected an `Arr` of length `2`');
+
+ throwsWithMessage(function () {
+ sum('a', 2);
+ }, 'Invalid argument `value` = `a` supplied to irreducible type `Num`');
+
+ });
+
+ it('should check the return value', function () {
+
+ var T = func([Num, Num], Num);
+ var sum = T.of(function () {
+ return 'a';
+ });
+
+ throwsWithMessage(function () {
+ sum(1, 2);
+ }, 'Invalid argument `value` = `a` supplied to irreducible type `Num`');
+
+ });
+
+ it('should preserve `this`', function () {
+ var o = {name: 'giulio'};
+ (o).getTypeName = func([], Str).of(function () {
+ return this.name;
+ });
+ eq((o).getTypeName(), 'giulio');
+ });
+
+ it('should handle function types', function () {
+ var A = func([Str], Str);
+ var B = func([Str, A], Str);
+
+ var f = A.of(function (s:any) {
+ return s + '!';
+ });
+ var g = B.of(function (str:any, strAction:any) {
+ return strAction(str);
+ });
+
+ eq(g('hello', f), 'hello!');
+ });
+
+ it('should be idempotent', function () {
+ var f = function (s:any) { return s; };
+ var g = func([Str], Str).of(f);
+ var h = func([Str], Str).of(g);
+ ok(h === g);
+ });
+
+ });
+
+ describe('currying', function () {
+
+ it('should curry functions', function () {
+ var Type = func([Num, Num, Num], Num);
+ var sum = Type.of(function (a:any, b:any, c:any) {
+ return a + b + c;
+ });
+ eq(sum(1, 2, 3), 6);
+ eq(sum(1, 2)(3), 6);
+ eq(sum(1)(2, 3), 6);
+ eq(sum(1)(2)(3), 6);
+
+ // important: the curried function must be of the correct type
+ var CurriedType = func([Num, Num], Num);
+ var sum1 = sum(1);
+ eq(sum1(2, 3), 6);
+ eq(sum1(2)(3), 6);
+ ok(CurriedType.is(sum1));
+ });
+
+ it('should throw if partial arguments are wrong', function () {
+
+ var T = func([Num, Num], Num);
+ var sum = T.of(function (a:any, b:any) {
+ return a + b;
+ });
+
+ throwsWithMessage(function () {
+ sum('a');
+ }, 'Invalid argument `value` = `a` supplied to irreducible type `Num`');
+
+ throwsWithMessage(function () {
+ var sum1 = sum(1);
+ sum1('a');
+ }, 'Invalid argument `value` = `a` supplied to irreducible type `Num`');
+
+ });
+
+ });
+
+});
diff --git a/tcomb/tcomb.d.ts b/tcomb/tcomb.d.ts
index 91c004160f..91add803a9 100644
--- a/tcomb/tcomb.d.ts
+++ b/tcomb/tcomb.d.ts
@@ -83,7 +83,7 @@ declare module TComb {
}
export interface Any_Static extends TCombBase {
-
+
new (value: any): Any_Instance;
(value: any): Any_Instance;
}