Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(211)

Side by Side Diff: pkg/mdv/test/template_element_test.dart

Issue 34453003: Rename mdv -> template_binding, remove experiemntal apis from dart:html (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/mdv/test/node_bindings_test.dart ('k') | pkg/polymer/lib/polymer.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 library template_element_test;
6
7 import 'dart:async';
8 import 'dart:collection';
9 import 'dart:html';
10 import 'dart:math' as math;
11 import 'package:mdv/mdv.dart' as mdv;
12 import 'package:observe/observe.dart';
13 import 'package:unittest/html_config.dart';
14 import 'package:unittest/unittest.dart';
15 import 'mdv_test_utils.dart';
16
17 // Note: this file ported from
18 // https://github.com/toolkitchen/mdv/blob/master/tests/template_element.js
19 // TODO(jmesserly): submit a small cleanup patch to original. I fixed some
20 // cases where "div" and "t" were unintentionally using the JS global scope;
21 // look for "assertNodesAre".
22
23 main() {
24 mdv.initialize();
25 useHtmlConfiguration();
26 group('Template Element', templateElementTests);
27 }
28
29 templateElementTests() {
30 var testDiv;
31
32 setUp(() {
33 document.body.append(testDiv = new DivElement());
34 });
35
36 tearDown(() {
37 testDiv.remove();
38 testDiv = null;
39 });
40
41 createTestHtml(s) {
42 var div = new DivElement();
43 div.setInnerHtml(s, treeSanitizer: new NullTreeSanitizer());
44 testDiv.append(div);
45
46 for (var node in div.queryAll('*')) {
47 if (node.isTemplate) TemplateElement.decorate(node);
48 }
49
50 return div;
51 }
52
53 createShadowTestHtml(s) {
54 var div = new DivElement();
55 var root = div.createShadowRoot();
56 root.innerHtml = s;
57 testDiv.append(div);
58
59 for (var node in root.queryAll('*')) {
60 if (node.isTemplate) TemplateElement.decorate(node);
61 }
62
63 return root;
64 }
65
66 var expando = new Expando('observeTest');
67 void addExpandos(node) {
68 while (node != null) {
69 expando[node] = node.text;
70 node = node.nextNode;
71 }
72 }
73
74 void checkExpandos(node) {
75 expect(node, isNotNull);
76 while (node != null) {
77 expect(expando[node], node.text);
78 node = node.nextNode;
79 }
80 }
81
82 observeTest('Template', () {
83 var div = createTestHtml('<template bind={{}}>text</template>');
84 recursivelySetTemplateModel(div, null);
85 performMicrotaskCheckpoint();
86 expect(div.nodes.length, 2);
87 expect(div.nodes.last.text, 'text');
88 });
89
90 observeTest('Template bind, no parent', () {
91 var div = createTestHtml('<template bind>text</template>');
92 var template = div.nodes[0];
93 template.remove();
94
95 recursivelySetTemplateModel(template, toObservable({}));
96 performMicrotaskCheckpoint();
97 expect(template.nodes.length, 0);
98 expect(template.nextNode, null);
99 });
100
101 observeTest('Template bind, no defaultView', () {
102 var div = createTestHtml('<template bind>text</template>');
103 var template = div.nodes[0];
104 var doc = document.implementation.createHtmlDocument('');
105 doc.adoptNode(div);
106 recursivelySetTemplateModel(template, toObservable({}));
107 performMicrotaskCheckpoint();
108 expect(div.nodes.length, 1);
109 });
110
111 observeTest('Template-Empty Bind', () {
112 var div = createTestHtml('<template bind>text</template>');
113 recursivelySetTemplateModel(div, null);
114 performMicrotaskCheckpoint();
115 expect(div.nodes.length, 2);
116 expect(div.nodes.last.text, 'text');
117 });
118
119 observeTest('Template Bind If', () {
120 var div = createTestHtml('<template bind if="{{ foo }}">text</template>');
121 // Note: changed this value from 0->null because zero is not falsey in Dart.
122 // See https://code.google.com/p/dart/issues/detail?id=11956
123 var m = toObservable({ 'foo': null });
124 recursivelySetTemplateModel(div, m);
125 performMicrotaskCheckpoint();
126 expect(div.nodes.length, 1);
127
128 m['foo'] = 1;
129 performMicrotaskCheckpoint();
130 expect(div.nodes.length, 2);
131 expect(div.lastChild.text, 'text');
132 });
133
134 observeTest('Template Bind If, 2', () {
135 var div = createTestHtml(
136 '<template bind="{{ foo }}" if="{{ bar }}">{{ bat }}</template>');
137 var m = toObservable({ 'bar': null, 'foo': { 'bat': 'baz' } });
138 recursivelySetTemplateModel(div, m);
139 performMicrotaskCheckpoint();
140 expect(div.nodes.length, 1);
141
142 m['bar'] = 1;
143 performMicrotaskCheckpoint();
144 expect(div.nodes.length, 2);
145 expect(div.lastChild.text, 'baz');
146 });
147
148 observeTest('Template If', () {
149 var div = createTestHtml('<template if="{{ foo }}">{{ value }}</template>');
150 // Note: changed this value from 0->null because zero is not falsey in Dart.
151 // See https://code.google.com/p/dart/issues/detail?id=11956
152 var m = toObservable({ 'foo': null, 'value': 'foo' });
153 recursivelySetTemplateModel(div, m);
154 performMicrotaskCheckpoint();
155 expect(div.nodes.length, 1);
156
157 m['foo'] = 1;
158 performMicrotaskCheckpoint();
159 expect(div.nodes.length, 2);
160 expect(div.lastChild.text, 'foo');
161 });
162
163 observeTest('Template Repeat If', () {
164 var div = createTestHtml(
165 '<template repeat="{{ foo }}" if="{{ bar }}">{{ }}</template>');
166 // Note: changed this value from 0->null because zero is not falsey in Dart.
167 // See https://code.google.com/p/dart/issues/detail?id=11956
168 var m = toObservable({ 'bar': null, 'foo': [1, 2, 3] });
169 recursivelySetTemplateModel(div, m);
170 performMicrotaskCheckpoint();
171 expect(div.nodes.length, 1);
172
173 m['bar'] = 1;
174 performMicrotaskCheckpoint();
175 expect(div.nodes.length, 4);
176 expect(div.nodes[1].text, '1');
177 expect(div.nodes[2].text, '2');
178 expect(div.nodes[3].text, '3');
179 });
180
181 observeTest('TextTemplateWithNullStringBinding', () {
182 var div = createTestHtml('<template bind={{}}>a{{b}}c</template>');
183 var model = toObservable({'b': 'B'});
184 recursivelySetTemplateModel(div, model);
185
186 deliverChanges(model);
187 expect(div.nodes.length, 2);
188 expect(div.nodes.last.text, 'aBc');
189
190 model['b'] = 'b';
191 deliverChanges(model);
192 expect(div.nodes.last.text, 'abc');
193
194 model['b'] = null;
195 deliverChanges(model);
196 expect(div.nodes.last.text, 'ac');
197
198 model = null;
199 deliverChanges(model);
200 // setting model isn't observable.
201 expect(div.nodes.last.text, 'ac');
202 });
203
204 observeTest('TextTemplateWithBindingPath', () {
205 var div = createTestHtml(
206 '<template bind="{{ data }}">a{{b}}c</template>');
207 var model = toObservable({ 'data': {'b': 'B'} });
208 recursivelySetTemplateModel(div, model);
209
210 deliverChanges(model);
211 expect(div.nodes.length, 2);
212 expect(div.nodes.last.text, 'aBc');
213
214 model['data']['b'] = 'b';
215 deliverChanges(model);
216 expect(div.nodes.last.text, 'abc');
217
218 model['data'] = toObservable({'b': 'X'});
219 deliverChanges(model);
220 expect(div.nodes.last.text, 'aXc');
221
222 model['data'] = null;
223 deliverChanges(model);
224 expect(div.nodes.last.text, 'ac');
225 });
226
227 observeTest('TextTemplateWithBindingAndConditional', () {
228 var div = createTestHtml(
229 '<template bind="{{}}" if="{{ d }}">a{{b}}c</template>');
230 var model = toObservable({'b': 'B', 'd': 1});
231 recursivelySetTemplateModel(div, model);
232
233 deliverChanges(model);
234 expect(div.nodes.length, 2);
235 expect(div.nodes.last.text, 'aBc');
236
237 model['b'] = 'b';
238 deliverChanges(model);
239 expect(div.nodes.last.text, 'abc');
240
241 // TODO(jmesserly): MDV set this to empty string and relies on JS conversion
242 // rules. Is that intended?
243 // See https://github.com/toolkitchen/mdv/issues/59
244 model['d'] = null;
245 deliverChanges(model);
246 expect(div.nodes.length, 1);
247
248 model['d'] = 'here';
249 model['b'] = 'd';
250
251 deliverChanges(model);
252 expect(div.nodes.length, 2);
253 expect(div.nodes.last.text, 'adc');
254 });
255
256 observeTest('TemplateWithTextBinding2', () {
257 var div = createTestHtml(
258 '<template bind="{{ b }}">a{{value}}c</template>');
259 expect(div.nodes.length, 1);
260 var model = toObservable({'b': {'value': 'B'}});
261 recursivelySetTemplateModel(div, model);
262
263 deliverChanges(model);
264 expect(div.nodes.length, 2);
265 expect(div.nodes.last.text, 'aBc');
266
267 model['b'] = toObservable({'value': 'b'});
268 deliverChanges(model);
269 expect(div.nodes.last.text, 'abc');
270 });
271
272 observeTest('TemplateWithAttributeBinding', () {
273 var div = createTestHtml(
274 '<template bind="{{}}">'
275 '<div foo="a{{b}}c"></div>'
276 '</template>');
277 var model = toObservable({'b': 'B'});
278 recursivelySetTemplateModel(div, model);
279
280 deliverChanges(model);
281 expect(div.nodes.length, 2);
282 expect(div.nodes.last.attributes['foo'], 'aBc');
283
284 model['b'] = 'b';
285 deliverChanges(model);
286 expect(div.nodes.last.attributes['foo'], 'abc');
287
288 model['b'] = 'X';
289 deliverChanges(model);
290 expect(div.nodes.last.attributes['foo'], 'aXc');
291 });
292
293 observeTest('TemplateWithConditionalBinding', () {
294 var div = createTestHtml(
295 '<template bind="{{}}">'
296 '<div foo?="{{b}}"></div>'
297 '</template>');
298 var model = toObservable({'b': 'b'});
299 recursivelySetTemplateModel(div, model);
300
301 deliverChanges(model);
302 expect(div.nodes.length, 2);
303 expect(div.nodes.last.attributes['foo'], '');
304 expect(div.nodes.last.attributes, isNot(contains('foo?')));
305
306 model['b'] = null;
307 deliverChanges(model);
308 expect(div.nodes.last.attributes, isNot(contains('foo')));
309 });
310
311 observeTest('Repeat', () {
312 var div = createTestHtml(
313 '<template repeat="{{}}"">text</template>');
314
315 var model = toObservable([0, 1, 2]);
316 recursivelySetTemplateModel(div, model);
317
318 deliverChanges(model);
319 expect(div.nodes.length, 4);
320
321 model.length = 1;
322 deliverChanges(model);
323 expect(div.nodes.length, 2);
324
325 model.addAll(toObservable([3, 4]));
326 deliverChanges(model);
327 expect(div.nodes.length, 4);
328
329 model.removeRange(1, 2);
330 deliverChanges(model);
331 expect(div.nodes.length, 3);
332 });
333
334 observeTest('Repeat - Reuse Instances', () {
335 var div = createTestHtml('<template repeat>{{ val }}</template>');
336
337 var model = toObservable([
338 {'val': 10},
339 {'val': 5},
340 {'val': 2},
341 {'val': 8},
342 {'val': 1}
343 ]);
344 recursivelySetTemplateModel(div, model);
345
346 deliverChanges(model);
347 expect(div.nodes.length, 6);
348 var template = div.firstChild;
349
350 addExpandos(template.nextNode);
351 checkExpandos(template.nextNode);
352
353 model.sort((a, b) => a['val'] - b['val']);
354 deliverChanges(model);
355 checkExpandos(template.nextNode);
356
357 model = toObservable(model.reversed);
358 recursivelySetTemplateModel(div, model);
359 deliverChanges(model);
360 checkExpandos(template.nextNode);
361
362 for (var item in model) {
363 item['val'] += 1;
364 }
365
366 deliverChanges(model);
367 expect(div.nodes[1].text, "11");
368 expect(div.nodes[2].text, "9");
369 expect(div.nodes[3].text, "6");
370 expect(div.nodes[4].text, "3");
371 expect(div.nodes[5].text, "2");
372 });
373
374 observeTest('Bind - Reuse Instance', () {
375 var div = createTestHtml(
376 '<template bind="{{ foo }}">{{ bar }}</template>');
377
378 var model = toObservable({ 'foo': { 'bar': 5 }});
379 recursivelySetTemplateModel(div, model);
380
381 deliverChanges(model);
382 expect(div.nodes.length, 2);
383 var template = div.firstChild;
384
385 addExpandos(template.nextNode);
386 checkExpandos(template.nextNode);
387
388 model = toObservable({'foo': model['foo']});
389 recursivelySetTemplateModel(div, model);
390 deliverChanges(model);
391 checkExpandos(template.nextNode);
392 });
393
394 observeTest('Repeat-Empty', () {
395 var div = createTestHtml(
396 '<template repeat>text</template>');
397
398 var model = toObservable([0, 1, 2]);
399 recursivelySetTemplateModel(div, model);
400
401 deliverChanges(model);
402 expect(div.nodes.length, 4);
403
404 model.length = 1;
405 deliverChanges(model);
406 expect(div.nodes.length, 2);
407
408 model.addAll(toObservable([3, 4]));
409 deliverChanges(model);
410 expect(div.nodes.length, 4);
411
412 model.removeRange(1, 2);
413 deliverChanges(model);
414 expect(div.nodes.length, 3);
415 });
416
417 observeTest('Removal from iteration needs to unbind', () {
418 var div = createTestHtml(
419 '<template repeat="{{}}"><a>{{v}}</a></template>');
420 var model = toObservable([{'v': 0}, {'v': 1}, {'v': 2}, {'v': 3}, {'v': 4}]) ;
421 recursivelySetTemplateModel(div, model);
422 deliverChanges(model);
423
424 var nodes = div.nodes.skip(1).toList();
425 var vs = model.toList();
426
427 for (var i = 0; i < 5; i++) {
428 expect(nodes[i].text, '$i');
429 }
430
431 model.length = 3;
432 deliverChanges(model);
433 for (var i = 0; i < 5; i++) {
434 expect(nodes[i].text, '$i');
435 }
436
437 vs[3]['v'] = 33;
438 vs[4]['v'] = 44;
439 deliverChanges(model);
440 for (var i = 0; i < 5; i++) {
441 expect(nodes[i].text, '$i');
442 }
443 });
444
445 observeTest('DOM Stability on Iteration', () {
446 var div = createTestHtml(
447 '<template repeat="{{}}">{{}}</template>');
448 var model = toObservable([1, 2, 3, 4, 5]);
449 recursivelySetTemplateModel(div, model);
450
451 deliverChanges(model);
452
453 // Note: the node at index 0 is the <template>.
454 var nodes = div.nodes.toList();
455 expect(nodes.length, 6, reason: 'list has 5 items');
456
457 model.removeAt(0);
458 model.removeLast();
459
460 deliverChanges(model);
461 expect(div.nodes.length, 4, reason: 'list has 3 items');
462 expect(identical(div.nodes[1], nodes[2]), true, reason: '2 not removed');
463 expect(identical(div.nodes[2], nodes[3]), true, reason: '3 not removed');
464 expect(identical(div.nodes[3], nodes[4]), true, reason: '4 not removed');
465
466 model.insert(0, 5);
467 model[2] = 6;
468 model.add(7);
469
470 deliverChanges(model);
471
472 expect(div.nodes.length, 6, reason: 'list has 5 items');
473 expect(nodes.contains(div.nodes[1]), false, reason: '5 is a new node');
474 expect(identical(div.nodes[2], nodes[2]), true);
475 expect(nodes.contains(div.nodes[3]), false, reason: '6 is a new node');
476 expect(identical(div.nodes[4], nodes[4]), true);
477 expect(nodes.contains(div.nodes[5]), false, reason: '7 is a new node');
478
479 nodes = div.nodes.toList();
480
481 model.insert(2, 8);
482
483 deliverChanges(model);
484
485 expect(div.nodes.length, 7, reason: 'list has 6 items');
486 expect(identical(div.nodes[1], nodes[1]), true);
487 expect(identical(div.nodes[2], nodes[2]), true);
488 expect(nodes.contains(div.nodes[3]), false, reason: '8 is a new node');
489 expect(identical(div.nodes[4], nodes[3]), true);
490 expect(identical(div.nodes[5], nodes[4]), true);
491 expect(identical(div.nodes[6], nodes[5]), true);
492 });
493
494 observeTest('Repeat2', () {
495 var div = createTestHtml(
496 '<template repeat="{{}}">{{value}}</template>');
497 expect(div.nodes.length, 1);
498
499 var model = toObservable([
500 {'value': 0},
501 {'value': 1},
502 {'value': 2}
503 ]);
504 recursivelySetTemplateModel(div, model);
505
506 deliverChanges(model);
507 expect(div.nodes.length, 4);
508 expect(div.nodes[1].text, '0');
509 expect(div.nodes[2].text, '1');
510 expect(div.nodes[3].text, '2');
511
512 model[1]['value'] = 'One';
513 deliverChanges(model);
514 expect(div.nodes.length, 4);
515 expect(div.nodes[1].text, '0');
516 expect(div.nodes[2].text, 'One');
517 expect(div.nodes[3].text, '2');
518
519 model.replaceRange(0, 1, toObservable([{'value': 'Zero'}]));
520 deliverChanges(model);
521 expect(div.nodes.length, 4);
522 expect(div.nodes[1].text, 'Zero');
523 expect(div.nodes[2].text, 'One');
524 expect(div.nodes[3].text, '2');
525 });
526
527 observeTest('TemplateWithInputValue', () {
528 var div = createTestHtml(
529 '<template bind="{{}}">'
530 '<input value="{{x}}">'
531 '</template>');
532 var model = toObservable({'x': 'hi'});
533 recursivelySetTemplateModel(div, model);
534
535 deliverChanges(model);
536 expect(div.nodes.length, 2);
537 expect(div.nodes.last.value, 'hi');
538
539 model['x'] = 'bye';
540 expect(div.nodes.last.value, 'hi');
541 deliverChanges(model);
542 expect(div.nodes.last.value, 'bye');
543
544 div.nodes.last.value = 'hello';
545 dispatchEvent('input', div.nodes.last);
546 expect(model['x'], 'hello');
547 deliverChanges(model);
548 expect(div.nodes.last.value, 'hello');
549 });
550
551 //////////////////////////////////////////////////////////////////////////////
552
553 observeTest('Decorated', () {
554 var div = createTestHtml(
555 '<template bind="{{ XX }}" id="t1">'
556 '<p>Crew member: {{name}}, Job title: {{title}}</p>'
557 '</template>'
558 '<template bind="{{ XY }}" id="t2" ref="t1"></template>');
559
560 var model = toObservable({
561 'XX': {'name': 'Leela', 'title': 'Captain'},
562 'XY': {'name': 'Fry', 'title': 'Delivery boy'},
563 'XZ': {'name': 'Zoidberg', 'title': 'Doctor'}
564 });
565 recursivelySetTemplateModel(div, model);
566
567 deliverChanges(model);
568
569 var t1 = document.getElementById('t1');
570 var instance = t1.nextElementSibling;
571 expect(instance.text, 'Crew member: Leela, Job title: Captain');
572
573 var t2 = document.getElementById('t2');
574 instance = t2.nextElementSibling;
575 expect(instance.text, 'Crew member: Fry, Job title: Delivery boy');
576
577 expect(div.children.length, 4);
578 expect(div.nodes.length, 4);
579
580 expect(div.nodes[1].tagName, 'P');
581 expect(div.nodes[3].tagName, 'P');
582 });
583
584 observeTest('DefaultStyles', () {
585 var t = new Element.tag('template');
586 TemplateElement.decorate(t);
587
588 document.body.append(t);
589 expect(t.getComputedStyle().display, 'none');
590
591 t.remove();
592 });
593
594
595 observeTest('Bind', () {
596 var div = createTestHtml('<template bind="{{}}">Hi {{ name }}</template>');
597 var model = toObservable({'name': 'Leela'});
598 recursivelySetTemplateModel(div, model);
599
600 deliverChanges(model);
601 expect(div.nodes[1].text, 'Hi Leela');
602 });
603
604 observeTest('BindImperative', () {
605 var div = createTestHtml(
606 '<template>'
607 'Hi {{ name }}'
608 '</template>');
609 var t = div.nodes.first;
610
611 var model = toObservable({'name': 'Leela'});
612 t.bind('bind', model, '');
613
614 deliverChanges(model);
615 expect(div.nodes[1].text, 'Hi Leela');
616 });
617
618 observeTest('BindPlaceHolderHasNewLine', () {
619 var div = createTestHtml('<template bind="{{}}">Hi {{\nname\n}}</template>') ;
620 var model = toObservable({'name': 'Leela'});
621 recursivelySetTemplateModel(div, model);
622
623 deliverChanges(model);
624 expect(div.nodes[1].text, 'Hi Leela');
625 });
626
627 observeTest('BindWithRef', () {
628 var id = 't${new math.Random().nextDouble()}';
629 var div = createTestHtml(
630 '<template id="$id">'
631 'Hi {{ name }}'
632 '</template>'
633 '<template ref="$id" bind="{{}}"></template>');
634
635 var t1 = div.nodes.first;
636 var t2 = div.nodes[1];
637
638 expect(t2.ref, t1);
639
640 var model = toObservable({'name': 'Fry'});
641 recursivelySetTemplateModel(div, model);
642
643 deliverChanges(model);
644 expect(t2.nextNode.text, 'Hi Fry');
645 });
646
647 observeTest('BindChanged', () {
648 var model = toObservable({
649 'XX': {'name': 'Leela', 'title': 'Captain'},
650 'XY': {'name': 'Fry', 'title': 'Delivery boy'},
651 'XZ': {'name': 'Zoidberg', 'title': 'Doctor'}
652 });
653
654 var div = createTestHtml(
655 '<template bind="{{ XX }}">Hi {{ name }}</template>');
656
657 recursivelySetTemplateModel(div, model);
658
659 var t = div.nodes.first;
660 deliverChanges(model);
661
662 expect(div.nodes.length, 2);
663 expect(t.nextNode.text, 'Hi Leela');
664
665 t.bind('bind', model, 'XZ');
666 deliverChanges(model);
667
668 expect(div.nodes.length, 2);
669 expect(t.nextNode.text, 'Hi Zoidberg');
670 });
671
672 assertNodesAre(div, [arguments]) {
673 var expectedLength = arguments.length;
674 expect(div.nodes.length, expectedLength + 1);
675
676 for (var i = 0; i < arguments.length; i++) {
677 var targetNode = div.nodes[i + 1];
678 expect(targetNode.text, arguments[i]);
679 }
680 }
681
682 observeTest('Repeat3', () {
683 var div = createTestHtml(
684 '<template repeat="{{ contacts }}">Hi {{ name }}</template>');
685 var t = div.nodes.first;
686
687 var m = toObservable({
688 'contacts': [
689 {'name': 'Raf'},
690 {'name': 'Arv'},
691 {'name': 'Neal'}
692 ]
693 });
694
695 recursivelySetTemplateModel(div, m);
696 deliverChanges(m);
697
698 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']);
699
700 m['contacts'].add(toObservable({'name': 'Alex'}));
701 deliverChanges(m);
702 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal', 'Hi Alex']);
703
704 m['contacts'].replaceRange(0, 2,
705 toObservable([{'name': 'Rafael'}, {'name': 'Erik'}]));
706 deliverChanges(m);
707 assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Neal', 'Hi Alex']);
708
709 m['contacts'].removeRange(1, 3);
710 deliverChanges(m);
711 assertNodesAre(div, ['Hi Rafael', 'Hi Alex']);
712
713 m['contacts'].insertAll(1,
714 toObservable([{'name': 'Erik'}, {'name': 'Dimitri'}]));
715 deliverChanges(m);
716 assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Dimitri', 'Hi Alex']);
717
718 m['contacts'].replaceRange(0, 1,
719 toObservable([{'name': 'Tab'}, {'name': 'Neal'}]));
720 deliverChanges(m);
721 assertNodesAre(div, ['Hi Tab', 'Hi Neal', 'Hi Erik', 'Hi Dimitri', 'Hi Alex' ]);
722
723 m['contacts'] = toObservable([{'name': 'Alex'}]);
724 deliverChanges(m);
725 assertNodesAre(div, ['Hi Alex']);
726
727 m['contacts'].length = 0;
728 deliverChanges(m);
729 assertNodesAre(div, []);
730 });
731
732 observeTest('RepeatModelSet', () {
733 var div = createTestHtml(
734 '<template repeat="{{ contacts }}">'
735 'Hi {{ name }}'
736 '</template>');
737 var m = toObservable({
738 'contacts': [
739 {'name': 'Raf'},
740 {'name': 'Arv'},
741 {'name': 'Neal'}
742 ]
743 });
744 recursivelySetTemplateModel(div, m);
745
746 deliverChanges(m);
747 var t = div.nodes.first;
748
749 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']);
750 });
751
752 observeTest('RepeatEmptyPath', () {
753 var div = createTestHtml('<template repeat="{{}}">Hi {{ name }}</template>') ;
754 var t = div.nodes.first;
755
756 var m = toObservable([
757 {'name': 'Raf'},
758 {'name': 'Arv'},
759 {'name': 'Neal'}
760 ]);
761 recursivelySetTemplateModel(div, m);
762
763 deliverChanges(m);
764
765 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']);
766
767 m.add(toObservable({'name': 'Alex'}));
768 deliverChanges(m);
769 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal', 'Hi Alex']);
770
771 m.replaceRange(0, 2, toObservable([{'name': 'Rafael'}, {'name': 'Erik'}]));
772 deliverChanges(m);
773 assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Neal', 'Hi Alex']);
774
775 m.removeRange(1, 3);
776 deliverChanges(m);
777 assertNodesAre(div, ['Hi Rafael', 'Hi Alex']);
778
779 m.insertAll(1, toObservable([{'name': 'Erik'}, {'name': 'Dimitri'}]));
780 deliverChanges(m);
781 assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Dimitri', 'Hi Alex']);
782
783 m.replaceRange(0, 1, toObservable([{'name': 'Tab'}, {'name': 'Neal'}]));
784 deliverChanges(m);
785 assertNodesAre(div, ['Hi Tab', 'Hi Neal', 'Hi Erik', 'Hi Dimitri', 'Hi Alex' ]);
786
787 m.length = 0;
788 m.add(toObservable({'name': 'Alex'}));
789 deliverChanges(m);
790 assertNodesAre(div, ['Hi Alex']);
791 });
792
793 observeTest('RepeatNullModel', () {
794 var div = createTestHtml('<template repeat="{{}}">Hi {{ name }}</template>') ;
795 var t = div.nodes.first;
796
797 var m = null;
798 recursivelySetTemplateModel(div, m);
799
800 expect(div.nodes.length, 1);
801
802 t.attributes['iterate'] = '';
803 m = toObservable({});
804 recursivelySetTemplateModel(div, m);
805
806 deliverChanges(m);
807 expect(div.nodes.length, 1);
808 });
809
810 observeTest('RepeatReuse', () {
811 var div = createTestHtml('<template repeat="{{}}">Hi {{ name }}</template>') ;
812 var t = div.nodes.first;
813
814 var m = toObservable([
815 {'name': 'Raf'},
816 {'name': 'Arv'},
817 {'name': 'Neal'}
818 ]);
819 recursivelySetTemplateModel(div, m);
820 deliverChanges(m);
821
822 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']);
823 var node1 = div.nodes[1];
824 var node2 = div.nodes[2];
825 var node3 = div.nodes[3];
826
827 m.replaceRange(1, 2, toObservable([{'name': 'Erik'}]));
828 deliverChanges(m);
829 assertNodesAre(div, ['Hi Raf', 'Hi Erik', 'Hi Neal']);
830 expect(div.nodes[1], node1,
831 reason: 'model[0] did not change so the node should not have changed');
832 expect(div.nodes[2], isNot(equals(node2)),
833 reason: 'Should not reuse when replacing');
834 expect(div.nodes[3], node3,
835 reason: 'model[2] did not change so the node should not have changed');
836
837 node2 = div.nodes[2];
838 m.insert(0, toObservable({'name': 'Alex'}));
839 deliverChanges(m);
840 assertNodesAre(div, ['Hi Alex', 'Hi Raf', 'Hi Erik', 'Hi Neal']);
841 });
842
843 observeTest('TwoLevelsDeepBug', () {
844 var div = createTestHtml(
845 '<template bind="{{}}"><span><span>{{ foo }}</span></span></template>');
846
847 var model = toObservable({'foo': 'bar'});
848 recursivelySetTemplateModel(div, model);
849 deliverChanges(model);
850
851 expect(div.nodes[1].nodes[0].nodes[0].text, 'bar');
852 });
853
854 observeTest('Checked', () {
855 var div = createTestHtml(
856 '<template>'
857 '<input type="checkbox" checked="{{a}}">'
858 '</template>');
859 var t = div.nodes.first;
860 var m = toObservable({
861 'a': true
862 });
863 t.bind('bind', m, '');
864 deliverChanges(m);
865
866 var instanceInput = t.nextNode;
867 expect(instanceInput.checked, true);
868
869 instanceInput.click();
870 expect(instanceInput.checked, false);
871
872 instanceInput.click();
873 expect(instanceInput.checked, true);
874 });
875
876 nestedHelper(s, start) {
877 var div = createTestHtml(s);
878
879 var m = toObservable({
880 'a': {
881 'b': 1,
882 'c': {'d': 2}
883 },
884 });
885
886 recursivelySetTemplateModel(div, m);
887 deliverChanges(m);
888
889 var i = start;
890 expect(div.nodes[i++].text, '1');
891 expect(div.nodes[i++].tagName, 'TEMPLATE');
892 expect(div.nodes[i++].text, '2');
893
894 m['a']['b'] = 11;
895 deliverChanges(m);
896 expect(div.nodes[start].text, '11');
897
898 m['a']['c'] = toObservable({'d': 22});
899 deliverChanges(m);
900 expect(div.nodes[start + 2].text, '22');
901 }
902
903 observeTest('Nested', () {
904 nestedHelper(
905 '<template bind="{{a}}">'
906 '{{b}}'
907 '<template bind="{{c}}">'
908 '{{d}}'
909 '</template>'
910 '</template>', 1);
911 });
912
913 observeTest('NestedWithRef', () {
914 nestedHelper(
915 '<template id="inner">{{d}}</template>'
916 '<template id="outer" bind="{{a}}">'
917 '{{b}}'
918 '<template ref="inner" bind="{{c}}"></template>'
919 '</template>', 2);
920 });
921
922 nestedIterateInstantiateHelper(s, start) {
923 var div = createTestHtml(s);
924
925 var m = toObservable({
926 'a': [
927 {
928 'b': 1,
929 'c': {'d': 11}
930 },
931 {
932 'b': 2,
933 'c': {'d': 22}
934 }
935 ]
936 });
937
938 recursivelySetTemplateModel(div, m);
939 deliverChanges(m);
940
941 var i = start;
942 expect(div.nodes[i++].text, '1');
943 expect(div.nodes[i++].tagName, 'TEMPLATE');
944 expect(div.nodes[i++].text, '11');
945 expect(div.nodes[i++].text, '2');
946 expect(div.nodes[i++].tagName, 'TEMPLATE');
947 expect(div.nodes[i++].text, '22');
948
949 m['a'][1] = toObservable({
950 'b': 3,
951 'c': {'d': 33}
952 });
953
954 deliverChanges(m);
955 expect(div.nodes[start + 3].text, '3');
956 expect(div.nodes[start + 5].text, '33');
957 }
958
959 observeTest('NestedRepeatBind', () {
960 nestedIterateInstantiateHelper(
961 '<template repeat="{{a}}">'
962 '{{b}}'
963 '<template bind="{{c}}">'
964 '{{d}}'
965 '</template>'
966 '</template>', 1);
967 });
968
969 observeTest('NestedRepeatBindWithRef', () {
970 nestedIterateInstantiateHelper(
971 '<template id="inner">'
972 '{{d}}'
973 '</template>'
974 '<template repeat="{{a}}">'
975 '{{b}}'
976 '<template ref="inner" bind="{{c}}"></template>'
977 '</template>', 2);
978 });
979
980 nestedIterateIterateHelper(s, start) {
981 var div = createTestHtml(s);
982
983 var m = toObservable({
984 'a': [
985 {
986 'b': 1,
987 'c': [{'d': 11}, {'d': 12}]
988 },
989 {
990 'b': 2,
991 'c': [{'d': 21}, {'d': 22}]
992 }
993 ]
994 });
995
996 recursivelySetTemplateModel(div, m);
997 deliverChanges(m);
998
999 var i = start;
1000 expect(div.nodes[i++].text, '1');
1001 expect(div.nodes[i++].tagName, 'TEMPLATE');
1002 expect(div.nodes[i++].text, '11');
1003 expect(div.nodes[i++].text, '12');
1004 expect(div.nodes[i++].text, '2');
1005 expect(div.nodes[i++].tagName, 'TEMPLATE');
1006 expect(div.nodes[i++].text, '21');
1007 expect(div.nodes[i++].text, '22');
1008
1009 m['a'][1] = toObservable({
1010 'b': 3,
1011 'c': [{'d': 31}, {'d': 32}, {'d': 33}]
1012 });
1013
1014 i = start + 4;
1015 deliverChanges(m);
1016 expect(div.nodes[start + 4].text, '3');
1017 expect(div.nodes[start + 6].text, '31');
1018 expect(div.nodes[start + 7].text, '32');
1019 expect(div.nodes[start + 8].text, '33');
1020 }
1021
1022 observeTest('NestedRepeatBind', () {
1023 nestedIterateIterateHelper(
1024 '<template repeat="{{a}}">'
1025 '{{b}}'
1026 '<template repeat="{{c}}">'
1027 '{{d}}'
1028 '</template>'
1029 '</template>', 1);
1030 });
1031
1032 observeTest('NestedRepeatRepeatWithRef', () {
1033 nestedIterateIterateHelper(
1034 '<template id="inner">'
1035 '{{d}}'
1036 '</template>'
1037 '<template repeat="{{a}}">'
1038 '{{b}}'
1039 '<template ref="inner" repeat="{{c}}"></template>'
1040 '</template>', 2);
1041 });
1042
1043 observeTest('NestedRepeatSelfRef', () {
1044 var div = createTestHtml(
1045 '<template id="t" repeat="{{}}">'
1046 '{{name}}'
1047 '<template ref="t" repeat="{{items}}"></template>'
1048 '</template>');
1049
1050 var m = toObservable([
1051 {
1052 'name': 'Item 1',
1053 'items': [
1054 {
1055 'name': 'Item 1.1',
1056 'items': [
1057 {
1058 'name': 'Item 1.1.1',
1059 'items': []
1060 }
1061 ]
1062 },
1063 {
1064 'name': 'Item 1.2'
1065 }
1066 ]
1067 },
1068 {
1069 'name': 'Item 2',
1070 'items': []
1071 },
1072 ]);
1073
1074 recursivelySetTemplateModel(div, m);
1075 deliverChanges(m);
1076
1077 var i = 1;
1078 expect(div.nodes[i++].text, 'Item 1');
1079 expect(div.nodes[i++].tagName, 'TEMPLATE');
1080 expect(div.nodes[i++].text, 'Item 1.1');
1081 expect(div.nodes[i++].tagName, 'TEMPLATE');
1082 expect(div.nodes[i++].text, 'Item 1.1.1');
1083 expect(div.nodes[i++].tagName, 'TEMPLATE');
1084 expect(div.nodes[i++].text, 'Item 1.2');
1085 expect(div.nodes[i++].tagName, 'TEMPLATE');
1086 expect(div.nodes[i++].text, 'Item 2');
1087
1088 m[0] = toObservable({'name': 'Item 1 changed'});
1089
1090 i = 1;
1091 deliverChanges(m);
1092 expect(div.nodes[i++].text, 'Item 1 changed');
1093 expect(div.nodes[i++].tagName, 'TEMPLATE');
1094 expect(div.nodes[i++].text, 'Item 2');
1095 });
1096
1097 observeTest('Attribute Template Option/Optgroup', () {
1098 var div = createTestHtml(
1099 '<template bind>'
1100 '<select selectedIndex="{{ selected }}">'
1101 '<optgroup template repeat="{{ groups }}" label="{{ name }}">'
1102 '<option template repeat="{{ items }}">{{ val }}</option>'
1103 '</optgroup>'
1104 '</select>'
1105 '</template>');
1106
1107 var m = toObservable({
1108 'selected': 1,
1109 'groups': [{
1110 'name': 'one', 'items': [{ 'val': 0 }, { 'val': 1 }]
1111 }],
1112 });
1113
1114 recursivelySetTemplateModel(div, m);
1115 deliverChanges(m);
1116
1117 var select = div.nodes[0].nextNode;
1118 expect(select.nodes.length, 2);
1119
1120 scheduleMicrotask(expectAsync0(() {
1121 scheduleMicrotask(expectAsync0(() {
1122 // TODO(jmesserly): this should be called sooner.
1123 expect(select.selectedIndex, 1);
1124 }));
1125 }));
1126 expect(select.nodes[0].tagName, 'TEMPLATE');
1127 expect(select.nodes[0].ref.content.nodes[0].tagName, 'OPTGROUP');
1128
1129 var optgroup = select.nodes[1];
1130 expect(optgroup.nodes[0].tagName, 'TEMPLATE');
1131 expect(optgroup.nodes[1].tagName, 'OPTION');
1132 expect(optgroup.nodes[1].text, '0');
1133 expect(optgroup.nodes[2].tagName, 'OPTION');
1134 expect(optgroup.nodes[2].text, '1');
1135 });
1136
1137 observeTest('NestedIterateTableMixedSemanticNative', () {
1138 if (!parserHasNativeTemplate) return;
1139
1140 var div = createTestHtml(
1141 '<table><tbody>'
1142 '<template repeat="{{}}">'
1143 '<tr>'
1144 '<td template repeat="{{}}" class="{{ val }}">{{ val }}</td>'
1145 '</tr>'
1146 '</template>'
1147 '</tbody></table>');
1148
1149 var m = toObservable([
1150 [{ 'val': 0 }, { 'val': 1 }],
1151 [{ 'val': 2 }, { 'val': 3 }]
1152 ]);
1153
1154 recursivelySetTemplateModel(div, m);
1155 deliverChanges(m);
1156
1157 var tbody = div.nodes[0].nodes[0];
1158
1159 // 1 for the <tr template>, 2 * (1 tr)
1160 expect(tbody.nodes.length, 3);
1161
1162 // 1 for the <td template>, 2 * (1 td)
1163 expect(tbody.nodes[1].nodes.length, 3);
1164
1165 expect(tbody.nodes[1].nodes[1].text, '0');
1166 expect(tbody.nodes[1].nodes[2].text, '1');
1167
1168 // 1 for the <td template>, 2 * (1 td)
1169 expect(tbody.nodes[2].nodes.length, 3);
1170 expect(tbody.nodes[2].nodes[1].text, '2');
1171 expect(tbody.nodes[2].nodes[2].text, '3');
1172
1173 // Asset the 'class' binding is retained on the semantic template (just
1174 // check the last one).
1175 expect(tbody.nodes[2].nodes[2].attributes["class"], '3');
1176 });
1177
1178 observeTest('NestedIterateTable', () {
1179 var div = createTestHtml(
1180 '<table><tbody>'
1181 '<tr template repeat="{{}}">'
1182 '<td template repeat="{{}}" class="{{ val }}">{{ val }}</td>'
1183 '</tr>'
1184 '</tbody></table>');
1185
1186 var m = toObservable([
1187 [{ 'val': 0 }, { 'val': 1 }],
1188 [{ 'val': 2 }, { 'val': 3 }]
1189 ]);
1190
1191 recursivelySetTemplateModel(div, m);
1192 deliverChanges(m);
1193
1194 var i = 1;
1195 var tbody = div.nodes[0].nodes[0];
1196
1197 // 1 for the <tr template>, 2 * (1 tr)
1198 expect(tbody.nodes.length, 3);
1199
1200 // 1 for the <td template>, 2 * (1 td)
1201 expect(tbody.nodes[1].nodes.length, 3);
1202 expect(tbody.nodes[1].nodes[1].text, '0');
1203 expect(tbody.nodes[1].nodes[2].text, '1');
1204
1205 // 1 for the <td template>, 2 * (1 td)
1206 expect(tbody.nodes[2].nodes.length, 3);
1207 expect(tbody.nodes[2].nodes[1].text, '2');
1208 expect(tbody.nodes[2].nodes[2].text, '3');
1209
1210 // Asset the 'class' binding is retained on the semantic template (just chec k
1211 // the last one).
1212 expect(tbody.nodes[2].nodes[2].attributes['class'], '3');
1213 });
1214
1215 observeTest('NestedRepeatDeletionOfMultipleSubTemplates', () {
1216 var div = createTestHtml(
1217 '<ul>'
1218 '<template repeat="{{}}" id=t1>'
1219 '<li>{{name}}'
1220 '<ul>'
1221 '<template ref=t1 repaet="{{items}}"></template>'
1222 '</ul>'
1223 '</li>'
1224 '</template>'
1225 '</ul>');
1226
1227 var m = toObservable([
1228 {
1229 'name': 'Item 1',
1230 'items': [
1231 {
1232 'name': 'Item 1.1'
1233 }
1234 ]
1235 }
1236 ]);
1237
1238 recursivelySetTemplateModel(div, m);
1239
1240 deliverChanges(m);
1241 m.removeAt(0);
1242 deliverChanges(m);
1243 });
1244
1245 observeTest('DeepNested', () {
1246 var div = createTestHtml(
1247 '<template bind="{{a}}">'
1248 '<p>'
1249 '<template bind="{{b}}">'
1250 '{{ c }}'
1251 '</template>'
1252 '</p>'
1253 '</template>');
1254
1255 var m = toObservable({
1256 'a': {
1257 'b': {
1258 'c': 42
1259 }
1260 }
1261 });
1262 recursivelySetTemplateModel(div, m);
1263 deliverChanges(m);
1264
1265 expect(div.nodes[1].tagName, 'P');
1266 expect(div.nodes[1].nodes.first.tagName, 'TEMPLATE');
1267 expect(div.nodes[1].nodes[1].text, '42');
1268 });
1269
1270 observeTest('TemplateContentRemoved', () {
1271 var div = createTestHtml('<template bind="{{}}">{{ }}</template>');
1272 var model = 42;
1273
1274 recursivelySetTemplateModel(div, model);
1275 deliverChanges(model);
1276 expect(div.nodes[1].text, '42');
1277 expect(div.nodes[0].text, '');
1278 });
1279
1280 observeTest('TemplateContentRemovedEmptyArray', () {
1281 var div = createTestHtml('<template iterate>Remove me</template>');
1282 var model = toObservable([]);
1283
1284 recursivelySetTemplateModel(div, model);
1285 deliverChanges(model);
1286 expect(div.nodes.length, 1);
1287 expect(div.nodes[0].text, '');
1288 });
1289
1290 observeTest('TemplateContentRemovedNested', () {
1291 var div = createTestHtml(
1292 '<template bind="{{}}">'
1293 '{{ a }}'
1294 '<template bind="{{}}">'
1295 '{{ b }}'
1296 '</template>'
1297 '</template>');
1298
1299 var model = toObservable({
1300 'a': 1,
1301 'b': 2
1302 });
1303 recursivelySetTemplateModel(div, model);
1304 deliverChanges(model);
1305
1306 expect(div.nodes[0].text, '');
1307 expect(div.nodes[1].text, '1');
1308 expect(div.nodes[2].text, '');
1309 expect(div.nodes[3].text, '2');
1310 });
1311
1312 observeTest('BindWithUndefinedModel', () {
1313 var div = createTestHtml(
1314 '<template bind="{{}}" if="{{}}">{{ a }}</template>');
1315
1316 var model = toObservable({'a': 42});
1317 recursivelySetTemplateModel(div, model);
1318 deliverChanges(model);
1319 expect(div.nodes[1].text, '42');
1320
1321 model = null;
1322 recursivelySetTemplateModel(div, model);
1323 deliverChanges(model);
1324 expect(div.nodes.length, 1);
1325
1326 model = toObservable({'a': 42});
1327 recursivelySetTemplateModel(div, model);
1328 deliverChanges(model);
1329 expect(div.nodes[1].text, '42');
1330 });
1331
1332 observeTest('BindNested', () {
1333 var div = createTestHtml(
1334 '<template bind="{{}}">'
1335 'Name: {{ name }}'
1336 '<template bind="{{wife}}" if="{{wife}}">'
1337 'Wife: {{ name }}'
1338 '</template>'
1339 '<template bind="{{child}}" if="{{child}}">'
1340 'Child: {{ name }}'
1341 '</template>'
1342 '</template>');
1343
1344 var m = toObservable({
1345 'name': 'Hermes',
1346 'wife': {
1347 'name': 'LaBarbara'
1348 }
1349 });
1350 recursivelySetTemplateModel(div, m);
1351 deliverChanges(m);
1352
1353 expect(div.nodes.length, 5);
1354 expect(div.nodes[1].text, 'Name: Hermes');
1355 expect(div.nodes[3].text, 'Wife: LaBarbara');
1356
1357 m['child'] = toObservable({'name': 'Dwight'});
1358 deliverChanges(m);
1359 expect(div.nodes.length, 6);
1360 expect(div.nodes[5].text, 'Child: Dwight');
1361
1362 m.remove('wife');
1363 deliverChanges(m);
1364 expect(div.nodes.length, 5);
1365 expect(div.nodes[4].text, 'Child: Dwight');
1366 });
1367
1368 observeTest('BindRecursive', () {
1369 var div = createTestHtml(
1370 '<template bind="{{}}" if="{{}}" id="t">'
1371 'Name: {{ name }}'
1372 '<template bind="{{friend}}" if="{{friend}}" ref="t"></template>'
1373 '</template>');
1374
1375 var m = toObservable({
1376 'name': 'Fry',
1377 'friend': {
1378 'name': 'Bender'
1379 }
1380 });
1381 recursivelySetTemplateModel(div, m);
1382 deliverChanges(m);
1383
1384 expect(div.nodes.length, 5);
1385 expect(div.nodes[1].text, 'Name: Fry');
1386 expect(div.nodes[3].text, 'Name: Bender');
1387
1388 m['friend']['friend'] = toObservable({'name': 'Leela'});
1389 deliverChanges(m);
1390 expect(div.nodes.length, 7);
1391 expect(div.nodes[5].text, 'Name: Leela');
1392
1393 m['friend'] = toObservable({'name': 'Leela'});
1394 deliverChanges(m);
1395 expect(div.nodes.length, 5);
1396 expect(div.nodes[3].text, 'Name: Leela');
1397 });
1398
1399 observeTest('Template - Self is terminator', () {
1400 var div = createTestHtml(
1401 '<template repeat>{{ foo }}'
1402 '<template bind></template>'
1403 '</template>');
1404
1405 var m = toObservable([{ 'foo': 'bar' }]);
1406 recursivelySetTemplateModel(div, m);
1407 performMicrotaskCheckpoint();
1408
1409 m.add(toObservable({ 'foo': 'baz' }));
1410 recursivelySetTemplateModel(div, m);
1411 performMicrotaskCheckpoint();
1412
1413 expect(div.nodes.length, 5);
1414 expect(div.nodes[1].text, 'bar');
1415 expect(div.nodes[3].text, 'baz');
1416 });
1417
1418 observeTest('Template - Same Contents, Different Array has no effect', () {
1419 if (!MutationObserver.supported) return;
1420
1421 var div = createTestHtml('<template repeat>{{ foo }}</template>');
1422
1423 var m = toObservable([{ 'foo': 'bar' }, { 'foo': 'bat'}]);
1424 recursivelySetTemplateModel(div, m);
1425 performMicrotaskCheckpoint();
1426
1427 var observer = new MutationObserver((records, _) {});
1428 observer.observe(div, childList: true);
1429
1430 var template = div.firstChild;
1431 template.bind('repeat', toObservable(m.toList()), '');
1432 performMicrotaskCheckpoint();
1433 var records = observer.takeRecords();
1434 expect(records.length, 0);
1435 });
1436
1437 observeTest('RecursiveRef', () {
1438 var div = createTestHtml(
1439 '<template bind>'
1440 '<template id=src>{{ foo }}</template>'
1441 '<template bind ref=src></template>'
1442 '</template>');
1443
1444 var m = toObservable({'foo': 'bar'});
1445 recursivelySetTemplateModel(div, m);
1446 performMicrotaskCheckpoint();
1447
1448 expect(div.nodes.length, 4);
1449 expect(div.nodes[3].text, 'bar');
1450 });
1451
1452 observeTest('ChangeFromBindToRepeat', () {
1453 var div = createTestHtml(
1454 '<template bind="{{a}}">'
1455 '{{ length }}'
1456 '</template>');
1457 var template = div.nodes.first;
1458
1459 // Note: this test data is a little different from the JS version, because
1460 // we allow binding to the "length" field of the Map in preference to
1461 // binding keys.
1462 var m = toObservable({
1463 'a': [
1464 [],
1465 { 'b': [1,2,3,4] },
1466 // Note: this will use the Map "length" property, not the "length" key.
1467 {'length': 42, 'c': 123}
1468 ]
1469 });
1470 recursivelySetTemplateModel(div, m);
1471 deliverChanges(m);
1472
1473 expect(div.nodes.length, 2);
1474 expect(div.nodes[1].text, '3');
1475
1476 template.unbind('bind');
1477 template.bind('repeat', m, 'a');
1478 deliverChanges(m);
1479 expect(div.nodes.length, 4);
1480 expect(div.nodes[1].text, '0');
1481 expect(div.nodes[2].text, '1');
1482 expect(div.nodes[3].text, '2');
1483
1484 template.unbind('repeat');
1485 template.bind('bind', m, 'a.1.b');
1486
1487 deliverChanges(m);
1488 expect(div.nodes.length, 2);
1489 expect(div.nodes[1].text, '4');
1490 });
1491
1492 observeTest('ChangeRefId', () {
1493 var div = createTestHtml(
1494 '<template id="a">a:{{ }}</template>'
1495 '<template id="b">b:{{ }}</template>'
1496 '<template repeat="{{}}">'
1497 '<template ref="a" bind="{{}}"></template>'
1498 '</template>');
1499 var model = toObservable([]);
1500 recursivelySetTemplateModel(div, model);
1501 deliverChanges(model);
1502
1503 expect(div.nodes.length, 3);
1504
1505 document.getElementById('a').id = 'old-a';
1506 document.getElementById('b').id = 'a';
1507
1508 model..add(1)..add(2);
1509 deliverChanges(model);
1510
1511 expect(div.nodes.length, 7);
1512 expect(div.nodes[4].text, 'b:1');
1513 expect(div.nodes[6].text, 'b:2');
1514 });
1515
1516 observeTest('Content', () {
1517 var div = createTestHtml(
1518 '<template><a></a></template>'
1519 '<template><b></b></template>');
1520 var templateA = div.nodes.first;
1521 var templateB = div.nodes.last;
1522 var contentA = templateA.content;
1523 var contentB = templateB.content;
1524 expect(contentA, isNotNull);
1525
1526 expect(templateA.ownerDocument, isNot(equals(contentA.ownerDocument)));
1527 expect(templateB.ownerDocument, isNot(equals(contentB.ownerDocument)));
1528
1529 expect(templateB.ownerDocument, templateA.ownerDocument);
1530 expect(contentB.ownerDocument, contentA.ownerDocument);
1531
1532 expect(templateA.ownerDocument.window, window);
1533 expect(templateB.ownerDocument.window, window);
1534
1535 expect(contentA.ownerDocument.window, null);
1536 expect(contentB.ownerDocument.window, null);
1537
1538 expect(contentA.nodes.last, contentA.nodes.first);
1539 expect(contentA.nodes.first.tagName, 'A');
1540
1541 expect(contentB.nodes.last, contentB.nodes.first);
1542 expect(contentB.nodes.first.tagName, 'B');
1543 });
1544
1545 observeTest('NestedContent', () {
1546 var div = createTestHtml(
1547 '<template>'
1548 '<template></template>'
1549 '</template>');
1550 var templateA = div.nodes.first;
1551 var templateB = templateA.content.nodes.first;
1552
1553 expect(templateB.ownerDocument, templateA.content.ownerDocument);
1554 expect(templateB.content.ownerDocument, templateA.content.ownerDocument);
1555 });
1556
1557 observeTest('BindShadowDOM', () {
1558 if (ShadowRoot.supported) {
1559 var root = createShadowTestHtml(
1560 '<template bind="{{}}">Hi {{ name }}</template>');
1561 var model = toObservable({'name': 'Leela'});
1562 recursivelySetTemplateModel(root, model);
1563 deliverChanges(model);
1564 expect(root.nodes[1].text, 'Hi Leela');
1565 }
1566 });
1567
1568 observeTest('BindShadowDOM createInstance', () {
1569 if (ShadowRoot.supported) {
1570 var model = toObservable({'name': 'Leela'});
1571 var template = new Element.html('<template>Hi {{ name }}</template>');
1572 var root = createShadowTestHtml('');
1573 root.nodes.add(template.createInstance(model));
1574
1575 performMicrotaskCheckpoint();
1576 expect(root.text, 'Hi Leela');
1577
1578 model['name'] = 'Fry';
1579 performMicrotaskCheckpoint();
1580 expect(root.text, 'Hi Fry');
1581 }
1582 });
1583
1584 observeTest('BindShadowDOM Template Ref', () {
1585 if (ShadowRoot.supported) {
1586 var root = createShadowTestHtml(
1587 '<template id=foo>Hi</template><template bind ref=foo></template>');
1588 recursivelySetTemplateModel(root, toObservable({}));
1589 performMicrotaskCheckpoint();
1590 expect(root.nodes.length, 3);
1591 }
1592 });
1593
1594 // https://github.com/toolkitchen/mdv/issues/8
1595 observeTest('UnbindingInNestedBind', () {
1596 var div = createTestHtml(
1597 '<template bind="{{outer}}" if="{{outer}}" syntax="testHelper">'
1598 '<template bind="{{inner}}" if="{{inner}}">'
1599 '{{ age }}'
1600 '</template>'
1601 '</template>');
1602
1603 var syntax = new UnbindingInNestedBindSyntax();
1604 var model = toObservable({
1605 'outer': {
1606 'inner': {
1607 'age': 42
1608 }
1609 }
1610 });
1611
1612 recursivelySetTemplateModel(div, model, syntax);
1613
1614 deliverChanges(model);
1615 expect(syntax.count, 1);
1616
1617 var inner = model['outer']['inner'];
1618 model['outer'] = null;
1619
1620 deliverChanges(model);
1621 expect(syntax.count, 1);
1622
1623 model['outer'] = toObservable({'inner': {'age': 2}});
1624 syntax.expectedAge = 2;
1625
1626 deliverChanges(model);
1627 expect(syntax.count, 2);
1628 });
1629
1630 // https://github.com/toolkitchen/mdv/issues/8
1631 observeTest('DontCreateInstancesForAbandonedIterators', () {
1632 var div = createTestHtml(
1633 '<template bind="{{}} {{}}">'
1634 '<template bind="{{}}">Foo'
1635 '</template>'
1636 '</template>');
1637 recursivelySetTemplateModel(div, null);
1638 performMicrotaskCheckpoint();
1639 });
1640
1641 observeTest('CreateInstance', () {
1642 var div = createTestHtml(
1643 '<template bind="{{a}}">'
1644 '<template bind="{{b}}">'
1645 '{{ foo }}:{{ replaceme }}'
1646 '</template>'
1647 '</template>');
1648 var outer = div.nodes.first;
1649 var model = toObservable({'b': {'foo': 'bar'}});
1650
1651 var host = new DivElement();
1652 var instance = outer.createInstance(model, new TestBindingSyntax());
1653 expect(outer.content.nodes.first, instance.nodes.first.ref);
1654
1655 host.append(instance);
1656 performMicrotaskCheckpoint();
1657 expect(host.firstChild.nextNode.text, 'bar:replaced');
1658 });
1659
1660 observeTest('Bootstrap', () {
1661 var div = new DivElement();
1662 div.innerHtml =
1663 '<template>'
1664 '<div></div>'
1665 '<template>'
1666 'Hello'
1667 '</template>'
1668 '</template>';
1669
1670 TemplateElement.bootstrap(div);
1671 var template = div.nodes.first;
1672 expect(template.content.nodes.length, 2);
1673 var template2 = template.content.nodes.first.nextNode;
1674 expect(template2.content.nodes.length, 1);
1675 expect(template2.content.nodes.first.text, 'Hello');
1676
1677 template = new Element.tag('template');
1678 template.innerHtml =
1679 '<template>'
1680 '<div></div>'
1681 '<template>'
1682 'Hello'
1683 '</template>'
1684 '</template>';
1685
1686 TemplateElement.bootstrap(template);
1687 template2 = template.content.nodes.first;
1688 expect(template2.content.nodes.length, 2);
1689 var template3 = template2.content.nodes.first.nextNode;
1690 expect(template3.content.nodes.length, 1);
1691 expect(template3.content.nodes.first.text, 'Hello');
1692 });
1693
1694 observeTest('instanceCreated hack', () {
1695 var called = false;
1696
1697 callback(node) {
1698 called = true;
1699 expect(node.nodeType, Node.DOCUMENT_FRAGMENT_NODE);
1700 }
1701 mdv.instanceCreated.add(callback);
1702
1703 var div = createTestHtml('<template bind="{{}}">Foo</template>');
1704 expect(called, false);
1705
1706 recursivelySetTemplateModel(div, null);
1707 performMicrotaskCheckpoint();
1708 expect(called, true);
1709
1710 mdv.instanceCreated.remove(callback);
1711 });
1712 }
1713
1714 class TestBindingSyntax extends BindingDelegate {
1715 getBinding(model, String path, name, node) {
1716 if (path.trim() == 'replaceme') return new ObservableBox('replaced');
1717 return null;
1718 }
1719 }
1720
1721 class UnbindingInNestedBindSyntax extends BindingDelegate {
1722 int expectedAge = 42;
1723 int count = 0;
1724
1725 getBinding(model, path, name, node) {
1726 if (name != 'text' || path != 'age')
1727 return;
1728
1729 expect(model['age'], expectedAge);
1730 count++;
1731 }
1732 }
1733
1734 /**
1735 * Verifies that the model is Observable, then calls
1736 * [performMicrotaskCheckpoint].
1737 */
1738 void deliverChanges(model) {
1739 expectObservable(model);
1740 performMicrotaskCheckpoint();
1741 }
1742
1743 void expectObservable(model) {
1744 if (model is! Observable) {
1745 // This is here to eagerly catch a bug in the test; it means the test
1746 // forgot a toObservable somewhere.
1747 expect(identical(toObservable(model), model), true,
1748 reason: 'model type "${model.runtimeType}" should be observable');
1749 return;
1750 }
1751 if (model is ObservableList) {
1752 for (var item in model) {
1753 expectObservable(item);
1754 }
1755 } else if (model is ObservableMap) {
1756 model.forEach((k, v) {
1757 expectObservable(k);
1758 expectObservable(v);
1759 });
1760 }
1761 }
1762
1763 _deepToSymbol(value) {
1764 if (value is Map) {
1765 var result = new LinkedHashMap();
1766 value.forEach((k, v) {
1767 k = k is String ? new Symbol(k) : _deepToSymbol(k);
1768 result[k] = _deepToSymbol(v);
1769 });
1770 return result;
1771 }
1772 if (value is Iterable) {
1773 return value.map(_deepToSymbol).toList();
1774 }
1775 return value;
1776 }
1777
1778 /**
1779 * Sanitizer which does nothing.
1780 */
1781 class NullTreeSanitizer implements NodeTreeSanitizer {
1782 void sanitizeTree(Node node) {}
1783 }
OLDNEW
« no previous file with comments | « pkg/mdv/test/node_bindings_test.dart ('k') | pkg/polymer/lib/polymer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698