simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

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