From a3d7e541d3f79c2af2c880503b4a42e140dd7f31 Mon Sep 17 00:00:00 2001 From: Fabrice Marsaud Date: Wed, 1 Apr 2015 15:06:39 +0200 Subject: [PATCH] Events implemented and tested, without buffering --- .../xo-collection/collection.event.spec.js | 210 ++++++++++++++++++ packages/xo-collection/collection.js | 73 +++--- packages/xo-collection/collection.spec.js | 75 ++++--- packages/xo-collection/package.json | 4 +- 4 files changed, 300 insertions(+), 62 deletions(-) create mode 100644 packages/xo-collection/collection.event.spec.js diff --git a/packages/xo-collection/collection.event.spec.js b/packages/xo-collection/collection.event.spec.js new file mode 100644 index 000000000..dc4759533 --- /dev/null +++ b/packages/xo-collection/collection.event.spec.js @@ -0,0 +1,210 @@ +var chai = require('chai'); +var expect = chai.expect; +var dirtyChai = require('dirty-chai'); +chai.use(dirtyChai); +var leche = require('leche'); +var sinon = require('sinon'); + +var Collection = require('./collection'); + +var col = new Collection.Collection(); + +describe('collection events', function () { + + // ============================================================ + + var data1 = { + 'primitive value': ['foo1', 1], + 'array value': ['bar1', [1,2]], + 'object value': ['baz1', {a:1, b:2}] + }; + + var addSpy; + var updateSpy; + var removeSpy; + + beforeEach(function () { + + addSpy = sinon.spy(); + updateSpy = sinon.spy(); + removeSpy = sinon.spy(); + col.on('add', addSpy); + col.on('update', updateSpy); + col.on('remove', removeSpy); + + }); + + afterEach(function () { + + col.removeAllListeners(); + + }); + + // Collection is empty ======================================================= + + describe('add', function () { + + leche.withData(data1, function (key, value) { + + it('Emits an add event transporting the key and value', function () { + expect(addSpy.called).to.be.false(); + expect(updateSpy.called).to.be.false(); + expect(removeSpy.called).to.be.false(); + + expect(col.add(key, value)).to.eql(col); + expect(addSpy.called).to.be.true(); + expect(addSpy.calledWith({key: value})).to.be.true(); + + expect(updateSpy.called).to.be.false(); + expect(removeSpy.called).to.be.false(); + }); + + }); + + }); + + var updateData1 = { + 'primitive value': ['foo1', 3], + 'array value': ['bar1', [3,4]], + 'object value': ['baz1', {c:2, d:4}] + }; + + // Collection contains data1 ================================================= + + describe('update', function () { + + leche.withData(updateData1, function (key, value) { + + it('Emits an update event transporting the key and value', function () { + expect(addSpy.called).to.be.false(); + expect(updateSpy.called).to.be.false(); + expect(removeSpy.called).to.be.false(); + + expect(col.update(key, value)).to.eql(col); + expect(updateSpy.called).to.be.true(); + expect(updateSpy.calledWith({key: value})).to.be.true(); + + expect(addSpy.called).to.be.false(); + expect(removeSpy.called).to.be.false(); + }); + + }); + + }); + + var data2 = { + 'primitive value': ['foo2', 1], + 'array value': ['bar2', [1,2]], + 'object value': ['baz2', {a:1, b:2}] + }; + + // Collection contains data1 updated ========================================= + + describe('set', function () { + + leche.withData(data1, function (key, value) { + + it('Emits an update event for pre-existing keys', function () { + expect(addSpy.called).to.be.false(); + expect(updateSpy.called).to.be.false(); + expect(removeSpy.called).to.be.false(); + + expect(col.update(key, value)).to.eql(col); + expect(updateSpy.called).to.be.true(); + expect(updateSpy.calledWith({key: value})).to.be.true(); + + expect(addSpy.called).to.be.false(); + expect(removeSpy.called).to.be.false(); + }); + + }); + + // Collection contains data1 ============================================= + + leche.withData(data2, function (key, value) { + + it('Emits an add event for unexisting keys', function () { + expect(addSpy.called).to.be.false(); + expect(updateSpy.called).to.be.false(); + expect(removeSpy.called).to.be.false(); + + expect(col.add(key, value)).to.eql(col); + expect(addSpy.called).to.be.true(); + expect(addSpy.calledWith({key: value})).to.be.true(); + + expect(updateSpy.called).to.be.false(); + expect(removeSpy.called).to.be.false(); + }); + + }); + + }); + + // Collection contains data1 & data 2 ======================================== + + describe('remove', function () { + + leche.withData(data1, function (key, value) { + + it('Emits an remove event transporting the key and removed value', function () { + expect(addSpy.called).to.be.false(); + expect(updateSpy.called).to.be.false(); + expect(removeSpy.called).to.be.false(); + + expect(col.remove(key)).to.eql(col); + expect(removeSpy.called).to.be.true(); + expect(removeSpy.calledWith({key: value})).to.be.true(); + + expect(addSpy.called).to.be.false(); + expect(updateSpy.called).to.be.false(); + }); + + }); + + }); + + // Collection contains data 2 ================================================ + + describe('clear', function () { + + var clearedData; + + it('Emits a remove event', function() { + expect(addSpy.called).to.be.false(); + expect(updateSpy.called).to.be.false(); + expect(removeSpy.called).to.be.false(); + + expect(col.clear()).to.eq(col); + expect(removeSpy.calledOnce).to.be.true(); + + clearedData = removeSpy.lastCall.args[0]; + + expect(addSpy.called).to.be.false(); + expect(updateSpy.called).to.be.false(); + }); + + it('Emits no event if collection is empty', function() { + expect(addSpy.called).to.be.false(); + expect(updateSpy.called).to.be.false(); + expect(removeSpy.called).to.be.false(); + + expect(col.clear()).to.eq(col); + + expect(addSpy.called).to.be.false(); + expect(updateSpy.called).to.be.false(); + expect(removeSpy.called).to.be.false(); + }); + + leche.withData(data2, function (key, value) { + + it('Emits a remove event with all cleared data', function () { + + expect(clearedData[key]).to.eq(value); + + }); + + }); + + }); + +}); \ No newline at end of file diff --git a/packages/xo-collection/collection.js b/packages/xo-collection/collection.js index 6c6786893..2c8f14d19 100644 --- a/packages/xo-collection/collection.js +++ b/packages/xo-collection/collection.js @@ -1,4 +1,6 @@ import makeError from 'make-error'; +import events from 'events'; +import _forEach from 'lodash.foreach'; const AlreadyBuffering = makeError('AlreadyBuffering'); const NotBuffering = makeError('NotBuffering'); @@ -6,10 +8,12 @@ const IllegalAdd = makeError('IllegalAdd'); const DuplicateEntry = makeError('DuplicateEntry'); const NoSuchEntry = makeError('NoSuchEntry'); -class Collection { +class Collection extends events.EventEmitter { constructor () { + super(); + this._map = {}; this._buffering = false; this._size = 0; @@ -18,11 +22,7 @@ class Collection { _initBuffer () { - this._buffer = { - remove: [], - add: [], - update: [] - }; + this._buffer = {}; } @@ -50,25 +50,11 @@ class Collection { } - _touch (key, action) { + _touch (action, key, value) { - // TODO Buffers changes or emits an event + // TODO enable buffering - } - - _set (key, value) { - - this._map[key] = value; - this._touch(key, 'update'); - return this; - - } - - _unset (key) { - - delete this._map[key]; - this._touch(key, 'remove'); - return this; + this.emit(action, {key: value}); } @@ -127,9 +113,13 @@ class Collection { const [key, value] = this.resolveEntry.apply(this, arguments); this._assertHasNot(key); - this._size++; - return this._set(key, value); + this._map[key] = value; + + this._size++; + this._touch('add', key, value); + + return this; } @@ -137,11 +127,15 @@ class Collection { const [key, value] = this.resolveEntry.apply(this, arguments); - if (!this.has(key)) { + const action = this.has(key) ? 'update' : 'add'; + this._map[key] = value; + if ('add' === action) { this._size++; } - return this._set(key, value); + this._touch(action, key, value); + + return this; } @@ -158,7 +152,10 @@ class Collection { this._assertHas(key); - return this._set(key, value); + this._map[key] = value; + this._touch('update', key, value); + + return this; } @@ -167,9 +164,27 @@ class Collection { const [key] = this.resolveEntry(keyOrObjectWithId, null); this._assertHas(key); + + const oldValue = this.get(key); + delete this._map[key]; this._size--; - return this._unset(key); + this._touch('remove', key, oldValue); + + return this; + + } + + clear () { + + if (this._size > 0) { + this.emit('remove', this._map); + } + + this._map = {}; + this._size = 0; + + return this; } diff --git a/packages/xo-collection/collection.spec.js b/packages/xo-collection/collection.spec.js index fa00aa2e0..f682b2291 100644 --- a/packages/xo-collection/collection.spec.js +++ b/packages/xo-collection/collection.spec.js @@ -4,17 +4,15 @@ var dirtyChai = require('dirty-chai'); chai.use(dirtyChai); var leche = require('leche'); -console.log(expect); - var Collection = require('./collection'); var col = new Collection.Collection(); describe('collection', function () { - // ============================================================ + // Collection is empty ======================================================= - var fixtureValues1 = { + var data1 = { 'primitive value': ['foo1', 1], 'array value': ['bar1', [1,2]], 'object value': ['baz1', {a:1, b:2}] @@ -22,7 +20,7 @@ describe('collection', function () { describe('add', function () { - leche.withData(fixtureValues1, function (key, value) { + leche.withData(data1, function (key, value) { it('Adds a new entry to the collection', function () { expect(col.add(key, value)).to.eql(col); @@ -30,7 +28,7 @@ describe('collection', function () { }); - leche.withData(fixtureValues1, function (key, value) { + leche.withData(data1, function (key, value) { it('We cannot add on a pre-existing key', function () { expect(function () { @@ -42,9 +40,9 @@ describe('collection', function () { }); - // ============================================================ + // Collection contains data 1 ================================================ - var fixtureValues2 = { + var data2 = { 'primitive value': ['foo2', 1], 'array value': ['bar2', [1,2]], 'object value': ['baz2', {a:1, b:2}] @@ -52,7 +50,7 @@ describe('collection', function () { describe('set', function () { - leche.withData(fixtureValues2, function (key, value) { + leche.withData(data2, function (key, value) { it('Sets an entry of the collection...', function () { expect(col.set(key, value)).to.eql(col); @@ -60,7 +58,7 @@ describe('collection', function () { }); - leche.withData(fixtureValues2, function (key, value) { + leche.withData(data2, function (key, value) { it('...would it already exists or not', function () { expect(col.set(key, value)).to.eql(col); @@ -70,15 +68,15 @@ describe('collection', function () { }); - // ============================================================ + // Collection contains data 1 & 2 ============================================ - var fixtureUnexisting = { + var unexistingData = { 'Unexisting key/entry': ['wat', 'any'] }; describe('get', function () { - leche.withData(fixtureValues1, function (key, value) { + leche.withData(data1, function (key, value) { it('Returns the value of an entry of the collection...', function () { expect(col.get(key)).to.eql(value); @@ -86,7 +84,7 @@ describe('collection', function () { }); - leche.withData(fixtureValues2, function (key, value) { + leche.withData(data2, function (key, value) { it('Returns the value of an entry of the collection...', function () { expect(col.get(key)).to.eql(value); @@ -94,7 +92,7 @@ describe('collection', function () { }); - leche.withData(fixtureUnexisting, function (key) { + leche.withData(unexistingData, function (key) { it('...or throws if it does not exist', function () { expect(function () { @@ -106,9 +104,9 @@ describe('collection', function () { }); - // ============================================================ + // Collection contains data 1 & 2 ============================================ - var fixtureUpdates = { + var updateData2 = { 'primitive value': ['foo2', 3], 'array value': ['bar2', [3,4]], 'object value': ['baz2', {c:3, d:4}] @@ -116,7 +114,7 @@ describe('collection', function () { describe('update', function () { - leche.withData(fixtureUpdates, function (key, value) { + leche.withData(updateData2, function (key, value) { it('updates the given entries...', function () { expect(col.update(key, value)).to.eql(col); @@ -124,7 +122,7 @@ describe('collection', function () { }); - leche.withData(fixtureUpdates, function (key, value) { + leche.withData(updateData2, function (key, value) { it('...so we can see the values we get have changed accordingly', function () { expect(col.get(key)).to.eql(value); @@ -132,7 +130,7 @@ describe('collection', function () { }); - leche.withData(fixtureUnexisting, function (key, value) { + leche.withData(unexistingData, function (key, value) { it('If the entry does not exist, updating throws', function () { expect(function () { @@ -144,11 +142,11 @@ describe('collection', function () { }); - // ============================================================ + // Collection contains data 1 & 2 updated ==================================== describe('remove', function () { - leche.withData(fixtureValues2, function (key) { + leche.withData(data2, function (key) { it('removes the given entries...', function () { expect(col.remove(key)).to.eql(col); @@ -156,7 +154,7 @@ describe('collection', function () { }); - leche.withData(fixtureValues2, function (key) { + leche.withData(data2, function (key) { it('...so trying to get them again throws...', function () { expect(function () { @@ -166,7 +164,7 @@ describe('collection', function () { }); - leche.withData(fixtureValues2, function (key) { + leche.withData(data2, function (key) { it('...and trying to remove them again also throws', function () { expect(function () { @@ -178,11 +176,11 @@ describe('collection', function () { }); - // ============================================================ + // Collection contains data 1 ================================================ describe('has', function () { - leche.withData(fixtureValues1, function (key) { + leche.withData(data1, function (key) { it('Tells us if an entry exists...', function () { expect(col.has(key)).to.be.true(); @@ -190,7 +188,7 @@ describe('collection', function () { }); - leche.withData(fixtureValues2, function (key) { + leche.withData(data2, function (key) { it('...or not', function () { expect(col.has(key)).to.be.false(); @@ -200,21 +198,21 @@ describe('collection', function () { }); - // ============================================================ + // Collection contains data 1 ================================================ describe('size', function () { it('Reveals the number of existing entries', function () { - expect(col.size).to.eq(Object.keys(fixtureValues1).length); + expect(col.size).to.eq(Object.keys(data1).length); }); }); - // ============================================================ + // Collection contains data 1 ================================================ describe('all', function () { - leche.withData(fixtureValues1, function (key, value) { + leche.withData(data1, function (key, value) { it('Gives access to the internal collection...', function () { expect(col.all).to.have.ownProperty(key); @@ -223,7 +221,7 @@ describe('collection', function () { }); - leche.withData(fixtureValues2, function (key) { + leche.withData(data2, function (key) { it('Gives access to the internal collection...', function () { expect(col.all).to.not.have.ownProperty(key); @@ -238,4 +236,17 @@ describe('collection', function () { }); + // Collection contains data 1 ================================================ + + describe('clear', function () { + + it('wipes out all the collection', function () { + expect(col.clear()).to.eq(col); + + expect(col.size).to.eq(0); + expect(col.all).to.eql({}); + }); + + }); + }); \ No newline at end of file diff --git a/packages/xo-collection/package.json b/packages/xo-collection/package.json index fcf939331..22a0312ab 100644 --- a/packages/xo-collection/package.json +++ b/packages/xo-collection/package.json @@ -4,6 +4,7 @@ "description": "A generice batch collection attempt", "main": "collection.js", "dependencies": { + "lodash.foreach": "^3.0.2", "make-error": "^0.3.0" }, "devDependencies": { @@ -11,7 +12,8 @@ "chai": "^2.2.0", "dirty-chai": "^1.2.0", "leche": "^2.1.1", - "mocha": "^2.2.1" + "mocha": "^2.2.1", + "sinon": "^1.14.1" }, "scripts": { "test": "mocha --require babel/register *.spec.js"