diff --git a/lib/node-redis-pubsub.js b/lib/node-redis-pubsub.js index 460d740..d704fec 100644 --- a/lib/node-redis-pubsub.js +++ b/lib/node-redis-pubsub.js @@ -1,5 +1,5 @@ -"use strict"; -var redis = require('redis'); +const redis = require('redis') +const noop = () => {} /** * Create a new NodeRedisPubsub instance that can subscribe to channels and publish messages @@ -8,112 +8,112 @@ var redis = require('redis'); * scope - Optional, two NodeRedisPubsubs with different scopes will not share messages * url - Optional, a correctly formed redis connection url */ -function NodeRedisPubsub(options){ - if (!(this instanceof NodeRedisPubsub)){ return new NodeRedisPubsub(options); } - - if(!options) - options = {}; - - var auth = options.auth; - var redisUrl = options.url; - - options.port = options.port || 6379; // 6379 is Redis' default - options.host = options.host || '127.0.0.1'; - - // Need to create two Redis clients as one cannot be both in receiver and emitter mode - // I wonder why that is, by the way ... - if (!redisUrl) { - this.emitter = redis.createClient(options); - this.receiver = redis.createClient(options); - } else { - delete options.url; - this.emitter = redis.createClient(redisUrl, options); - this.receiver = redis.createClient(redisUrl, options); +class NodeRedisPubsub { + constructor(opts) { + const options = Object.assign({}, { port: 6379, host: '127.0.0.1' }, opts) + const { auth = null, redisUrl = null } = options + + if (!redisUrl) { + this.emitter = redis.createClient(options) + this.receiver = redis.createClient(options) + } else { + delete options.url + this.emitter = redis.createClient(redisUrl, options) + this.receiver = redis.createClient(redisUrl, options) + } + + if (auth) { + this.emitter.auth(auth) + this.receiver.auth(auth) + } + + this.receiver.setMaxListeners(0) + this.prefix = options.scope ? options.scope + ':' : '' } - if(auth){ - this.emitter.auth(auth); - this.receiver.auth(auth); + /** + * Return the emitter object to be used as a regular redis client to save resources. + */ + getRedisClient() { + return this.emitter } - this.receiver.setMaxListeners(0); - this.prefix = options.scope ? options.scope + ':' : ''; -} + /** + * Subscribe to a channel + * @param {String} channel The channel to subscribe to, can be a pattern e.g. 'user.*' + * @param {Function} handler Function to call with the received message. + * @param {Function} cb Optional callback to call once the handler is registered. + * + */ + on(channel, handler, callback = noop) { + if (channel === 'error') { + this.errorHandler = handler + this.emitter.on('error', handler) + this.receiver.on('error', handler) + callback() + return + } -/** - * Return the emitter object to be used as a regular redis client to save resources. - */ -NodeRedisPubsub.prototype.getRedisClient = function(){ - return this.emitter; -}; + const fullChannel = `${this.prefix}${channel}` -/** - * Subscribe to a channel - * @param {String} channel The channel to subscribe to, can be a pattern e.g. 'user.*' - * @param {Function} handler Function to call with the received message. - * @param {Function} cb Optional callback to call once the handler is registered. - * - */ -NodeRedisPubsub.prototype.on = NodeRedisPubsub.prototype.subscribe = function(channel, handler, callback){ - if(!callback) - callback = function(){}; - var self = this; - - if(channel === "error"){ - self.errorHandler = handler; - this.emitter.on("error", handler); - this.receiver.on("error", handler); - callback(); - return; - } + const pmessageHandler = (pattern, channel, message) => { + if (fullChannel !== pattern) return - var pmessageHandler = function(pattern, _channel, message){ - if(self.prefix + channel === pattern){ - try{ - return handler(JSON.parse(message), _channel); - } catch (ex){ - if(typeof self.errorHandler === 'function'){ - return self.errorHandler("Invalid JSON received! Channel: " + self.prefix + channel + " Message: " + message); + try { + return handler(JSON.parse(message), channel) + } catch (ex) { + if (typeof this.errorHandler === 'function') { + return this.errorHandler(`Invalid JSON received! Channel: ${fullChannel} Message: ${message}`) } - } + } } - }; - this.receiver.on('pmessage', pmessageHandler); + this.receiver.on('pmessage', pmessageHandler) + this.receiver.psubscribe(fullChannel, callback) - this.receiver.psubscribe(this.prefix + channel, callback); + const removeListener = callback => { + this.receiver.removeListener('pmessage', pmessageHandler) + return this.receiver.punsubscribe(fullChannel, callback) + } - var removeListener = function(callback){ - self.receiver.removeListener('pmessage', pmessageHandler); - return self.receiver.punsubscribe(self.prefix + channel, callback); - }; + return removeListener + } - return removeListener; -}; + subscribe(channel, handler, callback = noop) { + return this.on(channel, handler, callback) + } -/** - * Emit an event - * @param {String} channel Channel on which to emit the message - * @param {Object} message - */ -NodeRedisPubsub.prototype.emit = NodeRedisPubsub.prototype.publish = function (channel, message) { - return this.emitter.publish(this.prefix + channel, JSON.stringify(message)); -}; + off(channel, callback = noop) { + return this.receiver.punsubscribe(`${this.prefix}${channel}`, callback) + } -/** - * Safely close the redis connections 'soon' - */ -NodeRedisPubsub.prototype.quit = function() { - this.emitter.quit(); - this.receiver.quit(); -}; + /** + * Emit an event + * @param {String} channel Channel on which to emit the message + * @param {Object} message + */ + emit(channel, message) { + return this.emitter.publish(`${this.prefix}${channel}`, JSON.stringify(message)) + } -/** - * Dangerously close the redis connections immediately - */ -NodeRedisPubsub.prototype.end = function() { - this.emitter.end(); - this.receiver.end(); -}; + publish(channel, message) { + return this.emit(channel, message) + } + + /** + * Safely close the redis connections 'soon' + */ + quit() { + this.emitter.quit() + this.receiver.quit() + } -module.exports = NodeRedisPubsub; + /** + * Dangerously close the redis connections immediately + */ + end() { + this.emitter.end() + this.receiver.end() + } +} +module.exports = NodeRedisPubsub diff --git a/package.json b/package.json index 16fc6e5..70706df 100644 --- a/package.json +++ b/package.json @@ -1,40 +1,29 @@ { + "name": "node-redis-pubsub", + "version": "1.0.4", - "name" : "node-redis-pubsub" , - "version" : "1.0.3" , - - "author" : { - "name" : "Louis Chatriot" , - "email" : "louis.chatriot@gmail.com" + "author": { + "name": "Louis Chatriot", + "email": "louis.chatriot@gmail.com" }, - "contributors" : [ - "Louis Chatriot" , - "omarmohamed" , - "Martin Saint-Macary" , - "Narciso Guillen" - ], + "contributors": ["Louis Chatriot", "omarmohamed", "Martin Saint-Macary", "Narciso Guillen", "notVitaliy"], - "description" : "Redis PubSub client for Node", + "description": "Redis PubSub client for Node", - "keywords" : [ - "redis" , - "pubsub" , - "node" , - "simple" - ], + "keywords": ["redis", "pubsub", "node", "simple"], - "homepage" : "https://github.com/louischatriot/node-redis-pubsub", + "homepage": "https://github.com/louischatriot/node-redis-pubsub", - "dependencies" : { - "redis" : "^2.7.1" + "dependencies": { + "redis": "^2.8.0" }, - "devDependencies" : { - "chai" : "^3.5.0", - "mocha" : "*", - "should" : "*", - "sinon" : "^1.14.1", + "devDependencies": { + "chai": "^3.5.0", + "mocha": "*", + "should": "*", + "sinon": "^1.14.1", "sinon-chai": "^2.7.0" }, @@ -44,14 +33,14 @@ }, "engines": { - "node": "*" + "node": ">=6" }, - "main" : "index", + "main": "index", - "scripts" : { - "test" : "make test" + "scripts": { + "test": "make test" }, - "licence" : "mit" + "licence": "mit" } diff --git a/test/redisQueue.test.js b/test/redisQueue.test.js index ae71161..9f36bff 100644 --- a/test/redisQueue.test.js +++ b/test/redisQueue.test.js @@ -1,149 +1,172 @@ -var chai = require('chai') - , conf = { port: 6379, scope: 'onescope' } - , conf2 = { port: 6379, scope: 'anotherscope' } - , conf3 = { url: 'redis://127.0.0.1:6379/', scope: 'yetanotherscope' } - , NodeRedisPubsub = require('../index') - , sinon = require("sinon") - , sinonChai = require("sinon-chai") - ; - -chai.should(); +var chai = require('chai'), + conf = { port: 6379, scope: 'onescope' }, + conf2 = { port: 6379, scope: 'anotherscope' }, + conf3 = { url: 'redis://127.0.0.1:6379/', scope: 'yetanotherscope' }, + NodeRedisPubsub = require('../index'), + sinon = require('sinon'), + sinonChai = require('sinon-chai') + +chai.should() chai.use(sinonChai) -describe('Node Redis Pubsub', function () { - - it('Should send and receive standard messages correctly', function (done) { - var rq = new NodeRedisPubsub(conf); - - rq.on('a test', function (data, channel) { - data.first.should.equal('First message'); - data.second.should.equal('Second message'); - channel.should.equal("onescope:a test"); - done(); - } - , function () { - rq.emit('a test', { first: 'First message' - , second: 'Second message' }); - }); - }); - - it('Should send and receive standard messages correctly via url configuration', function (done) { - var rq = new NodeRedisPubsub(conf3); - - rq.on('a test', function (data, channel) { - data.first.should.equal('First message'); - data.second.should.equal('Second message'); - channel.should.equal("yetanotherscope:a test"); - done(); - } - , function () { - rq.emit('a test', { first: 'First message' - , second: 'Second message' }); - }); - }); - - it('Should receive pattern messages correctly', function (done) { - var rq = new NodeRedisPubsub(conf); - - rq.on('test:*', function (data, channel) { - data.first.should.equal('First message'); - data.second.should.equal('Second message'); - channel.should.equal("onescope:test:created"); - done(); - } - , function () { - rq.emit('test:created', { first: 'First message' - , second: 'Second message' }); - }); - }); - - it('Should only receive messages for his own scope', function (done) { +describe('Node Redis Pubsub', function() { + it('Should send and receive standard messages correctly', function(done) { var rq = new NodeRedisPubsub(conf) - , rq2 = new NodeRedisPubsub(conf2) - ; - - rq.on('thesame', function (data) { - data.first.should.equal('First message'); - data.second.should.equal('Second message'); - rq2.emit('thesame', { third: 'Third message' }); - } - , function () { - rq2.on('thesame', function (data) { - data.third.should.equal('Third message'); // Tests would fail here if rq2 received message destined to rq - done(); - }, function () { - rq.emit('thesame', { first: 'First message' - , second: 'Second message' }); - }); - }); - }); - - it('Should have the ability to unsubscribe', function (done) { - var rq = new NodeRedisPubsub(); - var called = false; - - rq.should.have.property('off'); - rq.on('a test', function (data){ - called = true; - }, function(){ - rq.off('a test'); - rq.emit('a test', { }); - }); - - setTimeout(function(){ - called.should.be.false; - done(); - }, 10); - - }); - - - it('Should gracefully handle invalid JSON message data', function (done) { - var rq = new NodeRedisPubsub(conf); - - rq.on('error', function (err) { - err.should.include('Invalid JSON received!'); - done(); - }); - rq.on('a test', function (data, channel){ - channel.should.equal("onescope:a test"); - data.should.equal({}); - var invalidJSON = 'hello'; - rq.emitter.publish(channel, invalidJSON); - } - , function () { - var validJSON = {}; - rq.emit('a test', validJSON); - }); - }); - - - describe("When shutting down connections", function () { - var sandbox, rq; - beforeEach(function () { - rq = new NodeRedisPubsub(); - sandbox = sinon.sandbox.create(); - sandbox.stub(rq.emitter, "quit"); - sandbox.stub(rq.receiver, "quit"); - sandbox.stub(rq.emitter, "end"); - sandbox.stub(rq.receiver, "end"); - }); - afterEach(function () { - sandbox.restore(); - }); - - it('Should safely shut down the connections', function () { - rq.quit(); - rq.emitter.quit.should.have.been.calledOnce; - rq.receiver.quit.should.have.been.calledOnce; - }); - - it('Should dangerously shut down the connections', function () { - rq.end(); - rq.emitter.end.should.have.been.calledOnce; - rq.receiver.end.should.have.been.calledOnce; - }); - - }); - -}); + + rq.on( + 'a test', + function(data, channel) { + data.first.should.equal('First message') + data.second.should.equal('Second message') + channel.should.equal('onescope:a test') + done() + }, + function() { + rq.emit('a test', { + first: 'First message', + second: 'Second message', + }) + }, + ) + }) + + it('Should send and receive standard messages correctly via url configuration', function(done) { + var rq = new NodeRedisPubsub(conf3) + + rq.on( + 'a test', + function(data, channel) { + data.first.should.equal('First message') + data.second.should.equal('Second message') + channel.should.equal('yetanotherscope:a test') + done() + }, + function() { + rq.emit('a test', { + first: 'First message', + second: 'Second message', + }) + }, + ) + }) + + it('Should receive pattern messages correctly', function(done) { + var rq = new NodeRedisPubsub(conf) + + rq.on( + 'test:*', + function(data, channel) { + data.first.should.equal('First message') + data.second.should.equal('Second message') + channel.should.equal('onescope:test:created') + done() + }, + function() { + rq.emit('test:created', { + first: 'First message', + second: 'Second message', + }) + }, + ) + }) + + it('Should only receive messages for his own scope', function(done) { + var rq = new NodeRedisPubsub(conf), + rq2 = new NodeRedisPubsub(conf2) + + rq.on( + 'thesame', + function(data) { + data.first.should.equal('First message') + data.second.should.equal('Second message') + rq2.emit('thesame', { third: 'Third message' }) + }, + function() { + rq2.on( + 'thesame', + function(data) { + data.third.should.equal('Third message') // Tests would fail here if rq2 received message destined to rq + done() + }, + function() { + rq.emit('thesame', { + first: 'First message', + second: 'Second message', + }) + }, + ) + }, + ) + }) + + it('Should have the ability to unsubscribe', function(done) { + var rq = new NodeRedisPubsub() + var called = false + + rq.should.have.property('off') + rq.on( + 'a test', + function(data) { + called = true + }, + function() { + rq.off('a test') + rq.emit('a test', {}) + }, + ) + + setTimeout(function() { + called.should.be.false + done() + }, 10) + }) + + it('Should gracefully handle invalid JSON message data', function(done) { + var rq = new NodeRedisPubsub(conf) + + rq.on('error', function(err) { + err.should.include('Invalid JSON received!') + done() + }) + rq.on( + 'a test', + function(data, channel) { + channel.should.equal('onescope:a test') + data.should.equal({}) + var invalidJSON = 'hello' + rq.emitter.publish(channel, invalidJSON) + }, + function() { + var validJSON = {} + rq.emit('a test', validJSON) + }, + ) + }) + + describe('When shutting down connections', function() { + var sandbox, rq + beforeEach(function() { + rq = new NodeRedisPubsub() + sandbox = sinon.sandbox.create() + sandbox.stub(rq.emitter, 'quit') + sandbox.stub(rq.receiver, 'quit') + sandbox.stub(rq.emitter, 'end') + sandbox.stub(rq.receiver, 'end') + }) + afterEach(function() { + sandbox.restore() + }) + + it('Should safely shut down the connections', function() { + rq.quit() + rq.emitter.quit.should.have.been.calledOnce + rq.receiver.quit.should.have.been.calledOnce + }) + + it('Should dangerously shut down the connections', function() { + rq.end() + rq.emitter.end.should.have.been.calledOnce + rq.receiver.end.should.have.been.calledOnce + }) + }) +}) diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..e024314 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,258 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +assertion-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +brace-expansion@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +browser-stdout@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" + +chai@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" + dependencies: + assertion-error "^1.0.1" + deep-eql "^0.1.3" + type-detect "^1.0.0" + +commander@2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + +deep-eql@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" + dependencies: + type-detect "0.1.1" + +diff@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" + +double-ended-queue@^2.1.0-0: + version "2.1.0-0" + resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c" + +escape-string-regexp@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +formatio@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9" + dependencies: + samsam "~1.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +glob@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +growl@1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + +he@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + +lolex@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +mkdirp@0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +mocha@*: + version "4.0.1" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.0.1.tgz#0aee5a95cf69a4618820f5e51fa31717117daf1b" + dependencies: + browser-stdout "1.3.0" + commander "2.11.0" + debug "3.1.0" + diff "3.3.1" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.3" + he "1.1.1" + mkdirp "0.5.1" + supports-color "4.4.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +redis-commands@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b" + +redis-parser@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" + +redis@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02" + dependencies: + double-ended-queue "^2.1.0-0" + redis-commands "^1.2.0" + redis-parser "^2.6.0" + +samsam@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" + +samsam@~1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621" + +should-equal@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" + dependencies: + should-type "^1.4.0" + +should-format@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/should-format/-/should-format-3.0.3.tgz#9bfc8f74fa39205c53d38c34d717303e277124f1" + dependencies: + should-type "^1.3.0" + should-type-adaptors "^1.0.1" + +should-type-adaptors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/should-type-adaptors/-/should-type-adaptors-1.0.1.tgz#efe5553cdf68cff66e5c5f51b712dc351c77beaa" + dependencies: + should-type "^1.3.0" + should-util "^1.0.0" + +should-type@^1.3.0, should-type@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/should-type/-/should-type-1.4.0.tgz#0756d8ce846dfd09843a6947719dfa0d4cff5cf3" + +should-util@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/should-util/-/should-util-1.0.0.tgz#c98cda374aa6b190df8ba87c9889c2b4db620063" + +should@*: + version "13.1.3" + resolved "https://registry.yarnpkg.com/should/-/should-13.1.3.tgz#a089bdf7979392a8272a712c8b63acbaafb7948f" + dependencies: + should-equal "^2.0.0" + should-format "^3.0.3" + should-type "^1.4.0" + should-type-adaptors "^1.0.1" + should-util "^1.0.0" + +sinon-chai@^2.7.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-2.14.0.tgz#da7dd4cc83cd6a260b67cca0f7a9fdae26a1205d" + +sinon@^1.14.1: + version "1.17.7" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" + dependencies: + formatio "1.1.1" + lolex "1.3.2" + samsam "1.1.2" + util ">=0.10.3 <1" + +supports-color@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" + dependencies: + has-flag "^2.0.0" + +type-detect@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" + +type-detect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" + +"util@>=0.10.3 <1": + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + dependencies: + inherits "2.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"