From 944be082b73702d253f0a4d8a2688674a7c4f091 Mon Sep 17 00:00:00 2001 From: tunnckoCore Date: Sat, 10 Oct 2015 00:31:38 +0300 Subject: [PATCH] implement :cat2: --- .editorconfig | 26 +++ .gitignore | 57 +++++++ .travis.yml | 29 ++++ CHANGELOG.md | 4 + CONTRIBUTING.md | 32 ++++ LICENSE | 5 +- README.md | 76 ++++++++- examples/fs.js | 37 ++++ examples/generators.js | 50 ++++++ examples/json.js | 38 +++++ index.js | 43 +++++ package.json | 29 ++++ test.js | 44 +++++ test/callbacks.js | 88 ++++++++++ test/co/arguments.js | 18 ++ test/co/arrays.js | 38 +++++ test/co/context.js | 18 ++ test/co/generator-functions.js | 49 ++++++ test/co/generators.js | 49 ++++++ test/co/invalid.js | 21 +++ test/co/objects.js | 90 ++++++++++ test/co/promises.js | 100 +++++++++++ test/co/recursion.js | 45 +++++ test/co/thunks.js | 300 +++++++++++++++++++++++++++++++++ test/co/wrap.js | 30 ++++ test/errors.js | 43 +++++ test/generators.js | 39 +++++ test/sync.js | 83 +++++++++ 28 files changed, 1476 insertions(+), 5 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 examples/fs.js create mode 100644 examples/generators.js create mode 100644 examples/json.js create mode 100644 index.js create mode 100644 package.json create mode 100644 test.js create mode 100644 test/callbacks.js create mode 100644 test/co/arguments.js create mode 100644 test/co/arrays.js create mode 100644 test/co/context.js create mode 100644 test/co/generator-functions.js create mode 100644 test/co/generators.js create mode 100644 test/co/invalid.js create mode 100644 test/co/objects.js create mode 100644 test/co/promises.js create mode 100644 test/co/recursion.js create mode 100644 test/co/thunks.js create mode 100644 test/co/wrap.js create mode 100644 test/errors.js create mode 100644 test/generators.js create mode 100644 test/sync.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..87bee69 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +# .editorconfig +# +# Copyright (c) 2015 Charlike Mike Reagent, contributors. +# Released under the MIT license. +# + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space + +[*.js] +insert_final_newline = true +trim_trailing_whitespace = true + +[*.php] +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e720db8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# .gitignore +# +# Copyright (c) 2015 Charlike Mike Reagent, contributors. +# Released under the MIT license. +# + +# Always-ignore dirs # +# #################### +_gh_pages +node_modules +bower_components +components +vendor +build +dest +dist +src +lib-cov +coverage +nbproject +cache +temp +tmp +letta + +# Packages # +# ########## +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# OS, Logs and databases # +# ######################### +*.pid +*.dat +*.log +*.sql +*.sqlite +*~ +~* + +# Another files # +# ############### +Icon? +.DS_Store* +Thumbs.db +ehthumbs.db +Desktop.ini +npm-debug.log +.directory +._* +lcov.info \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..969f268 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +language: "node_js" +sudo: false + +node_js: + - "0.10" + - "0.12" + - "4" + +notifications: + email: + on_success: never + on_failure: never + +before_script: + - npm install standard + - standard + +script: + - npm install istanbul-harmony + - node --harmony node_modules/.bin/istanbul cover test.js + +after_success: + - npm install coveralls + - cat coverage/lcov.info | coveralls + - mv coverage/lcov.info . + +matrix: + allow_failures: + - node_js: "0.10" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ff09283 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ + + +## 0.0.0 - 2015-10-09 +- Initial commit \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5312165 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,32 @@ +# Contributing Guidelines + +Contributions are always welcome! + +**Before spending lots of time on something, ask for feedback on your idea first!** + +Please search issues and pull requests before adding something new to avoid duplicating efforts and conversations. + + +## Installing + +Fork and clone the repo, then `npm install` to install all dependencies and `npm test` to ensure all is okey before you start anything. + + +## Testing + +Tests are run with `npm test`. Please ensure all tests are passing before submitting a pull request (unless you're creating a failing test to increase test coverage or show a problem). + +## Code Style + +[![standard][standard-image]][standard-url] + +This repository uses [`standard`][standard-url] to maintain code style and consistency, and to avoid style arguments. You are encouraged to install it globally. `npm test` runs `standard` so you don't have to! + +``` +npm i standard -g +``` + +It is intentional to don't have `standard`, `istanbul` and `coveralls` in the devDependencies. Travis will handle all that stuffs. That approach will save bandwidth also installing and development time. + +[standard-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg +[standard-url]: https://github.com/feross/standard \ No newline at end of file diff --git a/LICENSE b/LICENSE index 20efd1b..53eb8e4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 +Copyright (c) 2015 Charlike Mike Reagent Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,5 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 881214f..6fcb5b9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,74 @@ -# letta -Let's move to promises! Drop-in replacement for `co@4`, but on steroids. Accepts sync, async and generator functions. +# [letta][author-www-url] [![npmjs.com][npmjs-img]][npmjs-url] [![The MIT License][license-img]][license-url] + +> Let's move to promises! Drop-in replacement for `co@4`, but on steroids. Accepts sync, async and generator functions. + +[![code climate][codeclimate-img]][codeclimate-url] [![standard code style][standard-img]][standard-url] [![travis build status][travis-img]][travis-url] [![coverage status][coveralls-img]][coveralls-url] [![dependency status][david-img]][david-url] + + +## Install +``` +npm i letta --save +``` + + +## Usage +> For more use-cases see the [tests](./test.js) + +```js +var letta = require('letta') +``` + + +## Contributing +Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/hybridables/letta/issues/new). +But before doing anything, please read the [CONTRIBUTING.md](./CONTRIBUTING.md) guidelines. + + +## [Charlike Make Reagent](http://j.mp/1stW47C) [![new message to charlike][new-message-img]][new-message-url] [![freenode #charlike][freenode-img]][freenode-url] + +[![tunnckocore.tk][author-www-img]][author-www-url] [![keybase tunnckocore][keybase-img]][keybase-url] [![tunnckoCore npm][author-npm-img]][author-npm-url] [![tunnckoCore twitter][author-twitter-img]][author-twitter-url] [![tunnckoCore github][author-github-img]][author-github-url] + + +[npmjs-url]: https://www.npmjs.com/package/letta +[npmjs-img]: https://img.shields.io/npm/v/letta.svg?label=letta + +[license-url]: https://github.com/hybridables/letta/blob/master/LICENSE.md +[license-img]: https://img.shields.io/badge/license-MIT-blue.svg + + +[codeclimate-url]: https://codeclimate.com/github/hybridables/letta +[codeclimate-img]: https://img.shields.io/codeclimate/github/hybridables/letta.svg + +[travis-url]: https://travis-ci.org/hybridables/letta +[travis-img]: https://img.shields.io/travis/hybridables/letta.svg + +[coveralls-url]: https://coveralls.io/r/hybridables/letta +[coveralls-img]: https://img.shields.io/coveralls/hybridables/letta.svg + +[david-url]: https://david-dm.org/hybridables/letta +[david-img]: https://img.shields.io/david/hybridables/letta.svg + +[standard-url]: https://github.com/feross/standard +[standard-img]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg + + +[author-www-url]: http://www.tunnckocore.tk +[author-www-img]: https://img.shields.io/badge/www-tunnckocore.tk-fe7d37.svg + +[keybase-url]: https://keybase.io/tunnckocore +[keybase-img]: https://img.shields.io/badge/keybase-tunnckocore-8a7967.svg + +[author-npm-url]: https://www.npmjs.com/~tunnckocore +[author-npm-img]: https://img.shields.io/badge/npm-~tunnckocore-cb3837.svg + +[author-twitter-url]: https://twitter.com/tunnckoCore +[author-twitter-img]: https://img.shields.io/badge/twitter-@tunnckoCore-55acee.svg + +[author-github-url]: https://github.com/tunnckoCore +[author-github-img]: https://img.shields.io/badge/github-@tunnckoCore-4183c4.svg + +[freenode-url]: http://webchat.freenode.net/?channels=charlike +[freenode-img]: https://img.shields.io/badge/freenode-%23charlike-5654a4.svg + +[new-message-url]: https://github.com/tunnckoCore/ama +[new-message-img]: https://img.shields.io/badge/ask%20me-anything-green.svg \ No newline at end of file diff --git a/examples/fs.js b/examples/fs.js new file mode 100644 index 0000000..3d6446c --- /dev/null +++ b/examples/fs.js @@ -0,0 +1,37 @@ +/*! + * letta + * + * Copyright (c) 2015 Charlike Mike Reagent <@tunnckoCore> (http://www.tunnckocore.tk) + * Released under the MIT license. + */ + +'use strict' + +var fs = require('fs') +var path = require('path') +var letta = require('../index') +var filepath = path.join(path.dirname(__dirname), 'package.json') + +/** + * fs.readFileSync + */ + +letta(fs.readFileSync, filepath, 'utf-8').then(function (data) { + console.log(JSON.parse(data).name) // => 'letta' +}, console.error) + +/** + * fs.readFile + */ + +letta(fs.readFile, filepath, 'utf-8').then(function (data) { + console.log(data.indexOf('"license": "MIT"') !== -1) // => true +}, console.error) + +/** + * fs.stat + */ + +letta(fs.stat, filepath).then(function (stats) { + console.log(stats.isFile()) // => true +}, console.error) diff --git a/examples/generators.js b/examples/generators.js new file mode 100644 index 0000000..c2711bb --- /dev/null +++ b/examples/generators.js @@ -0,0 +1,50 @@ +/*! + * letta + * + * Copyright (c) 2015 Charlike Mike Reagent <@tunnckoCore> (http://www.tunnckocore.tk) + * Released under the MIT license. + */ + +/* jshint asi:true */ + +'use strict' + +var fs = require('fs') +var letta = require('../index') +var simpleGet = require('simple-get') + +/** + * readFile + */ + +function read (fp) { + return function (done) { + fs.readFile(fp, 'utf8', done) + } +} + +letta(function * () { + var data = yield read('package.json') + return JSON.parse(data) +}).then(function (json) { + console.log(json.name) // => 'letta' +}, console.error) + +/** + * http request + */ + +function get (url) { + return function (done) { + simpleGet.concat(url, done) + } +} + +letta(function * () { + var res = yield get('http://www.tunnckocore.tk') + var buf = res[0] + // var httpResponse = res[1] + return buf.toString() +}).then(function (res) { + console.log(res.indexOf('') !== -1) // => true +}, console.error) diff --git a/examples/json.js b/examples/json.js new file mode 100644 index 0000000..44e027d --- /dev/null +++ b/examples/json.js @@ -0,0 +1,38 @@ +/*! + * letta <https://github.com/hybridables/letta> + * + * Copyright (c) 2015 Charlike Mike Reagent <@tunnckoCore> (http://www.tunnckocore.tk) + * Released under the MIT license. + */ + +'use strict' + +var letta = require('../index') + +/** + * JSON.stringify without identation + */ + +letta(JSON.stringify, {foo: 'bar'}).then(function (data) { + console.log(data) // => {"foo":"bar"} +}, console.error) + +/** + * JSON.stringify with identation + */ + +letta(JSON.stringify, {foo: 'bar'}, null, 2).then(function (data) { + console.log(data) + // => + // { + // "foo": "bar" + // } +}, console.error) + +/** + * JSON.parse + */ + +letta(JSON.parse, '{"foo":"bar"}').then(function (data) { + console.log(data.foo) // => 'bar' +}, console.error) diff --git a/index.js b/index.js new file mode 100644 index 0000000..d4ea9f0 --- /dev/null +++ b/index.js @@ -0,0 +1,43 @@ +/*! + * letta <https://github.com/hybridables/letta> + * + * Copyright (c) 2015 Charlike Mike Reagent <@tunnckoCore> (http://www.tunnckocore.tk) + * Released under the MIT license. + */ + +'use strict' + +var sliced = require('sliced') +var isGenFn = require('is-es6-generator-function') +var getPromise = require('native-or-another') + +var letta = module.exports = function letta (fn) { + var Promize = getPromise(letta.promise) + var args = sliced(arguments, 1) + var self = this + + return new Promize(function (resolve, reject) { + process.once('uncaughtException', reject) + process.once('unhandledRejection', reject) + process.on('newListener', function (name) { + this.removeAllListeners(name) + }) + if (isGenFn(fn)) { + require('co').apply(self, [fn].concat(args)).then(resolve, reject) + return + } + require('redolent')(fn).apply(self, args).then(resolve, reject) + }) +} + +// just for 100% `co@4` comaptibility +// not needed really, because `letta` accept +// everything, on the fly, by default +letta.wrap = function voaWrap (val) { + function createPromise () { + var args = sliced(arguments) + return letta.apply(this, [val].concat(args)) + } + createPromise.__generatorFunction__ = val + return createPromise +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2ce4c5d --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "letta", + "version": "0.0.0", + "description": "Let's move to promises! Drop-in replacement for `co@4`, but on steroids. Accepts sync, async and generator functions.", + "repository": "hybridables/letta", + "author": "Charlike Mike Reagent <@tunnckoCore> (http://www.tunnckocore.tk)", + "main": "index.js", + "license": "MIT", + "scripts": { + "test": "standard && node test.js" + }, + "dependencies": { + "co": "^4.6.0", + "is-es6-generator-function": "^1.0.0", + "native-or-another": "^3.0.1", + "redolent": "^1.0.3", + "sliced": "^1.0.1" + }, + "devDependencies": { + "assertit": "^0.1.0", + "mz": "^2.0.0", + "semver": "^5.0.3", + "simple-get": "^1.4.3" + }, + "files": [ + "index.js" + ], + "keywords": [] +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..251b76d --- /dev/null +++ b/test.js @@ -0,0 +1,44 @@ +/*! + * letta <https://github.com/hybridables/letta> + * + * Copyright (c) 2015 Charlike Mike Reagent <@tunnckoCore> (http://www.tunnckocore.tk) + * Released under the MIT license. + */ + +/* jshint asi:true */ + +'use strict' + +var test = require('assertit') +var semver = require('semver') + +test('errors', function (done) { + require('./test/errors') + done() +}) + +test('callbacks', function (done) { + require('./test/callbacks') + done() +}) + +test('sync functions', function (done) { + require('./test/sync') + done() +}) + +if (semver.gte(process.version, '0.11.13')) { + test('generators', function (done) { + require('./test/generators') + done() + }) + test('co tests', function (done) { + var fs = require('fs') + var path = require('path') + var dir = path.join(__dirname, 'test', 'co') + fs.readdirSync(dir).forEach(function (filename) { + require(path.join(dir, filename)) + }) + done() + }) +} diff --git a/test/callbacks.js b/test/callbacks.js new file mode 100644 index 0000000..857ad19 --- /dev/null +++ b/test/callbacks.js @@ -0,0 +1,88 @@ +/*! + * letta <https://github.com/hybridables/letta> + * + * Copyright (c) 2015 Charlike Mike Reagent <@tunnckoCore> (http://www.tunnckocore.tk) + * Released under the MIT license. + */ + +/* jshint asi:true */ + +'use strict' + +var fs = require('fs') +var test = require('assertit') +var letta = require('../index') + +function successJsonParse (callback) { + callback(null, JSON.parse('{"foo":"bar"}')) +} + +function notSpreadArrays (callback) { + callback(null, [1, 2], 3, [4, 5]) +} + +function twoArgs (callback) { + callback(null, 1, 2) +} + +function failure (callback) { + callback(new Error('callback error')) +} + +function readFile (cb) { + fs.readFile('package.json', 'utf8', cb) +} + +test('should handle a successful callback', function (done) { + letta(successJsonParse).then(function (res) { + test.deepEqual(res, {foo: 'bar'}) + done() + }, done) +}) + +test('should handle an errored callback', function (done) { + letta(failure).catch(function (err) { + test.ifError(!err) + test.ok(err instanceof Error) + test.strictEqual(err.message, 'callback error') + done() + }) +}) + +test('should flatten arguments into array - e.g. cb(null, 1, 2)', function (done) { + letta(twoArgs).then(function (res) { + var one = res[0] + var two = res[1] + test.strictEqual(one, 1) + test.strictEqual(two, 2) + done() + }, done) +}) + +test('should flatten arrays - e.g. cb(null, [1, 2], 3)', function (done) { + letta(notSpreadArrays).then(function (res) { + var arrOne = res[0] + var three = res[1] + var arrTwo = res[2] + test.deepEqual(arrOne, [1, 2]) + test.strictEqual(three, 3) + test.deepEqual(arrTwo, [4, 5]) + done() + }, done) +}) + +test('should handle result of `fs.readFile`', function (done) { + letta(readFile).then(function (res) { + test.equal(typeof res, 'string') + test.ok(res.indexOf('"license": "MIT"') !== -1) + done() + }, done) +}) + +test('should handle buffer result from `fs.readFile` passed directly', function (done) { + letta(fs.readFile, 'package.json').then(function (res) { + test.ok(Buffer.isBuffer(res)) + test.ok(res.toString('utf8').indexOf('"license": "MIT"') !== -1) + done() + }, done) +}) diff --git a/test/co/arguments.js b/test/co/arguments.js new file mode 100644 index 0000000..9d99d9e --- /dev/null +++ b/test/co/arguments.js @@ -0,0 +1,18 @@ +'use strict' +var test = require('assertit') +var assert = require('assert') +var co = require('../../index') +var describe = test.describe +var it = test.it + +describe('co(gen, args)', function () { + it('should pass the rest of the arguments', function () { + return co(function * (num, str, arr, obj, fun) { + assert.strictEqual(num, 42) + assert.strictEqual(str, 'forty-two') + assert.strictEqual(arr[0], 42) + assert.strictEqual(obj.value, 42) + assert.strictEqual(fun instanceof Function, true) + }, 42, 'forty-two', [42], { value: 42 }, function () {}) + }) +}) diff --git a/test/co/arrays.js b/test/co/arrays.js new file mode 100644 index 0000000..ef21902 --- /dev/null +++ b/test/co/arrays.js @@ -0,0 +1,38 @@ +'use strict' +var test = require('assertit') +var assert = require('assert') +var co = require('../../index') +var describe = test.describe +var it = test.it + +var read = require('mz/fs').readFile + +describe('co(* -> yield [])', function () { + it('should aggregate several promises', function () { + return co(function * () { + var a = read('index.js', 'utf8') + var b = read('LICENSE', 'utf8') + var c = read('package.json', 'utf8') + + var res = yield [a, b, c] + assert.strictEqual(3, res.length) + assert.strictEqual(res[0].indexOf('exports') !== -1, true) + assert.strictEqual(res[1].indexOf('MIT') !== -1, true) + assert.strictEqual(res[2].indexOf('devDependencies') !== -1, true) + }) + }) + + it('should noop with no args', function () { + return co(function * () { + var res = yield [] + assert.strictEqual(0, res.length) + }) + }) + + it('should support an array of generators', function () { + return co(function * () { + var val = yield [(function * () { return 1 })()] + assert.deepEqual(val, [1]) + }) + }) +}) diff --git a/test/co/context.js b/test/co/context.js new file mode 100644 index 0000000..805d07b --- /dev/null +++ b/test/co/context.js @@ -0,0 +1,18 @@ +'use strict' +var test = require('assertit') +var assert = require('assert') +var co = require('../../index') +var describe = test.describe +var it = test.it + +var ctx = { + some: 'thing' +} + +describe('co.call(this)', function () { + it('should pass the context', function () { + return co.call(ctx, function * () { + assert.deepEqual(ctx, this) + }) + }) +}) diff --git a/test/co/generator-functions.js b/test/co/generator-functions.js new file mode 100644 index 0000000..cc844ab --- /dev/null +++ b/test/co/generator-functions.js @@ -0,0 +1,49 @@ +'use strict' +var test = require('assertit') +var assert = require('assert') +var co = require('../../index') +var describe = test.describe +var it = test.it + +function sleep (ms) { + return function (done) { + setTimeout(done, ms) + } +} + +function * work () { + yield sleep(50) + return 'yay' +} + +describe('co(fn*)', function () { + describe('with a generator function', function () { + it('should wrap with co()', function () { + return co(function * () { + var a = yield work + var b = yield work + var c = yield work + + assert.strictEqual(a, 'yay') + assert.strictEqual(b, 'yay') + assert.strictEqual(c, 'yay') + + var res = yield [work, work, work] + assert.deepEqual(res, ['yay', 'yay', 'yay']) + }) + }) + + it('should catch errors', function () { + return co(function * () { + yield function * () { + throw new Error('boom') + } + }).then(function () { + throw new Error('wtf') + }, function (err) { + assert.ifError(!err) + assert.strictEqual(err.message, 'boom') + }) + }) + }) +}) diff --git a/test/co/generators.js b/test/co/generators.js new file mode 100644 index 0000000..79f7ca3 --- /dev/null +++ b/test/co/generators.js @@ -0,0 +1,49 @@ +'use strict' +var test = require('assertit') +var assert = require('assert') +var co = require('../../index') +var describe = test.describe +var it = test.it + +function sleep (ms) { + return function (done) { + setTimeout(done, ms) + } +} + +function * work () { + yield sleep(50) + return 'yay' +} + +describe('co(*)', function () { + describe('with a generator function', function () { + it('should wrap with co()', function () { + return co(function * () { + var a = yield work + var b = yield work + var c = yield work + + assert.strictEqual(a, 'yay') + assert.strictEqual(b, 'yay') + assert.strictEqual(c, 'yay') + + var res = yield [work, work, work] + assert.deepEqual(res, ['yay', 'yay', 'yay']) + }) + }) + + it('should catch errors', function () { + return co(function * () { + yield function * () { + throw new Error('boom') + } + }).then(function () { + throw new Error('wtf') + }, function (err) { + assert.ifError(!err) + assert.strictEqual(err.message, 'boom') + }) + }) + }) +}) diff --git a/test/co/invalid.js b/test/co/invalid.js new file mode 100644 index 0000000..5040237 --- /dev/null +++ b/test/co/invalid.js @@ -0,0 +1,21 @@ +'use strict' +var test = require('assertit') +var assert = require('assert') +var co = require('../../index') +var describe = test.describe +var it = test.it + +describe('yield <invalid>', function () { + it('should throw an error', function () { + return co(function * () { + try { + yield null + throw new Error('lol') + } catch (err) { + assert.ifError(!err) + assert.strictEqual(err instanceof TypeError, true) + assert.strictEqual(err.message.indexOf('You may only yield') !== -1, true) + } + }) + }) +}) diff --git a/test/co/objects.js b/test/co/objects.js new file mode 100644 index 0000000..ac825b8 --- /dev/null +++ b/test/co/objects.js @@ -0,0 +1,90 @@ +'use strict' +var test = require('assertit') +var assert = require('assert') +var co = require('../../index') +var describe = test.describe +var it = test.it + +var read = require('mz/fs').readFile + +describe('co(* -> yield {})', function () { + // @notice + it('should aggregate several promises', function () { + return co(function * () { + var a = read('index.js', 'utf8') + var b = read('LICENSE', 'utf8') + var c = read('package.json', 'utf8') + + var res = yield { + a: a, + b: b, + c: c + } + + assert.strictEqual(3, Object.keys(res).length) + assert.strictEqual(res.a.indexOf('exports') !== -1, true) + assert.strictEqual(res.b.indexOf('MIT') !== -1, true) + assert.strictEqual(res.c.indexOf('devDependencies') !== -1, true) + }) + }) + + it('should noop with no args', function () { + return co(function * () { + var res = yield {} + assert.strictEqual(0, Object.keys(res).length) + }) + }) + + it('should ignore non-thunkable properties', function () { + return co(function * () { + var foo = { + name: { first: 'tobi' }, + age: 2, + address: read('index.js', 'utf8'), + tobi: new Pet('tobi'), + now: new Date(), + falsey: false, + nully: null, + undefiney: undefined + } + + var res = yield foo + + assert.strictEqual('tobi', res.name.first) + assert.strictEqual(2, res.age) + assert.strictEqual('tobi', res.tobi.name) + assert.strictEqual(foo.now, res.now) + assert.strictEqual(false, foo.falsey) + assert.strictEqual(null, foo.nully) + assert.strictEqual(undefined, foo.undefiney) + assert.strictEqual(res.address.indexOf('exports') !== -1, true) + }) + }) + + it('should preserve key order', function () { + function timedThunk (time) { + return function (cb) { + setTimeout(cb, time) + } + } + + return co(function * () { + var before = { + sun: timedThunk(30), + rain: timedThunk(20), + moon: timedThunk(10) + } + + var after = yield before + + var orderBefore = Object.keys(before).join(',') + var orderAfter = Object.keys(after).join(',') + assert.strictEqual(orderBefore, orderAfter) + }) + }) +}) + +function Pet (name) { + this.name = name + this.something = function () {} +} diff --git a/test/co/promises.js b/test/co/promises.js new file mode 100644 index 0000000..9e97b3e --- /dev/null +++ b/test/co/promises.js @@ -0,0 +1,100 @@ +'use strict' +var test = require('assertit') +var assert = require('assert') +var co = require('../../index') +var describe = test.describe +var it = test.it + +function getPromise (val, err) { + return new Promise(function (resolve, reject) { + if (err) reject(err) + else resolve(val) + }) +} + +describe('co(* -> yield <promise>', function () { + describe('with one promise yield', function () { + it('should work', function () { + return co(function * () { + var a = yield getPromise(1) + assert.strictEqual(a, 1) + }) + }) + }) + + describe('with several promise yields', function () { + it('should work', function () { + return co(function * () { + var a = yield getPromise(1) + var b = yield getPromise(2) + var c = yield getPromise(3) + + assert.deepEqual([a, b, c], [1, 2, 3]) + }) + }) + }) + + describe('when a promise is rejected', function () { + it('should throw and resume', function () { + var error + + return co(function * () { + try { + yield getPromise(1, new Error('boom')) + } catch (err) { + error = err + } + + assert.strictEqual(error.message, 'boom') + var ret = yield getPromise(1) + assert.strictEqual(ret, 1) + }) + }) + }) + + describe('when yielding a non-standard promise-like', function () { + it('should return a real Promise', function () { + var prom = co(function * () { + yield { then: function () {} } + }) + assert.strictEqual(prom instanceof Promise, true) + }) + }) +}) + +describe('co(function) -> promise', function () { + it('return value', function (done) { + co(function () { + return 1 + }).then(function (data) { + assert.strictEqual(data, 1) + done() + }) + }) + + it('return resolve promise', function () { + return co(function () { + return Promise.resolve(1) + }).then(function (data) { + assert.strictEqual(data, 1) + }) + }) + + it('return reject promise', function () { + return co(function () { + return Promise.reject(1) + }).catch(function (data) { + assert.strictEqual(data, 1) + }) + }) + + it('should catch errors', function () { + return co(function () { + throw new Error('boom') + }).then(function () { + throw new Error('nope') + }).catch(function (err) { + assert.strictEqual(err.message, 'boom') + }) + }) +}) diff --git a/test/co/recursion.js b/test/co/recursion.js new file mode 100644 index 0000000..1e916ec --- /dev/null +++ b/test/co/recursion.js @@ -0,0 +1,45 @@ +'use strict' +var test = require('assertit') +var assert = require('assert') +var co = require('../../index') +var describe = test.describe +var it = test.it + +var read = require('mz/fs').readFile + +describe('co() recursion', function () { + it('should aggregate arrays within arrays', function () { + return co(function * () { + var a = read('index.js', 'utf8') + var b = read('LICENSE', 'utf8') + var c = read('package.json', 'utf8') + + var res = yield [a, [b, c]] + assert.strictEqual(res.length, 2) + assert.strictEqual(res[0].indexOf('exports') !== -1, true) + assert.strictEqual(res[1].length, 2) + assert.strictEqual(res[1][0].indexOf('MIT') !== -1, true) + assert.strictEqual(res[1][1].indexOf('devDependencies') !== -1, true) + }) + }) + + it('should aggregate objects within objects', function () { + return co(function * () { + var a = read('index.js', 'utf8') + var b = read('LICENSE', 'utf8') + var c = read('package.json', 'utf8') + + var res = yield { + 0: a, + 1: { + 0: b, + 1: c + } + } + + assert.strictEqual(res[0].indexOf('exports') !== -1, true) + assert.strictEqual(res[1][0].indexOf('MIT') !== -1, true) + assert.strictEqual(res[1][1].indexOf('devDependencies') !== -1, true) + }) + }) +}) diff --git a/test/co/thunks.js b/test/co/thunks.js new file mode 100644 index 0000000..800d370 --- /dev/null +++ b/test/co/thunks.js @@ -0,0 +1,300 @@ +'use strict' +var test = require('assertit') +var assert = require('assert') +var co = require('../../index') +var describe = test.describe +var it = test.it + +function get (val, err, error) { + return function (done) { + if (error) throw error + setTimeout(function () { + done(err, val) + }, 10) + } +} + +describe('co(* -> yield fn(done))', function () { + describe('with no yields', function () { + it('should work', function () { + return co(function * () {}) + }) + }) + + describe('with one yield', function () { + it('should work', function () { + return co(function * () { + var a = yield get(1) + assert.strictEqual(a, 1) + }) + }) + }) + + describe('with several yields', function () { + it('should work', function () { + return co(function * () { + var a = yield get(1) + var b = yield get(2) + var c = yield get(3) + + assert.deepEqual([a, b, c], [1, 2, 3]) + }) + }) + }) + + describe('with many arguments', function () { + it('should return an array', function () { + function exec (cmd) { + return function (done) { + done(null, 'stdout', 'stderr') + } + } + + return co(function * () { + var out = yield exec('something') + assert.deepEqual(out, ['stdout', 'stderr']) + }) + }) + }) + + describe('when the function throws', function () { + it('should be caught', function () { + return co(function * () { + var a = 'default' + try { + a = yield get(1, null, new Error('boom')) + } catch (err) { + assert.strictEqual(err.message, 'boom') + } + assert.strictEqual(a, 'default') + }) + }) + }) + + describe('when an error is passed then thrown', function () { + it('should only catch the first error only', function () { + return co(function * () { + yield function (done) { + done(new Error('first')) + throw new Error('second') + } + }).then(function () { + throw new Error('wtf') + }, function (err) { + assert.strictEqual(err.message, 'first') + }) + }) + }) + + describe('when an error is passed', function () { + it('should throw and resume', function () { + var error + + return co(function * () { + try { + yield get(1, new Error('boom')) + } catch (err) { + error = err + } + + assert.strictEqual(error.message, 'boom') + var ret = yield get(1) + assert.strictEqual(ret, 1) + }) + }) + }) + + describe('with nested co()s', function () { + it('should work', function () { + var hit = [] + + return co(function * () { + var a = yield get(1) + var b = yield get(2) + var c = yield get(3) + hit.push('one') + + assert.deepEqual([a, b, c], [1, 2, 3]) + + yield co(function * () { + hit.push('two') + var a = yield get(1) + var b = yield get(2) + var c = yield get(3) + + assert.deepEqual([a, b, c], [1, 2, 3]) + + yield co(function * () { + hit.push('three') + var a = yield get(1) + var b = yield get(2) + var c = yield get(3) + + assert.deepEqual([a, b, c], [1, 2, 3]) + }) + }) + + yield co(function * () { + hit.push('four') + var a = yield get(1) + var b = yield get(2) + var c = yield get(3) + + assert.deepEqual([a, b, c], [1, 2, 3]) + }) + + assert.deepEqual(hit, ['one', 'two', 'three', 'four']) + }) + }) + }) + + describe('return values', function () { + describe('with a callback', function () { + it('should be passed', function () { + return co(function * () { + return [ + yield get(1), + yield get(2), + yield get(3) + ] + }).then(function (res) { + assert.deepEqual(res, [1, 2, 3]) + }) + }) + }) + + describe('when nested', function () { + it('should return the value', function () { + return co(function * () { + var other = yield co(function * () { + return [ + yield get(4), + yield get(5), + yield get(6) + ] + }) + + return [ + yield get(1), + yield get(2), + yield get(3) + ].concat(other) + }).then(function (res) { + assert.deepEqual(res, [1, 2, 3, 4, 5, 6]) + }) + }) + }) + }) + + describe('when yielding neither a function nor a promise', function () { + it('should throw', function () { + var errors = [] + + return co(function * () { + var a = 'a' + var b = 'b' + + try { + a = yield 'something' + } catch (err) { + errors.push(err.message) + } + + try { + b = yield 'something' + } catch (err) { + errors.push(err.message) + } + + assert.strictEqual(errors.length, 2) + var msg = 'yield a function, promise, generator, array, or object' + assert.strictEqual(errors[0].indexOf(msg) !== -1, true) + assert.strictEqual(errors[1].indexOf(msg) !== -1, true) + assert.strictEqual(a, 'a') + assert.strictEqual(b, 'b') + }) + }) + }) + + describe('with errors', function () { + it('should throw', function () { + var errors = [] + + return co(function * () { + var a = 'a' + var b = 'b' + + try { + a = yield get(1, new Error('foo')) + } catch (err) { + errors.push(err.message) + } + + try { + b = yield get(1, new Error('bar')) + } catch (err) { + errors.push(err.message) + } + + assert.deepEqual(errors, ['foo', 'bar']) + assert.strictEqual(a, 'a') + assert.strictEqual(b, 'b') + }) + }) + + it('should catch errors on .send()', function () { + var errors = [] + + return co(function * () { + var a = 'a' + var b = 'b' + + try { + a = yield get(1, null, new Error('foo')) + } catch (err) { + errors.push(err.message) + } + + try { + b = yield get(1, null, new Error('bar')) + } catch (err) { + errors.push(err.message) + } + + assert.deepEqual(errors, ['foo', 'bar']) + assert.strictEqual(a, 'a') + assert.strictEqual(b, 'b') + }) + }) + + it('should pass future errors to the callback', function () { + return co(function * () { + yield get(1) + yield get(2, null, new Error('fail')) + assert(false) + yield get(3) + }).catch(function (err) { + assert.strictEqual(err.message, 'fail') + }) + }) + + it('should pass immediate errors to the callback', function () { + return co(function * () { + yield get(1) + yield get(2, new Error('fail')) + assert(false) + yield get(3) + }).catch(function (err) { + assert.strictEqual(err.message, 'fail') + }) + }) + + it('should catch errors on the first invocation', function () { + return co(function * () { + throw new Error('fail') + }).catch(function (err) { + assert.strictEqual(err.message, 'fail') + }) + }) + }) +}) diff --git a/test/co/wrap.js b/test/co/wrap.js new file mode 100644 index 0000000..3cd47c7 --- /dev/null +++ b/test/co/wrap.js @@ -0,0 +1,30 @@ +'use strict' +var test = require('assertit') +var assert = require('assert') +var co = require('../../index') +var describe = test.describe +var it = test.it + +describe('co.wrap(fn*)', function () { + it('should pass context', function () { + var ctx = { + some: 'thing' + } + + return co.wrap(function * () { + assert.deepEqual(ctx, this) + }).call(ctx) + }) + + it('should pass arguments', function () { + return co.wrap(function * (a, b, c) { + assert.deepEqual([a, b, c], [1, 2, 3]) + })(1, 2, 3) + }) + + it('should expose the underlying generator function', function () { + var wrapped = co.wrap(function * (a, b, c) {}) + var source = Object.toString.call(wrapped.__generatorFunction__) + assert.strictEqual(source.indexOf('function*'), 0) + }) +}) diff --git a/test/errors.js b/test/errors.js new file mode 100644 index 0000000..2d85c52 --- /dev/null +++ b/test/errors.js @@ -0,0 +1,43 @@ +/*! + * letta <https://github.com/hybridables/letta> + * + * Copyright (c) 2015 Charlike Mike Reagent <@tunnckoCore> (http://www.tunnckocore.tk) + * Released under the MIT license. + */ + +/* jshint asi:true */ + +'use strict' + +var test = require('assertit') +var letta = require('../index') + +test('should catch TypeError thrown if not function', function (done) { + letta(1234).catch(function (err) { + test.strictEqual(/expect a function/.test(err.message), true) + done() + }) +}) + +test('should returned error be passed to `.then` function', function (done) { + letta(function () { + return new Error('foo bar baz') + }).then(function (res) { + test.ifError(!res) + test.ok(res instanceof Error) + test.equal(res.message, 'foo bar baz') + done() + }) +}) + +test('should mute all errors and pass them to `.catch` function', function (done) { + letta(function () { + foobar // eslint-disable-line no-undef + return 123 + }).catch(function (err) { + test.ifError(!err) + test.ok(err instanceof Error) + test.equal(err.name, 'ReferenceError') + done() + }) +}) diff --git a/test/generators.js b/test/generators.js new file mode 100644 index 0000000..6464688 --- /dev/null +++ b/test/generators.js @@ -0,0 +1,39 @@ +/*! + * letta <https://github.com/hybridables/letta> + * + * Copyright (c) 2015 Charlike Mike Reagent <@tunnckoCore> (http://www.tunnckocore.tk) + * Released under the MIT license. + */ + +/* jshint asi:true */ + +'use strict' + +var mzfs = require('mz/fs') +var test = require('assertit') +var letta = require('../index') + +function * success () { + return yield mzfs.readFile('package.json', 'utf8') +} + +function * failure () { + return yield mzfs.readFile('foobar.json') +} + +test('should handle successful generator function', function (done) { + letta(success).then(function (res) { + test.strictEqual(typeof res, 'string') + test.ok(res.indexOf('"license": "MIT"') !== -1) + done() + }, done) +}) + +test('should handle generator function errors', function (done) { + letta(failure).catch(function (err) { + test.ifError(!err) + test.ok(err instanceof Error) + test.strictEqual(err.code, 'ENOENT') + done() + }) +}) diff --git a/test/sync.js b/test/sync.js new file mode 100644 index 0000000..13828bb --- /dev/null +++ b/test/sync.js @@ -0,0 +1,83 @@ +/*! + * letta <https://github.com/hybridables/letta> + * + * Copyright (c) 2015 Charlike Mike Reagent <@tunnckoCore> (http://www.tunnckocore.tk) + * Released under the MIT license. + */ + +/* jshint asi:true */ + +'use strict' + +var fs = require('fs') +var test = require('assertit') +var letta = require('../index') + +function successJsonParse () { + return JSON.parse('{"foo":"bar"}') +} + +function returnFailingJsonParse () { + return JSON.parse('{"f') +} + +function noReturnFailJsonParse () { + JSON.parse('{"f') +} + +function returnArray () { + return [4, 5, 6] +} + +function successReadFile () { + return fs.readFileSync('package.json', 'utf-8') +} + +function failReadFile () { + return fs.readFileSync('foo-bar') +} + +test('should handle result when JSON.parse pass', function (done) { + letta(successJsonParse).then(function (res) { + test.deepEqual(res, {foo: 'bar'}) + done() + }, done) +}) + +test('should handle error when JSON.parse fail', function (done) { + letta(returnFailingJsonParse).catch(function (err) { + test.ifError(!err) + test.ok(err instanceof Error) + done() + }) +}) + +test('should handle result when fs.readFileSync pass', function (done) { + letta(successReadFile).then(function (res) { + test.ok(res.indexOf('"license": "MIT"') !== -1) + done() + }, done) +}) + +test('should handle error when fs.readFileSync fail', function (done) { + letta(failReadFile).catch(function (err) { + test.ifError(!err) + test.ok(err instanceof Error) + done() + }) +}) + +test('should handle thrown errors', function (done) { + letta(noReturnFailJsonParse).catch(function (err) { + test.ifError(!err) + test.ok(err instanceof Error) + done() + }) +}) + +test('should pass whole returned array to single argument', function (done) { + letta(returnArray).then(function (arr) { + test.deepEqual(arr, [4, 5, 6]) + done() + }) +})