1 |
|
2 | var test = require('tape');
|
3 | var AmpCollection = require('ampersand-collection');
|
4 | var AmpModel = require('ampersand-model');
|
5 | var AmpView = require('ampersand-view');
|
6 | var CollectionView = require('../ampersand-collection-view');
|
7 |
|
8 |
|
9 |
|
10 | var data = [
|
11 | {id: 1, name: 'mary', age: 20},
|
12 | {id: 2, name: 'sue', age: 25},
|
13 | {id: 3, name: 'dave', age: 30}
|
14 | ];
|
15 |
|
16 |
|
17 | var Person = AmpModel.extend({
|
18 | props: {id: 'number', name: 'string', age: 'number'},
|
19 | derived: {
|
20 | isStillYoung: {
|
21 | deps: ['age'],
|
22 | fn: function () {
|
23 | return this.age <= 20;
|
24 | }
|
25 | }
|
26 | }
|
27 | });
|
28 |
|
29 |
|
30 | var Collection = AmpCollection.extend({
|
31 | model: Person,
|
32 | last: function () {
|
33 | return this.models[this.models.length - 1];
|
34 | },
|
35 | first: function () {
|
36 | return this.models[0];
|
37 | }
|
38 | });
|
39 |
|
40 | var ItemView = AmpView.extend({
|
41 | template: '<div></div>',
|
42 | bindings: {
|
43 | 'model.name': ''
|
44 | },
|
45 | render: function () {
|
46 | this.renderWithTemplate();
|
47 | this.el.id = '_' + this.model.id;
|
48 | return this;
|
49 | }
|
50 | });
|
51 |
|
52 | var AlternativeItemView = AmpView.extend({
|
53 | template: '<span></span>',
|
54 | bindings: {
|
55 | 'model.name': ''
|
56 | }
|
57 | });
|
58 |
|
59 | var MainView = AmpView.extend({
|
60 | initialize: function () {
|
61 | this.el = document.createElement('div');
|
62 | this.el.id = 'container';
|
63 | this.collection = new Collection(data);
|
64 | },
|
65 | render: function (opts) {
|
66 | this.el.innerHTML = '<ul></ul>';
|
67 | this.renderCollection(this.collection, ItemView, this.query('ul'), opts);
|
68 | return this;
|
69 | }
|
70 | });
|
71 |
|
72 | function getRendered(view) {
|
73 | return Array.prototype.slice.call(view.el.querySelectorAll('div'));
|
74 | }
|
75 |
|
76 | function numberRendered(view) {
|
77 | return getRendered(view).length;
|
78 | }
|
79 |
|
80 | test('should render all when calling `render`', function (t) {
|
81 | var coll = new Collection(data);
|
82 | var div = document.createElement('div');
|
83 | var cv = new CollectionView({
|
84 | el: div,
|
85 | collection: coll,
|
86 | view: ItemView
|
87 | });
|
88 | t.equal(cv.el.innerHTML, '');
|
89 | cv.render();
|
90 | t.equal(cv.el.innerHTML, '<div id="_1">mary</div><div id="_2">sue</div><div id="_3">dave</div>');
|
91 | t.end();
|
92 | });
|
93 |
|
94 | test('should render different view based on a given expression', function (t) {
|
95 | var coll = new Collection(data);
|
96 | var div = document.createElement('div');
|
97 | var cv = new CollectionView({
|
98 | el: div,
|
99 | collection: coll,
|
100 | view: function (options) {
|
101 | if (options.model.isStillYoung) {
|
102 | return new AlternativeItemView(options);
|
103 | }
|
104 |
|
105 | return new ItemView(options);
|
106 | }
|
107 | });
|
108 | t.equal(cv.el.innerHTML, '');
|
109 | cv.render();
|
110 | t.equal(cv.el.innerHTML, '<span>mary</span><div id="_2">sue</div><div id="_3">dave</div>');
|
111 | t.end();
|
112 | });
|
113 |
|
114 | test('should call `remove` on view corresponding to removed model', function (t) {
|
115 | var coll = new Collection(data);
|
116 | var div = document.createElement('div');
|
117 | var cv = new CollectionView({
|
118 | el: div,
|
119 | collection: coll,
|
120 | view: ItemView
|
121 | });
|
122 | var count = 0;
|
123 | cv.render();
|
124 | t.equal(cv.views.length, 3);
|
125 | var firstView = cv.views[0];
|
126 | firstView.remove = function () {
|
127 | count++;
|
128 | };
|
129 | coll.remove(coll.at(0));
|
130 | t.equal(cv.views.length, 2);
|
131 | t.equal(count, 1, 'remove should have been called once');
|
132 | t.end();
|
133 | });
|
134 |
|
135 | test('adding to collection should work', function (t) {
|
136 | var coll = new Collection(data);
|
137 | var div = document.createElement('div');
|
138 | var cv = new CollectionView({
|
139 | el: div,
|
140 | collection: coll,
|
141 | view: ItemView
|
142 | });
|
143 | cv.render();
|
144 | var firstView = cv.views[0];
|
145 | var firstEl = firstView && firstView.el;
|
146 |
|
147 | coll.add({name: 'henrik', id: 4});
|
148 | t.equal(cv.views.length, 4);
|
149 | t.equal(cv.el.innerHTML, '<div id="_1">mary</div><div id="_2">sue</div><div id="_3">dave</div><div id="_4">henrik</div>');
|
150 | t.equal(cv.views[0], firstView);
|
151 | t.equal(cv.views[0].el, firstEl);
|
152 |
|
153 | t.end();
|
154 | });
|
155 |
|
156 | test('adding at specific index should work', function (t) {
|
157 | var coll = new Collection(data);
|
158 | var div = document.createElement('div');
|
159 | var cv = new CollectionView({
|
160 | el: div,
|
161 | collection: coll,
|
162 | view: ItemView
|
163 | });
|
164 | cv.render();
|
165 |
|
166 | coll.add({name: 'henrik', id: 4}, { at: 1 });
|
167 | t.equal(cv.el.innerHTML, '<div id="_1">mary</div><div id="_4">henrik</div><div id="_2">sue</div><div id="_3">dave</div>');
|
168 |
|
169 | t.end();
|
170 | });
|
171 |
|
172 | test('add', function (t) {
|
173 | var coll = new Collection(data);
|
174 | var div = document.createElement('div');
|
175 | var view = new CollectionView({
|
176 | el: div,
|
177 | collection: coll,
|
178 | view: ItemView
|
179 | });
|
180 | view.render();
|
181 | view.collection.add({id: 6});
|
182 | t.equal(numberRendered(view), view.collection.length);
|
183 | t.end();
|
184 | });
|
185 |
|
186 | test('remove', function (t) {
|
187 | var coll = new Collection(data);
|
188 | var div = document.createElement('div');
|
189 | var view = new CollectionView({
|
190 | el: div,
|
191 | collection: coll,
|
192 | view: ItemView
|
193 | });
|
194 | view.render();
|
195 | view.collection.remove(view.collection.models[view.collection.models.length - 1]);
|
196 | t.equal(numberRendered(view), view.collection.length);
|
197 | t.end();
|
198 | });
|
199 |
|
200 | test('reset', function (t) {
|
201 | var coll = new Collection(data);
|
202 | var div = document.createElement('div');
|
203 | var view = new CollectionView({
|
204 | el: div,
|
205 | collection: coll,
|
206 | view: ItemView
|
207 | });
|
208 | view.render();
|
209 | t.equal(numberRendered(view), 3);
|
210 |
|
211 | view.collection.reset();
|
212 | t.equal(numberRendered(view), view.collection.length);
|
213 | t.equal(numberRendered(view), 0);
|
214 |
|
215 | view.collection.reset([
|
216 | {id: 10, name: 'mary', age: 20},
|
217 | ]);
|
218 | t.equal(view.collection.length, 1);
|
219 | t.equal(view.views.length, 1);
|
220 | t.equal(numberRendered(view), 1);
|
221 |
|
222 | t.end();
|
223 | });
|
224 |
|
225 | test('sort and resort', function (t) {
|
226 | var domIds = [];
|
227 | var coll = new Collection(data);
|
228 | var div = document.createElement('div');
|
229 | var view = new CollectionView({
|
230 | el: div,
|
231 | collection: coll,
|
232 | view: ItemView
|
233 | });
|
234 | view.render();
|
235 |
|
236 |
|
237 | view.collection.comparator = function (model) {
|
238 | return model.get('name');
|
239 | };
|
240 | view.collection.sort();
|
241 |
|
242 |
|
243 | t.equal(numberRendered(view), view.collection.length);
|
244 | domIds = [];
|
245 | getRendered(view).forEach(function (el) {
|
246 | domIds.push(Number(el.id.slice(1)));
|
247 | });
|
248 | t.deepEqual(domIds, [3, 1, 2]);
|
249 |
|
250 |
|
251 | view.collection.comparator = function (model) {
|
252 | return model.get('age');
|
253 | };
|
254 | view.collection.sort();
|
255 |
|
256 |
|
257 | t.equal(numberRendered(view), view.collection.length);
|
258 | domIds = [];
|
259 | getRendered(view).forEach(function (el) {
|
260 | domIds.push(Number(el.id.slice(1)));
|
261 | });
|
262 | t.deepEqual(domIds, [1, 2, 3]);
|
263 |
|
264 | t.end();
|
265 | });
|
266 |
|
267 | test('sort and resort reversed', function (t) {
|
268 | var domIds = [];
|
269 | var coll = new Collection(data);
|
270 | var div = document.createElement('div');
|
271 | var view = new CollectionView({
|
272 | el: div,
|
273 | collection: coll,
|
274 | view: ItemView,
|
275 | reverse: true
|
276 | });
|
277 | view.render();
|
278 |
|
279 |
|
280 | view.collection.comparator = function (model) {
|
281 | return model.get('name');
|
282 | };
|
283 | view.collection.sort();
|
284 |
|
285 |
|
286 | t.equal(numberRendered(view), view.collection.length);
|
287 | domIds = [];
|
288 | getRendered(view).forEach(function (el) {
|
289 | domIds.push(Number(el.id.slice(1)));
|
290 | });
|
291 | t.deepEqual(domIds, [2, 1, 3]);
|
292 |
|
293 |
|
294 | view.collection.comparator = function (model) {
|
295 | return model.get('age');
|
296 | };
|
297 | view.collection.sort();
|
298 |
|
299 |
|
300 | t.equal(numberRendered(view), view.collection.length);
|
301 | domIds = [];
|
302 | getRendered(view).forEach(function (el) {
|
303 | domIds.push(Number(el.id.slice(1)));
|
304 | });
|
305 | t.deepEqual(domIds, [3, 2, 1]);
|
306 |
|
307 | t.end();
|
308 | });
|
309 |
|
310 | test('animateRemove', function (t) {
|
311 | var coll = new Collection(data);
|
312 | var div = document.createElement('div');
|
313 | var view = new CollectionView({
|
314 | el: div,
|
315 | collection: coll,
|
316 | view: ItemView
|
317 | });
|
318 | view.render();
|
319 | var prevAnimateRemove = ItemView.prototype.animateRemove;
|
320 | ItemView.prototype.animateRemove = function () {
|
321 | var self = this;
|
322 | this.el.className = 'fadeOut';
|
323 | setTimeout(function () {
|
324 | self.remove();
|
325 | }, 100);
|
326 | t.ok('animateRemove called');
|
327 | };
|
328 | view.collection.remove(view.collection.last());
|
329 | setTimeout(function () {
|
330 | t.equal(numberRendered(view), view.collection.length);
|
331 |
|
332 | ItemView.prototype.animateRemove = prevAnimateRemove;
|
333 | t.end();
|
334 | }, 150);
|
335 | });
|
336 |
|
337 | test('filtered', function (t) {
|
338 | var view = new MainView();
|
339 | view.render({
|
340 | filter: function (model) {
|
341 | return model.get('name').length > 3;
|
342 | }
|
343 | });
|
344 | t.equal(numberRendered(view), 2);
|
345 | t.end();
|
346 | });
|
347 |
|
348 | test('reversed', function (t) {
|
349 | var view = new MainView();
|
350 | view.render({
|
351 | reverse: true
|
352 | });
|
353 | var domIds = [];
|
354 | getRendered(view).forEach(function (el) {
|
355 | domIds.push(Number(el.id.slice(1)));
|
356 | });
|
357 | t.deepEqual(domIds, [3, 2, 1]);
|
358 | t.end();
|
359 | });
|
360 |
|
361 | test('cleanup', function (t) {
|
362 | var coll = new Collection(data);
|
363 | var div = document.createElement('div');
|
364 | var view = new CollectionView({
|
365 | el: div,
|
366 | collection: coll,
|
367 | view: ItemView
|
368 | });
|
369 | view.render();
|
370 | t.equal(numberRendered(view), view.collection.length);
|
371 | var firstModel = view.collection.first();
|
372 | var firstView = view.views[0];
|
373 | firstView.listenTo(firstModel, 'change:something', function () {});
|
374 | t.equal(view.collection.first()._events['change:something'].length, 1);
|
375 | view.remove();
|
376 |
|
377 |
|
378 | t.notOk(view.collection.first()._events['change:something']);
|
379 | t.end();
|
380 | });
|
381 |
|
382 | test('child view can choose to insert self', function (t) {
|
383 | var view = new MainView();
|
384 | ItemView.prototype.insertSelf = true;
|
385 | ItemView.prototype.render = function (extraInfo) {
|
386 | t.ok(extraInfo.containerEl);
|
387 | this.renderWithTemplate();
|
388 | };
|
389 |
|
390 | view.render();
|
391 | t.equal(numberRendered(view), 0, 'Parent should not have rendered anything');
|
392 | view.remove();
|
393 | t.end();
|
394 | });
|
395 |
|
396 | test('helpful exceptions for missing parameters', function (t) {
|
397 | var coll = new Collection(data);
|
398 | var div = document.createElement('div');
|
399 | t.throws(function noParams() {
|
400 | new CollectionView();
|
401 | }, ReferenceError, 'Collection view missing required parameters: collection, el');
|
402 | t.throws(function noCollection() {
|
403 | new CollectionView({ el: div });
|
404 | }, ReferenceError, 'Collection view requires a collection');
|
405 | t.throws(function noCollection() {
|
406 | new CollectionView({ collection: coll });
|
407 | }, ReferenceError, 'Collection view requires an el');
|
408 | t.end();
|
409 | });
|
410 |
|