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

Side by Side Diff: tests/html/template_element_test.dart

Issue 14732003: Implement Model-Driven-Views spec for Dart (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: trying upload again Created 7 years, 7 months 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
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 node_bindings_test;
6
7 import 'dart:async';
8 import 'dart:html';
9 import 'dart:math' as math;
10 import 'dart:observe';
11 import '../../pkg/unittest/lib/unittest.dart';
12 import '../../pkg/unittest/lib/html_config.dart';
13
14 // Note: this file ported from
15 // https://github.com/toolkitchen/mdv/blob/master/tests/template_element.js
16 // TODO(jmesserly): submit a small cleanup patch to original. I fixed some
17 // cases where "div" and "t" were unintentionally using the JS global scope;
18 // look for "assertNodesAre".
19
20 main() {
21 useHtmlConfiguration();
22 group('Template Element', templateElementTests);
23 }
24
25 templateElementTests() {
26 var testDiv;
27
28 setUp(() {
29 document.body.nodes.add(testDiv = new DivElement());
30 });
31
32 tearDown(() {
33 testDiv.remove();
34 testDiv = null;
35 });
36
37 createTestHtml(s) {
38 var div = new DivElement();
39 div.innerHtml = s;
40 testDiv.nodes.add(div);
41
42 for (var node in div.queryAll('*')) {
43 if (node.isTemplate) TemplateElement.decorate(node);
44 }
45
46 return div;
47 }
48
49 createShadowTestHtml(s) {
50 var div = new DivElement();
51 var root = div.createShadowRoot();
52 root.innerHtml = s;
53 testDiv.nodes.add(div);
54
55 for (var node in root.queryAll('*')) {
56 if (node.isTemplate) TemplateElement.decorate(node);
57 }
58
59 return root;
60 }
61
62 recursivelySetTemplateModel(element, model) {
63 for (var node in element.queryAll('*')) {
64 if (node.isTemplate) node.model = model;
65 }
66 }
67
68 dispatchEvent(type, target) {
69 target.dispatchEvent(new Event(type, cancelable: false));
70 }
71
72 test('Template', () {
73 var div = createTestHtml(
74 '<template bind={{}}>text</template>');
75 recursivelySetTemplateModel(div, null);
76 deliverChangeRecords();
77 expect(div.nodes.length, 2);
78 expect(div.nodes.last.text, 'text');
79 });
80
81 test('Template-Empty Bind', () {
82 var div = createTestHtml(
83 '<template bind>text</template>');
84 recursivelySetTemplateModel(div, null);
85 deliverChangeRecords();
86 expect(div.nodes.length, 2);
87 expect(div.nodes.last.text, 'text');
88 });
89
90 test('TextTemplateWithNullStringBinding', () {
91 var div = createTestHtml(
92 '<template bind={{}}>a{{b}}c</template>');
93 var model = toObservable({'b': 'B'});
94 recursivelySetTemplateModel(div, model);
95
96 deliverChanges(model);
97 expect(div.nodes.length, 2);
98 expect(div.nodes.last.text, 'aBc');
99
100 model['b'] = 'b';
101 deliverChanges(model);
102 expect(div.nodes.last.text, 'abc');
103
104 model['b'] = null;
105 deliverChanges(model);
106 expect(div.nodes.last.text, 'ac');
107
108 model = null;
109 deliverChanges(model);
110 // setting model isn't observable.
111 expect(div.nodes.last.text, 'ac');
112 });
113
114 test('TextTemplateWithBindingPath', () {
115 var div = createTestHtml(
116 '<template bind="{{ data }}">a{{b}}c</template>');
117 var model = toObservable({ 'data': {'b': 'B'} });
118 recursivelySetTemplateModel(div, model);
119
120 deliverChanges(model);
121 expect(div.nodes.length, 2);
122 expect(div.nodes.last.text, 'aBc');
123
124 model['data']['b'] = 'b';
125 deliverChanges(model);
126 expect(div.nodes.last.text, 'abc');
127
128 model['data'] = toObservable({'b': 'X'});
129 deliverChanges(model);
130 expect(div.nodes.last.text, 'aXc');
131
132 model['data'] = null;
133 deliverChanges(model);
134 expect(div.nodes.last.text, 'ac');
135 });
136
137 test('TextTemplateWithBindingAndConditional', () {
138 var div = createTestHtml(
139 '<template bind="{{}}" if="{{ d }}">a{{b}}c</template>');
140 var model = toObservable({'b': 'B', 'd': 1});
141 recursivelySetTemplateModel(div, model);
142
143 deliverChanges(model);
144 expect(div.nodes.length, 2);
145 expect(div.nodes.last.text, 'aBc');
146
147 model['b'] = 'b';
148 deliverChanges(model);
149 expect(div.nodes.last.text, 'abc');
150
151 // TODO(jmesserly): MDV set this to empty string and relies on JS conversion
152 // rules. Is that intended?
153 model['d'] = null;
154 deliverChanges(model);
155 expect(div.nodes.length, 1);
156
157 model['d'] = 'here';
158 model['b'] = 'd';
159
160 deliverChanges(model);
161 expect(div.nodes.length, 2);
162 expect(div.nodes.last.text, 'adc');
163 });
164
165 test('TemplateWithTextBinding2', () {
166 var div = createTestHtml(
167 '<template bind="{{ b }}">a{{value}}c</template>');
168 expect(div.nodes.length, 1);
169 var model = toObservable({'b': {'value': 'B'}});
170 recursivelySetTemplateModel(div, model);
171
172 deliverChanges(model);
173 expect(div.nodes.length, 2);
174 expect(div.nodes.last.text, 'aBc');
175
176 model['b'] = toObservable({'value': 'b'});
177 deliverChanges(model);
178 expect(div.nodes.last.text, 'abc');
179 });
180
181 test('TemplateWithAttributeBinding', () {
182 var div = createTestHtml(
183 '<template bind="{{}}">'
184 '<div foo="a{{b}}c"></div>'
185 '</template>');
186 var model = toObservable({'b': 'B'});
187 recursivelySetTemplateModel(div, model);
188
189 deliverChanges(model);
190 expect(div.nodes.length, 2);
191 expect(div.nodes.last.attributes['foo'], 'aBc');
192
193 model['b'] = 'b';
194 deliverChanges(model);
195 expect(div.nodes.last.attributes['foo'], 'abc');
196
197 model['b'] = 'X';
198 deliverChanges(model);
199 expect(div.nodes.last.attributes['foo'], 'aXc');
200 });
201
202 test('TemplateWithConditionalBinding', () {
203 var div = createTestHtml(
204 '<template bind="{{}}">'
205 '<div foo?="{{b}}"></div>'
206 '</template>');
207 var model = toObservable({'b': 'b'});
208 recursivelySetTemplateModel(div, model);
209
210 deliverChanges(model);
211 expect(div.nodes.length, 2);
212 expect(div.nodes.last.attributes['foo'], '');
213 expect(div.nodes.last.attributes, isNot(contains('foo?')));
214
215 model['b'] = null;
216 deliverChanges(model);
217 expect(div.nodes.last.attributes, isNot(contains('foo')));
218 });
219
220 test('Repeat', () {
221 var div = createTestHtml(
222 '<template repeat="{{}}"">text</template>');
223
224 var model = toObservable([0, 1, 2]);
225 recursivelySetTemplateModel(div, model);
226
227 deliverChanges(model);
228 expect(div.nodes.length, 4);
229
230 model.length = 1;
231 deliverChanges(model);
232 expect(div.nodes.length, 2);
233
234 model.addAll(toObservable([3, 4]));
235 deliverChanges(model);
236 expect(div.nodes.length, 4);
237
238 model.removeRange(1, 2);
239 deliverChanges(model);
240 expect(div.nodes.length, 3);
241 });
242
243 test('Repeat-Empty', () {
244 var div = createTestHtml(
245 '<template repeat>text</template>');
246
247 var model = toObservable([0, 1, 2]);
248 recursivelySetTemplateModel(div, model);
249
250 deliverChanges(model);
251 expect(div.nodes.length, 4);
252
253 model.length = 1;
254 deliverChanges(model);
255 expect(div.nodes.length, 2);
256
257 model.addAll(toObservable([3, 4]));
258 deliverChanges(model);
259 expect(div.nodes.length, 4);
260
261 model.removeRange(1, 2);
262 deliverChanges(model);
263 expect(div.nodes.length, 3);
264 });
265
266 test('Removal from iteration needs to unbind', () {
267 var div = createTestHtml(
268 '<template repeat="{{}}"><a>{{v}}</a></template>');
269 var model = toObservable([{'v': 0}, {'v': 1}, {'v': 2}, {'v': 3},
270 {'v': 4}]);
271 recursivelySetTemplateModel(div, model);
272 deliverChanges(model);
273
274 var nodes = div.nodes.skip(1).toList();
275 var vs = model.toList();
276
277 for (var i = 0; i < 5; i++) {
278 expect(nodes[i].text, '$i');
279 }
280
281 model.length = 3;
282 deliverChanges(model);
283 for (var i = 0; i < 5; i++) {
284 expect(nodes[i].text, '$i');
285 }
286
287 vs[3]['v'] = 33;
288 vs[4]['v'] = 44;
289 deliverChanges(model);
290 for (var i = 0; i < 5; i++) {
291 expect(nodes[i].text, '$i');
292 }
293 });
294
295 test('DOM Stability on Iteration', () {
296 var div = createTestHtml(
297 '<template repeat="{{}}">{{}}</template>');
298 var model = toObservable([1, 2, 3, 4, 5]);
299 recursivelySetTemplateModel(div, model);
300
301 deliverChanges(model);
302
303 // Note: the node at index 0 is the <template>.
304 var nodes = div.nodes.toList();
305 expect(nodes.length, 6, reason: 'list has 5 items');
306
307 model.removeAt(0);
308 model.removeLast();
309
310 deliverChanges(model);
311 expect(div.nodes.length, 4, reason: 'list has 3 items');
312 expect(identical(div.nodes[1], nodes[2]), true, reason: '2 not removed');
313 expect(identical(div.nodes[2], nodes[3]), true, reason: '3 not removed');
314 expect(identical(div.nodes[3], nodes[4]), true, reason: '4 not removed');
315
316 model.insert(0, 5);
317 model[2] = 6;
318 model.add(7);
319
320 deliverChanges(model);
321
322 expect(div.nodes.length, 6, reason: 'list has 5 items');
323 expect(nodes.contains(div.nodes[1]), false, reason: '5 is a new node');
324 expect(identical(div.nodes[2], nodes[2]), true);
325 expect(nodes.contains(div.nodes[3]), false, reason: '6 is a new node');
326 expect(identical(div.nodes[4], nodes[4]), true);
327 expect(nodes.contains(div.nodes[5]), false, reason: '7 is a new node');
328
329 nodes = div.nodes.toList();
330
331 model.insert(2, 8);
332
333 deliverChanges(model);
334
335 expect(div.nodes.length, 7, reason: 'list has 6 items');
336 expect(identical(div.nodes[1], nodes[1]), true);
337 expect(identical(div.nodes[2], nodes[2]), true);
338 expect(nodes.contains(div.nodes[3]), false, reason: '8 is a new node');
339 expect(identical(div.nodes[4], nodes[3]), true);
340 expect(identical(div.nodes[5], nodes[4]), true);
341 expect(identical(div.nodes[6], nodes[5]), true);
342 });
343
344 test('Repeat2', () {
345 var div = createTestHtml(
346 '<template repeat="{{}}">{{value}}</template>');
347 expect(div.nodes.length, 1);
348
349 var model = toObservable([
350 {'value': 0},
351 {'value': 1},
352 {'value': 2}
353 ]);
354 recursivelySetTemplateModel(div, model);
355
356 deliverChanges(model);
357 expect(div.nodes.length, 4);
358 expect(div.nodes[1].text, '0');
359 expect(div.nodes[2].text, '1');
360 expect(div.nodes[3].text, '2');
361
362 model[1]['value'] = 'One';
363 deliverChanges(model);
364 expect(div.nodes.length, 4);
365 expect(div.nodes[1].text, '0');
366 expect(div.nodes[2].text, 'One');
367 expect(div.nodes[3].text, '2');
368
369 model.replaceRange(0, 1, toObservable([{'value': 'Zero'}]));
370 deliverChanges(model);
371 expect(div.nodes.length, 4);
372 expect(div.nodes[1].text, 'Zero');
373 expect(div.nodes[2].text, 'One');
374 expect(div.nodes[3].text, '2');
375 });
376
377 test('TemplateWithInputValue', () {
378 var div = createTestHtml(
379 '<template bind="{{}}">'
380 '<input value="{{x}}">'
381 '</template>');
382 var model = toObservable({'x': 'hi'});
383 recursivelySetTemplateModel(div, model);
384
385 deliverChanges(model);
386 expect(div.nodes.length, 2);
387 expect(div.nodes.last.value, 'hi');
388
389 model['x'] = 'bye';
390 expect(div.nodes.last.value, 'hi');
391 deliverChanges(model);
392 expect(div.nodes.last.value, 'bye');
393
394 div.nodes.last.value = 'hello';
395 dispatchEvent('input', div.nodes.last);
396 expect(model['x'], 'hello');
397 deliverChanges(model);
398 expect(div.nodes.last.value, 'hello');
399 });
400
401 //////////////////////////////////////////////////////////////////////////////
402
403 test('Decorated', () {
404 var div = createTestHtml(
405 '<template bind="{{ XX }}" id="t1">'
406 '<p>Crew member: {{name}}, Job title: {{title}}</p>'
407 '</template>'
408 '<template bind="{{ XY }}" id="t2" ref="t1"></template>');
409
410 var model = toObservable({
411 'XX': {'name': 'Leela', 'title': 'Captain'},
412 'XY': {'name': 'Fry', 'title': 'Delivery boy'},
413 'XZ': {'name': 'Zoidberg', 'title': 'Doctor'}
414 });
415 recursivelySetTemplateModel(div, model);
416
417 deliverChanges(model);
418
419 var t1 = document.getElementById('t1');
420 var instance = t1.nextElementSibling;
421 expect(instance.text, 'Crew member: Leela, Job title: Captain');
422
423 var t2 = document.getElementById('t2');
424 instance = t2.nextElementSibling;
425 expect(instance.text, 'Crew member: Fry, Job title: Delivery boy');
426
427 expect(div.children.length, 4);
428 expect(div.nodes.length, 4);
429
430 expect(div.nodes[1].tagName, 'P');
431 expect(div.nodes[3].tagName, 'P');
432 });
433
434 test('DefaultStyles', () {
435 var t = new Element.tag('template');
436 TemplateElement.decorate(t);
437
438 document.body.nodes.add(t);
439 expect(t.getComputedStyle().display, 'none');
440
441 t.remove();
442 });
443
444
445 test('Bind', () {
446 var div = createTestHtml('<template bind="{{}}">Hi {{ name }}</template>');
447 var model = toObservable({'name': 'Leela'});
448 recursivelySetTemplateModel(div, model);
449
450 deliverChanges(model);
451 expect(div.nodes[1].text, 'Hi Leela');
452 });
453
454 test('BindImperative', () {
455 var div = createTestHtml(
456 '<template>'
457 'Hi {{ name }}'
458 '</template>');
459 var t = div.nodes.first;
460
461 var model = toObservable({'name': 'Leela'});
462 t.bind('bind', model, '');
463
464 deliverChanges(model);
465 expect(div.nodes[1].text, 'Hi Leela');
466 });
467
468 test('BindPlaceHolderHasNewLine', () {
469 var div = createTestHtml('<template bind="{{}}">Hi {{\nname\n}}</template>') ;
470 var model = toObservable({'name': 'Leela'});
471 recursivelySetTemplateModel(div, model);
472
473 deliverChanges(model);
474 expect(div.nodes[1].text, 'Hi Leela');
475 });
476
477 test('BindWithRef', () {
478 var id = 't${new math.Random().nextDouble()}';
479 var div = createTestHtml(
480 '<template id="$id">'
481 'Hi {{ name }}'
482 '</template>'
483 '<template ref="$id" bind="{{}}"></template>');
484
485 var t1 = div.nodes.first;
486 var t2 = div.nodes[1];
487
488 expect(t2.ref, t1);
489
490 var model = toObservable({'name': 'Fry'});
491 recursivelySetTemplateModel(div, model);
492
493 deliverChanges(model);
494 expect(t2.nextNode.text, 'Hi Fry');
495 });
496
497 test('BindChanged', () {
498 var model = toObservable({
499 'XX': {'name': 'Leela', 'title': 'Captain'},
500 'XY': {'name': 'Fry', 'title': 'Delivery boy'},
501 'XZ': {'name': 'Zoidberg', 'title': 'Doctor'}
502 });
503
504 var div = createTestHtml(
505 '<template bind="{{ XX }}">Hi {{ name }}</template>');
506
507 recursivelySetTemplateModel(div, model);
508
509 var t = div.nodes.first;
510 deliverChanges(model);
511
512 expect(div.nodes.length, 2);
513 expect(t.nextNode.text, 'Hi Leela');
514
515 t.bind('bind', model, 'XZ');
516 deliverChanges(model);
517
518 expect(div.nodes.length, 2);
519 expect(t.nextNode.text, 'Hi Zoidberg');
520 });
521
522 assertNodesAre(div, [arguments]) {
523 var expectedLength = arguments.length;
524 expect(div.nodes.length, expectedLength + 1);
525
526 for (var i = 0; i < arguments.length; i++) {
527 var targetNode = div.nodes[i + 1];
528 expect(targetNode.text, arguments[i]);
529 }
530 }
531
532 test('Repeat3', () {
533 var div = createTestHtml(
534 '<template repeat="{{ contacts }}">Hi {{ name }}</template>');
535 var t = div.nodes.first;
536
537 var m = toObservable({
538 'contacts': [
539 {'name': 'Raf'},
540 {'name': 'Arv'},
541 {'name': 'Neal'}
542 ]
543 });
544
545 recursivelySetTemplateModel(div, m);
546 deliverChanges(m);
547
548 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']);
549
550 m['contacts'].add(toObservable({'name': 'Alex'}));
551 deliverChanges(m);
552 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal', 'Hi Alex']);
553
554 m['contacts'].replaceRange(0, 2,
555 toObservable([{'name': 'Rafael'}, {'name': 'Erik'}]));
556 deliverChanges(m);
557 assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Neal', 'Hi Alex']);
558
559 m['contacts'].removeRange(1, 3);
560 deliverChanges(m);
561 assertNodesAre(div, ['Hi Rafael', 'Hi Alex']);
562
563 m['contacts'].insertAll(1,
564 toObservable([{'name': 'Erik'}, {'name': 'Dimitri'}]));
565 deliverChanges(m);
566 assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Dimitri', 'Hi Alex']);
567
568 m['contacts'].replaceRange(0, 1,
569 toObservable([{'name': 'Tab'}, {'name': 'Neal'}]));
570 deliverChanges(m);
571 assertNodesAre(div, ['Hi Tab', 'Hi Neal', 'Hi Erik', 'Hi Dimitri', 'Hi Alex' ]);
572
573 m['contacts'] = toObservable([{'name': 'Alex'}]);
574 deliverChanges(m);
575 assertNodesAre(div, ['Hi Alex']);
576
577 m['contacts'].length = 0;
578 deliverChanges(m);
579 assertNodesAre(div, []);
580 });
581
582 test('RepeatModelSet', () {
583 var div = createTestHtml(
584 '<template repeat="{{ contacts }}">'
585 'Hi {{ name }}'
586 '</template>');
587 var m = toObservable({
588 'contacts': [
589 {'name': 'Raf'},
590 {'name': 'Arv'},
591 {'name': 'Neal'}
592 ]
593 });
594 recursivelySetTemplateModel(div, m);
595
596 deliverChanges(m);
597 var t = div.nodes.first;
598
599 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']);
600 });
601
602 test('RepeatEmptyPath', () {
603 var div = createTestHtml('<template repeat="{{}}">Hi {{ name }}</template>') ;
604 var t = div.nodes.first;
605
606 var m = toObservable([
607 {'name': 'Raf'},
608 {'name': 'Arv'},
609 {'name': 'Neal'}
610 ]);
611 recursivelySetTemplateModel(div, m);
612
613 deliverChanges(m);
614
615 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']);
616
617 m.add(toObservable({'name': 'Alex'}));
618 deliverChanges(m);
619 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal', 'Hi Alex']);
620
621 m.replaceRange(0, 2, toObservable([{'name': 'Rafael'}, {'name': 'Erik'}]));
622 deliverChanges(m);
623 assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Neal', 'Hi Alex']);
624
625 m.removeRange(1, 3);
626 deliverChanges(m);
627 assertNodesAre(div, ['Hi Rafael', 'Hi Alex']);
628
629 m.insertAll(1, toObservable([{'name': 'Erik'}, {'name': 'Dimitri'}]));
630 deliverChanges(m);
631 assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Dimitri', 'Hi Alex']);
632
633 m.replaceRange(0, 1, toObservable([{'name': 'Tab'}, {'name': 'Neal'}]));
634 deliverChanges(m);
635 assertNodesAre(div, ['Hi Tab', 'Hi Neal', 'Hi Erik', 'Hi Dimitri', 'Hi Alex' ]);
636
637 m.length = 0;
638 m.add(toObservable({'name': 'Alex'}));
639 deliverChanges(m);
640 assertNodesAre(div, ['Hi Alex']);
641 });
642
643 test('RepeatNullModel', () {
644 var div = createTestHtml('<template repeat="{{}}">Hi {{ name }}</template>') ;
645 var t = div.nodes.first;
646
647 var m = null;
648 recursivelySetTemplateModel(div, m);
649
650 expect(div.nodes.length, 1);
651
652 t.attributes['iterate'] = '';
653 m = toObservable({});
654 recursivelySetTemplateModel(div, m);
655
656 deliverChanges(m);
657 expect(div.nodes.length, 1);
658 });
659
660 test('RepeatReuse', () {
661 var div = createTestHtml('<template repeat="{{}}">Hi {{ name }}</template>') ;
662 var t = div.nodes.first;
663
664 var m = toObservable([
665 {'name': 'Raf'},
666 {'name': 'Arv'},
667 {'name': 'Neal'}
668 ]);
669 recursivelySetTemplateModel(div, m);
670 deliverChanges(m);
671
672 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']);
673 var node1 = div.nodes[1];
674 var node2 = div.nodes[2];
675 var node3 = div.nodes[3];
676
677 m.replaceRange(1, 2, toObservable([{'name': 'Erik'}]));
678 deliverChanges(m);
679 assertNodesAre(div, ['Hi Raf', 'Hi Erik', 'Hi Neal']);
680 expect(div.nodes[1], node1,
681 reason: 'model[0] did not change so the node should not have changed');
682 expect(div.nodes[2], isNot(equals(node2)),
683 reason: 'Should not reuse when replacing');
684 expect(div.nodes[3], node3,
685 reason: 'model[2] did not change so the node should not have changed');
686
687 node2 = div.nodes[2];
688 m.insert(0, toObservable({'name': 'Alex'}));
689 deliverChanges(m);
690 assertNodesAre(div, ['Hi Alex', 'Hi Raf', 'Hi Erik', 'Hi Neal']);
691 });
692
693 test('TwoLevelsDeepBug', () {
694 var div = createTestHtml(
695 '<template bind="{{}}"><span><span>{{ foo }}</span></span></template>');
696
697 var model = toObservable({'foo': 'bar'});
698 recursivelySetTemplateModel(div, model);
699 deliverChanges(model);
700
701 expect(div.nodes[1].nodes[0].nodes[0].text, 'bar');
702 });
703
704 test('Checked', () {
705 var div = createTestHtml(
706 '<template>'
707 '<input type="checkbox" checked="{{a}}">'
708 '</template>');
709 var t = div.nodes.first;
710 var m = toObservable({
711 'a': true
712 });
713 t.bind('bind', m, '');
714 deliverChanges(m);
715
716 var instanceInput = t.nextNode;
717 expect(instanceInput.checked, true);
718
719 instanceInput.click();
720 expect(instanceInput.checked, false);
721
722 instanceInput.click();
723 expect(instanceInput.checked, true);
724 });
725
726 nestedHelper(s, start) {
727 var div = createTestHtml(s);
728
729 var m = toObservable({
730 'a': {
731 'b': 1,
732 'c': {'d': 2}
733 },
734 });
735
736 recursivelySetTemplateModel(div, m);
737 deliverChanges(m);
738
739 var i = start;
740 expect(div.nodes[i++].text, '1');
741 expect(div.nodes[i++].tagName, 'TEMPLATE');
742 expect(div.nodes[i++].text, '2');
743
744 m['a']['b'] = 11;
745 deliverChanges(m);
746 expect(div.nodes[start].text, '11');
747
748 m['a']['c'] = toObservable({'d': 22});
749 deliverChanges(m);
750 expect(div.nodes[start + 2].text, '22');
751 }
752
753 test('Nested', () {
754 nestedHelper(
755 '<template bind="{{a}}">'
756 '{{b}}'
757 '<template bind="{{c}}">'
758 '{{d}}'
759 '</template>'
760 '</template>', 1);
761 });
762
763 test('NestedWithRef', () {
764 nestedHelper(
765 '<template id="inner">{{d}}</template>'
766 '<template id="outer" bind="{{a}}">'
767 '{{b}}'
768 '<template ref="inner" bind="{{c}}"></template>'
769 '</template>', 2);
770 });
771
772 nestedIterateInstantiateHelper(s, start) {
773 var div = createTestHtml(s);
774
775 var m = toObservable({
776 'a': [
777 {
778 'b': 1,
779 'c': {'d': 11}
780 },
781 {
782 'b': 2,
783 'c': {'d': 22}
784 }
785 ]
786 });
787
788 recursivelySetTemplateModel(div, m);
789 deliverChanges(m);
790
791 var i = start;
792 expect(div.nodes[i++].text, '1');
793 expect(div.nodes[i++].tagName, 'TEMPLATE');
794 expect(div.nodes[i++].text, '11');
795 expect(div.nodes[i++].text, '2');
796 expect(div.nodes[i++].tagName, 'TEMPLATE');
797 expect(div.nodes[i++].text, '22');
798
799 m['a'][1] = toObservable({
800 'b': 3,
801 'c': {'d': 33}
802 });
803
804 deliverChanges(m);
805 expect(div.nodes[start + 3].text, '3');
806 expect(div.nodes[start + 5].text, '33');
807 }
808
809 test('NestedRepeatBind', () {
810 nestedIterateInstantiateHelper(
811 '<template repeat="{{a}}">'
812 '{{b}}'
813 '<template bind="{{c}}">'
814 '{{d}}'
815 '</template>'
816 '</template>', 1);
817 });
818
819 test('NestedRepeatBindWithRef', () {
820 nestedIterateInstantiateHelper(
821 '<template id="inner">'
822 '{{d}}'
823 '</template>'
824 '<template repeat="{{a}}">'
825 '{{b}}'
826 '<template ref="inner" bind="{{c}}"></template>'
827 '</template>', 2);
828 });
829
830 nestedIterateIterateHelper(s, start) {
831 var div = createTestHtml(s);
832
833 var m = toObservable({
834 'a': [
835 {
836 'b': 1,
837 'c': [{'d': 11}, {'d': 12}]
838 },
839 {
840 'b': 2,
841 'c': [{'d': 21}, {'d': 22}]
842 }
843 ]
844 });
845
846 recursivelySetTemplateModel(div, m);
847 deliverChanges(m);
848
849 var i = start;
850 expect(div.nodes[i++].text, '1');
851 expect(div.nodes[i++].tagName, 'TEMPLATE');
852 expect(div.nodes[i++].text, '11');
853 expect(div.nodes[i++].text, '12');
854 expect(div.nodes[i++].text, '2');
855 expect(div.nodes[i++].tagName, 'TEMPLATE');
856 expect(div.nodes[i++].text, '21');
857 expect(div.nodes[i++].text, '22');
858
859 m['a'][1] = toObservable({
860 'b': 3,
861 'c': [{'d': 31}, {'d': 32}, {'d': 33}]
862 });
863
864 i = start + 4;
865 deliverChanges(m);
866 expect(div.nodes[start + 4].text, '3');
867 expect(div.nodes[start + 6].text, '31');
868 expect(div.nodes[start + 7].text, '32');
869 expect(div.nodes[start + 8].text, '33');
870 }
871
872 test('NestedRepeatBind', () {
873 nestedIterateIterateHelper(
874 '<template repeat="{{a}}">'
875 '{{b}}'
876 '<template repeat="{{c}}">'
877 '{{d}}'
878 '</template>'
879 '</template>', 1);
880 });
881
882 test('NestedRepeatRepeatWithRef', () {
883 nestedIterateIterateHelper(
884 '<template id="inner">'
885 '{{d}}'
886 '</template>'
887 '<template repeat="{{a}}">'
888 '{{b}}'
889 '<template ref="inner" repeat="{{c}}"></template>'
890 '</template>', 2);
891 });
892
893 test('NestedRepeatSelfRef', () {
894 var div = createTestHtml(
895 '<template id="t" repeat="{{}}">'
896 '{{name}}'
897 '<template ref="t" repeat="{{items}}"></template>'
898 '</template>');
899
900 var m = toObservable([
901 {
902 'name': 'Item 1',
903 'items': [
904 {
905 'name': 'Item 1.1',
906 'items': [
907 {
908 'name': 'Item 1.1.1',
909 'items': []
910 }
911 ]
912 },
913 {
914 'name': 'Item 1.2'
915 }
916 ]
917 },
918 {
919 'name': 'Item 2',
920 'items': []
921 },
922 ]);
923
924 recursivelySetTemplateModel(div, m);
925 deliverChanges(m);
926
927 var i = 1;
928 expect(div.nodes[i++].text, 'Item 1');
929 expect(div.nodes[i++].tagName, 'TEMPLATE');
930 expect(div.nodes[i++].text, 'Item 1.1');
931 expect(div.nodes[i++].tagName, 'TEMPLATE');
932 expect(div.nodes[i++].text, 'Item 1.1.1');
933 expect(div.nodes[i++].tagName, 'TEMPLATE');
934 expect(div.nodes[i++].text, 'Item 1.2');
935 expect(div.nodes[i++].tagName, 'TEMPLATE');
936 expect(div.nodes[i++].text, 'Item 2');
937
938 m[0] = toObservable({
939 'name': 'Item 1 changed'
940 });
941
942 i = 1;
943 deliverChanges(m);
944 expect(div.nodes[i++].text, 'Item 1 changed');
945 expect(div.nodes[i++].tagName, 'TEMPLATE');
946 expect(div.nodes[i++].text, 'Item 2');
947 });
948
949 test('NestedIterateTableMixedSemanticNative', () {
950 if (!TemplateElement.supported) return;
951
952 var div = createTestHtml(
953 '<table><tbody>'
954 '<template repeat="{{}}">'
955 '<tr>'
956 '<td template repeat="{{}}" class="{{ val }}">{{ val }}</td>'
957 '</tr>'
958 '</template>'
959 '</tbody></table>');
960
961 var m = toObservable([
962 [{ 'val': 0 }, { 'val': 1 }],
963 [{ 'val': 2 }, { 'val': 3 }]
964 ]);
965
966 recursivelySetTemplateModel(div, m);
967 deliverChanges(m);
968
969 var i = 1;
970 var tbody = div.nodes[0].nodes[0];
971
972 // 1 for the <tr template>, 2 * (1 tr)
973 expect(tbody.nodes.length, 3);
974
975 // 1 for the <td template>, 2 * (1 td)
976 expect(tbody.nodes[1].nodes.length, 3);
977 expect(tbody.nodes[1].nodes[1].text, '0');
978 expect(tbody.nodes[1].nodes[2].text, '1');
979
980 // 1 for the <td template>, 2 * (1 td)
981 expect(tbody.nodes[2].nodes.length, 3);
982 expect(tbody.nodes[2].nodes[1].text, '2');
983 expect(tbody.nodes[2].nodes[2].text, '3');
984
985 // Asset the 'class' binding is retained on the semantic template (just
986 // check the last one).
987 expect(tbody.nodes[2].nodes[2].attributes["class"], '3');
988 });
989
990 test('NestedIterateTable', () {
991 var div = createTestHtml(
992 '<table><tbody>'
993 '<tr template repeat="{{}}">'
994 '<td template repeat="{{}}" class="{{ val }}">{{ val }}</td>'
995 '</tr>'
996 '</tbody></table>');
997
998 var m = toObservable([
999 [{ 'val': 0 }, { 'val': 1 }],
1000 [{ 'val': 2 }, { 'val': 3 }]
1001 ]);
1002
1003 recursivelySetTemplateModel(div, m);
1004 deliverChanges(m);
1005
1006 var i = 1;
1007 var tbody = div.nodes[0].nodes[0];
1008
1009 // 1 for the <tr template>, 2 * (1 tr)
1010 expect(tbody.nodes.length, 3);
1011
1012 // 1 for the <td template>, 2 * (1 td)
1013 expect(tbody.nodes[1].nodes.length, 3);
1014 expect(tbody.nodes[1].nodes[1].text, '0');
1015 expect(tbody.nodes[1].nodes[2].text, '1');
1016
1017 // 1 for the <td template>, 2 * (1 td)
1018 expect(tbody.nodes[2].nodes.length, 3);
1019 expect(tbody.nodes[2].nodes[1].text, '2');
1020 expect(tbody.nodes[2].nodes[2].text, '3');
1021
1022 // Asset the 'class' binding is retained on the semantic template (just chec k
1023 // the last one).
1024 expect(tbody.nodes[2].nodes[2].attributes['class'], '3');
1025 });
1026
1027 test('NestedRepeatDeletionOfMultipleSubTemplates', () {
1028 var div = createTestHtml(
1029 '<ul>'
1030 '<template repeat="{{}}" id=t1>'
1031 '<li>{{name}}'
1032 '<ul>'
1033 '<template ref=t1 repaet="{{items}}"></template>'
1034 '</ul>'
1035 '</li>'
1036 '</template>'
1037 '</ul>');
1038
1039 var m = toObservable([
1040 {
1041 'name': 'Item 1',
1042 'items': [
1043 {
1044 'name': 'Item 1.1'
1045 }
1046 ]
1047 }
1048 ]);
1049
1050 recursivelySetTemplateModel(div, m);
1051
1052 deliverChanges(m);
1053 m.removeAt(0);
1054 deliverChanges(m);
1055 });
1056
1057 test('DeepNested', () {
1058 var div = createTestHtml(
1059 '<template bind="{{a}}">'
1060 '<p>'
1061 '<template bind="{{b}}">'
1062 '{{ c }}'
1063 '</template>'
1064 '</p>'
1065 '</template>');
1066
1067 var m = toObservable({
1068 'a': {
1069 'b': {
1070 'c': 42
1071 }
1072 }
1073 });
1074 recursivelySetTemplateModel(div, m);
1075 deliverChanges(m);
1076
1077 expect(div.nodes[1].tagName, 'P');
1078 expect(div.nodes[1].nodes.first.tagName, 'TEMPLATE');
1079 expect(div.nodes[1].nodes[1].text, '42');
1080 });
1081
1082 test('TemplateContentRemoved', () {
1083 var div = createTestHtml('<template bind="{{}}">{{ }}</template>');
1084 var model = 42;
1085
1086 recursivelySetTemplateModel(div, model);
1087 deliverChanges(model);
1088 expect(div.nodes[1].text, '42');
1089 expect(div.nodes[0].text, '');
1090 });
1091
1092 test('TemplateContentRemovedEmptyArray', () {
1093 var div = createTestHtml('<template iterate>Remove me</template>');
1094 var model = toObservable([]);
1095
1096 recursivelySetTemplateModel(div, model);
1097 deliverChanges(model);
1098 expect(div.nodes.length, 1);
1099 expect(div.nodes[0].text, '');
1100 });
1101
1102 test('TemplateContentRemovedNested', () {
1103 var div = createTestHtml(
1104 '<template bind="{{}}">'
1105 '{{ a }}'
1106 '<template bind="{{}}">'
1107 '{{ b }}'
1108 '</template>'
1109 '</template>');
1110
1111 var model = toObservable({
1112 'a': 1,
1113 'b': 2
1114 });
1115 recursivelySetTemplateModel(div, model);
1116 deliverChanges(model);
1117
1118 expect(div.nodes[0].text, '');
1119 expect(div.nodes[1].text, '1');
1120 expect(div.nodes[2].text, '');
1121 expect(div.nodes[3].text, '2');
1122 });
1123
1124 test('BindWithUndefinedModel', () {
1125 var div = createTestHtml(
1126 '<template bind="{{}}" if="{{}}">{{ a }}</template>');
1127
1128 var model = toObservable({'a': 42});
1129 recursivelySetTemplateModel(div, model);
1130 deliverChanges(model);
1131 expect(div.nodes[1].text, '42');
1132
1133 model = null;
1134 recursivelySetTemplateModel(div, model);
1135 deliverChanges(model);
1136 expect(div.nodes.length, 1);
1137
1138 model = toObservable({'a': 42});
1139 recursivelySetTemplateModel(div, model);
1140 deliverChanges(model);
1141 expect(div.nodes[1].text, '42');
1142 });
1143
1144 test('BindNested', () {
1145 var div = createTestHtml(
1146 '<template bind="{{}}">'
1147 'Name: {{ name }}'
1148 '<template bind="{{wife}}" if="{{wife}}">'
1149 'Wife: {{ name }}'
1150 '</template>'
1151 '<template bind="{{child}}" if="{{child}}">'
1152 'Child: {{ name }}'
1153 '</template>'
1154 '</template>');
1155
1156 var m = toObservable({
1157 'name': 'Hermes',
1158 'wife': {
1159 'name': 'LaBarbara'
1160 }
1161 });
1162 recursivelySetTemplateModel(div, m);
1163 deliverChanges(m);
1164
1165 expect(div.nodes.length, 5);
1166 expect(div.nodes[1].text, 'Name: Hermes');
1167 expect(div.nodes[3].text, 'Wife: LaBarbara');
1168
1169 m['child'] = toObservable({'name': 'Dwight'});
1170 deliverChanges(m);
1171 expect(div.nodes.length, 6);
1172 expect(div.nodes[5].text, 'Child: Dwight');
1173
1174 m.remove('wife');
1175 deliverChanges(m);
1176 expect(div.nodes.length, 5);
1177 expect(div.nodes[4].text, 'Child: Dwight');
1178 });
1179
1180 test('BindRecursive', () {
1181 var div = createTestHtml(
1182 '<template bind="{{}}" if="{{}}" id="t">'
1183 'Name: {{ name }}'
1184 '<template bind="{{friend}}" if="{{friend}}" ref="t"></template>'
1185 '</template>');
1186
1187 var m = toObservable({
1188 'name': 'Fry',
1189 'friend': {
1190 'name': 'Bender'
1191 }
1192 });
1193 recursivelySetTemplateModel(div, m);
1194 deliverChanges(m);
1195
1196 expect(div.nodes.length, 5);
1197 expect(div.nodes[1].text, 'Name: Fry');
1198 expect(div.nodes[3].text, 'Name: Bender');
1199
1200 m['friend']['friend'] = toObservable({'name': 'Leela'});
1201 deliverChanges(m);
1202 expect(div.nodes.length, 7);
1203 expect(div.nodes[5].text, 'Name: Leela');
1204
1205 m['friend'] = toObservable({'name': 'Leela'});
1206 deliverChanges(m);
1207 expect(div.nodes.length, 5);
1208 expect(div.nodes[3].text, 'Name: Leela');
1209 });
1210
1211 test('ChangeFromBindToRepeat', () {
1212 var div = createTestHtml(
1213 '<template bind="{{a}}">'
1214 '{{ length }}'
1215 '</template>');
1216 var template = div.nodes.first;
1217
1218 var m = toObservable({
1219 'a': [
1220 {'length': 0},
1221 {
1222 'length': 1,
1223 'b': {'length': 4}
1224 },
1225 {'length': 2}
1226 ]
1227 });
1228 recursivelySetTemplateModel(div, m);
1229 deliverChanges(m);
1230
1231 expect(div.nodes.length, 2);
1232 expect(div.nodes[1].text, '3');
1233
1234 template.unbind('bind');
1235 template.bind('repeat', m, 'a');
1236 deliverChanges(m);
1237 expect(div.nodes.length, 4);
1238 expect(div.nodes[1].text, '0');
1239 expect(div.nodes[2].text, '1');
1240 expect(div.nodes[3].text, '2');
1241
1242 template.unbind('repeat');
1243 template.bind('bind', m, 'a.1.b');
1244
1245 deliverChanges(m);
1246 expect(div.nodes.length, 2);
1247 expect(div.nodes[1].text, '4');
1248 });
1249
1250 test('ChangeRefId', () {
1251 var div = createTestHtml(
1252 '<template id="a">a:{{ }}</template>'
1253 '<template id="b">b:{{ }}</template>'
1254 '<template repeat="{{}}">'
1255 '<template ref="a" bind="{{}}"></template>'
1256 '</template>');
1257 var model = toObservable([]);
1258 recursivelySetTemplateModel(div, model);
1259 deliverChanges(model);
1260
1261 expect(div.nodes.length, 3);
1262
1263 document.getElementById('a').id = 'old-a';
1264 document.getElementById('b').id = 'a';
1265
1266 model..add(1)..add(2);
1267 deliverChanges(model);
1268
1269 expect(div.nodes.length, 7);
1270 expect(div.nodes[4].text, 'b:1');
1271 expect(div.nodes[6].text, 'b:2');
1272 });
1273
1274 test('Content', () {
1275 var div = createTestHtml(
1276 '<template><a></a></template>'
1277 '<template><b></b></template>');
1278 var templateA = div.nodes.first;
1279 var templateB = div.nodes.last;
1280 var contentA = templateA.content;
1281 var contentB = templateB.content;
1282 expect(contentA, isNotNull);
1283
1284 expect(templateA.document, isNot(equals(contentA.document)));
1285 expect(templateB.document, isNot(equals(contentB.document)));
1286
1287 expect(templateB.document, templateA.document);
1288 expect(contentB.document, contentA.document);
1289
1290 expect(templateA.document.window, window);
1291 expect(templateB.document.window, window);
1292
1293 expect(contentA.document.window, null);
1294 expect(contentB.document.window, null);
1295
1296 expect(contentA.nodes.last, contentA.nodes.first);
1297 expect(contentA.nodes.first.tagName, 'A');
1298
1299 expect(contentB.nodes.last, contentB.nodes.first);
1300 expect(contentB.nodes.first.tagName, 'B');
1301 });
1302
1303 test('NestedContent', () {
1304 var div = createTestHtml(
1305 '<template>'
1306 '<template></template>'
1307 '</template>');
1308 var templateA = div.nodes.first;
1309 var templateB = templateA.content.nodes.first;
1310
1311 expect(templateB.document, templateA.content.document);
1312 expect(templateB.content.document, templateA.content.document);
1313 });
1314
1315 test('BindShadowDOM', () {
1316 if (ShadowRoot.supported) {
1317 var root = createShadowTestHtml(
1318 '<template bind="{{}}">Hi {{ name }}</template>');
1319 var model = toObservable({'name': 'Leela'});
1320 recursivelySetTemplateModel(root, model);
1321 deliverChanges(model);
1322 expect(root.nodes[1].text, 'Hi Leela');
1323 }
1324 });
1325
1326 // https://github.com/toolkitchen/mdv/issues/8
1327 test('UnbindingInNestedBind', () {
1328 var div = createTestHtml(
1329 '<template bind="{{outer}}" if="{{outer}}" syntax="testHelper">'
1330 '<template bind="{{inner}}" if="{{inner}}">'
1331 '{{ age }}'
1332 '</template>'
1333 '</template>');
1334
1335 var syntax = new UnbindingInNestedBindSyntax();
1336 TemplateElement.syntax['testHelper'] = syntax;
1337 try {
1338 var model = toObservable({
1339 'outer': {
1340 'inner': {
1341 'age': 42
1342 }
1343 }
1344 });
1345
1346 recursivelySetTemplateModel(div, model);
1347
1348 deliverChanges(model);
1349 expect(syntax.count, 1);
1350
1351 var inner = model['outer']['inner'];
1352 model['outer'] = null;
1353
1354 deliverChanges(model);
1355 expect(syntax.count, 1);
1356
1357 model['outer'] = toObservable({'inner': {'age': 2}});
1358 syntax.expectedAge = 2;
1359
1360 deliverChanges(model);
1361 expect(syntax.count, 2);
1362 } finally {
1363 TemplateElement.syntax.remove('testHelper');
1364 }
1365 });
1366
1367 // https://github.com/toolkitchen/mdv/issues/8
1368 test('DontCreateInstancesForAbandonedIterators', () {
1369 var div = createTestHtml(
1370 '<template bind="{{}} {{}}">'
1371 '<template bind="{{}}">Foo'
1372 '</template>'
1373 '</template>');
1374 recursivelySetTemplateModel(div, null);
1375 // TODO(jmesserly): how to fix this test?
1376 // Perhaps revive the original?
1377 // https://github.com/toolkitchen/mdv/commit/8bc1e3466aeb6930150c0d3148f0e83 0184bf599#L3R1278
1378 //expect(!!ChangeSummary._errorThrownDuringCallback, false);
1379 });
1380
1381 test('CreateInstance', () {
1382 var div = createTestHtml(
1383 '<template bind="{{a}}">'
1384 '<template bind="{{b}}">'
1385 '{{text}}'
1386 '</template>'
1387 '</template>');
1388 var outer = div.nodes.first;
1389
1390 var instance = outer.createInstance();
1391 expect(outer.content.nodes.first, instance.nodes.first.ref);
1392
1393 var instance2 = outer.createInstance();
1394 expect(instance2.nodes.first.ref, instance.nodes.first.ref);
1395 });
1396
1397 test('Bootstrap', () {
1398 var div = new DivElement();
1399 div.innerHtml =
1400 '<template>'
1401 '<div></div>'
1402 '<template>'
1403 'Hello'
1404 '</template>'
1405 '</template>';
1406
1407 TemplateElement.bootstrap(div);
1408 var template = div.nodes.first;
1409 expect(template.content.nodes.length, 2);
1410 var template2 = template.content.nodes.first.nextNode;
1411 expect(template2.content.nodes.length, 1);
1412 expect(template2.content.nodes.first.text, 'Hello');
1413
1414 template = new Element.tag('template');
1415 template.innerHtml =
1416 '<template>'
1417 '<div></div>'
1418 '<template>'
1419 'Hello'
1420 '</template>'
1421 '</template>';
1422
1423 TemplateElement.bootstrap(template);
1424 template2 = template.content.nodes.first;
1425 expect(template2.content.nodes.length, 2);
1426 var template3 = template2.content.nodes.first.nextNode;
1427 expect(template3.content.nodes.length, 1);
1428 expect(template3.content.nodes.first.text, 'Hello');
1429 });
1430
1431 test('instanceCreated hack', () {
1432 var called = false;
1433 var sub = TemplateElement.instanceCreated.listen((node) {
1434 called = true;
1435 expect(node.nodeType, Node.DOCUMENT_FRAGMENT_NODE);
1436 });
1437
1438 var div = createTestHtml('<template bind="{{}}">Foo</template>');
1439 expect(called, false);
1440
1441 recursivelySetTemplateModel(div, null);
1442 deliverChangeRecords();
1443 expect(called, true);
1444
1445 sub.cancel();
1446 });
1447 }
1448
1449 class UnbindingInNestedBindSyntax extends CustomBindingSyntax {
1450 int expectedAge = 42;
1451 int count = 0;
1452
1453 getBinding(model, path, name, node) {
1454 if (name != 'text' || path != 'age')
1455 return;
1456
1457 expect(model['age'], expectedAge);
1458 count++;
1459 }
1460 }
1461
1462 /** Verifies that the model is Observable, then calls [deliverChangeRecords]. */
1463 void deliverChanges(model) {
1464 expectObservable(model);
1465 deliverChangeRecords();
1466 }
1467
1468 void expectObservable(model) {
1469 if (model is! Observable) {
1470 // This is here to eagerly catch a bug in the test; it means the test
1471 // forgot a toObservable somewhere.
1472 expect(identical(toObservable(model), model), true,
1473 reason: 'model type "${model.runtimeType}" should be observable');
1474 return;
1475 }
1476 if (model is ObservableList) {
1477 for (var item in model) {
1478 expectObservable(item);
1479 }
1480 } else if (model is ObservableMap) {
1481 model.forEach((k, v) {
1482 expectObservable(k);
1483 expectObservable(v);
1484 });
1485 }
1486 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698