branch: master commit 8982c75937e11537f5e57d803ab2a2d9a45d06ff Merge: 1b7e0a5 f7f4fe8 Author: Jackson Ray Hamilton <jack...@jacksonrayhamilton.com> Commit: Jackson Ray Hamilton <jack...@jacksonrayhamilton.com>
Merge branch 'feature/tests' into develop --- .jslintrc | 3 +- test/context-coloring-test.el | 48 ++++--- test/createEmacsBuffer.js | 52 +++++++ test/fixtures/function-scopes.js | 5 + test/fixtures/nested.el | 34 ----- test/fixtures/nested.js | 32 ----- test/fixtures/scopes.el | 10 -- test/fixtures/scopes.js | 8 - test/fixtures/vow.el | 272 -------------------------------------- test/fixtures/vow.js | 270 ------------------------------------- test/fixtures/vow.json | 1 - test/specs.js | 85 +++++++----- test/threes.js | 13 ++ 13 files changed, 153 insertions(+), 680 deletions(-) diff --git a/.jslintrc b/.jslintrc index 03c17c5..52eea7e 100644 --- a/.jslintrc +++ b/.jslintrc @@ -1,4 +1,5 @@ { "node": true, - "nomen": true + "nomen": true, + "vars": true } diff --git a/test/context-coloring-test.el b/test/context-coloring-test.el index b942558..d997d8b 100644 --- a/test/context-coloring-test.el +++ b/test/context-coloring-test.el @@ -4,11 +4,6 @@ (defun context-coloring-test-resolve-path (path) (expand-file-name path context-coloring-test-path)) -;; Load expected output constants. -(load-file (context-coloring-test-resolve-path "./fixtures/scopes.el")) -(load-file (context-coloring-test-resolve-path "./fixtures/nested.el")) -(load-file (context-coloring-test-resolve-path "./fixtures/vow.el")) - (defun get-string-from-file (path) (with-temp-buffer (insert-file-contents path) @@ -25,19 +20,34 @@ FIXTURE." (context-coloring-mode) ,@body)) -(ert-deftest context-coloring-test-scopes () - (context-coloring-test-with-fixture "./fixtures/scopes.js" - (should (equal (buffer-substring (point-min) (point-max)) - context-coloring-test-expected-scopes)))) - -(ert-deftest context-coloring-test-nested () - (context-coloring-test-with-fixture "./fixtures/nested.js" - (should (equal (buffer-substring (point-min) (point-max)) - context-coloring-test-expected-nested)))) - -(ert-deftest context-coloring-test-vow () - (context-coloring-test-with-fixture "./fixtures/vow.js" - (should (equal (buffer-substring (point-min) (point-max)) - context-coloring-test-expected-vow)))) +(defun context-coloring-test-region-level-p (start end level) + (let ((i 0) + (length (- end start))) + (while (< i length) + (let ((point (+ i start))) + (should (equal (get-text-property point 'face) + (intern-soft (concat "context-coloring-level-" + (number-to-string level) + "-face"))))) + (setq i (+ i 1))))) + +(ert-deftest context-coloring-test-function-scopes () + (context-coloring-test-with-fixture + "./fixtures/function-scopes.js" + + (sleep-for .25) ; Wait for asynchronous coloring to complete. + + (context-coloring-test-region-level-p 1 9 0) + (context-coloring-test-region-level-p 9 23 1) + (context-coloring-test-region-level-p 23 25 0) + (context-coloring-test-region-level-p 25 34 1) + (context-coloring-test-region-level-p 34 35 0) + (context-coloring-test-region-level-p 35 52 1) + (context-coloring-test-region-level-p 52 66 2) + (context-coloring-test-region-level-p 66 72 1) + (context-coloring-test-region-level-p 72 81 2) + (context-coloring-test-region-level-p 81 82 1) + (context-coloring-test-region-level-p 82 87 2) + (context-coloring-test-region-level-p 87 89 1))) (provide 'context-coloring-test) diff --git a/test/createEmacsBuffer.js b/test/createEmacsBuffer.js new file mode 100644 index 0000000..18f0926 --- /dev/null +++ b/test/createEmacsBuffer.js @@ -0,0 +1,52 @@ +'use strict'; + +var threes = require('./threes'); + +function createEmacsBuffer() { + var points = []; + + function noPointInRegion(start, end, fn) { + var i, + length, + index; + for (i = 0, length = end - start; i < length; i += 1) { + index = i + start - 1; + if (fn(points[index], index)) { + return false; + } + } + return true; + } + + function setLevelForRegion(start, end, level) { + noPointInRegion(start, end, function (point, index) { + /*jslint unparam: true */ + points[index] = level; + }); + } + + function isLevelForRegion(start, end, level) { + return noPointInRegion(start, end, function (point) { + return point !== level; + }); + } + + function isLevelAtPoint(point, level) { + return points[point - 1] === level; + } + + function applyTokens(tokens) { + threes(tokens).forEach(function (threesome) { + setLevelForRegion.apply(null, threesome); + }); + } + + return { + setLevelForRegion: setLevelForRegion, + isLevelForRegion: isLevelForRegion, + isLevelAtPoint: isLevelAtPoint, + applyTokens: applyTokens + }; +} + +module.exports = createEmacsBuffer; diff --git a/test/fixtures/function-scopes.js b/test/fixtures/function-scopes.js new file mode 100644 index 0000000..2f6ed32 --- /dev/null +++ b/test/fixtures/function-scopes.js @@ -0,0 +1,5 @@ +var a = function () {}; +function b() { + var c = function () {}; + function d() {} +} diff --git a/test/fixtures/nested.el b/test/fixtures/nested.el deleted file mode 100644 index 076e6ac..0000000 --- a/test/fixtures/nested.el +++ /dev/null @@ -1,34 +0,0 @@ -(defconst context-coloring-test-expected-nested - #("function a() { - var A = a; - function b() { - var B = b; - function c() { - var C = c; - function d() { - var D = d; - function e() { - var E = e; - function f() { - var F = f; - function g() { - var G = g; - function h() { - var H = [ - A, - B, - C, - D, - E, - F, - G - ]; - } - } - } - } - } - } - } -} -" 0 8 (font-lock-face font-lock-keyword-face fontified nil face context-coloring-depth-1-face rear-nonsticky t) 8 9 (fontified nil face context-coloring-depth-1-face rear-nonsticky t) 9 10 (font-lock-face font-lock-function-name-face fontified nil face context-coloring-depth-0-bold-face rear-nonsticky t) 10 19 (fontified nil face context-coloring-depth-1-face rear-nonsticky t) 19 22 (font-lock-face font-lock-keyword-face fontified nil face context-coloring-depth-1-face rear-nonsticky t) [...] diff --git a/test/fixtures/nested.js b/test/fixtures/nested.js deleted file mode 100644 index 1621e06..0000000 --- a/test/fixtures/nested.js +++ /dev/null @@ -1,32 +0,0 @@ -function a() { - var A = a; - function b() { - var B = b; - function c() { - var C = c; - function d() { - var D = d; - function e() { - var E = e; - function f() { - var F = f; - function g() { - var G = g; - function h() { - var H = [ - A, - B, - C, - D, - E, - F, - G - ]; - } - } - } - } - } - } - } -} diff --git a/test/fixtures/scopes.el b/test/fixtures/scopes.el deleted file mode 100644 index eb94b6e..0000000 --- a/test/fixtures/scopes.el +++ /dev/null @@ -1,10 +0,0 @@ -(defconst context-coloring-test-expected-scopes - #("(function () { - var a; - var b = 0; -}()); -var A; -var B = 1; -window.setTimeout(); -ooga = 2; -" 0 1 (fontified nil face context-coloring-depth-0-face rear-nonsticky t) 1 9 (font-lock-face font-lock-keyword-face fontified nil face context-coloring-depth-1-face rear-nonsticky t) 9 19 (fontified nil face context-coloring-depth-1-face rear-nonsticky t) 19 22 (font-lock-face font-lock-keyword-face fontified nil face context-coloring-depth-1-face rear-nonsticky t) 22 23 (fontified nil face context-coloring-depth-1-face rear-nonsticky t) 23 24 (font-lock-face font-lock-variable-name-fac [...] diff --git a/test/fixtures/scopes.js b/test/fixtures/scopes.js deleted file mode 100644 index 696d812..0000000 --- a/test/fixtures/scopes.js +++ /dev/null @@ -1,8 +0,0 @@ -(function () { - var a; - var b = 0; -}()); -var A; -var B = 1; -window.setTimeout(); -ooga = 2; diff --git a/test/fixtures/vow.el b/test/fixtures/vow.el deleted file mode 100644 index 37c3440..0000000 --- a/test/fixtures/vow.el +++ /dev/null @@ -1,272 +0,0 @@ -(defconst context-coloring-test-expected-vow - #("// vow.js -// Douglas Crockford -// 2013-09-20 - -// Public Domain - -/*global setImmediate */ - - -var VOW = (function () { - 'use strict'; - -// The VOW object contains a .make function that is used to make vows. -// It may also contain other useful functions. -// In some mythologies, 'VOW' is called 'deferrer'. - - function enlighten(queue, fate) { - -// enlighten is a helper function of herald and .when. It schedules the -// processing of all of the resolution functions in either the keepers queue -// or the breakers queue in later turns with the promise's fate. - - queue.forEach(function (func) { - setImmediate(func, fate); - }); - } - - return { - make: function make() { - -// The make function makes new vows. A vow contains a promise object and the -// two resolution functions (break and keep) that determine the fate of the -// promise. - - var breakers = [], // .when's broken queue - fate, // The promise's ultimate value - keepers = [], // .when's kept queue - status = 'pending'; // 'broken', 'kept', or 'pending' - - function enqueue( - resolution, // 'keep' or 'break' - func, // A function that was registered with .when - vow // A vow that provides the resolution functions - ) { - -// enqueue is a helper function used by .when. It will append a function to -// either the keepers queue or the breakers queue. - - var queue = resolution === 'keep' ? keepers : breakers; - queue[queue.length] = typeof func !== 'function' - -// If func is not a function, push the resolver so that the value passes to -// the next cascaded .when. - - ? vow[resolution] - -// If the func is a function, push a function that calls func with a value. -// The result can be a promise, or not a promise, or an exception. - - : function (value) { - try { - var result = func(value); - -// If the result is a promise, then register our resolver with that promise. - - if (result && result.is_promise === true) { - result.when(vow.keep, vow.break); - -// But if it is not a promise, then use the result to resolve our promise. - - } else { - vow.keep(result); - } - -// But if func throws an exception, then break our promise. - - } catch (e) { - vow.break(e); - } - }; - } - - function herald(state, value, queue) { - -// The herald function is a helper function of break and keep. -// It seals the promise's fate, updates its status, enlightens -// one of the queues, and empties both queues. - - if (status !== 'pending') { - throw \"overpromise\"; - } - fate = value; - status = state; - enlighten(queue, fate); - keepers.length = 0; - breakers.length = 0; - } - -// Construct and return the vow object. - - return { - 'break': function (value) { - -// The break method breaks the promise. - - herald('broken', value, breakers); - }, - keep: function keep(value) { - -// The keep method keeps the promise. - - herald('kept', value, keepers); - }, - promise: { - -// The promise is an object with a .when method. - - is_promise: true, - -// The .when method is the promise monad's bind. The .when method can take two -// optional functions. One of those functions may be called, depending on the -// promise's resolution. Both could be called if the the kept function throws. - - when: function (kept, broken) { - -// Make a new vow. Return the new promise. - - var vow = make(); - switch (status) { - -// If this promise is still pending, then enqueue both kept and broken. - - case 'pending': - enqueue('keep', kept, vow); - enqueue('break', broken, vow); - break; - -// If the promise has already been kept, then enqueue only the kept function, -// and enlighten it. - - case 'kept': - enqueue('keep', kept, vow); - enlighten(keepers, fate); - break; - -// If the promise has already been broken, then enqueue only the broken -// function, and enlighten it. - - case 'broken': - enqueue('break', broken, vow); - enlighten(breakers, fate); - break; - } - return vow.promise; - } - } - }; - }, - every: function every(array) { - -// The every function takes an array of promises and returns a promise that -// will deliver an array of results only if every promise is kept. - - var remaining = array.length, results = [], vow = VOW.make(); - - if (!remaining) { - vow.break(array); - } else { - array.forEach(function (promise, i) { - promise.when(function (value) { - results[i] = value; - remaining -= 1; - if (remaining === 0) { - vow.keep(results); - } - }, function (reason) { - remaining = NaN; - vow.break(reason); - }); - }); - } - return vow.promise; - }, - first: function first(array) { - -// The first function takes an array of promises and returns a promise to -// deliver the first observed kept promise, or a broken promise if all of -// the promises are broken. - - var found = false, remaining = array.length, vow = VOW.make(); - - function check() { - remaining -= 1; - if (remaining === 0 && !found) { - vow.break(); - } - } - - if (remaining === 0) { - vow.break(array); - } else { - array.forEach(function (promise) { - promise.when(function (value) { - if (!found) { - found = true; - vow.keep(value); - } - check(); - }, check); - }); - } - return vow.promise; - }, - any: function any(array) { - -// The any function takes an array of promises and returns a promise that -// will deliver a possibly sparse array of results of any kept promises. -// The result will contain an undefined element for each broken promise. - - var remaining = array.length, results = [], vow = VOW.make(); - - function check() { - remaining -= 1; - if (remaining === 0) { - vow.keep(results); - } - } - - if (!remaining) { - vow.keep(results); - } else { - array.forEach(function (promise, i) { - promise.when(function (value) { - results[i] = value; - check(); - }, check); - }); - } - return vow.promise; - }, - kept: function (value) { - -// Returns a new kept promise. - - var vow = VOW.make(); - vow.keep(value); - return vow.promise; - }, - broken: function (reason) { - -// Returns a new broken promise. - - var vow = VOW.make(); - vow.break(reason); - return vow.promise; - } - }; -}()); - - -// If your system does not have setImmediate, then simulate it with setTimeout. - -if (typeof setImmediate !== 'function') { - setImmediate = function setImmediate(func, param) { - 'use strict'; - return setTimeout(function () { - func(param); - }, 0); - }; -} -" 0 9 (font-lock-face font-lock-comment-face fontified nil face context-coloring-depth--1-italic-face rear-nonsticky t) 9 10 (font-lock-face font-lock-comment-face fontified nil) 10 30 (font-lock-face font-lock-comment-face fontified nil face context-coloring-depth--1-italic-face rear-nonsticky t) 30 31 (font-lock-face font-lock-comment-face fontified nil) 31 44 (font-lock-face font-lock-comment-face fontified nil face context-coloring-depth--1-italic-face rear-nonsticky t) 44 45 (font-l [...] diff --git a/test/fixtures/vow.js b/test/fixtures/vow.js deleted file mode 100644 index 374853e..0000000 --- a/test/fixtures/vow.js +++ /dev/null @@ -1,270 +0,0 @@ -// vow.js -// Douglas Crockford -// 2013-09-20 - -// Public Domain - -/*global setImmediate */ - - -var VOW = (function () { - 'use strict'; - -// The VOW object contains a .make function that is used to make vows. -// It may also contain other useful functions. -// In some mythologies, 'VOW' is called 'deferrer'. - - function enlighten(queue, fate) { - -// enlighten is a helper function of herald and .when. It schedules the -// processing of all of the resolution functions in either the keepers queue -// or the breakers queue in later turns with the promise's fate. - - queue.forEach(function (func) { - setImmediate(func, fate); - }); - } - - return { - make: function make() { - -// The make function makes new vows. A vow contains a promise object and the -// two resolution functions (break and keep) that determine the fate of the -// promise. - - var breakers = [], // .when's broken queue - fate, // The promise's ultimate value - keepers = [], // .when's kept queue - status = 'pending'; // 'broken', 'kept', or 'pending' - - function enqueue( - resolution, // 'keep' or 'break' - func, // A function that was registered with .when - vow // A vow that provides the resolution functions - ) { - -// enqueue is a helper function used by .when. It will append a function to -// either the keepers queue or the breakers queue. - - var queue = resolution === 'keep' ? keepers : breakers; - queue[queue.length] = typeof func !== 'function' - -// If func is not a function, push the resolver so that the value passes to -// the next cascaded .when. - - ? vow[resolution] - -// If the func is a function, push a function that calls func with a value. -// The result can be a promise, or not a promise, or an exception. - - : function (value) { - try { - var result = func(value); - -// If the result is a promise, then register our resolver with that promise. - - if (result && result.is_promise === true) { - result.when(vow.keep, vow.break); - -// But if it is not a promise, then use the result to resolve our promise. - - } else { - vow.keep(result); - } - -// But if func throws an exception, then break our promise. - - } catch (e) { - vow.break(e); - } - }; - } - - function herald(state, value, queue) { - -// The herald function is a helper function of break and keep. -// It seals the promise's fate, updates its status, enlightens -// one of the queues, and empties both queues. - - if (status !== 'pending') { - throw "overpromise"; - } - fate = value; - status = state; - enlighten(queue, fate); - keepers.length = 0; - breakers.length = 0; - } - -// Construct and return the vow object. - - return { - 'break': function (value) { - -// The break method breaks the promise. - - herald('broken', value, breakers); - }, - keep: function keep(value) { - -// The keep method keeps the promise. - - herald('kept', value, keepers); - }, - promise: { - -// The promise is an object with a .when method. - - is_promise: true, - -// The .when method is the promise monad's bind. The .when method can take two -// optional functions. One of those functions may be called, depending on the -// promise's resolution. Both could be called if the the kept function throws. - - when: function (kept, broken) { - -// Make a new vow. Return the new promise. - - var vow = make(); - switch (status) { - -// If this promise is still pending, then enqueue both kept and broken. - - case 'pending': - enqueue('keep', kept, vow); - enqueue('break', broken, vow); - break; - -// If the promise has already been kept, then enqueue only the kept function, -// and enlighten it. - - case 'kept': - enqueue('keep', kept, vow); - enlighten(keepers, fate); - break; - -// If the promise has already been broken, then enqueue only the broken -// function, and enlighten it. - - case 'broken': - enqueue('break', broken, vow); - enlighten(breakers, fate); - break; - } - return vow.promise; - } - } - }; - }, - every: function every(array) { - -// The every function takes an array of promises and returns a promise that -// will deliver an array of results only if every promise is kept. - - var remaining = array.length, results = [], vow = VOW.make(); - - if (!remaining) { - vow.break(array); - } else { - array.forEach(function (promise, i) { - promise.when(function (value) { - results[i] = value; - remaining -= 1; - if (remaining === 0) { - vow.keep(results); - } - }, function (reason) { - remaining = NaN; - vow.break(reason); - }); - }); - } - return vow.promise; - }, - first: function first(array) { - -// The first function takes an array of promises and returns a promise to -// deliver the first observed kept promise, or a broken promise if all of -// the promises are broken. - - var found = false, remaining = array.length, vow = VOW.make(); - - function check() { - remaining -= 1; - if (remaining === 0 && !found) { - vow.break(); - } - } - - if (remaining === 0) { - vow.break(array); - } else { - array.forEach(function (promise) { - promise.when(function (value) { - if (!found) { - found = true; - vow.keep(value); - } - check(); - }, check); - }); - } - return vow.promise; - }, - any: function any(array) { - -// The any function takes an array of promises and returns a promise that -// will deliver a possibly sparse array of results of any kept promises. -// The result will contain an undefined element for each broken promise. - - var remaining = array.length, results = [], vow = VOW.make(); - - function check() { - remaining -= 1; - if (remaining === 0) { - vow.keep(results); - } - } - - if (!remaining) { - vow.keep(results); - } else { - array.forEach(function (promise, i) { - promise.when(function (value) { - results[i] = value; - check(); - }, check); - }); - } - return vow.promise; - }, - kept: function (value) { - -// Returns a new kept promise. - - var vow = VOW.make(); - vow.keep(value); - return vow.promise; - }, - broken: function (reason) { - -// Returns a new broken promise. - - var vow = VOW.make(); - vow.break(reason); - return vow.promise; - } - }; -}()); - - -// If your system does not have setImmediate, then simulate it with setTimeout. - -if (typeof setImmediate !== 'function') { - setImmediate = function setImmediate(func, param) { - 'use strict'; - return setTimeout(function () { - func(param); - }, 0); - }; -} diff --git a/test/fixtures/vow.json b/test/fixtures/vow.json deleted file mode 100644 index b059873..0000000 --- a/test/fixtures/vow.json +++ /dev/null @@ -1 +0,0 @@ -[92,8470,0,0,103,8174,1,0,311,656,2,0,583,648,3,0,685,5093,2,0,1155,2682,3,0,1964,2667,4,0,2566,2645,5,0,2696,3201,3,0,3290,3423,3,0,3447,3579,3,0,3961,5050,3,0,5110,5975,2,0,5469,5917,3,0,5526,5770,4,0,5772,5897,4,0,5992,6931,2,0,6282,6446,3,0,6568,6873,3,0,6622,6846,4,0,6946,7814,2,0,7277,7437,3,0,7555,7756,3,0,7612,7729,4,0,7830,7986,2,0,8004,8165,2,0,8323,8467,1,0,8408,8456,2,0,96,99,0,1,8273,8285,0,0,8308,8320,0,1,320,329,1,1,330,335,2,1,337,341,2,1,569,574,2,0,593,597,3,1,613,625,0 [...] diff --git a/test/specs.js b/test/specs.js index d59bc59..8eaa15c 100644 --- a/test/specs.js +++ b/test/specs.js @@ -1,46 +1,65 @@ +/*jslint stupid: true */ + 'use strict'; -var assert = require('assert'), - fs = require('fs'), - path = require('path'), +var assert = require('assert'); +var fs = require('fs'); +var path = require('path'); +var scopifier = require('../scopifier'); +var createEmacsBuffer = require('./createEmacsBuffer'); +var threes = require('./threes'); - scopifier = require('../scopifier'), +describe('emacsBuffer', function () { - inputPath = path.join(__dirname, 'fixtures', 'vow.js'), - outputPath = path.join(__dirname, 'fixtures', 'vow.json'); + it('should set levels', function () { + var emacsBuffer = createEmacsBuffer(3); -describe('scopifier', function () { + emacsBuffer.setLevelForRegion(1, 4, 0); + assert(emacsBuffer.isLevelForRegion(1, 4, 0)); - var input, output; - - before(function (done) { - fs.readFile(inputPath, 'utf8', function (error, contents) { - if (error) { - done(error); - return; - } - input = contents; - done(); - }); + emacsBuffer.setLevelForRegion(2, 3, 1); + assert.strictEqual(emacsBuffer.isLevelForRegion(1, 4, 0), false); + assert(emacsBuffer.isLevelAtPoint(1, 0)); + assert(emacsBuffer.isLevelAtPoint(2, 1)); + assert(emacsBuffer.isLevelAtPoint(3, 0)); }); - before(function (done) { - fs.readFile(outputPath, 'utf8', function (readError, contents) { - if (readError) { - done(readError); - return; - } - try { - output = JSON.parse(contents); - } catch (parseError) { - done(parseError); - } - done(); - }); + it('should apply tokens', function () { + var emacsBuffer = createEmacsBuffer(3); + + emacsBuffer.applyTokens([ + 1, 4, 0, + 2, 3, 1 + ]); + assert(emacsBuffer.isLevelAtPoint(1, 0)); + assert(emacsBuffer.isLevelAtPoint(2, 1)); + assert(emacsBuffer.isLevelAtPoint(3, 0)); }); - it('should work', function () { - assert.deepEqual(scopifier(input), output); +}); + +describe('scopifier', function () { + + it('should recognize scope levels', function () { + var fixturePath = path.join(__dirname, 'fixtures/function-scopes.js'); + var input = fs.readFileSync(fixturePath, 'utf8'); + var output = scopifier(input); + var emacsBuffer = createEmacsBuffer(); + + emacsBuffer.applyTokens(output); + + assert(emacsBuffer.isLevelForRegion(1, 9, 0)); + assert(emacsBuffer.isLevelForRegion(9, 23, 1)); + assert(emacsBuffer.isLevelForRegion(23, 25, 0)); + assert(emacsBuffer.isLevelForRegion(25, 34, 1)); + assert(emacsBuffer.isLevelForRegion(34, 35, 0)); + assert(emacsBuffer.isLevelForRegion(35, 52, 1)); + assert(emacsBuffer.isLevelForRegion(52, 66, 2)); + assert(emacsBuffer.isLevelForRegion(66, 72, 1)); + assert(emacsBuffer.isLevelForRegion(72, 81, 2)); + assert(emacsBuffer.isLevelForRegion(81, 82, 1)); + assert(emacsBuffer.isLevelForRegion(82, 87, 2)); + assert(emacsBuffer.isLevelForRegion(87, 89, 1)); }); }); diff --git a/test/threes.js b/test/threes.js new file mode 100644 index 0000000..0833913 --- /dev/null +++ b/test/threes.js @@ -0,0 +1,13 @@ +'use strict'; + +function threes(array) { + return array.reduce(function (soFar, number) { + if (!soFar[soFar.length - 1] || soFar[soFar.length - 1].length === 3) { + soFar.push([]); + } + soFar[soFar.length - 1].push(number); + return soFar; + }, []); +} + +module.exports = threes;