Repository: gcanti/tcomb
Pull request #132 (2015-09-01 10:26:17)

v2.4.0


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f4b7cb9..b9c53fa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,14 @@
 **Note**: Gaps between patch versions are faulty/broken releases.
 **Note**: A feature tagged as Experimental is in a high state of flux, you're at risk of it changing without notice.
 
+## v2.4.0
+
+- **New Feature**
+  - unions
+    - added `update` function, #127
+    - the default `dispatch` implementation now handles unions of unions, #126
+    - show the offended union type in error messages
+
 ## v2.3.0
 
 - **New Feature**
diff --git a/LICENSE b/LICENSE
index 6efcb2c..392a731 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2014-2015 Giulio Canti
+Copyright (c) 2014 Giulio Canti
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/index.js b/index.js
index 8d75a81..a9a35f3 100644
--- a/index.js
+++ b/index.js
@@ -1,3 +1,11 @@
+/*! @preserve
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014 Giulio Canti
+ *
+ */
+
 'use strict';
 
 // configurable
@@ -396,6 +404,7 @@ function union(types, name) {
     if (process.env.NODE_ENV !== 'production') {
       path = path || [displayName];
       assert(isType(type), function () { return 'Invalid value ' + exports.stringify(value) + ' supplied to ' + path.join('/'); });
+      path[path.length - 1] += '(' + getTypeName(type) + ')';
     }
 
     return create(type, value, path);
@@ -424,19 +433,14 @@ function union(types, name) {
           return t;
         }
       }
-      if (is(x, type)) {
+      else if (is(x, type)) {
         return type;
       }
     }
   };
 
   Union.update = function (instance, spec) {
-    for (var i = 0, len = types.length; i < len; i++ ) {
-      var type = types[i];
-      if (is(instance, type)) {
-        return type.update(instance, spec);
-      }
-    }
+    return Union(exports.update(instance, spec));
   };
 
   return Union;
