UNPKG

forejs

Version:

A lightweight module which provides powerful functionality to organize asynchronous JavaScript code.

1,117 lines (1,026 loc) 28.6 kB
const expect = require("chai").expect; const it = require("mocha").it; const describe = require("mocha").describe; const forePath = process.argv.length === 4 ? process.argv[3] : "src/forejs"; console.log("Testing file: '" + forePath + "'"); const fore = require(require("path").join("../", forePath)); function delay(res, callback) { setTimeout(function () { callback(null, res); }, Math.random() * 10); } function one(callback) { delay(1, callback); } function plusOne(n, callback) { delay(n + 1, callback); } function plus(n, m, callback) { delay(n + m, callback); } function toUpperCase(callback) { delay(this.toUpperCase(), callback); } function error(error, callback) { setTimeout(function () { callback(error) }, Math.random() * 10) } describe("General functionality", function () { describe("Chain mode/inject", function () { it("one - inject with 0 arguments", function (done) { fore( one, function (res) { expect(res).to.equal(1); done(); } ) }); it("plusOne - inject with 1 argument", function (done) { fore( plusOne.inject.args(0), function (res) { expect(res).to.equal(1); done(); } ) }); it("plus - inject with 2 arguments", function (done) { fore( plus.inject.args(0, 1), function (res) { expect(res).to.equal(1); done(); } ) }); it("one plusOne plus 1 - chain them all", function (done) { fore( one, plusOne, plus.inject.args(1), function (res) { expect(res).to.equal(3); done(); } ) }); }); describe("This injections", function () { it("Chain mode", function (done) { fore( function (callback) { callback(null, "abc") }, toUpperCase.inject.this(), function (res) { expect(res).to.equal("ABC"); done(); } ) }); it("Auto mode", function (done) { fore({ str: function (callback) { callback(null, "abc") }, upperCase: toUpperCase.inject.this(fore.ref("str")), _: (function (res) { expect(res).to.equal("ABC"); done(); }).inject.args(fore.ref("upperCase")) }) }); it("Auto mode - combined with args", function (done) { fore({ str: function (callback) { callback(null, "abc") }, res: (function (str, callback) { callback(null, this.toUpperCase() + str.toUpperCase()); }).inject.this(fore.ref("str")).args(fore.ref("str")), _: (function (res) { expect(res).to.equal("ABCABC"); done(); }).inject.args(fore.ref("res")) }) }); }); describe("Catching errors", function () { it("catch", function (done) { fore( error.inject.args("msg").catch(function (error) { expect(error).to.equal("msg"); done(); }) ); }); it("catch - stop propagation on error", function (done) { fore( error.inject.args("msg").catch(function (error) { expect(error).to.equal("msg"); done(); }), function () { expect.fail(); done(); } ); }); it("catch - auto mode", function (done) { fore({ msg: function (callback) { callback(null, "msg") }, error: error.inject.args(fore.ref("msg")).catch(function (error) { expect(error).to.equal("msg"); done(); }), _: (function () { expect.fail(); done(); }).inject.args(fore.ref("error")) }); }); it("general catch - first function throws error", function (done) { fore.try( error.inject.args("msg"), one, function () { expect.fail(); done(); } ).catch(function (err) { expect(err).to.equal("msg"); done(); }) }); it("general catch - second function throws error", function (done) { fore.try( one, function (one, cb) { expect(one).to.equal(1); cb(null, "msg"); }, error, function () { expect.fail(); done(); } ).catch(function (err) { expect(err).to.equal("msg"); done(); }) }); it("general catch - auto mode", function (done) { fore.try({ msg: function (callback) { callback(null, "msg") }, error: error.inject.args(fore.ref("msg")), _: (function () { expect.fail(); done(); }).inject.args(fore.ref("error")) }).catch(function (error) { expect(error).to.equal("msg"); done(); }); }); // this functionality was removed due to performance issues // it("throw", function (done) { // fore.try( // function () { // throw new Error("msg") // }, // function () { // expect.fail(); // } // ).catch(function (err) { // expect(err.message).to.equal("msg"); // done(); // }) // }) }); describe("Complex dependencies", function () { it("plus(one, plusOne(1))", function (done) { fore({ one: one, onePlusOne: plusOne.inject.args(1), plus: plus.inject.args(fore.ref("one"), fore.ref("onePlusOne")), _: (function (plus) { expect(plus).to.equal(3); done(); }).inject.args(fore.ref("plus")) }) }); it("plus(plusOne(1), plusOne(1)) - duplicate dependency", function (done) { fore({ onePlusOne: plusOne.inject.args(1), plus: plus.inject.args(fore.ref("onePlusOne"), fore.ref("onePlusOne")), _: (function (plus) { expect(plus).to.equal(4); done(); }).inject.args(fore.ref("plus")) }) }); it("plus(plusOne(one), one)", function (done) { fore({ one: one, two: plusOne.inject.args(fore.ref("one")), three: plus.inject.args(fore.ref("two"), fore.ref("one")), _: (function (res) { expect(res).to.equal(3); done(); }).inject.args(fore.ref("three")) }) }) }); describe("Support for synchronous functions", function () { function oneSync() { return 1; } function plusOneSync(n) { return n + 1; } function plusSync(n, m) { return n + m; } it("Chain mode", function (done) { fore( oneSync, plusOneSync, plusOne, plusSync.inject.args(1), function (n) { expect(n).to.equal(4); done(); } ); }); it("Auto mode", function (done) { fore({ one: oneSync, two: ["one", plusOneSync], three: ["two", plusOne], four: [1, "three", plusSync], _: ["four", function (n) { expect(n).to.equal(4); done(); }] }); }); }); describe("nesting", function () { it("Chain mode", function (done) { fore( one, function (one, callback) { fore( plusOne.inject.args(one), plusOne, // we cannot write: // callback.inject.args(null) // because callback would be called with three arguments (null, 3, callback). The "callback" argument // is automatically added by fore function (three) { callback(null, three); } ) }, plusOne, function (n) { expect(n).to.equal(4); done(); } ) }); it("Auto mode", function (done) { fore({ one: one, three: ["one", function (one, callback) { fore({ two: plusOne.inject.args(one), three: ["two", plusOne], // same here _: ["three", function (three) { callback(null, three) }] }) }], four: ["three", plusOne], _: ["four", function (n) { expect(n).to.equal(4); done(); }] }) }); }); describe("multiple 'return values'", function () { it("Chain mode", function (done) { fore( function (callback) { callback(null, 1, 2, 3); }, function (one, two, three, callback) { expect(one).equal(1); expect(two).equal(2); expect(three).equal(3); expect(callback).to.be.a("function"); done(); } ) }); it("Chain mode and this injection", function (done) { fore( function (callback) { callback(null, "a", "b", "c") }, toUpperCase.inject.this(), function (a) { expect(a).equal("A"); done(); } ) }); it("Auto mode", function (done) { fore({ oneTwoThree: function (callback) { callback(null, 1, 2, 3); }, tenTwentyThirty: function (callback) { callback(null, 10, 20, 30); }, _: ["oneTwoThree", "tenTwentyThirty", function (oneTwoThree, tenTwentyThirty, callback) { expect(oneTwoThree).to.have.members([1, 2, 3]); expect(tenTwentyThirty).to.have.members([10, 20, 30]); expect(callback).to.be.a("function"); done(); }] }) }) }) }); describe("Error reports", function () { it("unbound identifier", function () { expect(function() { fore({ _: ["a", function (a) { expect.fail(); }] }) }).to.throw(/Unbound identifier/); }) }); describe("Promise support", function () { function promiseOne() { return new Promise(function (resolve, reject) { one(function (err, res) { resolve(res); }); }); } function promisePlusOne(n) { return new Promise(function (resolve, reject) { plusOne(n, function (err, res) { resolve(res); }); }); } function promisePlus(n, m) { return new Promise(function (resolve, reject) { plus(n, m, function (err, res) { resolve(res); }); }); } function promiseError() { return new Promise(function (resolve, reject) { error("msg", function (err, res) { reject(err); }); }); } describe("Chain mode", function () { it("one", function (done) { fore( promiseOne, function (res) { expect(res).to.equal(1); done(); } ) }); it("one plusOne", function (done) { fore( promiseOne, promisePlusOne, function (res) { expect(res).to.equal(2); done(); } ) }); it("one plusOne plus(1)", function (done) { fore( promiseOne, promisePlusOne, promisePlus.inject.args(1), function (res) { expect(res).to.equal(3); done(); } ) }); }); describe("Auto mode", function () { it("one", function (done) { fore({ one: promiseOne, _: (function (res) { expect(res).to.equal(1); done(); }).inject.args(fore.ref("one")) }) }); it("one plusOne", function (done) { fore({ one: promiseOne, two: promisePlusOne.inject.args(fore.ref("one")), _: (function (res) { expect(res).to.equal(2); done(); }).inject.args(fore.ref("two")) }) }); it("one plusOne plus(1)", function (done) { fore({ one: promiseOne, two: promisePlusOne.inject.args(fore.ref("one")), three: promisePlus.inject.args(fore.ref("two"), 1), _: (function (res) { expect(res).to.equal(3); done(); }).inject.args(fore.ref("three")) }) }); }); describe("catch", function () { it("simple", function (done) { fore( promiseError.inject.args("msg").catch(function (err) { expect(err).to.equal("msg"); done(); }), function () { expect.fail(); done(); } ) }); it("Auto mode", function (done) { fore({ err: promiseError.inject.args("msg").catch(function (err) { expect(err).to.equal("msg"); done(); }), _: (function () { expect.fail(); done(); }).inject.args(fore.ref("err")) }) }); it("general", function (done) { fore.try( promiseError.inject.args("msg"), function () { expect.fail(); done(); } ).catch(function (err) { expect(err).to.equal("msg"); done(); }) }); }); describe("direct promises", function () { it("Chain mode", function (done) { fore( promiseOne(), function (one) { expect(one).to.equal(1); done(); } ) }); it("Auto mode", function (done) { fore({ one: promiseOne(), _: ["one", function (one) { expect(one).to.equal(1); done(); }] }); }); }) }); describe("syntactic sugar: injections as array", function () { it("Chain mode", function (done) { fore( one, [1, plus], function (two) { expect(two).to.equal(2); done(); } ) }); it("end position", function (done) { fore({ one: one, _: ["one", function (one) { expect(one).to.equal(1); done(); }] }) }); it("middle position", function (done) { fore({ one: one, two: ["one", function (one, callback) { callback(null, one + 1); }], _: ["one", "two", function (one, two, callback) { expect(one).to.equal(1); expect(two).to.equal(2); expect(callback).to.be.a("function"); expect(arguments.length).to.equal(3); done(); }] }) }); it("mixed refs and real arguments", function (done) { fore({ one: one, two: [1, "one", plus], _: ["two", function (two) { expect(two).to.equal(2); done(); }] }) }); it("general catch and array syntactic sugar", function (done) { fore.try( [1, function (one, callback) { callback("msg"); }] ).catch(function (err) { expect(err).equal("msg"); done(); }); }); describe("Multiple functions", function () { it("Without injections", function (done) { fore({ two: [one, plusOne], _: ["two", function (two) { expect(two).equal(2); done(); }] }); }); it("With injections", function (done) { fore({ one: one, three: ["one", plusOne, plusOne], _: ["three", function (three) { expect(three).equal(3); done(); }] }); }); it("With (nested) injections", function (done) { fore({ one: one, three: ["one", plusOne, plus.inject.args(1)], _: ["three", function (three) { expect(three).equal(3); done(); }] }); }); }); }); describe("each", function () { function* oneTwoThree() { yield* [1, 2, 3]; } describe("Chain mode", function () { it("array", function (done) { fore( fore.each([1, 2, 3]), (function () { let counter = 1; return function (res) { expect(res).to.equal(counter++); if (res === 3) { done(); } } })() ) }); it("oneTwoThree", function (done) { fore( fore.each(oneTwoThree), (function () { let counter = 1; return function (res) { expect(res).to.equal(counter++); if (res === 3) { done(); } } })() ) }); it("proper propagation", function (done) { fore( fore.each(oneTwoThree), (function () { let counter = 1; return function (res, callback) { expect(res).to.equal(counter++); callback(null, res + 1); } })(), (function () { let counter = 2; return function (res) { expect(res).to.equal(counter++); if (res === 4) { done(); } } })() ) }); it("chained fore.each", function (done) { fore( fore.each(oneTwoThree), fore.each(function* (n) { yield* [n, n + 10, n + 20] }), (function () { let i = 0; let values = [1, 11, 21, 2, 12, 22, 3, 13, 23]; return function (res) { expect(res).to.equal(values[i++]); if (i === values.length) { done(); } } })() ) }); }); describe("Complex", function () { it("2-dimensional", function (done) { let counter = 0; let expectedValues = [11, 12, 13, 21, 22, 23, 31, 32, 33]; fore({ ones: fore.each(oneTwoThree), tens: fore.each([10, 20, 30]), _: ["ones", "tens", function (ones, tens) { let index = expectedValues.indexOf(ones + tens); if (index < 0) { expect.fail(); done(); } else { counter++; expectedValues[index] = true; } if (counter === expectedValues.length) { expect(expectedValues.every((t) => t)).to.equal(true); done(); } }] }) }); it("3-dimensional", function (done) { let counter = 0; let expectedValues = [ 111, 112, 113, 121, 122, 123, 131, 132, 133, 211, 212, 213, 221, 222, 223, 231, 232, 233, 311, 312, 313, 321, 322, 323, 331, 332, 333 ]; fore({ ones: fore.each(oneTwoThree), tens: fore.each([10, 20, 30]), hundreds: fore.each({ [Symbol.iterator]: function* () { yield* [100, 200, 300]; } }), onesDelayed: ["ones", delay], tensDelayed: ["tens", delay], hundredsDelayed: ["hundreds", delay], _: ["onesDelayed", "tensDelayed", "hundredsDelayed", function (ones, tens, hundreds) { let index = expectedValues.indexOf(ones + tens + hundreds); if (index < 0) { expect.fail(); done(); } else { counter++; expectedValues[index] = true; } if (counter === expectedValues.length) { expect(expectedValues.every((t) => t)).to.equal(true); done(); } }] }) }); }); describe("Promise iterators", function () { function* oneTwoThreePromise() { yield Promise.resolve(1); yield Promise.resolve(2); yield Promise.resolve(3); } it("Chain mode", function (done) { fore( fore.each(oneTwoThreePromise), (function () { let counter = 0; return function (n) { expect(n).to.be.oneOf([1, 2, 3]); counter++; if (counter === 3) { done(); } if (counter > 3) { expect.fail(); } } })() ) }); it("Complex", function (done) { fore({ numbers: fore.each(oneTwoThreePromise), _: ["numbers", (function () { let counter = 0; return function (n) { expect(n).to.be.oneOf([1, 2, 3]); counter++; if (counter === 3) { done(); } if (counter > 3) { expect.fail(); } } })()] }) }); it("catch", function (done) { fore( fore.each((function* () { yield Promise.reject("msg"); }).inject.catch(function (err) { expect(err).to.equal("msg"); done(); })), function () { expect.fail(); } ) }); it("general catch", function (done) { fore.try( fore.each(function* () { yield Promise.reject("msg"); }), function () { expect.fail(); } ).catch(function (err) { expect(err).to.equal("msg"); done(); }) }); it("last promise rejected", function (done) { let counter = 0; function ok() { if (counter++ === 1) { done(); } } fore.try( fore.each(function* () { yield Promise.resolve(1); yield Promise.resolve(2); yield Promise.reject("msg"); }), fore.collect(function (res) { expect(res).to.have.members([1, 2]); ok(); }) ).catch(function (err) { expect(err).to.equal("msg"); ok(); }) }) }); }); describe("collect", function () { describe("Chain mode", function () { it("1-dimensional", function (done) { fore( fore.each([1, 2, 3]), delay, fore.collect(function (values) { expect(values).to.have.members([1, 2, 3]); done(); }) ) }); it("proper propagation", function (done) { fore( fore.each([1, 2, 3]), fore.collect(function (res, callback) { callback(null, res); }), delay, function (values) { expect(values).to.have.members([1, 2, 3]); done(); } ) }); }); describe("Auto mode", function () { it("1-dimensional", function (done) { fore({ "ones": fore.each([1, 2, 3]), "onesDelayed": ["ones", delay], "_": fore.collect(["onesDelayed", function (ones) { expect(ones).to.have.members([1, 2, 3]); done(); }]) }) }); it("3-dimensional", function (done) { fore({ ones: fore.each([1, 2, 3]), tens: fore.each([10, 20, 30]), hundreds: fore.each([100, 200, 300]), onesDelayed: ["ones", delay], tensDelayed: ["tens", delay], hundredsDelayed: ["hundreds", delay], _: fore.collect(["onesDelayed", "tensDelayed", "hundredsDelayed", function (ones, tens, hundreds) { expect(ones).to.have.members([1, 2, 3]); expect(tens).to.have.members([10, 20, 30]); expect(hundreds).to.have.members([100, 200, 300]); done(); }]) }) }); }) }); describe("reduce", function () { describe("Chain mode", function () { it("1-dimensional", function (done) { fore( fore.each([1, 2, 3]), delay, fore.reduce(function (array, value, callback) { callback(null, array.concat(value)); }, []), function (values) { expect(values).to.have.members([1, 2, 3]); done(); } ) }); }); describe("Auto mode", function () { it("1-dimensional", function (done) { fore({ "ones": fore.each([1, 2, 3]), "onesDelayed": ["ones", delay], "array": fore.reduce(["onesDelayed", function (array, n, callback) { callback(null, array.concat(n)); }], []), "_": ["array", function (array) { expect(array).to.have.members([1, 2, 3]); done(); }] }) }); it("3-dimensional", function (done) { fore({ ones: fore.each([1, 2, 3]), tens: fore.each([10, 20, 30]), hundreds: fore.each([100, 200, 300]), onesDelayed: ["ones", delay], tensDelayed: ["tens", delay], hundredsDelayed: ["hundreds", delay], array: fore.reduce(["onesDelayed", "tensDelayed", "hundredsDelayed", function (array, ones, tens, hundreds, callback) { callback(null, array.concat(ones + tens + hundreds)); }], []), "_": ["array", function (array) { expect(array).to.have.members([ 111, 112, 113, 121, 122, 123, 131, 132, 133, 211, 212, 213, 221, 222, 223, 231, 232, 233, 311, 312, 313, 321, 322, 323, 331, 332, 333 ]); done(); }] }) }); }); }); describe("Or-injections", function () { it("Simple", function (done) { let received = []; fore({ a: () => "a", b: () => "b", _: ["a|b", function (aOrB) { if (received.indexOf(aOrB) >= 0) { expect.fail(); return done(); } else { received.push(aOrB); } if (received.length === 2) { expect(received).to.have.members(["a", "b"]); done(); } }] }); }); it("This injection", function (done) { let received = []; fore({ a: () => "a", b: () => "b", _: (function () { if (received.indexOf(this) >= 0) { expect.fail(); return done(); } else { received.push(this + ""); // somehow the test framework thinks "this" is {0: "a"} instead of just "a" } if (received.length === 2) { expect(received).to.have.members(["a", "b"]); done(); } }).inject.this(fore.ref("a|b")) }); }); it("each/collect", function (done) { fore({ ab: fore.each(["a", "b"]), c: () => "c", abDelayed: ["ab", delay], cDelayed: ["c", delay], _: fore.collect(["abDelayed|cDelayed", function (chars) { expect(chars).to.have.members(["a", "b", "c"]); done(); }]) }); }); it("each/reduce", function (done) { fore({ ab: fore.each(["a", "b"]), c: () => "c", chars: fore.reduce(["ab|c", (array, char) => array.concat(char)], []), _: ["chars", function (chars) { expect(chars).to.have.members(["a", "b", "c"]); done(); }] }); }); }); describe("config", function () { it("dontHackFunctionPrototype", function (done) { expect(Object.getOwnPropertyDescriptor(Function.prototype, "inject").get).to.be.a("function"); fore.config({dontHackFunctionPrototype: true}); expect(Function.prototype.inject).to.be.a("undefined"); expect(fore.inject).to.be.a("function"); fore( one, fore.inject(plus).args(1), two => { expect(two).equal(2); fore.config({dontHackFunctionPrototype: false}); expect(Object.getOwnPropertyDescriptor(Function.prototype, "inject").get).to.be.a("function"); done(); } ) }); });