construction.test.js (9996B)
1 // test parse 2 var assert = require('assert'); 3 var typed = require('../typed-function'); 4 5 describe('construction', function() { 6 7 it('should throw an error when not providing any signatures', function() { 8 assert.throws(function () { 9 typed({}); 10 }, /Error: No signatures provided/); 11 }); 12 13 it('should create a named function', function() { 14 var fn = typed('myFunction', { 15 'string': function (str) { 16 return 'foo'; 17 } 18 }); 19 20 assert.equal(fn('bar'), 'foo'); 21 assert.equal(fn.name, 'myFunction'); 22 }); 23 24 it('should create a typed function from a regular function with a signature', function() { 25 function myFunction(str) { 26 return 'foo'; 27 } 28 myFunction.signature = 'string' 29 30 var fn = typed(myFunction); 31 32 assert.equal(fn('bar'), 'foo'); 33 assert.equal(fn.name, 'myFunction'); 34 assert.deepEqual(Object.keys(fn.signatures), ['string']); 35 }); 36 37 it('should create an unnamed function', function() { 38 var fn = typed({ 39 'string': function (str) { 40 return 'foo'; 41 } 42 }); 43 44 assert.equal(fn('bar'), 'foo'); 45 assert.equal(fn.name, ''); 46 }); 47 48 it('should inherit the name of typed functions', function() { 49 var fn = typed({ 50 'string': typed('fn1', { 51 'string': function (str) { 52 return 'foo'; 53 } 54 }) 55 }); 56 57 assert.equal(fn('bar'), 'foo'); 58 assert.equal(fn.name, 'fn1'); 59 }); 60 61 it('should not inherit the name of the JavaScript functions (only from typed functions)', function() { 62 var fn = typed({ 63 'string': function fn1 (str) { 64 return 'foo'; 65 } 66 }); 67 68 assert.equal(fn('bar'), 'foo'); 69 assert.equal(fn.name, ''); 70 }); 71 72 it('should compose a function with zero arguments', function() { 73 var signatures = { 74 '': function () { 75 return 'noargs'; 76 } 77 }; 78 var fn = typed(signatures); 79 80 assert.equal(fn(), 'noargs'); 81 assert(fn.signatures instanceof Object); 82 assert.strictEqual(Object.keys(fn.signatures).length, 1); 83 assert.strictEqual(fn.signatures[''], signatures['']); 84 }); 85 86 it('should create a typed function with one argument', function() { 87 var fn = typed({ 88 'string': function () { 89 return 'string'; 90 } 91 }); 92 93 assert.equal(fn('hi'), 'string'); 94 }); 95 96 it('should create a typed function with two arguments', function() { 97 var fn = typed({ 98 'string, boolean': function () { 99 return 'foo'; 100 } 101 }); 102 103 assert.equal(fn('hi', true), 'foo'); 104 }); 105 106 it('should create a named, typed function', function() { 107 var fn = typed('myFunction', { 108 'string, boolean': function () { 109 return 'noargs'; 110 } 111 }); 112 113 assert.equal(fn('hi', true), 'noargs'); 114 assert.equal(fn.name, 'myFunction'); 115 }); 116 117 it('should correctly recognize Date from Object (both are an Object)', function() { 118 var signatures = { 119 'Object': function (value) { 120 assert(value instanceof Object); 121 return 'Object'; 122 }, 123 'Date': function (value) { 124 assert(value instanceof Date); 125 return 'Date'; 126 } 127 }; 128 var fn = typed(signatures); 129 130 assert.equal(fn({foo: 'bar'}), 'Object'); 131 assert.equal(fn(new Date()), 'Date'); 132 }); 133 134 it('should correctly handle null', function () { 135 var fn = typed({ 136 'Object': function (a) { 137 return 'Object'; 138 }, 139 'null': function (a) { 140 return 'null'; 141 }, 142 'undefined': function (a) { 143 return 'undefined'; 144 } 145 }); 146 147 assert.equal(fn(new Object(null)), 'Object'); 148 assert.equal(fn(null), 'null'); 149 assert.equal(fn(undefined), 'undefined'); 150 }); 151 152 it('should throw correct error message when passing null from an Object', function() { 153 var signatures = { 154 'Object': function (value) { 155 assert(value instanceof Object); 156 return 'Object'; 157 } 158 }; 159 var fn = typed(signatures); 160 161 assert.equal(fn({}), 'Object'); 162 assert.throws(function () { fn(null) }, 163 /TypeError: Unexpected type of argument in function unnamed \(expected: Object, actual: null, index: 0\)/); 164 }); 165 166 it('should create a new, isolated instance of typed-function', function() { 167 var typed1 = typed.create(); 168 var typed2 = typed.create(); 169 function Person() {} 170 171 typed1.types.push({ 172 name: 'Person', 173 test: function (x) { 174 return x instanceof Person; 175 } 176 }); 177 178 assert.strictEqual(typed.create, typed1.create); 179 assert.notStrictEqual(typed.types, typed1.types); 180 assert.notStrictEqual(typed.conversions, typed1.conversions); 181 182 assert.strictEqual(typed.create, typed2.create); 183 assert.notStrictEqual(typed.types, typed2.types); 184 assert.notStrictEqual(typed.conversions, typed2.conversions); 185 186 assert.strictEqual(typed1.create, typed2.create); 187 assert.notStrictEqual(typed1.types, typed2.types); 188 assert.notStrictEqual(typed1.conversions, typed2.conversions); 189 190 typed1({ 191 'Person': function (p) {return 'Person'} 192 }); 193 194 assert.throws(function () { 195 typed2({ 196 'Person': function (p) {return 'Person'} 197 }); 198 }, /Error: Unknown type "Person"/) 199 }); 200 201 it('should add a type using addType (before object)', function() { 202 var typed2 = typed.create(); 203 function Person() {} 204 205 var newType = { 206 name: 'Person', 207 test: function (x) { 208 return x instanceof Person; 209 } 210 }; 211 212 var objectEntry = typed2.types.find(function (entry) { 213 return entry.name === 'Object'; 214 }); 215 var objectIndex = typed2.types.indexOf(objectEntry); 216 217 typed2.addType(newType); 218 219 assert.strictEqual(typed2.types[objectIndex], newType); 220 }); 221 222 it('should add a type using addType at the end (after Object)', function() { 223 var typed2 = typed.create(); 224 function Person() {} 225 226 var newType = { 227 name: 'Person', 228 test: function (x) { 229 return x instanceof Person; 230 } 231 }; 232 233 typed2.addType(newType, false); 234 235 assert.strictEqual(typed2.types[typed2.types.length - 1], newType); 236 }); 237 238 it('should throw an error when passing an invalid type to addType', function() { 239 var typed2 = typed.create(); 240 var errMsg = /TypeError: Object with properties {name: string, test: function} expected/; 241 242 assert.throws(function () {typed2.addType({})}, errMsg); 243 assert.throws(function () {typed2.addType({name: 2, test: function () {}})}, errMsg); 244 assert.throws(function () {typed2.addType({name: 'foo', test: 'bar'})}, errMsg); 245 }); 246 247 it('should throw an error when providing an unsupported type of argument', function() { 248 var fn = typed('fn1', { 249 'number': function (value) { 250 return 'number:' + value; 251 } 252 }); 253 254 assert.throws(function () {fn(new Date())}, /TypeError: Unexpected type of argument in function fn1 \(expected: number, actual: Date, index: 0\)/); 255 }); 256 257 it('should throw an error when providing a wrong function signature', function() { 258 var fn = typed('fn1', { 259 'number': function (value) { 260 return 'number:' + value; 261 } 262 }); 263 264 assert.throws(function () {fn(1, 2)}, /TypeError: Too many arguments in function fn1 \(expected: 1, actual: 2\)/); 265 }); 266 267 it('should throw an error when composing with an unknown type', function() { 268 assert.throws(function () { 269 var fn = typed({ 270 'foo': function (value) { 271 return 'number:' + value; 272 } 273 }); 274 }, /Error: Unknown type "foo"/); 275 }); 276 277 it('should ignore types from typed.ignore', function() { 278 var typed2 = typed.create(); 279 typed2.ignore = ['string']; 280 281 var fn = typed2({ 282 'number': function () {}, 283 'number, number': function () {}, 284 285 'string, number': function () {}, 286 'number, string': function () {}, 287 'boolean | string, boolean': function () {}, 288 'any, ...string': function () {}, 289 'string': function () {} 290 }); 291 292 assert.deepEqual(Object.keys(fn.signatures).sort(), ['boolean,boolean', 'number', 'number,number']); 293 }); 294 295 it('should give a hint when composing with a wrongly cased type', function() { 296 assert.throws(function () { 297 var fn = typed({ 298 'array': function (value) { 299 return 'array:' + value; 300 } 301 }); 302 }, /Error: Unknown type "array". Did you mean "Array"?/); 303 304 assert.throws(function () { 305 var fn = typed({ 306 'function': function (value) { 307 return 'Function:' + value; 308 } 309 }); 310 }, /Error: Unknown type "function". Did you mean "Function"?/); 311 }); 312 313 it('should attach signatures to the created typed-function', function() { 314 var fn1 = function () {} 315 var fn2 = function () {} 316 var fn3 = function () {} 317 var fn4 = function () {} 318 319 var fn = typed({ 320 'string': fn1, 321 'string, boolean': fn2, 322 'number | Date, boolean': fn3, 323 'Array | Object, string | RegExp': fn3, 324 'number, ...string | number': fn4 325 }); 326 327 assert.deepStrictEqual(fn.signatures, { 328 'string': fn1, 329 'string,boolean': fn2, 330 'number,boolean': fn3, 331 'Date,boolean': fn3, 332 'Array,string': fn3, 333 'Array,RegExp': fn3, 334 'Object,string': fn3, 335 'Object,RegExp': fn3, 336 'number,...string|number': fn4 337 }); 338 }); 339 340 it('should correctly order signatures', function () { 341 var fn = typed({ 342 'boolean': function (a) { 343 return 'boolean'; 344 }, 345 'string': function (a) { 346 return 'string'; 347 }, 348 'number': function (a) { 349 return 'number'; 350 } 351 }); 352 353 // TODO: this is tricky, object keys do not officially have a guaranteed order 354 assert.deepEqual(Object.keys(fn.signatures), 355 ['number', 'string', 'boolean']); 356 }); 357 358 it('should allow a function to be defined recursively', function () { 359 var fn = typed({ 360 'number': function (value) { 361 return 'number:' + value; 362 }, 363 'string': function (value) { 364 return this(parseInt(value, 10)); 365 } 366 }); 367 368 assert.equal(fn('2'), 'number:2'); 369 }) 370 371 });