UNPKG

15.6 kBJavaScriptView Raw
1var test = require('tape');
2var mixins = require('ampersand-collection-underscore-mixin');
3var Collection = require('ampersand-collection').extend(mixins);
4var SubCollection = require('../ampersand-subcollection');
5var Model = require('ampersand-state');
6var _ = require('underscore');
7
8
9// our widget model
10var Widget = Model.extend({
11 props: {
12 id: 'number',
13 name: 'string',
14 awesomeness: 'number',
15 sweet: 'boolean'
16 }
17});
18
19// our base collection
20var Widgets = Collection.extend(mixins, {
21 model: Widget,
22 comparator: 'awesomeness'
23});
24
25// helper for getting a base collection
26function getBaseCollection() {
27 var widgets = new Widgets();
28
29 // add a hundred items to our base collection
30 var items = 100;
31 while (items--) {
32 widgets.add({
33 id: items,
34 name: 'abcdefghij'.split('')[items % 10],
35 awesomeness: (items % 10),
36 sweet: (items % 2 === 0)
37 });
38 }
39 return widgets;
40}
41
42test('basic init, length', function (t) {
43 var base = getBaseCollection();
44 var sub = new SubCollection(base);
45 t.equal(sub.length, 100);
46 t.end();
47});
48
49test('it should report as being a collection', function (t) {
50 var base = getBaseCollection();
51 var sub = new SubCollection(base);
52 t.ok(sub.isCollection);
53
54 sub.isCollection = false;
55 t.ok(sub.isCollection);
56 t.end();
57});
58
59test('basic `where` filtering', function (t) {
60 var base = getBaseCollection();
61 var sub = new SubCollection(base, {
62 where: {
63 sweet: true
64 }
65 });
66 t.equal(sub.length, 50);
67 t.end();
68});
69
70test('add a filter after init', function (t) {
71 var base = getBaseCollection();
72 var sub = new SubCollection(base);
73 sub.addFilter(function (model) {
74 return model.awesomeness === 5;
75 });
76
77 t.equal(sub.length, 10);
78 t.end();
79});
80
81test('remove a filter', function (t) {
82 var base = getBaseCollection();
83 var sub = new SubCollection(base);
84 var isPartiallyAwesome = function (model) {
85 return model.awesomeness === 5;
86 };
87
88 sub.addFilter(isPartiallyAwesome);
89 t.equal(sub.length, 10);
90
91 sub.removeFilter(isPartiallyAwesome);
92 t.equal(sub.length, 100);
93
94 t.end();
95});
96
97test('swap filters', function (t) {
98 var base = getBaseCollection();
99 var sub = new SubCollection(base);
100 var isPartiallyAwesome = function (model) {
101 return model.awesomeness === 5;
102 };
103 var isSweet = function (model) {
104 return model.sweet;
105 };
106
107 sub.addFilter(isPartiallyAwesome);
108 t.equal(sub.length, 10);
109
110 sub.swapFilters(isSweet, isPartiallyAwesome);
111 t.equal(sub.length, 50);
112
113 t.end();
114});
115
116test('swap all filters', function (t) {
117 var base = getBaseCollection();
118 var sub = new SubCollection(base);
119 var isPartiallyAwesome = function (model) {
120 return model.awesomeness === 5;
121 };
122 var isSweet = function (model) {
123 return model.sweet;
124 };
125
126 sub.addFilter(isPartiallyAwesome);
127 t.equal(sub.length, 10);
128
129 sub.swapFilters(isSweet);
130 t.equal(sub.length, 50);
131
132 t.end();
133});
134
135test('function based filtering', function (t) {
136 var base = getBaseCollection();
137 var sub = new SubCollection(base, {
138 filter: function (model) {
139 return model.awesomeness > 5;
140 }
141 });
142 t.ok(sub.length > 0, 'should have some that match');
143 t.ok(sub.length < 100, 'but not all');
144 t.end();
145});
146
147test('multiple filter functions', function (t) {
148 var base = getBaseCollection();
149 var sub = new SubCollection(base, {
150 filters: [
151 function (model) {
152 return model.awesomeness > 5;
153 },
154 function (model) {
155 return model.name === 'j';
156 }
157 ]
158 });
159 t.equal(sub.length, 10);
160 t.end();
161});
162
163test('mixed filter and `where`', function (t) {
164 var base = getBaseCollection();
165 var sub = new SubCollection(base, {
166 filter: function (model) {
167 return model.awesomeness > 5;
168 },
169 where: {
170 name: 'j'
171 }
172 });
173 t.equal(sub.length, 10);
174 t.end();
175});
176
177test('should sort independent of base', function (t) {
178 var base = getBaseCollection();
179 var sub = new SubCollection(base, {
180 comparator: 'id'
181 });
182 t.equal(sub.length, 100);
183 t.notEqual(sub.at(0), base.at(0));
184 t.end();
185});
186
187test('should be able to specify/update offset and limit', function (t) {
188 var base = getBaseCollection();
189 var sub = new SubCollection(base, {
190 comparator: 'id',
191 limit: 10
192 });
193 t.equal(sub.length, 10);
194 t.equal(sub.at(0).id, 0);
195 sub.configure({limit: 5});
196 t.equal(sub.length, 5);
197 sub.configure({offset: 5});
198 t.equal(sub.at(0).id, 5);
199 sub.configure({offset: null});
200 sub.configure({limit: null});
201 t.equal(sub.length, 100);
202 t.end();
203});
204
205test('should fire `add` events only if removed items match filter', function (t) {
206 t.plan(1);
207 var base = getBaseCollection();
208 var sub = new SubCollection(base, {
209 filter: function (model) {
210 return model.awesomeness > 5;
211 }
212 });
213 var awesomeWidget = new Widget({
214 name: 'awesome',
215 id: 999,
216 awesomeness: 11,
217 sweet: true
218 });
219 var lameWidget = new Widget({
220 name: 'lame',
221 id: 1000,
222 awesomeness: 0,
223 sweet: false
224 });
225 sub.on('add', function (model) {
226 t.equal(model, awesomeWidget);
227 t.end();
228 });
229 base.add([lameWidget, awesomeWidget]);
230});
231
232test('should fire `remove` events only if removed items match filter', function (t) {
233 t.plan(3);
234 var base = getBaseCollection();
235 var sub = new SubCollection(base, {
236 filter: function (model) {
237 return model.awesomeness > 5;
238 }
239 });
240 // grab a lame widget
241 var lameWidget = base.find(function (model) {
242 return model.awesomeness < 5;
243 });
244 // grab an awesome widget
245 var awesomeWidget = base.find(function (model) {
246 return model.awesomeness > 5;
247 });
248 sub.on('remove', function (model) {
249 t.equal(model, awesomeWidget);
250 t.end();
251 });
252 t.ok(awesomeWidget);
253 t.ok(lameWidget);
254 base.remove([lameWidget, awesomeWidget]);
255});
256
257test('should fire `add` and `remove` events after models are updated', function (t) {
258 t.plan(2);
259 var base = getBaseCollection();
260 var sub = new SubCollection(base);
261 var awesomeWidget = new Widget({
262 name: 'awesome',
263 id: 999,
264 awesomeness: 11,
265 sweet: true
266 });
267 sub.on('add', function () {
268 t.equal(sub.models.length, 101);
269 });
270 sub.on('remove', function () {
271 t.equal(sub.models.length, 100);
272 t.end();
273 });
274 base.add(awesomeWidget);
275 base.remove(awesomeWidget);
276});
277
278test('make sure changes to `where` properties are reflected in sub collections', function (t) {
279 t.plan(3);
280 var base = getBaseCollection();
281 var sub = new SubCollection(base, {
282 where: {
283 sweet: true
284 }
285 });
286 var firstSweet = sub.first();
287 sub.on('remove', function (model) {
288 t.equal(model, firstSweet);
289 t.equal(firstSweet.sweet, false);
290 t.end();
291 });
292 t.ok(firstSweet);
293 firstSweet.sweet = false;
294});
295
296test('should be able to `get` a model by id or other index', function (t) {
297 var base = getBaseCollection();
298 var sub = new SubCollection(base, {
299 where: {
300 sweet: true
301 }
302 });
303 var cool = sub.first();
304 var lame = base.find(function (model) {
305 return model.sweet === false;
306 });
307 // sanity checks
308 t.ok(cool.sweet);
309 t.notOk(lame.sweet);
310 t.ok(sub.models.indexOf(lame) === -1);
311 t.ok(base.models.indexOf(lame) !== -1);
312 t.notEqual(sub.get(lame.id), lame);
313 t.equal(sub.get(cool.id), cool);
314 t.end();
315});
316
317test('should be able to listen for `change:x` events on subcollection', function (t) {
318 t.plan(1);
319 var base = getBaseCollection();
320 var sub = new SubCollection(base, {
321 where: {
322 sweet: true
323 }
324 });
325 sub.on('change:name', function () {
326 t.pass('handler called');
327 t.end();
328 });
329 var cool = sub.first();
330 cool.name = 'new name';
331});
332
333test('should be able to listen for general `change` events on subcollection', function (t) {
334 t.plan(1);
335 var base = getBaseCollection();
336 var sub = new SubCollection(base, {
337 where: {
338 sweet: true
339 }
340 });
341 sub.on('change', function () {
342 t.pass('handler called');
343 t.end();
344 });
345 var cool = sub.first();
346 cool.name = 'new name';
347});
348
349test('have the correct ordering saved when processing a sort event', function (t) {
350 t.plan(3);
351 var base = getBaseCollection();
352 var sub = new SubCollection(base, {
353 where: {
354 sweet: true
355 },
356 comparator: 'name'
357 });
358
359 var third = sub.at(42);
360
361 third.sweet = false;
362
363 t.notEqual(third.id, sub.at(42).id);
364 t.notOk(sub.get(third.id));
365
366 sub.on('sort', function () {
367 t.equal(third.id, sub.at(42).id);
368 });
369
370 third.sweet = true;
371});
372
373test('reset works correctly/efficiently when passed to configure', function (t) {
374 var base = getBaseCollection();
375 var sub = new SubCollection(base, {
376 where: {
377 sweet: true
378 }
379 });
380 var itemsRemoved = [];
381 var itemsAdded = [];
382
383 t.equal(sub.length, 50, 'should be 50 that match initial filter');
384
385 sub.on('remove', function (model) {
386 itemsRemoved.push(model);
387 });
388 sub.on('add', function (model) {
389 itemsAdded.push(model);
390 });
391
392 var oldResetFilters = sub._resetFilters;
393 var resetCalls = [];
394 sub._resetFilters = function (resetComparator) {
395 resetCalls.push(_.toArray(arguments));
396 oldResetFilters.call(this, resetComparator);
397 };
398
399 sub.configure({
400 where: {
401 sweet: true,
402 awesomeness: 6
403 }
404 }, true);
405
406 t.same(resetCalls[0], [true], 'configure calls _resetFilters(true) when reset is true');
407 sub._resetFilters = oldResetFilters;
408
409 t.equal(sub.length, 10, 'should be 10 that match second filter');
410 t.equal(itemsRemoved.length, 40, '10 of the items should have been removed');
411 t.equal(itemsAdded.length, 0, 'nothing should have been added');
412 t.equal(sub.comparator, void 0, 'comparator is reset');
413
414 t.ok(_.every(itemsRemoved, function (item) {
415 return item.sweet === true && item.awesomeness !== 6;
416 }), 'every item removed should have awesomeness not 6 and be sweet: true');
417
418 t.end();
419});
420
421test('_watched contains members of spec.watched but is not spec.watched', function (t) {
422 t.plan(2);
423 var watched = ['name', 'awesomeness'];
424 var comparator = 'sweet';
425 var base = getBaseCollection();
426 var sub = new SubCollection(base, {
427 limit: 10,
428 comparator: comparator,
429 watched: watched,
430 filter: function (item) {
431 return item.awesomeness > 5 || item.name > 'd';
432 }
433 });
434
435 t.notEqual(sub._watched, watched, '_watched should not be the same array as spec.watched');
436 t.same(sub._watched, watched, '_watched should contain spec.watched members');
437 t.end();
438});
439
440test('_resetFilters', function (t) {
441 t.plan(6);
442 var comparator = 'sweet';
443 var where = {
444 awesomeness: 5,
445 name: 'b'
446 };
447 var base = getBaseCollection();
448 var sub = new SubCollection(base, {
449 comparator: comparator,
450 where: where,
451 watched: ['id']
452 });
453
454 t.same(sub._watched.sort(), _.keys(where).concat('id').sort(), '_watched should contain spec.watched members');
455
456 sub._resetFilters();
457
458 t.same(sub._watched, [], '_resetFilters() empties _watched');
459 t.same(sub._filters, [], '_resetFilters() empties _filters');
460 t.same([sub.offset, sub.limit], [void 0, void 0], '_resetFilters() unsets offset and limit');
461 t.equal(sub.comparator, comparator, '_resetFilters() does NOT reset comparator');
462
463 sub._resetFilters(true);
464
465 t.equal(sub.comparator, void 0, '_resetFilters(true) resets comparator to undefined');
466
467 t.end();
468});
469
470test('string comparator causes _runFilters to be called when comparator prop changes on models', function (t) {
471 t.plan(1);
472 var comparator = 'sweet';
473 var where = {
474 awesomeness: 5,
475 name: 'b'
476 };
477 var base = getBaseCollection();
478 var sub = new SubCollection(base, {
479 comparator: comparator,
480 where: where
481 });
482 var filtersRan = 0;
483 sub._runFilters = function () { filtersRan++; };
484 sub.models.forEach(function (model) { model.sweet = !model.sweet; });
485 t.equal(filtersRan, sub.models.length, '_runFilters called once for each model');
486 t.end();
487});
488
489test('reset', function (t) {
490 var base = getBaseCollection();
491 var sub = new SubCollection(base, {
492 where: {
493 sweet: true
494 },
495 comparator: 'id',
496 filter: function (model) {
497 return model.awesomeness === 6;
498 }
499 });
500
501 var itemsRemoved = [];
502 var itemsAdded = [];
503 var sortTriggered = 0;
504
505 t.equal(sub.length, 10, 'should be 10 that match initial filters');
506
507 sub.on('remove', function (model) {
508 itemsRemoved.push(model);
509 });
510 sub.on('add', function (model) {
511 itemsAdded.push(model);
512 });
513 sub.on('sort', function () {
514 sortTriggered++;
515 });
516
517 sub.reset();
518
519 t.equal(sub.length, base.length, 'should be same as base length');
520 t.equal(itemsAdded.length, 90, '90 should have been added back');
521 t.equal(itemsRemoved.length, 0, '0 should have been removed');
522
523 t.deepEqual(sub._watched, [], 'should not be watching any properties');
524 t.equal(sub.comparator, void 0, 'comparator should be undefined');
525 t.equal(sortTriggered, 1, 'should have triggered a `sort`');
526
527 t.deepEqual(_.pluck(sub.models, 'id'), base.pluck('id'));
528
529 t.ok(_.every(itemsAdded, function (item) {
530 return item.sweet !== true || item.awesomeness !== 6;
531 }), 'every item added back should either not be sweet or have awesomness of 6');
532
533 t.end();
534});
535
536test('clear filters', function (t) {
537 var base = getBaseCollection();
538 var sub = new SubCollection(base, {
539 where: {
540 sweet: true
541 },
542 comparator: 'id',
543 filter: function (model) {
544 return model.awesomeness === 6;
545 }
546 });
547
548 var itemsRemoved = [];
549 var itemsAdded = [];
550 var sortTriggered = 0;
551
552 t.equal(sub.length, 10, 'should be 10 that match initial filters');
553
554 sub.on('remove', function (model) {
555 itemsRemoved.push(model);
556 });
557 sub.on('add', function (model) {
558 itemsAdded.push(model);
559 });
560 sub.on('sort', function () {
561 sortTriggered++;
562 });
563
564 sub.clearFilters();
565
566 t.equal(sub.length, base.length, 'should be same as base length');
567 t.equal(itemsAdded.length, 90, '90 should have been added back');
568 t.equal(itemsRemoved.length, 0, '0 should have been removed');
569
570 t.deepEqual(sub._watched, [], 'should not be watching any properties');
571 t.equal(sub.comparator, 'id', 'should still have comparator');
572 t.equal(sortTriggered, 1, 'should trigger `sort` for renderers sake');
573
574 t.ok(_.every(itemsAdded, function (item) {
575 return item.sweet !== true || item.awesomeness !== 6;
576 }), 'every item added back should either not be sweet or have awesomness of 6');
577
578 t.end();
579});