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