// ========================================================================== // Project: SproutCore Costello - Property Observing Library // Copyright: ©2006-2011 Strobe Inc. and contributors. // Portions ©2008-2010 Apple Inc. All rights reserved. // License: Licensed under MIT license (see license.js) // ========================================================================== /*globals module test ok equals same CoreTest */ sc_require('debug/test_suites/array/base'); SC.ArraySuite.define(function(T) { var expected, array, observer, rangeObserver ; // .......................................................... // MODULE: isDeep = YES // module(T.desc("RangeObserver Methods"), { setup: function() { expected = T.objects(10); array = T.newObject(expected); observer = T.observer(); rangeObserver = array.addRangeObserver(SC.IndexSet.create(2,3), observer, observer.rangeDidChange, null, NO); }, teardown: function() { T.destroyObject(array); } }); test("returns RangeObserver object", function() { ok(rangeObserver && rangeObserver.isRangeObserver, 'returns a range observer object'); }); // NOTE: Deep Property Observing is disabled for SproutCore 1.0 // // // .......................................................... // // EDIT PROPERTIES // // // // test("editing property on object in range should fire observer", function() { // var obj = array.objectAt(3); // obj.set('foo', 'BAR'); // observer.expectRangeChange(array, obj, 'foo', SC.IndexSet.create(3)); // }); // // test("editing property on object outside of range should NOT fire observer", function() { // var obj = array.objectAt(0); // obj.set('foo', 'BAR'); // equals(observer.callCount, 0, 'observer should not fire'); // }); // // // test("updating property after changing observer range", function() { // array.updateRangeObserver(rangeObserver, SC.IndexSet.create(8,2)); // observer.callCount = 0 ;// reset b/c callback should happen here // // var obj = array.objectAt(3); // obj.set('foo', 'BAR'); // equals(observer.callCount, 0, 'modifying object in old range should not fire observer'); // // obj = array.objectAt(9); // obj.set('foo', 'BAR'); // observer.expectRangeChange(array, obj, 'foo', SC.IndexSet.create(9)); // // }); // // test("updating a property after removing an range should not longer update", function() { // array.removeRangeObserver(rangeObserver); // // observer.callCount = 0 ;// reset b/c callback should happen here // // var obj = array.objectAt(3); // obj.set('foo', 'BAR'); // equals(observer.callCount, 0, 'modifying object in old range should not fire observer'); // // }); // .......................................................... // REPLACE // test("replacing object in range fires observer with index set covering only the effected item", function() { array.replace(2, 1, T.objects(1)); observer.expectRangeChange(array, null, '[]', SC.IndexSet.create(2,1)); }); test("replacing object before range", function() { array.replace(0, 1, T.objects(1)); equals(observer.callCount, 0, 'observer should not fire'); }); test("replacing object after range", function() { array.replace(9, 1, T.objects(1)); equals(observer.callCount, 0, 'observer should not fire'); }); test("updating range should be reflected by replace operations", function() { array.updateRangeObserver(rangeObserver, SC.IndexSet.create(9,1)); observer.callCount = 0 ; array.replace(2, 1, T.objects(1)); equals(observer.callCount, 0, 'observer should not fire'); observer.callCount = 0 ; array.replace(0, 1, T.objects(1)); equals(observer.callCount, 0, 'observer should not fire'); observer.callCount = 0 ; array.replace(9, 1, T.objects(1)); observer.expectRangeChange(array, null, '[]', SC.IndexSet.create(9)); }); test("removing range should no longer fire observers", function() { array.removeRangeObserver(rangeObserver); observer.callCount = 0 ; array.replace(2, 1, T.objects(1)); equals(observer.callCount, 0, 'observer should not fire'); observer.callCount = 0 ; array.replace(0, 1, T.objects(1)); equals(observer.callCount, 0, 'observer should not fire'); observer.callCount = 0 ; array.replace(9, 1, T.objects(1)); equals(observer.callCount, 0, 'observer should not fire'); }); // .......................................................... // GROUPED CHANGES // test("grouping property changes should notify observer only once at end with single IndexSet", function() { array.beginPropertyChanges(); array.replace(2, 1, T.objects(1)); array.replace(4, 1, T.objects(1)); array.endPropertyChanges(); var set = SC.IndexSet.create().add(2).add(4); // both edits observer.expectRangeChange(array, null, '[]', set); }); test("should notify observer when some but not all grouped changes are inside range", function() { array.beginPropertyChanges(); array.replace(2, 1, T.objects(1)); array.replace(9, 1, T.objects(1)); array.endPropertyChanges(); var set = SC.IndexSet.create().add(2).add(9); // both edits observer.expectRangeChange(array, null, '[]', set); }); test("should NOT notify observer when grouping changes all outside of observer", function() { array.beginPropertyChanges(); array.replace(0, 1, T.objects(1)); array.replace(9, 1, T.objects(1)); array.endPropertyChanges(); equals(observer.callCount, 0, 'observer should not fire'); }); // .......................................................... // INSERTING // test("insertAt in range fires observer with index set covering edit to end of array", function() { var newItem = T.objects(1)[0], set = SC.IndexSet.create(3,array.get('length')-2); array.insertAt(3, newItem); observer.expectRangeChange(array, null, '[]', set); }); test("insertAt BEFORE range fires observer with index set covering edit to end of array", function() { var newItem = T.objects(1)[0], set = SC.IndexSet.create(0,array.get('length')+1); array.insertAt(0, newItem); observer.expectRangeChange(array, null, '[]', set); }); test("insertAt AFTER range does not fire observer", function() { var newItem = T.objects(1)[0]; array.insertAt(9, newItem); equals(observer.callCount, 0, 'observer should not fire'); }); // .......................................................... // REMOVING // test("removeAt IN range fires observer with index set covering edit to end of array plus delta", function() { var set = SC.IndexSet.create(3,array.get('length')-3); array.removeAt(3); observer.expectRangeChange(array, null, '[]', set); }); test("removeAt BEFORE range fires observer with index set covering edit to end of array plus delta", function() { var set = SC.IndexSet.create(0,array.get('length')); array.removeAt(0); observer.expectRangeChange(array, null, '[]', set); }); test("removeAt AFTER range does not fire observer", function() { array.removeAt(9); equals(observer.callCount, 0, 'observer should not fire'); }); // .......................................................... // MODULE: No explicit range // module(T.desc("RangeObserver Methods - No explicit range"), { setup: function() { expected = T.objects(10); array = T.newObject(expected); observer = T.observer(); rangeObserver = array.addRangeObserver(null, observer, observer.rangeDidChange, null, NO); }, teardown: function() { T.destroyObject(array); } }); test("returns RangeObserver object", function() { ok(rangeObserver && rangeObserver.isRangeObserver, 'returns a range observer object'); }); // .......................................................... // REPLACE // test("replacing object in range fires observer with index set covering only the effected item", function() { array.replace(2, 1, T.objects(1)); observer.expectRangeChange(array, null, '[]', SC.IndexSet.create(2,1)); }); test("replacing at start of array", function() { array.replace(0, 1, T.objects(1)); observer.expectRangeChange(array, null, '[]', SC.IndexSet.create(0,1)); }); test("replacing object at end of array", function() { array.replace(9, 1, T.objects(1)); observer.expectRangeChange(array, null, '[]', SC.IndexSet.create(9,1)); }); test("removing range should no longer fire observers", function() { array.removeRangeObserver(rangeObserver); observer.callCount = 0 ; array.replace(2, 1, T.objects(1)); equals(observer.callCount, 0, 'observer should not fire'); observer.callCount = 0 ; array.replace(0, 1, T.objects(1)); equals(observer.callCount, 0, 'observer should not fire'); observer.callCount = 0 ; array.replace(9, 1, T.objects(1)); equals(observer.callCount, 0, 'observer should not fire'); }); // .......................................................... // GROUPED CHANGES // test("grouping property changes should notify observer only once at end with single IndexSet", function() { array.beginPropertyChanges(); array.replace(2, 1, T.objects(1)); array.replace(4, 1, T.objects(1)); array.endPropertyChanges(); var set = SC.IndexSet.create().add(2).add(4); // both edits observer.expectRangeChange(array, null, '[]', set); }); // .......................................................... // INSERTING // test("insertAt in range fires observer with index set covering edit to end of array", function() { var newItem = T.objects(1)[0], set = SC.IndexSet.create(3,array.get('length')-2); array.insertAt(3, newItem); observer.expectRangeChange(array, null, '[]', set); }); test("adding object fires observer", function() { var newItem = T.objects(1)[0]; var set = SC.IndexSet.create(array.get('length')); array.pushObject(newItem); observer.expectRangeChange(array, null, '[]', set); }); // .......................................................... // REMOVING // test("removeAt fires observer with index set covering edit to end of array", function() { var set = SC.IndexSet.create(3,array.get('length')-3); array.removeAt(3); observer.expectRangeChange(array, null, '[]', set); }); test("popObject fires observer with index set covering removed range", function() { var set = SC.IndexSet.create(array.get('length')-1); array.popObject(); observer.expectRangeChange(array, null, '[]', set); }); // .......................................................... // MODULE: isDeep = NO // module(T.desc("RangeObserver Methods - isDeep NO"), { setup: function() { expected = T.objects(10); array = T.newObject(expected); observer = T.observer(); rangeObserver = array.addRangeObserver(SC.IndexSet.create(2,3), observer, observer.rangeDidChange, null, NO); }, teardown: function() { T.destroyObject(array); } }); test("editing property on object at any point should not fire observer", function() { var indexes = [0,3,9], loc = 3, obj,idx; while(--loc>=0) { idx = indexes[loc]; obj = array.objectAt(idx); obj.set('foo', 'BAR'); equals(observer.callCount, 0, 'observer should not fire when editing object at index %@'.fmt(idx)); } }); test("replacing object in range fires observer with index set", function() { array.replace(2, 1, T.objects(1)); observer.expectRangeChange(array, null, '[]', SC.IndexSet.create(2,1)); }); });