Events implemented and tested, without buffering

This commit is contained in:
Fabrice Marsaud 2015-04-01 15:06:39 +02:00
parent 92d7d61926
commit a3d7e541d3
4 changed files with 300 additions and 62 deletions

View File

@ -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);
});
});
});
});

View File

@ -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;
}

View File

@ -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({});
});
});
});

View File

@ -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"