1 // ==========================================================================
  2 // Project:   SproutCore Costello - Property Observing Library
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 /*globals module test ok equals same CoreTest */
  9 
 10 sc_require('debug/test_suites/array/base');
 11 
 12 SC.ArraySuite.define(function(T) {
 13 
 14   var expected, array, observer, rangeObserver ;
 15 
 16   // ..........................................................
 17   // MODULE: isDeep = YES
 18   //
 19   module(T.desc("RangeObserver Methods"), {
 20     setup: function() {
 21       expected = T.objects(10);
 22       array = T.newObject(expected);
 23 
 24       observer = T.observer();
 25       rangeObserver = array.addRangeObserver(SC.IndexSet.create(2,3),
 26                 observer, observer.rangeDidChange, null, NO);
 27 
 28     },
 29 
 30     teardown: function() {
 31       T.destroyObject(array);
 32     }
 33   });
 34 
 35   test("returns RangeObserver object", function() {
 36     ok(rangeObserver && rangeObserver.isRangeObserver, 'returns a range observer object');
 37   });
 38 
 39   // NOTE: Deep Property Observing is disabled for SproutCore 1.0
 40   //
 41   // // ..........................................................
 42   // // EDIT PROPERTIES
 43   // //
 44   //
 45   // test("editing property on object in range should fire observer", function() {
 46   //   var obj = array.objectAt(3);
 47   //   obj.set('foo', 'BAR');
 48   //   observer.expectRangeChange(array, obj, 'foo', SC.IndexSet.create(3));
 49   // });
 50   //
 51   // test("editing property on object outside of range should NOT fire observer", function() {
 52   //   var obj = array.objectAt(0);
 53   //   obj.set('foo', 'BAR');
 54   //   equals(observer.callCount, 0, 'observer should not fire');
 55   // });
 56   //
 57   //
 58   // test("updating property after changing observer range", function() {
 59   //   array.updateRangeObserver(rangeObserver, SC.IndexSet.create(8,2));
 60   //   observer.callCount = 0 ;// reset b/c callback should happen here
 61   //
 62   //   var obj = array.objectAt(3);
 63   //   obj.set('foo', 'BAR');
 64   //   equals(observer.callCount, 0, 'modifying object in old range should not fire observer');
 65   //
 66   //   obj = array.objectAt(9);
 67   //   obj.set('foo', 'BAR');
 68   //   observer.expectRangeChange(array, obj, 'foo', SC.IndexSet.create(9));
 69   //
 70   // });
 71   //
 72   // test("updating a property after removing an range should not longer update", function() {
 73   //   array.removeRangeObserver(rangeObserver);
 74   //
 75   //   observer.callCount = 0 ;// reset b/c callback should happen here
 76   //
 77   //   var obj = array.objectAt(3);
 78   //   obj.set('foo', 'BAR');
 79   //   equals(observer.callCount, 0, 'modifying object in old range should not fire observer');
 80   //
 81   // });
 82 
 83   // ..........................................................
 84   // REPLACE
 85   //
 86 
 87   test("replacing object in range fires observer with index set covering only the effected item", function() {
 88     array.replace(2, 1, T.objects(1));
 89     observer.expectRangeChange(array, null, '[]', SC.IndexSet.create(2,1));
 90   });
 91 
 92   test("replacing object before range", function() {
 93     array.replace(0, 1, T.objects(1));
 94     equals(observer.callCount, 0, 'observer should not fire');
 95   });
 96 
 97   test("replacing object after range", function() {
 98     array.replace(9, 1, T.objects(1));
 99     equals(observer.callCount, 0, 'observer should not fire');
100   });
101 
102   test("updating range should be reflected by replace operations", function() {
103     array.updateRangeObserver(rangeObserver, SC.IndexSet.create(9,1));
104 
105     observer.callCount = 0 ;
106     array.replace(2, 1, T.objects(1));
107     equals(observer.callCount, 0, 'observer should not fire');
108 
109     observer.callCount = 0 ;
110     array.replace(0, 1, T.objects(1));
111     equals(observer.callCount, 0, 'observer should not fire');
112 
113     observer.callCount = 0 ;
114     array.replace(9, 1, T.objects(1));
115     observer.expectRangeChange(array, null, '[]', SC.IndexSet.create(9));
116   });
117 
118   test("removing range should no longer fire observers", function() {
119     array.removeRangeObserver(rangeObserver);
120 
121     observer.callCount = 0 ;
122     array.replace(2, 1, T.objects(1));
123     equals(observer.callCount, 0, 'observer should not fire');
124 
125     observer.callCount = 0 ;
126     array.replace(0, 1, T.objects(1));
127     equals(observer.callCount, 0, 'observer should not fire');
128 
129     observer.callCount = 0 ;
130     array.replace(9, 1, T.objects(1));
131     equals(observer.callCount, 0, 'observer should not fire');
132   });
133 
134   // ..........................................................
135   // GROUPED CHANGES
136   //
137 
138   test("grouping property changes should notify observer only once at end with single IndexSet", function() {
139 
140     array.beginPropertyChanges();
141     array.replace(2, 1, T.objects(1));
142     array.replace(4, 1, T.objects(1));
143     array.endPropertyChanges();
144 
145     var set = SC.IndexSet.create().add(2).add(4); // both edits
146     observer.expectRangeChange(array, null, '[]', set);
147   });
148 
149   test("should notify observer when some but not all grouped changes are inside range", function() {
150 
151     array.beginPropertyChanges();
152     array.replace(2, 1, T.objects(1));
153     array.replace(9, 1, T.objects(1));
154     array.endPropertyChanges();
155 
156     var set = SC.IndexSet.create().add(2).add(9); // both edits
157     observer.expectRangeChange(array, null, '[]', set);
158   });
159 
160   test("should NOT notify observer when grouping changes all outside of observer", function() {
161 
162     array.beginPropertyChanges();
163     array.replace(0, 1, T.objects(1));
164     array.replace(9, 1, T.objects(1));
165     array.endPropertyChanges();
166 
167     equals(observer.callCount, 0, 'observer should not fire');
168   });
169 
170   // ..........................................................
171   // INSERTING
172   //
173 
174   test("insertAt in range fires observer with index set covering edit to end of array", function() {
175     var newItem = T.objects(1)[0],
176         set     = SC.IndexSet.create(3,array.get('length')-2);
177 
178     array.insertAt(3, newItem);
179     observer.expectRangeChange(array, null, '[]', set);
180   });
181 
182   test("insertAt BEFORE range fires observer with index set covering edit to end of array", function() {
183     var newItem = T.objects(1)[0],
184         set     = SC.IndexSet.create(0,array.get('length')+1);
185 
186     array.insertAt(0, newItem);
187     observer.expectRangeChange(array, null, '[]', set);
188   });
189 
190   test("insertAt AFTER range does not fire observer", function() {
191     var newItem = T.objects(1)[0];
192 
193     array.insertAt(9, newItem);
194     equals(observer.callCount, 0, 'observer should not fire');
195   });
196 
197   // ..........................................................
198   // REMOVING
199   //
200 
201   test("removeAt IN range fires observer with index set covering edit to end of array plus delta", function() {
202     var set     = SC.IndexSet.create(3,array.get('length')-3);
203     array.removeAt(3);
204     observer.expectRangeChange(array, null, '[]', set);
205   });
206 
207   test("removeAt BEFORE range fires observer with index set covering edit to end of array plus delta", function() {
208     var set     = SC.IndexSet.create(0,array.get('length'));
209     array.removeAt(0);
210     observer.expectRangeChange(array, null, '[]', set);
211   });
212 
213   test("removeAt AFTER range does not fire observer", function() {
214     array.removeAt(9);
215     equals(observer.callCount, 0, 'observer should not fire');
216   });
217 
218 
219 
220 
221   // ..........................................................
222   // MODULE: No explicit range
223   //
224   module(T.desc("RangeObserver Methods - No explicit range"), {
225     setup: function() {
226       expected = T.objects(10);
227       array = T.newObject(expected);
228 
229       observer = T.observer();
230       rangeObserver = array.addRangeObserver(null, observer,
231                           observer.rangeDidChange, null, NO);
232 
233     },
234 
235     teardown: function() {
236       T.destroyObject(array);
237     }
238   });
239 
240   test("returns RangeObserver object", function() {
241     ok(rangeObserver && rangeObserver.isRangeObserver, 'returns a range observer object');
242   });
243 
244   // ..........................................................
245   // REPLACE
246   //
247 
248   test("replacing object in range fires observer with index set covering only the effected item", function() {
249     array.replace(2, 1, T.objects(1));
250     observer.expectRangeChange(array, null, '[]', SC.IndexSet.create(2,1));
251   });
252 
253   test("replacing at start of array", function() {
254     array.replace(0, 1, T.objects(1));
255     observer.expectRangeChange(array, null, '[]', SC.IndexSet.create(0,1));
256   });
257 
258   test("replacing object at end of array", function() {
259     array.replace(9, 1, T.objects(1));
260     observer.expectRangeChange(array, null, '[]', SC.IndexSet.create(9,1));
261   });
262 
263   test("removing range should no longer fire observers", function() {
264     array.removeRangeObserver(rangeObserver);
265 
266     observer.callCount = 0 ;
267     array.replace(2, 1, T.objects(1));
268     equals(observer.callCount, 0, 'observer should not fire');
269 
270     observer.callCount = 0 ;
271     array.replace(0, 1, T.objects(1));
272     equals(observer.callCount, 0, 'observer should not fire');
273 
274     observer.callCount = 0 ;
275     array.replace(9, 1, T.objects(1));
276     equals(observer.callCount, 0, 'observer should not fire');
277   });
278 
279   // ..........................................................
280   // GROUPED CHANGES
281   //
282 
283   test("grouping property changes should notify observer only once at end with single IndexSet", function() {
284 
285     array.beginPropertyChanges();
286     array.replace(2, 1, T.objects(1));
287     array.replace(4, 1, T.objects(1));
288     array.endPropertyChanges();
289 
290     var set = SC.IndexSet.create().add(2).add(4); // both edits
291     observer.expectRangeChange(array, null, '[]', set);
292   });
293 
294   // ..........................................................
295   // INSERTING
296   //
297 
298   test("insertAt in range fires observer with index set covering edit to end of array", function() {
299     var newItem = T.objects(1)[0],
300         set     = SC.IndexSet.create(3,array.get('length')-2);
301 
302     array.insertAt(3, newItem);
303     observer.expectRangeChange(array, null, '[]', set);
304   });
305 
306   test("adding object fires observer", function() {
307     var newItem = T.objects(1)[0];
308     var set = SC.IndexSet.create(array.get('length'));
309 
310     array.pushObject(newItem);
311     observer.expectRangeChange(array, null, '[]', set);
312   });
313 
314   // ..........................................................
315   // REMOVING
316   //
317 
318   test("removeAt fires observer with index set covering edit to end of array", function() {
319     var set     = SC.IndexSet.create(3,array.get('length')-3);
320     array.removeAt(3);
321     observer.expectRangeChange(array, null, '[]', set);
322   });
323 
324   test("popObject fires observer with index set covering removed range", function() {
325     var set = SC.IndexSet.create(array.get('length')-1);
326     array.popObject();
327     observer.expectRangeChange(array, null, '[]', set);
328   });
329 
330 
331   // ..........................................................
332   // MODULE: isDeep = NO
333   //
334   module(T.desc("RangeObserver Methods - isDeep NO"), {
335     setup: function() {
336       expected = T.objects(10);
337       array = T.newObject(expected);
338 
339       observer = T.observer();
340       rangeObserver = array.addRangeObserver(SC.IndexSet.create(2,3),
341                 observer, observer.rangeDidChange, null, NO);
342 
343     },
344 
345     teardown: function() {
346       T.destroyObject(array);
347     }
348   });
349 
350   test("editing property on object at any point should not fire observer", function() {
351 
352     var indexes = [0,3,9],
353         loc     = 3,
354         obj,idx;
355 
356     while(--loc>=0) {
357       idx = indexes[loc];
358       obj = array.objectAt(idx);
359       obj.set('foo', 'BAR');
360       equals(observer.callCount, 0, 'observer should not fire when editing object at index %@'.fmt(idx));
361     }
362   });
363 
364   test("replacing object in range fires observer with index set", function() {
365     array.replace(2, 1, T.objects(1));
366     observer.expectRangeChange(array, null, '[]', SC.IndexSet.create(2,1));
367   });
368 
369 
370 });
371 
372