diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..c13c5f6 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015"] +} diff --git a/.gitignore b/.gitignore index f06235c..5f7c57e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules dist +#lib diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..0f30a4f --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +#src/ diff --git a/lib/assert.js b/lib/assert.js new file mode 100644 index 0000000..60ce01f --- /dev/null +++ b/lib/assert.js @@ -0,0 +1,96 @@ +'use strict'; + +var _require = require('./helpers'), + isStream = _require.isStream, + isReadable = _require.isReadable, + isWritable = _require.isWritable, + waitUntilEndEventFired = _require.waitUntilEndEventFired, + TimeoutError = _require.TimeoutError; + +function extendTDDStyle(chai, utils) { + var assert = chai.assert, + AssertionError = chai.AssertionError; + + /* + * Examples: + * - assert.isStream(obj) + * - assert.isNotStream(obj) + */ + + assert.isStream = function (actual) { + assert(isStream(actual), 'expected ' + actual + ' to be a Stream '); + }; + + assert.isNotStream = function (actual) { + assert(!isStream(actual), 'expected ' + actual + ' to not be a Stream '); + }; + + /* + * Examples: + * - assert.isReadableStream(obj) + * - assert.isNotReadableStream(obj) + */ + assert.isReadableStream = function (actual) { + assert.isStream(actual); + assert(isReadable(actual), 'expected ' + actual + ' to be a ReadableStream '); + }; + + assert.isNotReadableStream = function (actual) { + assert.isStream(actual); + assert(!isReadable(actual), 'expected ' + actual + ' to not be a ReadableStream '); + }; + + /* + * Examples: + * - assert.isWritableStream(obj) + * - assert.isNotWritableStream(obj) + */ + assert.isWritableStream = function (actual) { + assert.isStream(actual); + assert(isWritable(actual), 'expected ' + actual + ' to be a WritableStream '); + }; + + assert.isNotWritableStream = function (actual) { + assert.isStream(actual); + assert(!isWritable(actual), 'expected ' + actual + ' to not be a WritableStream '); + }; + + /* + * Examples: + * - assert.streamWillEnd(stream); + * - assert.streamWillNotEnd(stream); + */ + assert.streamWillEnd = function (actual) { + return new Promise(function (onFulfilled) { + assert.isReadableStream(actual); + onFulfilled(); + }).then(function () { + return waitUntilEndEventFired(actual, 1000); + }).catch(function (error) { + if (error instanceof TimeoutError) { + throw new AssertionError('expected the stream to end but not end'); + } + + throw error; + }); + }; + + assert.streamWillNotEnd = function (actual) { + return new Promise(function (onFulfilled) { + assert.isReadableStream(actual); + onFulfilled(); + }).then(function () { + return waitUntilEndEventFired(actual, 1000); + }).then(function () { + throw new AssertionError('expect the stream to not end but end'); + }, function (error) { + if (error instanceof TimeoutError) { + return; + } + + throw error; + }); + }; +} + +module.exports = extendTDDStyle; \ No newline at end of file diff --git a/lib/browser.js b/lib/browser.js new file mode 100644 index 0000000..66faa6c --- /dev/null +++ b/lib/browser.js @@ -0,0 +1,15 @@ +'use strict'; + +(function () { + var chaiStream = require('./index'); + + if (typeof define === 'function' && define.amd) { + // AMD + define(function () { + return chaiAsPromised; + }); + } else { + chai.use(chaiAsPromised); + self.chaiAsPromised = chaiAsPromised; + } +}); \ No newline at end of file diff --git a/lib/expect.js b/lib/expect.js new file mode 100644 index 0000000..173221b --- /dev/null +++ b/lib/expect.js @@ -0,0 +1,82 @@ +'use strict'; + +var _require = require('./helpers'), + isStream = _require.isStream, + isReadable = _require.isReadable, + isWritable = _require.isWritable, + waitUntilEndEventFired = _require.waitUntilEndEventFired, + TimeoutError = _require.TimeoutError; + +function extendExpectStyle(chai, utils) { + var Assertion = chai.Assertion, + AssertionError = chai.AssertionError; + + /* + * Examples: + * - expect(obj).to.be.a.Stream + * - expect(obj).to.not.be.a.Stream + */ + + utils.addProperty(Assertion.prototype, 'Stream', function () { + var actual = this._obj; + this.assert(isStream(actual), 'expect #{this} to be a Stream', 'expect #{this} to not be a Stream'); + }); + + /* + * Examples: + * - expect(obj).to.be.a.ReadableStream + * - expect(obj).to.not.be.a.ReadableStream + */ + utils.addProperty(Assertion.prototype, 'ReadableStream', function () { + var actual = this._obj; + + new Assertion(this._obj).to.be.a.Stream; + + this.assert(isReadable(actual), 'expect #{this} to be a readable stream but got not readable', 'expect #{this} to not be a readable stream but got readable'); + }); + + /* + * Examples: + * - expect(obj).to.be.a.WritableStream + * - expect(obj).to.not.be.a.WritableStream + */ + utils.addProperty(Assertion.prototype, 'WritableStream', function () { + var actual = this._obj; + + new Assertion(this._obj).to.be.a.Stream; + + this.assert(isWritable(actual), 'expect #{this} to be a writable stream but got not writable', 'expect #{this} to not be a writable stream but got writable'); + }); + + /* + * Examples: + * - expect(obj).to.be.a.WritableStream + * - expect(obj).to.not.be.a.WritableStream + */ + utils.addProperty(Assertion.prototype, 'end', function () { + var _this = this; + + var actual = this._obj; + + return new Promise(function (onFulfilled) { + new Assertion(actual).to.be.a.ReadableStream; + onFulfilled(); + }).then(function () { + return waitUntilEndEventFired(actual, 1000); + }).then(function () { + // For supporting .to.not.end + _this.assert(true, 'expected the stream to not end but end'); + return actual; + }, function (error) { + if (error instanceof TimeoutError) { + // For supporting .to.not.end + _this.assert(false, 'expected the stream to end but not end'); + return; + } + + throw error; + }); + }); +} + +module.exports = extendExpectStyle; \ No newline at end of file diff --git a/lib/helpers.js b/lib/helpers.js new file mode 100644 index 0000000..26ff35b --- /dev/null +++ b/lib/helpers.js @@ -0,0 +1,79 @@ +'use strict'; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var highland = require('highland'); +var Error = require('es6-error'); + +function isStream(obj) { + return obj && typeof obj.pipe === 'function'; +} + +function isReadable(obj) { + // highland streams is readable but it do not have `read()`, so we cannot + // detect redability of highland streams by using it. But highland streams + // have a `resume()` and only readable streams have it. So we can detect + // readability of streams (including highland) by using `resume()`. + return obj && typeof obj.resume === 'function'; +} + +function isWritable(obj) { + return obj && typeof obj.write === 'function'; +} + +function waitUntilEndEventFired(stream, timeoutMsec) { + var isOutdated = false; + return new Promise(function (onFulfilled, onRejected) { + setTimeout(function () { + if (isOutdated) { + return; + } + isOutdated = true; + onRejected(TimeoutError.create(timeoutMsec)); + }, timeoutMsec); + + highland(stream).toArray(function (data) { + if (isOutdated) { + return; + } + isOutdated = true; + onFulfilled(); + }); + }); +} + +var TimeoutError = function (_Error) { + _inherits(TimeoutError, _Error); + + function TimeoutError(msg) { + _classCallCheck(this, TimeoutError); + + var _this = _possibleConstructorReturn(this, (TimeoutError.__proto__ || Object.getPrototypeOf(TimeoutError)).call(this, msg)); + + _this.message = msg; + return _this; + } + + _createClass(TimeoutError, null, [{ + key: 'create', + value: function create(timeoutMsec) { + return new TimeoutError('Timeout of ' + timeoutMsec + ' ms exceeded'); + } + }]); + + return TimeoutError; +}(Error); + +module.exports = { + isStream: isStream, + isReadable: isReadable, + isWritable: isWritable, + waitUntilEndEventFired: waitUntilEndEventFired, + TimeoutError: TimeoutError +}; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..ea299e0 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,11 @@ +'use strict'; + +var Promise = require('es6-promise'); +var highland = require('highland'); + +function chaiStream(chai, utils) { + require('./assert')(chai, utils); + require('./expect')(chai, utils); +} + +module.exports = chaiStream; \ No newline at end of file diff --git a/package.json b/package.json index 645c64d..131d89e 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,11 @@ "name": "chai-stream", "version": "0.0.0", "description": "Extends Chai with assertions about streams.", - "main": "src/index.js", + "main": "lib/index.js", "scripts": { - "prepublish": "mkdir -p dist && browserify src/browser.js -t babelify --outfile dist/chai-stream.js", - "test": "mocha --compilers js:mocha-babel tests --timeout 10000" + "compile": "babel -d lib/ src/", + "prepublish": "npm run compile && mkdir -p dist && browserify src/browser.js -t babelify --outfile dist/chai-stream.js", + "test": "npm run compile && mocha --compilers js:babel-register tests --timeout 10000" }, "repository": { "type": "git", @@ -24,18 +25,22 @@ }, "homepage": "https://github.com/Kuniwak/chai-stream", "dependencies": { + "es6-error": "^4.0.0", "es6-promise": "^3.0.2", + "es6-error": "^4.0.0", "highland": "^2.5.1" }, "devDependencies": { - "babel-runtime": "^5.8.20", - "babelify": "^6.3.0", + "babel-cli": "^6.18.0", + "babel-core": "^6.18.2", + "babel-preset-es2015": "^6.18.0", + "babel-register": "^6.18.0", + "babelify": "", "browserify": "^11.0.1", "chai": "^3.2.0", "chai-as-promised": "^5.1.0", "eslint": "^1.3.1", "jscs": "^2.1.1", - "mocha": "^2.2.5", - "mocha-babel": "^3.0.0" + "mocha": "^2.2.5" } } diff --git a/src/helpers.js b/src/helpers.js index 85458aa..b41822a 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,4 +1,5 @@ const highland = require('highland'); +const Error = require('es6-error'); function isStream(obj) { return obj && typeof obj.pipe === 'function';