addon-sdk/source/test/test-functional.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/addon-sdk/source/test/test-functional.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,463 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +"use strict";
     1.8 +
     1.9 +const { setTimeout } = require('sdk/timers');
    1.10 +const utils = require('sdk/lang/functional');
    1.11 +const { invoke, defer, partial, compose, memoize, once, is, isnt,
    1.12 +  delay, wrap, curry, chainable, field, query, isInstance, debounce, throttle
    1.13 +} = utils;
    1.14 +const { LoaderWithHookedConsole } = require('sdk/test/loader');
    1.15 +
    1.16 +exports['test forwardApply'] = function(assert) {
    1.17 +  function sum(b, c) { return this.a + b + c; }
    1.18 +  assert.equal(invoke(sum, [2, 3], { a: 1 }), 6,
    1.19 +               'passed arguments and pseoude-variable are used');
    1.20 +
    1.21 +  assert.equal(invoke(sum.bind({ a: 2 }), [2, 3], { a: 1 }), 7,
    1.22 +               'bounded `this` pseoudo variable is used');
    1.23 +};
    1.24 +
    1.25 +exports['test deferred function'] = function(assert, done) {
    1.26 +  let nextTurn = false;
    1.27 +  function sum(b, c) {
    1.28 +    assert.ok(nextTurn, 'enqueued is called in next turn of event loop');
    1.29 +    assert.equal(this.a + b + c, 6,
    1.30 +                 'passed arguments an pseoude-variable are used');
    1.31 +    done();
    1.32 +  }
    1.33 +
    1.34 +  let fixture = { a: 1, method: defer(sum) };
    1.35 +  fixture.method(2, 3);
    1.36 +  nextTurn = true;
    1.37 +};
    1.38 +
    1.39 +exports['test partial function'] = function(assert) {
    1.40 +  function sum(b, c) { return this.a + b + c; }
    1.41 +
    1.42 +  let foo = { a : 5 };
    1.43 +
    1.44 +  foo.sum7 = partial(sum, 7);
    1.45 +  foo.sum8and4 = partial(sum, 8, 4);
    1.46 +
    1.47 +  assert.equal(foo.sum7(2), 14, 'partial one arguments works');
    1.48 +
    1.49 +  assert.equal(foo.sum8and4(), 17, 'partial both arguments works');
    1.50 +};
    1.51 +
    1.52 +exports["test curry defined numeber of arguments"] = function(assert) {
    1.53 +  var sum = curry(function(a, b, c) {
    1.54 +    return a + b + c;
    1.55 +  });
    1.56 +
    1.57 +  assert.equal(sum(2, 2, 1), 5, "sum(2, 2, 1) => 5");
    1.58 +  assert.equal(sum(2, 4)(1), 7, "sum(2, 4)(1) => 7");
    1.59 +  assert.equal(sum(2)(4, 2), 8, "sum(2)(4, 2) => 8");
    1.60 +  assert.equal(sum(2)(4)(3), 9, "sum(2)(4)(3) => 9");
    1.61 +};
    1.62 +
    1.63 +exports['test compose'] = function(assert) {
    1.64 +  let greet = function(name) { return 'hi: ' + name; };
    1.65 +  let exclaim = function(sentence) { return sentence + '!'; };
    1.66 +
    1.67 +  assert.equal(compose(exclaim, greet)('moe'), 'hi: moe!',
    1.68 +               'can compose a function that takes another');
    1.69 +
    1.70 +  assert.equal(compose(greet, exclaim)('moe'), 'hi: moe!',
    1.71 +               'in this case, the functions are also commutative');
    1.72 +
    1.73 +  let target = {
    1.74 +    name: 'Joe',
    1.75 +    greet: compose(function exclaim(sentence) {
    1.76 +      return sentence + '!';
    1.77 +    }, function(title) {
    1.78 +      return 'hi : ' + title + ' ' + this.name;
    1.79 +    })
    1.80 +  };
    1.81 +
    1.82 +  assert.equal(target.greet('Mr'), 'hi : Mr Joe!',
    1.83 +               'this can be passed in');
    1.84 +  assert.equal(target.greet.call({ name: 'Alex' }, 'Dr'), 'hi : Dr Alex!',
    1.85 +               'this can be applied');
    1.86 +
    1.87 +  let single = compose(function(value) {
    1.88 +    return value + ':suffix';
    1.89 +  });
    1.90 +
    1.91 +  assert.equal(single('text'), 'text:suffix', 'works with single function');
    1.92 +
    1.93 +  let identity = compose();
    1.94 +  assert.equal(identity('bla'), 'bla', 'works with zero functions');
    1.95 +};
    1.96 +
    1.97 +exports['test wrap'] = function(assert) {
    1.98 +  let greet = function(name) { return 'hi: ' + name; };
    1.99 +  let backwards = wrap(greet, function(f, name) {
   1.100 +    return f(name) + ' ' + name.split('').reverse().join('');
   1.101 +  });
   1.102 +
   1.103 +  assert.equal(backwards('moe'), 'hi: moe eom',
   1.104 +               'wrapped the saluation function');
   1.105 +
   1.106 +  let inner = function () { return 'Hello '; };
   1.107 +  let target = {
   1.108 +    name: 'Matteo',
   1.109 +    hi: wrap(inner, function(f) { return f() + this.name; })
   1.110 +  };
   1.111 +
   1.112 +  assert.equal(target.hi(), 'Hello Matteo', 'works with this');
   1.113 +
   1.114 +  function noop() { }
   1.115 +  let wrapped = wrap(noop, function(f) {
   1.116 +    return Array.slice(arguments);
   1.117 +  });
   1.118 +
   1.119 +  let actual = wrapped([ 'whats', 'your' ], 'vector', 'victor');
   1.120 +  assert.deepEqual(actual, [ noop, ['whats', 'your'], 'vector', 'victor' ],
   1.121 +                   'works with fancy stuff');
   1.122 +};
   1.123 +
   1.124 +exports['test memoize'] = function(assert) {
   1.125 +  const fib = n => n < 2 ? n : fib(n - 1) + fib(n - 2);
   1.126 +  let fibnitro = memoize(fib);
   1.127 +
   1.128 +  assert.equal(fib(10), 55,
   1.129 +        'a memoized version of fibonacci produces identical results');
   1.130 +  assert.equal(fibnitro(10), 55,
   1.131 +        'a memoized version of fibonacci produces identical results');
   1.132 +
   1.133 +  function o(key, value) { return value; }
   1.134 +  let oo = memoize(o), v1 = {}, v2 = {};
   1.135 +
   1.136 +
   1.137 +  assert.equal(oo(1, v1), v1, 'returns value back');
   1.138 +  assert.equal(oo(1, v2), v1, 'memoized by a first argument');
   1.139 +  assert.equal(oo(2, v2), v2, 'returns back value if not memoized');
   1.140 +  assert.equal(oo(2), v2, 'memoized new value');
   1.141 +  assert.notEqual(oo(1), oo(2), 'values do not override');
   1.142 +  assert.equal(o(3, v2), oo(2, 3), 'returns same value as un-memoized');
   1.143 +
   1.144 +  let get = memoize(function(attribute) { return this[attribute]; });
   1.145 +  let target = { name: 'Bob', get: get };
   1.146 +
   1.147 +  assert.equal(target.get('name'), 'Bob', 'has correct `this`');
   1.148 +  assert.equal(target.get.call({ name: 'Jack' }, 'name'), 'Bob',
   1.149 +               'name is memoized');
   1.150 +  assert.equal(get('name'), 'Bob', 'once memoized can be called without this');
   1.151 +};
   1.152 +
   1.153 +exports['test delay'] = function(assert, done) {
   1.154 +  let delayed = false;
   1.155 +  delay(function() {
   1.156 +    assert.ok(delayed, 'delayed the function');
   1.157 +    done();
   1.158 +  }, 1);
   1.159 +  delayed = true;
   1.160 +};
   1.161 +
   1.162 +exports['test delay with this'] = function(assert, done) {
   1.163 +  let context = {};
   1.164 +  delay.call(context, function(name) {
   1.165 +    assert.equal(this, context, 'this was passed in');
   1.166 +    assert.equal(name, 'Tom', 'argument was passed in');
   1.167 +    done();
   1.168 +  }, 10, 'Tom');
   1.169 +};
   1.170 +
   1.171 +exports['test once'] = function(assert) {
   1.172 +  let n = 0;
   1.173 +  let increment = once(function() { n ++; });
   1.174 +
   1.175 +  increment();
   1.176 +  increment();
   1.177 +
   1.178 +  assert.equal(n, 1, 'only incremented once');
   1.179 +
   1.180 +  let target = {
   1.181 +    state: 0,
   1.182 +    update: once(function() {
   1.183 +      return this.state ++;
   1.184 +    })
   1.185 +  };
   1.186 +
   1.187 +  target.update();
   1.188 +  target.update();
   1.189 +
   1.190 +  assert.equal(target.state, 1, 'this was passed in and called only once');
   1.191 +};
   1.192 +
   1.193 +exports['test once with argument'] = function(assert) {
   1.194 +  let n = 0;
   1.195 +  let increment = once(a => n++);
   1.196 +
   1.197 +  increment();
   1.198 +  increment('foo');
   1.199 +
   1.200 +  assert.equal(n, 1, 'only incremented once');
   1.201 +
   1.202 +  increment();
   1.203 +  increment('foo');
   1.204 +
   1.205 +  assert.equal(n, 1, 'only incremented once');
   1.206 +};
   1.207 +
   1.208 +exports['test complement'] = assert => {
   1.209 +  let { complement } = require("sdk/lang/functional");
   1.210 +
   1.211 +  let isOdd = x => Boolean(x % 2);
   1.212 +
   1.213 +  assert.equal(isOdd(1), true);
   1.214 +  assert.equal(isOdd(2), false);
   1.215 +
   1.216 +  let isEven = complement(isOdd);
   1.217 +
   1.218 +  assert.equal(isEven(1), false);
   1.219 +  assert.equal(isEven(2), true);
   1.220 +
   1.221 +  let foo = {};
   1.222 +  let isFoo = function() { return this === foo; };
   1.223 +  let insntFoo = complement(isFoo);
   1.224 +
   1.225 +  assert.equal(insntFoo.call(foo), false);
   1.226 +  assert.equal(insntFoo.call({}), true);
   1.227 +};
   1.228 +
   1.229 +exports['test constant'] = assert => {
   1.230 +  let { constant } = require("sdk/lang/functional");
   1.231 +
   1.232 +  let one = constant(1);
   1.233 +
   1.234 +  assert.equal(one(1), 1);
   1.235 +  assert.equal(one(2), 1);
   1.236 +};
   1.237 +
   1.238 +exports['test apply'] = assert => {
   1.239 +  let { apply } = require("sdk/lang/functional");
   1.240 +
   1.241 +  let dashify = (...args) => args.join("-");
   1.242 +
   1.243 +  assert.equal(apply(dashify, 1, [2, 3]), "1-2-3");
   1.244 +  assert.equal(apply(dashify, "a"), "a");
   1.245 +  assert.equal(apply(dashify, ["a", "b"]), "a-b");
   1.246 +  assert.equal(apply(dashify, ["a", "b"], "c"), "a,b-c");
   1.247 +  assert.equal(apply(dashify, [1, 2], [3, 4]), "1,2-3-4");
   1.248 +};
   1.249 +
   1.250 +exports['test flip'] = assert => {
   1.251 +  let { flip } = require("sdk/lang/functional");
   1.252 +
   1.253 +  let append = (left, right) => left + " " + right;
   1.254 +  let prepend = flip(append);
   1.255 +
   1.256 +  assert.equal(append("hello", "world"), "hello world");
   1.257 +  assert.equal(prepend("hello", "world"), "world hello");
   1.258 +
   1.259 +  let wrap = function(left, right) {
   1.260 +    return left + " " + this + " " + right;
   1.261 +  };
   1.262 +  let invertWrap = flip(wrap);
   1.263 +
   1.264 +  assert.equal(wrap.call("@", "hello", "world"), "hello @ world");
   1.265 +  assert.equal(invertWrap.call("@", "hello", "world"), "world @ hello");
   1.266 +
   1.267 +  let reverse = flip((...args) => args);
   1.268 +
   1.269 +  assert.deepEqual(reverse(1, 2, 3, 4), [4, 3, 2, 1]);
   1.270 +  assert.deepEqual(reverse(1), [1]);
   1.271 +  assert.deepEqual(reverse(), []);
   1.272 +
   1.273 +  // currying still works
   1.274 +  let prependr = curry(prepend);
   1.275 +
   1.276 +  assert.equal(prependr("hello", "world"), "world hello");
   1.277 +  assert.equal(prependr("hello")("world"), "world hello");
   1.278 +};
   1.279 +
   1.280 +exports["test when"] = assert => {
   1.281 +  let { when } = require("sdk/lang/functional");
   1.282 +
   1.283 +  let areNums = (...xs) => xs.every(x => typeof(x) === "number");
   1.284 +
   1.285 +  let sum = when(areNums, (...xs) => xs.reduce((y, x) => x + y, 0));
   1.286 +
   1.287 +  assert.equal(sum(1, 2, 3), 6);
   1.288 +  assert.equal(sum(1, 2, "3"), undefined);
   1.289 +
   1.290 +  let multiply = when(areNums,
   1.291 +                      (...xs) => xs.reduce((y, x) => x * y, 1),
   1.292 +                      (...xs) => xs);
   1.293 +
   1.294 +  assert.equal(multiply(2), 2);
   1.295 +  assert.equal(multiply(2, 3), 6);
   1.296 +  assert.deepEqual(multiply(2, "4"), [2, "4"]);
   1.297 +
   1.298 +  function Point(x, y) {
   1.299 +    this.x = x;
   1.300 +    this.y = y;
   1.301 +  }
   1.302 +
   1.303 +  let isPoint = x => x instanceof Point;
   1.304 +
   1.305 +  let inc = when(isPoint, ({x, y}) => new Point(x + 1, y + 1));
   1.306 +
   1.307 +  assert.equal(inc({}), undefined);
   1.308 +  assert.deepEqual(inc(new Point(0, 0)), { x: 1, y: 1 });
   1.309 +
   1.310 +  let axis = when(isPoint,
   1.311 +                  ({ x, y }) => [x, y],
   1.312 +                  _ => [0, 0]);
   1.313 +
   1.314 +  assert.deepEqual(axis(new Point(1, 4)), [1, 4]);
   1.315 +  assert.deepEqual(axis({ foo: "bar" }), [0, 0]);
   1.316 +};
   1.317 +
   1.318 +exports["test chainable"] = function(assert) {
   1.319 +  let Player = function () { this.volume = 5; };
   1.320 +  Player.prototype = {
   1.321 +    setBand: chainable(function (band) { return (this.band = band); }),
   1.322 +    incVolume: chainable(function () { return this.volume++; })
   1.323 +  };
   1.324 +  let player = new Player();
   1.325 +  player
   1.326 +    .setBand('Animals As Leaders')
   1.327 +    .incVolume().incVolume().incVolume().incVolume().incVolume().incVolume();
   1.328 +
   1.329 +  assert.equal(player.band, 'Animals As Leaders', 'passes arguments into chained');
   1.330 +  assert.equal(player.volume, 11, 'accepts no arguments in chain');
   1.331 +};
   1.332 +
   1.333 +exports["test field"] = assert => {
   1.334 +  let Num = field("constructor", 0);
   1.335 +  assert.equal(Num.name, Number.name);
   1.336 +  assert.ok(typeof(Num), "function");
   1.337 +
   1.338 +  let x = field("x");
   1.339 +
   1.340 +  [
   1.341 +    [field("foo", { foo: 1 }), 1],
   1.342 +    [field("foo")({ foo: 1 }), 1],
   1.343 +    [field("bar", {}), undefined],
   1.344 +    [field("bar")({}), undefined],
   1.345 +    [field("hey", undefined), undefined],
   1.346 +    [field("hey")(undefined), undefined],
   1.347 +    [field("how", null), null],
   1.348 +    [field("how")(null), null],
   1.349 +    [x(1), undefined],
   1.350 +    [x(undefined), undefined],
   1.351 +    [x(null), null],
   1.352 +    [x({ x: 1 }), 1],
   1.353 +    [x({ x: 2 }), 2],
   1.354 +  ].forEach(([actual, expected]) => assert.equal(actual, expected));
   1.355 +};
   1.356 +
   1.357 +exports["test query"] = assert => {
   1.358 +  let Num = query("constructor", 0);
   1.359 +  assert.equal(Num.name, Number.name);
   1.360 +  assert.ok(typeof(Num), "function");
   1.361 +
   1.362 +  let x = query("x");
   1.363 +  let xy = query("x.y");
   1.364 +
   1.365 +  [
   1.366 +    [query("foo", { foo: 1 }), 1],
   1.367 +    [query("foo")({ foo: 1 }), 1],
   1.368 +    [query("foo.bar", { foo: { bar: 2 } }), 2],
   1.369 +    [query("foo.bar")({ foo: { bar: 2 } }), 2],
   1.370 +    [query("foo.bar", { foo: 1 }), undefined],
   1.371 +    [query("foo.bar")({ foo: 1 }), undefined],
   1.372 +    [x(1), undefined],
   1.373 +    [x(undefined), undefined],
   1.374 +    [x(null), null],
   1.375 +    [x({ x: 1 }), 1],
   1.376 +    [x({ x: 2 }), 2],
   1.377 +    [xy(1), undefined],
   1.378 +    [xy(undefined), undefined],
   1.379 +    [xy(null), null],
   1.380 +    [xy({ x: 1 }), undefined],
   1.381 +    [xy({ x: 2 }), undefined],
   1.382 +    [xy({ x: { y: 1 } }), 1],
   1.383 +    [xy({ x: { y: 2 } }), 2]
   1.384 +  ].forEach(([actual, expected]) => assert.equal(actual, expected));
   1.385 +};
   1.386 +
   1.387 +exports["test isInstance"] = assert => {
   1.388 +  function X() {}
   1.389 +  function Y() {}
   1.390 +  let isX = isInstance(X);
   1.391 +
   1.392 +  [
   1.393 +    isInstance(X, new X()),
   1.394 +    isInstance(X)(new X()),
   1.395 +    !isInstance(X, new Y()),
   1.396 +    !isInstance(X)(new Y()),
   1.397 +    isX(new X()),
   1.398 +    !isX(new Y())
   1.399 +  ].forEach(x => assert.ok(x));
   1.400 +};
   1.401 +
   1.402 +exports["test is"] = assert => {
   1.403 +
   1.404 +  assert.deepEqual([ 1, 0, 1, 0, 1 ].map(is(1)),
   1.405 +                   [ true, false, true, false, true ],
   1.406 +                   "is can be partially applied");
   1.407 +
   1.408 +  assert.ok(is(1, 1));
   1.409 +  assert.ok(!is({}, {}));
   1.410 +  assert.ok(is()(1)()(1), "is is curried");
   1.411 +  assert.ok(!is()(1)()(2));
   1.412 +};
   1.413 +
   1.414 +exports["test isnt"] = assert => {
   1.415 +
   1.416 +  assert.deepEqual([ 1, 0, 1, 0, 1 ].map(isnt(0)),
   1.417 +                   [ true, false, true, false, true ],
   1.418 +                   "is can be partially applied");
   1.419 +
   1.420 +  assert.ok(!isnt(1, 1));
   1.421 +  assert.ok(isnt({}, {}));
   1.422 +  assert.ok(!isnt()(1)()(1));
   1.423 +  assert.ok(isnt()(1)()(2));
   1.424 +};
   1.425 +
   1.426 +exports["test debounce"] = (assert, done) => {
   1.427 +  let counter = 0;
   1.428 +  let fn = debounce(() => counter++, 100);
   1.429 +
   1.430 +  new Array(10).join(0).split("").forEach(fn);
   1.431 +
   1.432 +  assert.equal(counter, 0, "debounce does not fire immediately");
   1.433 +  setTimeout(() => {
   1.434 +    assert.equal(counter, 1, "function called after wait time");
   1.435 +    fn();
   1.436 +    setTimeout(() => {
   1.437 +      assert.equal(counter, 2, "function able to be called again after wait");
   1.438 +      done();
   1.439 +    }, 150);
   1.440 +  }, 200);
   1.441 +};
   1.442 +
   1.443 +exports["test throttle"] = (assert, done) => {
   1.444 +  let called = 0;
   1.445 +  let attempt = 0;
   1.446 +  let atleast100ms = false;
   1.447 +  let throttledFn = throttle(() => {
   1.448 +    called++;
   1.449 +    if (called === 2) {
   1.450 +      assert.equal(attempt, 10, "called twice, but attempted 10 times");
   1.451 +      fn();
   1.452 +    }
   1.453 +    if (called === 3) {
   1.454 +      assert.ok(atleast100ms, "atleast 100ms have passed");
   1.455 +      assert.equal(attempt, 11, "called third, waits for delay to happen");
   1.456 +      done();
   1.457 +    }
   1.458 +  }, 200);
   1.459 +  let fn = () => ++attempt && throttledFn();
   1.460 +
   1.461 +  setTimeout(() => atleast100ms = true, 100);
   1.462 +
   1.463 +  new Array(11).join(0).split("").forEach(fn);
   1.464 +};
   1.465 +
   1.466 +require('test').run(exports);

mercurial