conversion.test.js (14879B)
1 var assert = require('assert'); 2 var typed = require('../typed-function'); 3 var strictEqualArray = require('./strictEqualArray'); 4 5 describe('conversion', function () { 6 7 before(function () { 8 typed.conversions = [ 9 {from: 'boolean', to: 'number', convert: function (x) {return +x;}}, 10 {from: 'boolean', to: 'string', convert: function (x) {return x + '';}}, 11 {from: 'number', to: 'string', convert: function (x) {return x + '';}}, 12 { 13 from: 'string', 14 to: 'Date', 15 convert: function (x) { 16 var d = new Date(x); 17 return isNaN(d.valueOf()) ? undefined : d; 18 }, 19 fallible: true // TODO: not yet supported 20 } 21 ]; 22 }); 23 24 after(function () { 25 // cleanup conversions 26 typed.conversions = []; 27 }); 28 29 it('should add conversions to a function with one argument', function() { 30 var fn = typed({ 31 'string': function (a) { 32 return a; 33 } 34 }); 35 36 assert.equal(fn(2), '2'); 37 assert.equal(fn(false), 'false'); 38 assert.equal(fn('foo'), 'foo'); 39 }); 40 41 it('should add a conversion using addConversion', function() { 42 var typed2 = typed.create(); 43 44 var conversion = { 45 from: 'number', 46 to: 'string', 47 convert: function (x) { 48 return x + ''; 49 } 50 }; 51 52 assert.equal(typed2.conversions.length, 0); 53 54 typed2.addConversion(conversion); 55 56 assert.equal(typed2.conversions.length, 1); 57 assert.strictEqual(typed2.conversions[0], conversion); 58 }); 59 60 it('should throw an error when passing an invalid conversion object to addConversion', function() { 61 var typed2 = typed.create(); 62 var errMsg = /TypeError: Object with properties \{from: string, to: string, convert: function} expected/; 63 64 assert.throws(function () {typed2.addConversion({})}, errMsg); 65 assert.throws(function () {typed2.addConversion({from: 'number', to: 'string'})}, errMsg); 66 assert.throws(function () {typed2.addConversion({from: 'number', convert: function () {}})}, errMsg); 67 assert.throws(function () {typed2.addConversion({to: 'string', convert: function () {}})}, errMsg); 68 assert.throws(function () {typed2.addConversion({from: 2, to: 'string', convert: function () {}})}, errMsg); 69 assert.throws(function () {typed2.addConversion({from: 'number', to: 2, convert: function () {}})}, errMsg); 70 assert.throws(function () {typed2.addConversion({from: 'number', to: 'string', convert: 'foo'})}, errMsg); 71 }); 72 73 it('should add conversions to a function with multiple arguments', function() { 74 // note: we add 'string, string' first, and `string, number` afterwards, 75 // to test whether the conversions are correctly ordered. 76 var fn = typed({ 77 'string, string': function (a, b) { 78 assert.equal(typeof a, 'string'); 79 assert.equal(typeof b, 'string'); 80 return 'string, string'; 81 }, 82 'string, number': function (a, b) { 83 assert.equal(typeof a, 'string'); 84 assert.equal(typeof b, 'number'); 85 return 'string, number'; 86 } 87 }); 88 89 assert.equal(fn(true, false), 'string, number'); 90 assert.equal(fn(true, 2), 'string, number'); 91 assert.equal(fn(true, 'foo'), 'string, string'); 92 assert.equal(fn(2, false), 'string, number'); 93 assert.equal(fn(2, 3), 'string, number'); 94 assert.equal(fn(2, 'foo'), 'string, string'); 95 assert.equal(fn('foo', true), 'string, number'); 96 assert.equal(fn('foo', 2), 'string, number'); 97 assert.equal(fn('foo', 'foo'), 'string, string'); 98 assert.deepEqual(Object.keys(fn.signatures), [ 99 'string,number', 100 'string,string' 101 ]); 102 }); 103 104 it('should add conversions to a function with rest parameters (1)', function() { 105 var toNumber = typed({ 106 '...number': function (values) { 107 assert(Array.isArray(values)); 108 return values; 109 } 110 }); 111 112 assert.deepStrictEqual(toNumber(2,3,4), [2,3,4]); 113 assert.deepStrictEqual(toNumber(2,true,4), [2,1,4]); 114 assert.deepStrictEqual(toNumber(1,2,false), [1,2,0]); 115 assert.deepStrictEqual(toNumber(1,2,true), [1,2,1]); 116 assert.deepStrictEqual(toNumber(true,1,2), [1,1,2]); 117 assert.deepStrictEqual(toNumber(true,false, true), [1,0,1]); 118 }); 119 120 it('should add conversions to a function with rest parameters (2)', function() { 121 var sum = typed({ 122 'string, ...number': function (name, values) { 123 assert.equal(typeof name, 'string'); 124 assert(Array.isArray(values)); 125 var sum = 0; 126 for (var i = 0; i < values.length; i++) { 127 sum += values[i]; 128 } 129 return sum; 130 } 131 }); 132 133 assert.equal(sum('foo', 2,3,4), 9); 134 assert.equal(sum('foo', 2,true,4), 7); 135 assert.equal(sum('foo', 1,2,false), 3); 136 assert.equal(sum('foo', 1,2,true), 4); 137 assert.equal(sum('foo', true,1,2), 4); 138 assert.equal(sum('foo', true,false, true), 2); 139 assert.equal(sum(123, 2,3), 5); 140 assert.equal(sum(false, 2,3), 5); 141 }); 142 143 it('should add conversions to a function with rest parameters in a non-conflicting way', function() { 144 var fn = typed({ 145 '...number': function (values) { 146 return values; 147 }, 148 'boolean': function (value) { 149 assert.equal(typeof value, 'boolean'); 150 return 'boolean'; 151 } 152 }); 153 154 assert.deepStrictEqual(fn(2,3,4), [2,3,4]); 155 assert.deepStrictEqual(fn(2,true,4), [2,1,4]); 156 assert.deepStrictEqual(fn(true,3,4), [1,3,4]); 157 assert.equal(fn(false), 'boolean'); 158 assert.equal(fn(true), 'boolean'); 159 }); 160 161 it('should add conversions to a function with rest parameters in a non-conflicting way', function() { 162 var typed2 = typed.create(); 163 typed2.conversions = [ 164 {from: 'boolean', to: 'number', convert: function (x) {return +x}}, 165 {from: 'string', to: 'number', convert: function (x) {return parseFloat(x)}}, 166 {from: 'string', to: 'boolean', convert: function (x) {return !!x}} 167 ]; 168 169 // booleans can be converted to numbers, so the `...number` signature 170 // will match. But the `...boolean` signature is a better (exact) match so that 171 // should be picked 172 var fn = typed2({ 173 '...number': function (values) { 174 return values; 175 }, 176 '...boolean': function (values) { 177 return values; 178 } 179 }); 180 181 assert.deepStrictEqual(fn(2,3,4), [2,3,4]); 182 assert.deepStrictEqual(fn(2,true,4), [2,1,4]); 183 assert.deepStrictEqual(fn(true,true,true), [true,true,true]); 184 }); 185 186 it('should add conversions to a function with variable and union arguments', function() { 187 var fn = typed({ 188 '...string | number': function (values) { 189 assert(Array.isArray(values)); 190 return values; 191 } 192 }); 193 194 assert.deepStrictEqual(fn(2,3,4), [2,3,4]); 195 assert.deepStrictEqual(fn(2,true,4), [2,1,4]); 196 assert.deepStrictEqual(fn(2,'str'), [2,'str']); 197 assert.deepStrictEqual(fn('str', true, false), ['str', 1, 0]); 198 assert.deepStrictEqual(fn('str', 2, false), ['str', 2, 0]); 199 200 assert.throws(function () {fn(new Date(), '2')}, /TypeError: Unexpected type of argument in function unnamed \(expected: string or number or boolean, actual: Date, index: 0\)/) 201 }); 202 203 it('should order conversions and type Object correctly ', function() { 204 var typed2 = typed.create(); 205 typed2.conversions = [ 206 {from: 'Date', to: 'string', convert: function (x) {return x.toISOString()}} 207 ]; 208 209 var fn = typed2({ 210 'string': function () { 211 return 'string'; 212 }, 213 'Object': function () { 214 return 'object'; 215 } 216 }); 217 218 assert.equal(fn('foo'), 'string'); 219 assert.equal(fn(new Date(2018, 1, 20)), 'string'); 220 assert.equal(fn({a: 2}), 'object'); 221 }); 222 223 it('should add non-conflicting conversions to a function with one argument', function() { 224 var fn = typed({ 225 'number': function (a) { 226 return a; 227 }, 228 'string': function (a) { 229 return a; 230 } 231 }); 232 233 // booleans should be converted to number 234 assert.strictEqual(fn(false), 0); 235 assert.strictEqual(fn(true), 1); 236 237 // numbers and strings should be left as is 238 assert.strictEqual(fn(2), 2); 239 assert.strictEqual(fn('foo'), 'foo'); 240 }); 241 242 it('should add non-conflicting conversions to a function with one argument', function() { 243 var fn = typed({ 244 'boolean': function (a) { 245 return a; 246 } 247 }); 248 249 // booleans should be converted to number 250 assert.equal(fn(false), 0); 251 assert.equal(fn(true), 1); 252 }); 253 254 it('should add non-conflicting conversions to a function with two arguments', function() { 255 var fn = typed({ 256 'boolean, boolean': function (a, b) { 257 return 'boolean, boolean'; 258 }, 259 'number, number': function (a, b) { 260 return 'number, number'; 261 } 262 }); 263 264 //console.log('FN', fn.toString()); 265 266 // booleans should be converted to number 267 assert.equal(fn(false, true), 'boolean, boolean'); 268 assert.equal(fn(2, 4), 'number, number'); 269 assert.equal(fn(false, 4), 'number, number'); 270 assert.equal(fn(2, true), 'number, number'); 271 }); 272 273 it('should add non-conflicting conversions to a function with three arguments', function() { 274 var fn = typed({ 275 'boolean, boolean, boolean': function (a, b, c) { 276 return 'booleans'; 277 }, 278 'number, number, number': function (a, b, c) { 279 return 'numbers'; 280 } 281 }); 282 283 //console.log('FN', fn.toString()); 284 285 // booleans should be converted to number 286 assert.equal(fn(false, true, true), 'booleans'); 287 assert.equal(fn(false, false, 5), 'numbers'); 288 assert.equal(fn(false, 4, false), 'numbers'); 289 assert.equal(fn(2, false, false), 'numbers'); 290 assert.equal(fn(false, 4, 5), 'numbers'); 291 assert.equal(fn(2, false, 5), 'numbers'); 292 assert.equal(fn(2, 4, false), 'numbers'); 293 assert.equal(fn(2, 4, 5), 'numbers'); 294 }); 295 296 it('should not apply conversions when having an any type argument', function() { 297 var fn = typed({ 298 'number': function (a) { 299 return 'number'; 300 }, 301 'any': function (a) { 302 return 'any'; 303 } 304 }); 305 306 assert.equal(fn(2), 'number'); 307 assert.equal(fn(true), 'any'); 308 assert.equal(fn('foo'), 'any'); 309 assert.equal(fn('{}'), 'any'); 310 }); 311 312 describe ('ordering', function () { 313 314 it('should correctly select the signatures with the least amount of conversions', function () { 315 typed.conversions = [ 316 {from: 'boolean', to: 'number', convert: function (x) {return +x;}}, 317 {from: 'number', to: 'string', convert: function (x) {return x + '';}}, 318 {from: 'boolean', to: 'string', convert: function (x) {return x + '';}} 319 ]; 320 321 var fn = typed({ 322 'boolean, boolean': function (a, b) { 323 assert.equal(typeof a, 'boolean'); 324 assert.equal(typeof b, 'boolean'); 325 return 'booleans'; 326 }, 327 'number, number': function (a, b) { 328 assert.equal(typeof a, 'number'); 329 assert.equal(typeof b, 'number'); 330 return 'numbers'; 331 }, 332 'string, string': function (a, b) { 333 assert.equal(typeof a, 'string'); 334 assert.equal(typeof b, 'string'); 335 return 'strings'; 336 } 337 }); 338 339 assert.equal(fn(true, true), 'booleans'); 340 assert.equal(fn(2, true), 'numbers'); 341 assert.equal(fn(true, 2), 'numbers'); 342 assert.equal(fn(2, 2), 'numbers'); 343 assert.equal(fn('foo', 'bar'), 'strings'); 344 assert.equal(fn('foo', 2), 'strings'); 345 assert.equal(fn(2, 'foo'), 'strings'); 346 assert.equal(fn(true, 'foo'), 'strings'); 347 assert.equal(fn('foo', true), 'strings'); 348 349 assert.deepEqual(Object.keys(fn.signatures), [ 350 'number,number', 351 'string,string', 352 'boolean,boolean' 353 ]); 354 }); 355 356 it('should select the signatures with the conversion with the lowest index (1)', function () { 357 typed.conversions = [ 358 {from: 'boolean', to: 'string', convert: function (x) {return x + '';}}, 359 {from: 'boolean', to: 'number', convert: function (x) {return x + 0;}} 360 ]; 361 362 // in the following typed function, a boolean input can be converted to 363 // both a string or a number, which is both ok. In that case, 364 // the conversion with the lowest index should be picked: boolean -> string 365 var fn = typed({ 366 'string | number': function (a) { 367 return a; 368 } 369 }); 370 371 assert.strictEqual(fn(true), 'true'); 372 373 assert.deepEqual(Object.keys(fn.signatures), [ 374 'number', 375 'string' 376 ]); 377 }); 378 379 it('should select the signatures with the conversion with the lowest index (2)', function () { 380 typed.conversions = [ 381 {from: 'boolean', to: 'number', convert: function (x) {return x + 0;}}, 382 {from: 'boolean', to: 'string', convert: function (x) {return x + '';}} 383 ]; 384 385 // in the following typed function, a boolean input can be converted to 386 // both a string or a number, which is both ok. In that case, 387 // the conversion with the lowest index should be picked: boolean -> number 388 var fn = typed({ 389 'string | number': function (a) { 390 return a; 391 } 392 }); 393 394 assert.strictEqual(fn(true), 1); 395 }); 396 397 it('should select the signatures with least needed conversions (1)', function () { 398 typed.conversions = [ 399 {from: 'number', to: 'boolean', convert: function (x) {return !!x }}, 400 {from: 'number', to: 'string', convert: function (x) {return x + '' }}, 401 {from: 'boolean', to: 'string', convert: function (x) {return x + '' }} 402 ]; 403 404 // in the following typed function, the number input can be converted to 405 // both a string or a boolean, which is both ok. It should pick the 406 // conversion to boolean because that is defined first 407 var fn = typed({ 408 'string': function (a) { return a; }, 409 'boolean': function (a) { return a; } 410 }); 411 412 assert.strictEqual(fn(1), true); 413 }); 414 415 it('should select the signatures with least needed conversions (2)', function () { 416 typed.conversions = [ 417 {from: 'number', to: 'boolean', convert: function (x) {return !!x }}, 418 {from: 'number', to: 'string', convert: function (x) {return x + '' }}, 419 {from: 'boolean', to: 'string', convert: function (x) {return x + '' }} 420 ]; 421 422 // in the following typed function, the number input can be converted to 423 // both a string or a boolean, which is both ok. It should pick the 424 // conversion to boolean because that conversion is defined first 425 var fn = typed({ 426 'number, number': function (a, b) { return [a, b]; }, 427 'string, string': function (a, b) { return [a, b]; }, 428 'boolean, boolean': function (a, b) { return [a, b]; } 429 }); 430 431 assert.deepStrictEqual(fn('foo', 2), ['foo', '2']); 432 assert.deepStrictEqual(fn(1, true), [true, true]); 433 }); 434 435 }); 436 437 });