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; }