Skip to content

Commit

Permalink
Made events fire when model and collection states are consistent (fix…
Browse files Browse the repository at this point in the history
  • Loading branch information
hashchange committed Jan 14, 2014
1 parent 7df7da3 commit ec27f47
Show file tree
Hide file tree
Showing 5 changed files with 437 additions and 29 deletions.
52 changes: 51 additions & 1 deletion spec/javascripts/multiSelect.deselect.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,12 @@ describe("multi-select collection deselecting", function(){
});

describe("when all models are selected, and deselecting all", function(){
var m1, m2, collection;
var m1, m2, collection, selectedEventState, selectNoneEventState;

beforeEach(function(){
selectedEventState = { model: {}, collection: {} };
selectNoneEventState = { m1: {}, m2: {}, collection: {} };

m1 = new Model();
m2 = new Model();

Expand All @@ -137,6 +140,20 @@ describe("multi-select collection deselecting", function(){
m2.select();

spyOn(collection, "trigger").andCallThrough();

m1.on('deselected', function (model) {
selectedEventState.model.selected = model && model.selected;
selectedEventState.collection.selected = _.clone(collection.selected);
selectedEventState.collection.selectedLength = collection.selectedLength;
});

collection.on('select:none', function () {
selectNoneEventState.m1.selected = m1.selected;
selectNoneEventState.m2.selected = m2.selected;
selectNoneEventState.collection.selected = _.clone(collection.selected);
selectNoneEventState.collection.selectedLength = collection.selectedLength;
});

collection.selectNone();
});

Expand All @@ -152,6 +169,39 @@ describe("multi-select collection deselecting", function(){
var size = _.size(collection.selected);
expect(size).toBe(0);
});

it('should trigger a model\'s deselected event after the model status has been updated', function () {
expect(selectedEventState.model.selected).toEqual(false);
});

it('should trigger a model\'s selected event after the collection\'s selected models have been updated with that model', function () {
// m2 doesn't necessarily have to be removed from collection.selected at
// this time. The point is that events are fired when model and collection
// states are consistent. When m1 fires the 'deselected' event, only m1
// must have been removed from the collection.
expect(selectedEventState.collection.selected[m1.cid]).toBeUndefined();
});

it('should trigger a model\'s selected event after the collection\'s selected length has been updated', function () {
// collection.selectedLength could be 0 or 1 at this time. Again, all we
// are asking for is consistency - see comment above.
expect(selectedEventState.collection.selectedLength).toBeLessThan(2);
expect(selectedEventState.collection.selectedLength).toEqual(_.size(selectedEventState.collection.selected));
});

it('should trigger the collection\'s select:none event after the model status has been updated', function () {
expect(selectNoneEventState.m1.selected).toEqual(false);
expect(selectNoneEventState.m2.selected).toEqual(false);
});

it('should trigger the collection\'s select:none event after the collection\'s selected models have been updated', function () {
expect(selectNoneEventState.collection.selected).toEqual({});
});

it('should trigger the collection\'s select:none event after the collection\'s selected length has been updated', function () {
expect(selectNoneEventState.collection.selectedLength).toBe(0);
});

});

describe("when all models are selected, and deselecting all, with options.silent enabled", function(){
Expand Down
52 changes: 51 additions & 1 deletion spec/javascripts/multiSelect.select.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,31 @@ describe("multi-select collection selecting", function(){
});

describe("when no models are selected, and selecting all", function(){
var m1, m2, collection;
var m1, m2, collection, selectedEventState, selectAllEventState;

beforeEach(function(){
selectedEventState = { model: {}, collection: {} };
selectAllEventState = { m1: {}, m2: {}, collection: {} };

m1 = new Model();
m2 = new Model();

collection = new Collection([m1, m2]);
spyOn(collection, "trigger").andCallThrough();

m1.on('selected', function (model) {
selectedEventState.model.selected = model && model.selected;
selectedEventState.collection.selected = _.clone(collection.selected);
selectedEventState.collection.selectedLength = collection.selectedLength;
});

collection.on('select:all', function () {
selectAllEventState.m1.selected = m1.selected;
selectAllEventState.m2.selected = m2.selected;
selectAllEventState.collection.selected = _.clone(collection.selected);
selectAllEventState.collection.selectedLength = collection.selectedLength;
});

collection.selectAll();
});

Expand All @@ -43,6 +59,40 @@ describe("multi-select collection selecting", function(){
it("should have the second selected model in the selected list", function(){
expect(collection.selected[m2.cid]).not.toBeUndefined();
});

it('should trigger a model\'s selected event after the model status has been updated', function () {
expect(selectedEventState.model.selected).toEqual(true);
});

it('should trigger a model\'s selected event after the collection\'s selected models have been updated with that model', function () {
// m2 doesn't necessarily have to be part of collection.selected at this
// time. The point is that events are fired when model and collection
// states are consistent. When m1 fires the 'selected' event, only m1 must
// be part of the collection.
expect(selectedEventState.collection.selected[m1.cid]).toBe(m1);
});

it('should trigger a model\'s selected event after the collection\'s selected length has been updated', function () {
// collection.selectedLength could be 1 or 2 at this time. Again, all we
// are asking for is consistency - see comment above.
expect(selectedEventState.collection.selectedLength).toBeGreaterThan(0);
expect(selectedEventState.collection.selectedLength).toEqual(_.size(selectedEventState.collection.selected));
});

it('should trigger the collection\'s select:all event after the model status has been updated', function () {
expect(selectAllEventState.m1.selected).toEqual(true);
expect(selectAllEventState.m2.selected).toEqual(true);
});

it('should trigger the collection\'s select:all event after the collection\'s selected models have been updated', function () {
expect(selectAllEventState.collection.selected[m1.cid]).toBe(m1);
expect(selectAllEventState.collection.selected[m2.cid]).toBe(m2);
});

it('should trigger the collection\'s select:all event after the collection\'s selected length has been updated', function () {
expect(selectAllEventState.collection.selectedLength).toBe(2);
});

});

describe("when no models are selected, and selecting all, with options.silent enabled", function(){
Expand Down
168 changes: 164 additions & 4 deletions spec/javascripts/multiSelect.selectable.interaction.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,30 @@ describe("multi-select selectable interaction", function(){
describe("select / deselect the model directly", function(){

describe("when 1 out of 2 models in a collection is selected", function(){
var m1, m2, collection;
var m1, m2, collection, selectedEventState, selectSomeEventState;

beforeEach(function(){
selectedEventState = { model: {}, collection: {} };
selectSomeEventState = { m1: {}, collection: {} };

m1 = new Model();
m2 = new Model();

collection = new Collection([m1, m2]);
spyOn(collection, "trigger").andCallThrough();

m1.on('selected', function (model) {
selectedEventState.model.selected = model.selected;
selectedEventState.collection.selected = _.clone(collection.selected);
selectedEventState.collection.selectedLength = collection.selectedLength;
});

collection.on('select:some', function () {
selectSomeEventState.m1.selected = m1.selected;
selectSomeEventState.collection.selected = _.clone(collection.selected);
selectSomeEventState.collection.selectedLength = collection.selectedLength;
});

m1.select();
});

Expand All @@ -46,6 +61,30 @@ describe("multi-select selectable interaction", function(){
expect(collection.selected[m2.cid]).toBeUndefined();
});

it('should trigger the model\'s selected event after the model status has been updated', function () {
expect(selectedEventState.model.selected).toEqual(true);
});

it('should trigger the model\'s selected event after the collection\'s selected models have been updated', function () {
expect(selectedEventState.collection.selected[m1.cid]).toEqual(m1);
});

it('should trigger the model\'s selected event after the collection\'s selected length has been updated', function () {
expect(selectedEventState.collection.selectedLength).toBe(1);
});

it('should trigger the collection\'s select:some event after the model status has been updated', function () {
expect(selectSomeEventState.m1.selected).toEqual(true);
});

it('should trigger the collection\'s select:some event after the collection\'s selected models have been updated', function () {
expect(selectSomeEventState.collection.selected[m1.cid]).toBe(m1);
});

it('should trigger the collection\'s select:some event after the collection\'s selected length has been updated', function () {
expect(selectSomeEventState.collection.selectedLength).toBe(1);
});

});

describe("when 1 out of 2 models in a collection is selected, with options.silent enabled", function(){
Expand Down Expand Up @@ -142,15 +181,31 @@ describe("multi-select selectable interaction", function(){
});

describe("when a model is selected and then deselected", function(){
var m1, collection;
var m1, collection, selectedEventState, selectNoneEventState;

beforeEach(function(){
selectedEventState = { model: {}, collection: {} };
selectNoneEventState = { m1: {}, collection: {} };

m1 = new Model();

collection = new Collection([m1]);
spyOn(collection, "trigger").andCallThrough();

m1.select();

m1.on('deselected', function (model) {
selectedEventState.model.selected = model && model.selected;
selectedEventState.collection.selected = _.clone(collection.selected);
selectedEventState.collection.selectedLength = collection.selectedLength;
});

collection.on('select:none', function () {
selectNoneEventState.m1.selected = m1.selected;
selectNoneEventState.collection.selected = _.clone(collection.selected);
selectNoneEventState.collection.selectedLength = collection.selectedLength;
});

m1.deselect();
});

Expand All @@ -173,6 +228,31 @@ describe("multi-select selectable interaction", function(){
it("should not have the model in the selected list", function(){
expect(collection.selected[m1.cid]).toBeUndefined();
});

it('should trigger the model\'s deselected event after the model status has been updated', function () {
expect(selectedEventState.model.selected).toEqual(false);
});

it('should trigger the model\'s deselected event after the collection\'s selected models have been updated', function () {
expect(selectedEventState.collection.selected).toEqual({});
});

it('should trigger the model\'s deselected event after the collection\'s selected length has been updated', function () {
expect(selectedEventState.collection.selectedLength).toBe(0);
});

it('should trigger the collection\'s select:none event after the model status has been updated', function () {
expect(selectNoneEventState.m1.selected).toEqual(false);
});

it('should trigger the collection\'s select:none event after the collection\'s selected models have been updated', function () {
expect(selectNoneEventState.collection.selected).toEqual({});
});

it('should trigger the collection\'s select:none event after the collection\'s selected length has been updated', function () {
expect(selectNoneEventState.collection.selectedLength).toBe(0);
});

});

describe("when a model is selected and then deselected, with options.silent enabled", function(){
Expand Down Expand Up @@ -406,37 +486,117 @@ describe("multi-select selectable interaction", function(){
describe("select / deselect through the collection", function(){

describe("when selecting a model through the collection's select method", function(){
var m1, collection;
var m1, collection, selectedEventState, selectAllEventState;

beforeEach(function(){
selectedEventState = { model: {}, collection: {} };
selectAllEventState = { m1: {}, collection: {} };

m1 = new Model();
spyOn(m1, "select").andCallThrough();
collection = new Collection([m1]);

m1.on('selected', function (model) {
selectedEventState.model.selected = model && model.selected;
selectedEventState.collection.selected = _.clone(collection.selected);
selectedEventState.collection.selectedLength = collection.selectedLength;
});

collection.on('select:all', function () {
selectAllEventState.m1.selected = m1.selected;
selectAllEventState.collection.selected = _.clone(collection.selected);
selectAllEventState.collection.selectedLength = collection.selectedLength;
});

collection.select(m1);
});

it("should select the model", function(){
expect(m1.select).toHaveBeenCalled();
});

it('should trigger the model\'s selected event after the model status has been updated', function () {
expect(selectedEventState.model.selected).toEqual(true);
});

it('should trigger the model\'s selected event after the collection\'s selected models have been updated', function () {
expect(selectedEventState.collection.selected[m1.cid]).toBe(m1);
});

it('should trigger the model\'s selected event after the collection\'s selected length has been updated', function () {
expect(selectedEventState.collection.selectedLength).toBe(1);
});

it('should trigger the collection\'s select:all event after the model status has been updated', function () {
expect(selectAllEventState.m1.selected).toEqual(true);
});

it('should trigger the collection\'s select:all event after the collection\'s selected models have been updated', function () {
expect(selectAllEventState.collection.selected[m1.cid]).toBe(m1);
});

it('should trigger the collection\'s select:all event after the collection\'s selected length has been updated', function () {
expect(selectAllEventState.collection.selectedLength).toBe(1);
});

});

describe("when deselecting a model through the collection's select method", function(){
var m1, collection;
var m1, collection, selectedEventState, selectNoneEventState;

beforeEach(function(){
selectedEventState = { model: {}, collection: {} };
selectNoneEventState = { m1: {}, collection: {} };

m1 = new Model();
spyOn(m1, "deselect").andCallThrough();

collection = new Collection([m1]);
m1.select();

m1.on('deselected', function (model) {
selectedEventState.model.selected = model && model.selected;
selectedEventState.collection.selected = _.clone(collection.selected);
selectedEventState.collection.selectedLength = collection.selectedLength;
});

collection.on('select:none', function () {
selectNoneEventState.m1.selected = m1.selected;
selectNoneEventState.collection.selected = _.clone(collection.selected);
selectNoneEventState.collection.selectedLength = collection.selectedLength;
});

collection.deselect(m1);
});

it("should deselect the model", function(){
expect(m1.deselect).toHaveBeenCalled();
});

it('should trigger the model\'s deselected event after the model status has been updated', function () {
expect(selectedEventState.model.selected).toEqual(false);
});

it('should trigger the model\'s deselected event after the collection\'s selected models have been updated', function () {
expect(selectedEventState.collection.selected).toEqual({});
});

it('should trigger the model\'s deselected event after the collection\'s selected length has been updated', function () {
expect(selectedEventState.collection.selectedLength).toBe(0);
});

it('should trigger the collection\'s select:none event after the model status has been updated', function () {
expect(selectNoneEventState.m1.selected).toEqual(false);
});

it('should trigger the collection\'s select:none event after the collection\'s selected models have been updated', function () {
expect(selectNoneEventState.collection.selected).toEqual({});
});

it('should trigger the collection\'s select:none event after the collection\'s selected length has been updated', function () {
expect(selectNoneEventState.collection.selectedLength).toBe(0);
});

});

describe("when 1 out of 2 models in a collection is selected, and selecting the last one via the collection's select", function(){
Expand Down
Loading

0 comments on commit ec27f47

Please sign in to comment.