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