@@ -718,8 +722,8 @@ function list(type, name) {
   function List(value, path) {
 
     if (process.env.NODE_ENV !== 'production') {
-      assert(isArray(value), function () { return 'Invalid value ' + exports.stringify(value) + ' supplied to ' + displayName + ' (expected an array of ' + typeNameCache + ')'; });
       path = path || [displayName];
+      assert(isArray(value), function () { return 'Invalid value ' + exports.stringify(value) + ' supplied to ' + path.join('/') + ' (expected an array of ' + typeNameCache + ')'; });
     }
 
     if (isList(value)) { // makes List idempotent
@@ -791,8 +795,8 @@ function dict(domain, codomain, name) {
   function Dict(value, path) {
 
     if (process.env.NODE_ENV !== 'production') {
-      assert(isObject(value), function () { return 'Invalid value ' + exports.stringify(value) + ' supplied to ' + displayName; });
       path = path || [displayName];
+      assert(isObject(value), function () { return 'Invalid value ' + exports.stringify(value) + ' supplied to ' + path.join('/'); });
     }
 
     if (isDict(value)) { // makes Dict idempotent
diff --git a/package.json b/package.json
index be2d34b..887259a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "tcomb",
-  "version": "2.3.0",
+  "version": "2.4.0",
   "description": "Type checking and DDD for JavaScript",
   "main": "index.js",
   "scripts": {


Test patch:
diff --git a/test/getTypeName.js b/test/getTypeName.js
index c28c790..9b22363 100644
--- a/test/getTypeName.js
+++ b/test/getTypeName.js
@@ -4,25 +4,25 @@ var t = require('../index');
 
 describe('t.getTypeName(type)', function () {
 
-  var UnnamedStruct = t.struct({});
+  var NamelessStruct = t.struct({});
   var NamedStruct = t.struct({}, 'NamedStruct');
-  var UnnamedUnion = t.union([t.String, t.Number]);
+  var NamelessUnion = t.union([t.String, t.Number]);
   var NamedUnion = t.union([t.String, t.Number], 'NamedUnion');
-  var UnnamedMaybe = t.maybe(t.String);
+  var NamelessMaybe = t.maybe(t.String);
   var NamedMaybe = t.maybe(t.String, 'NamedMaybe');
-  var UnnamedEnums = t.enums({a: 'A', b: 'B'});
+  var NamelessEnums = t.enums({a: 'A', b: 'B'});
   var NamedEnums = t.enums({}, 'NamedEnums');
-  var UnnamedTuple = t.tuple([t.String, t.Number]);
+  var NamelessTuple = t.tuple([t.String, t.Number]);
   var NamedTuple = t.tuple([t.String, t.Number], 'NamedTuple');
-  var UnnamedSubtype = t.subtype(t.String, function notEmpty(x) { return x !== ''; });
+  var NamelessSubtype = t.subtype(t.String, function notEmpty(x) { return x !== ''; });
   var NamedSubtype = t.subtype(t.String, function (x) { return x !== ''; }, 'NamedSubtype');
-  var UnnamedList = t.list(t.String);
+  var NamelessList = t.list(t.String);
   var NamedList = t.list(t.String, 'NamedList');
-  var UnnamedDict = t.dict(t.String, t.String);
+  var NamelessDict = t.dict(t.String, t.String);
   var NamedDict = t.dict(t.String, t.String, 'NamedDict');
-  var UnnamedFunc = t.func(t.String, t.String);
+  var NamelessFunc = t.func(t.String, t.String);
   var NamedFunc = t.func(t.String, t.String, 'NamedFunc');
-  var UnnamedIntersection = t.intersection([t.String, t.Number]);
+  var NamelessIntersection = t.intersection([t.String, t.Number]);
   var NamedIntersection = t.intersection([t.String, t.Number], 'NamedIntersection');
 
   it('should return the name of a function', function () {
@@ -42,17 +42,17 @@ describe('t.getTypeName(type)', function () {
     assert.deepEqual(t.getTypeName(NamedIntersection), 'NamedIntersection');
   });
 
-  it('should return a meaningful name of a unnamed type', function () {
-    assert.deepEqual(t.getTypeName(UnnamedStruct), '{}');
-    assert.deepEqual(t.getTypeName(UnnamedUnion), 'String | Number');
-    assert.deepEqual(t.getTypeName(UnnamedMaybe), '?String');
-    assert.deepEqual(t.getTypeName(UnnamedEnums), '"a" | "b"');
-    assert.deepEqual(t.getTypeName(UnnamedTuple), '[String, Number]');
-    assert.deepEqual(t.getTypeName(UnnamedSubtype), '{String | notEmpty}');
-    assert.deepEqual(t.getTypeName(UnnamedList), 'Array<String>');
-    assert.deepEqual(t.getTypeName(UnnamedDict), '{[key: String]: String}');
-    assert.deepEqual(t.getTypeName(UnnamedFunc), '(String) => String');
-    assert.deepEqual(t.getTypeName(UnnamedIntersection), 'String & Number');
+  it('should return a meaningful name of a Nameless type', function () {
+    assert.deepEqual(t.getTypeName(NamelessStruct), '{}');
+    assert.deepEqual(t.getTypeName(NamelessUnion), 'String | Number');
+    assert.deepEqual(t.getTypeName(NamelessMaybe), '?String');
+    assert.deepEqual(t.getTypeName(NamelessEnums), '"a" | "b"');
+    assert.deepEqual(t.getTypeName(NamelessTuple), '[String, Number]');
+    assert.deepEqual(t.getTypeName(NamelessSubtype), '{String | notEmpty}');
+    assert.deepEqual(t.getTypeName(NamelessList), 'Array<String>');
+    assert.deepEqual(t.getTypeName(NamelessDict), '{[key: String]: String}');
+    assert.deepEqual(t.getTypeName(NamelessFunc), '(String) => String');
+    assert.deepEqual(t.getTypeName(NamelessIntersection), 'String & Number');
   });
 
 });
diff --git a/test/union.js b/test/union.js
index fdd61b9..8e735da 100644
--- a/test/union.js
+++ b/test/union.js
@@ -8,26 +8,26 @@ var Point = t.struct({
   y: t.Number
 });
 
-describe('t.union(types, [name])', function () {
-
-  var Circle = t.struct({
-    center: Point,
-    radius: t.Number
-  }, 'Circle');
+var Circle = t.struct({
+  center: Point,
+  radius: t.Number
+}, 'Circle');
+
+var Rectangle = t.struct({
+  a: Point,
+  b: Point
+});
 
-  var Rectangle = t.struct({
-    a: Point,
-    b: Point
-  });
+var Shape = t.union([Circle, Rectangle], 'Shape');
 
-  var Shape = t.union([Circle, Rectangle], 'Shape');
+Shape.dispatch = function (values) {
+  assert(t.Object.is(values));
+  return values.hasOwnProperty('center') ?
+    Circle :
+    Rectangle;
+};
 
-  Shape.dispatch = function (values) {
-    assert(t.Object.is(values));
-    return values.hasOwnProperty('center') ?
-      Circle :
-      Rectangle;
-  };
+describe('t.union(types, [name])', function () {
 
   describe('combinator', function () {
 
@@ -55,25 +55,6 @@ describe('t.union(types, [name])', function () {
 
   describe('constructor', function () {
 
-    it('should have a default dispatch() implementation', function () {
-      var T = t.union([t.String, t.Number], 'T');
-      assert.deepEqual(T(1), 1);
-    });
-
-    it('should have a default dispatch() implementation handling union of unions', function () {
-      var T1 = t.union([t.String, t.Number], 'T1');
-      var T2 = t.union([t.Boolean, t.Object], 'T2');
-      var T = t.union([T1, T2], 'T');
-      assert.deepEqual(T({foo: "bar"}), {foo: "bar"});
-    });
-
-    it('should throw when dispatch() does not return a type', function () {
-      throwsWithMessage(function () {
-        var T = t.union([t.String, t.Number], 'T');
-        T(true);
-      }, '[tcomb] Invalid value true supplied to T');
-    });
-
     it('should build instances when dispatch() is implemented', function () {
       var circle = Shape({center: {x: 0, y: 0}, radius: 10});
       assert.ok(Circle.is(circle));
@@ -87,6 +68,12 @@ describe('t.union(types, [name])', function () {
       }, '[tcomb] Cannot use the new operator to instantiate the type T');
     });
 
+    it('should show the offended union type in error messages', function () {
+      throwsWithMessage(function () {
+        Shape({center: {x: 0, y: 0}});
+      }, '[tcomb] Invalid value undefined supplied to Shape(Circle)/radius: Number');
+    });
+
     it('should not throw if used with new and union types are instantiables with new', function () {
       assert.doesNotThrow(function () {
         Shape({center: {x: 0, y: 0}, radius: 10});
@@ -112,4 +99,38 @@ describe('t.union(types, [name])', function () {
 
   });
 
+  describe('#dispatch(x)', function () {
+
+    it('should have a default implementation', function () {
+      var T = t.union([t.String, t.Number], 'T');
+      assert.deepEqual(T(1), 1);
+    });
+
+    it('should handle union of unions', function () {
+      var T1 = t.union([t.String, t.Number], 'T1');
+      var T2 = t.union([t.Boolean, t.Object], 'T2');
+      var T = t.union([T1, T2, t.Array], 'T');
+      assert.strictEqual(T.dispatch(1), t.Number);
+      assert.strictEqual(T.dispatch({foo: "bar"}), t.Object);
+      assert.strictEqual(T.dispatch([]), t.Array);
+    });
+
+    it('should throw if does not return a type', function () {
+      throwsWithMessage(function () {
+        var T = t.union([t.String, t.Number], 'T');
+        T(true);
+      }, '[tcomb] Invalid value true supplied to T');
+    });
+
+  });
+
+  describe('#update(instance, spec)', function () {
+
+    it('should update the right instance', function () {
+      var circle = Shape.update({ center: { x: 0, y: 0 }, radius: 10 }, { radius: { $set: 15 } });
+      assert.strictEqual(Circle.is(circle), true);
+    });
+
+  });
+
 });