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_binding_test; | |
6 | |
7 import 'dart:async'; | |
8 import 'dart:html'; | |
9 import 'dart:js' show JsObject; | |
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 'package:observe/mirrors_used.dart'; // make test smaller | |
16 import 'package:smoke/mirrors.dart' as smoke; | |
17 | |
18 // TODO(jmesserly): merge this file? | |
19 import 'binding_syntax.dart' show syntaxTests; | |
20 import 'utils.dart'; | |
21 | |
22 // Note: this file ported from TemplateBinding's tests/tests.js | |
23 | |
24 // TODO(jmesserly): submit a small cleanup patch to original. I fixed some | |
25 // cases where "div" and "t" were unintentionally using the JS global scope; | |
26 // look for "assertNodesAre". | |
27 | |
28 main() => dirtyCheckZone().run(() { | |
29 smoke.useMirrors(); | |
30 useHtmlConfiguration(); | |
31 | |
32 setUp(() { | |
33 document.body.append(testDiv = new DivElement()); | |
34 }); | |
35 | |
36 tearDown(() { | |
37 testDiv.remove(); | |
38 clearAllTemplates(testDiv); | |
39 testDiv = null; | |
40 }); | |
41 | |
42 test('MutationObserver is supported', () { | |
43 expect(MutationObserver.supported, true, reason: 'polyfill was loaded.'); | |
44 }); | |
45 | |
46 group('Template', templateInstantiationTests); | |
47 | |
48 group('Binding Delegate API', () { | |
49 group('with Observable', () { | |
50 syntaxTests(([f, b]) => new FooBarModel(f, b)); | |
51 }); | |
52 | |
53 group('with ChangeNotifier', () { | |
54 syntaxTests(([f, b]) => new FooBarNotifyModel(f, b)); | |
55 }); | |
56 }); | |
57 | |
58 group('Compat', compatTests); | |
59 }); | |
60 | |
61 var expando = new Expando('test'); | |
62 void addExpandos(node) { | |
63 while (node != null) { | |
64 expando[node] = node.text; | |
65 node = node.nextNode; | |
66 } | |
67 } | |
68 | |
69 void checkExpandos(node) { | |
70 expect(node, isNotNull); | |
71 while (node != null) { | |
72 expect(expando[node], node.text); | |
73 node = node.nextNode; | |
74 } | |
75 } | |
76 | |
77 templateInstantiationTests() { | |
78 // Dart note: renamed some of these tests to have unique names | |
79 | |
80 test('accessing bindingDelegate getter without Bind', () { | |
81 var div = createTestHtml('<template>'); | |
82 var template = div.firstChild; | |
83 expect(templateBind(template).bindingDelegate, null); | |
84 }); | |
85 | |
86 test('Bind - simple', () { | |
87 var div = createTestHtml('<template bind={{}}>text</template>'); | |
88 templateBind(div.firstChild).model = {}; | |
89 return new Future(() { | |
90 expect(div.nodes.length, 2); | |
91 expect(div.nodes.last.text, 'text'); | |
92 | |
93 // Dart note: null is used instead of undefined to clear the template. | |
94 templateBind(div.firstChild).model = null; | |
95 | |
96 }).then(endOfMicrotask).then((_) { | |
97 expect(div.nodes.length, 1); | |
98 templateBind(div.firstChild).model = 123; | |
99 | |
100 }).then(endOfMicrotask).then((_) { | |
101 expect(div.nodes.length, 2); | |
102 expect(div.nodes.last.text, 'text'); | |
103 }); | |
104 }); | |
105 | |
106 test('oneTime-Bind', () { | |
107 var div = createTestHtml('<template bind="[[ bound ]]">text</template>'); | |
108 var model = toObservable({'bound': 1}); | |
109 templateBind(div.firstChild).model = model; | |
110 return new Future(() { | |
111 expect(div.nodes.length, 2); | |
112 expect(div.nodes.last.text, 'text'); | |
113 | |
114 model['bound'] = false; | |
115 | |
116 }).then(endOfMicrotask).then((_) { | |
117 expect(div.nodes.length, 2); | |
118 expect(div.nodes.last.text, 'text'); | |
119 }); | |
120 }); | |
121 | |
122 test('Bind - no parent', () { | |
123 var div = createTestHtml('<template bind>text</template>'); | |
124 var template = div.firstChild; | |
125 template.remove(); | |
126 | |
127 templateBind(template).model = {}; | |
128 return new Future(() { | |
129 expect(template.nodes.length, 0); | |
130 expect(template.nextNode, null); | |
131 }); | |
132 }); | |
133 | |
134 test('Bind - no defaultView', () { | |
135 var div = createTestHtml('<template bind>text</template>'); | |
136 var template = div.firstChild; | |
137 var doc = document.implementation.createHtmlDocument(''); | |
138 doc.adoptNode(div); | |
139 templateBind(template).model = {}; | |
140 return new Future(() => expect(div.nodes.length, 2)); | |
141 }); | |
142 | |
143 test('Empty Bind', () { | |
144 var div = createTestHtml('<template bind>text</template>'); | |
145 var template = div.firstChild; | |
146 templateBind(template).model = {}; | |
147 return new Future(() { | |
148 expect(div.nodes.length, 2); | |
149 expect(div.nodes.last.text, 'text'); | |
150 }); | |
151 }); | |
152 | |
153 test('Bind If', () { | |
154 var div = createTestHtml( | |
155 '<template bind="{{ bound }}" if="{{ predicate }}">' | |
156 'value:{{ value }}' | |
157 '</template>'); | |
158 // Dart note: predicate changed from 0->null because 0 isn't falsey in Dart. | |
159 // See https://code.google.com/p/dart/issues/detail?id=11956 | |
160 // Changed bound from null->1 since null is equivalent to JS undefined, | |
161 // and would cause the template to not be expanded. | |
162 var m = toObservable({ 'predicate': null, 'bound': 1 }); | |
163 var template = div.firstChild; | |
164 bool errorSeen = false; | |
165 runZoned(() { | |
166 templateBind(template).model = m; | |
167 }, onError: (e, s) { | |
168 _expectNoSuchMethod(e); | |
169 errorSeen = true; | |
170 }); | |
171 return new Future(() { | |
172 expect(div.nodes.length, 1); | |
173 | |
174 m['predicate'] = 1; | |
175 | |
176 expect(errorSeen, isFalse); | |
177 }).then(nextMicrotask).then((_) { | |
178 expect(errorSeen, isTrue); | |
179 expect(div.nodes.length, 1); | |
180 | |
181 m['bound'] = toObservable({ 'value': 2 }); | |
182 | |
183 }).then(endOfMicrotask).then((_) { | |
184 expect(div.nodes.length, 2); | |
185 expect(div.lastChild.text, 'value:2'); | |
186 | |
187 m['bound']['value'] = 3; | |
188 | |
189 }).then(endOfMicrotask).then((_) { | |
190 expect(div.nodes.length, 2); | |
191 expect(div.lastChild.text, 'value:3'); | |
192 | |
193 templateBind(template).model = null; | |
194 | |
195 }).then(endOfMicrotask).then((_) { | |
196 expect(div.nodes.length, 1); | |
197 }); | |
198 }); | |
199 | |
200 test('Bind oneTime-If - predicate false', () { | |
201 var div = createTestHtml( | |
202 '<template bind="{{ bound }}" if="[[ predicate ]]">' | |
203 'value:{{ value }}' | |
204 '</template>'); | |
205 // Dart note: predicate changed from 0->null because 0 isn't falsey in Dart. | |
206 // See https://code.google.com/p/dart/issues/detail?id=11956 | |
207 // Changed bound from null->1 since null is equivalent to JS undefined, | |
208 // and would cause the template to not be expanded. | |
209 var m = toObservable({ 'predicate': null, 'bound': 1 }); | |
210 var template = div.firstChild; | |
211 templateBind(template).model = m; | |
212 | |
213 return new Future(() { | |
214 expect(div.nodes.length, 1); | |
215 | |
216 m['predicate'] = 1; | |
217 | |
218 }).then(endOfMicrotask).then((_) { | |
219 expect(div.nodes.length, 1); | |
220 | |
221 m['bound'] = toObservable({ 'value': 2 }); | |
222 | |
223 }).then(endOfMicrotask).then((_) { | |
224 expect(div.nodes.length, 1); | |
225 | |
226 m['bound']['value'] = 3; | |
227 | |
228 }).then(endOfMicrotask).then((_) { | |
229 expect(div.nodes.length, 1); | |
230 | |
231 templateBind(template).model = null; | |
232 | |
233 }).then(endOfMicrotask).then((_) { | |
234 expect(div.nodes.length, 1); | |
235 }); | |
236 }); | |
237 | |
238 test('Bind oneTime-If - predicate true', () { | |
239 var div = createTestHtml( | |
240 '<template bind="{{ bound }}" if="[[ predicate ]]">' | |
241 'value:{{ value }}' | |
242 '</template>'); | |
243 | |
244 // Dart note: changed bound from null->1 since null is equivalent to JS | |
245 // undefined, and would cause the template to not be expanded. | |
246 var m = toObservable({ 'predicate': 1, 'bound': 1 }); | |
247 var template = div.firstChild; | |
248 bool errorSeen = false; | |
249 runZoned(() { | |
250 templateBind(template).model = m; | |
251 }, onError: (e, s) { | |
252 _expectNoSuchMethod(e); | |
253 errorSeen = true; | |
254 }); | |
255 | |
256 return new Future(() { | |
257 expect(div.nodes.length, 1); | |
258 m['bound'] = toObservable({ 'value': 2 }); | |
259 expect(errorSeen, isTrue); | |
260 }).then(endOfMicrotask).then((_) { | |
261 expect(div.nodes.length, 2); | |
262 expect(div.lastChild.text, 'value:2'); | |
263 | |
264 m['bound']['value'] = 3; | |
265 | |
266 }).then(endOfMicrotask).then((_) { | |
267 expect(div.nodes.length, 2); | |
268 expect(div.lastChild.text, 'value:3'); | |
269 | |
270 m['predicate'] = null; // will have no effect | |
271 | |
272 }).then(endOfMicrotask).then((_) { | |
273 expect(div.nodes.length, 2); | |
274 expect(div.lastChild.text, 'value:3'); | |
275 | |
276 templateBind(template).model = null; | |
277 | |
278 }).then(endOfMicrotask).then((_) { | |
279 expect(div.nodes.length, 1); | |
280 }); | |
281 }); | |
282 | |
283 test('oneTime-Bind If', () { | |
284 var div = createTestHtml( | |
285 '<template bind="[[ bound ]]" if="{{ predicate }}">' | |
286 'value:{{ value }}' | |
287 '</template>'); | |
288 | |
289 var m = toObservable({'predicate': null, 'bound': {'value': 2}}); | |
290 var template = div.firstChild; | |
291 templateBind(template).model = m; | |
292 | |
293 return new Future(() { | |
294 expect(div.nodes.length, 1); | |
295 | |
296 m['predicate'] = 1; | |
297 | |
298 }).then(endOfMicrotask).then((_) { | |
299 expect(div.nodes.length, 2); | |
300 expect(div.lastChild.text, 'value:2'); | |
301 | |
302 m['bound']['value'] = 3; | |
303 | |
304 }).then(endOfMicrotask).then((_) { | |
305 expect(div.nodes.length, 2); | |
306 expect(div.lastChild.text, 'value:3'); | |
307 | |
308 m['bound'] = toObservable({'value': 4 }); | |
309 | |
310 }).then(endOfMicrotask).then((_) { | |
311 expect(div.nodes.length, 2); | |
312 expect(div.lastChild.text, 'value:3'); | |
313 | |
314 templateBind(template).model = null; | |
315 | |
316 }).then(endOfMicrotask).then((_) { | |
317 expect(div.nodes.length, 1); | |
318 }); | |
319 }); | |
320 | |
321 test('oneTime-Bind oneTime-If', () { | |
322 var div = createTestHtml( | |
323 '<template bind="[[ bound ]]" if="[[ predicate ]]">' | |
324 'value:{{ value }}' | |
325 '</template>'); | |
326 | |
327 var m = toObservable({'predicate': 1, 'bound': {'value': 2}}); | |
328 var template = div.firstChild; | |
329 templateBind(template).model = m; | |
330 | |
331 return new Future(() { | |
332 expect(div.nodes.length, 2); | |
333 expect(div.lastChild.text, 'value:2'); | |
334 | |
335 m['bound']['value'] = 3; | |
336 | |
337 }).then(endOfMicrotask).then((_) { | |
338 expect(div.nodes.length, 2); | |
339 expect(div.lastChild.text, 'value:3'); | |
340 | |
341 m['bound'] = toObservable({'value': 4 }); | |
342 | |
343 }).then(endOfMicrotask).then((_) { | |
344 expect(div.nodes.length, 2); | |
345 expect(div.lastChild.text, 'value:3'); | |
346 | |
347 m['predicate'] = false; | |
348 | |
349 }).then(endOfMicrotask).then((_) { | |
350 expect(div.nodes.length, 2); | |
351 expect(div.lastChild.text, 'value:3'); | |
352 | |
353 templateBind(template).model = null; | |
354 | |
355 }).then(endOfMicrotask).then((_) { | |
356 expect(div.nodes.length, 1); | |
357 }); | |
358 }); | |
359 | |
360 test('Bind If, 2', () { | |
361 var div = createTestHtml( | |
362 '<template bind="{{ foo }}" if="{{ bar }}">{{ bat }}</template>'); | |
363 var template = div.firstChild; | |
364 var m = toObservable({ 'bar': null, 'foo': { 'bat': 'baz' } }); | |
365 templateBind(template).model = m; | |
366 return new Future(() { | |
367 expect(div.nodes.length, 1); | |
368 | |
369 m['bar'] = 1; | |
370 }).then(endOfMicrotask).then((_) { | |
371 expect(div.nodes.length, 2); | |
372 expect(div.lastChild.text, 'baz'); | |
373 }); | |
374 }); | |
375 | |
376 test('If', () { | |
377 var div = createTestHtml('<template if="{{ foo }}">{{ value }}</template>'); | |
378 // Dart note: foo changed from 0->null because 0 isn't falsey in Dart. | |
379 // See https://code.google.com/p/dart/issues/detail?id=11956 | |
380 var m = toObservable({ 'foo': null, 'value': 'foo' }); | |
381 var template = div.firstChild; | |
382 templateBind(template).model = m; | |
383 return new Future(() { | |
384 expect(div.nodes.length, 1); | |
385 | |
386 m['foo'] = 1; | |
387 }).then(endOfMicrotask).then((_) { | |
388 expect(div.nodes.length, 2); | |
389 expect(div.lastChild.text, 'foo'); | |
390 | |
391 templateBind(template).model = null; | |
392 }).then(endOfMicrotask).then((_) { | |
393 expect(div.nodes.length, 1); | |
394 }); | |
395 }); | |
396 | |
397 test('Bind If minimal discardChanges', () { | |
398 var div = createTestHtml( | |
399 '<template bind="{{bound}}" if="{{predicate}}">value:{{ value }}' | |
400 '</template>'); | |
401 // Dart Note: bound changed from null->{}. | |
402 var m = toObservable({ 'bound': {}, 'predicate': null }); | |
403 var template = div.firstChild; | |
404 | |
405 var discardChangesCalled = { 'bound': 0, 'predicate': 0 }; | |
406 templateBind(template) | |
407 ..model = m | |
408 ..bindingDelegate = | |
409 new BindIfMinimalDiscardChanges(discardChangesCalled); | |
410 | |
411 return new Future(() { | |
412 expect(discardChangesCalled['bound'], 0); | |
413 expect(discardChangesCalled['predicate'], 0); | |
414 expect(div.childNodes.length, 1); | |
415 m['predicate'] = 1; | |
416 }).then(endOfMicrotask).then((_) { | |
417 expect(discardChangesCalled['bound'], 1); | |
418 expect(discardChangesCalled['predicate'], 0); | |
419 | |
420 expect(div.nodes.length, 2); | |
421 expect(div.lastChild.text, 'value:'); | |
422 | |
423 m['bound'] = toObservable({'value': 2}); | |
424 }).then(endOfMicrotask).then((_) { | |
425 expect(discardChangesCalled['bound'], 1); | |
426 expect(discardChangesCalled['predicate'], 1); | |
427 | |
428 expect(div.nodes.length, 2); | |
429 expect(div.lastChild.text, 'value:2'); | |
430 | |
431 m['bound']['value'] = 3; | |
432 | |
433 }).then(endOfMicrotask).then((_) { | |
434 expect(discardChangesCalled['bound'], 1); | |
435 expect(discardChangesCalled['predicate'], 1); | |
436 | |
437 expect(div.nodes.length, 2); | |
438 expect(div.lastChild.text, 'value:3'); | |
439 | |
440 templateBind(template).model = null; | |
441 }).then(endOfMicrotask).then((_) { | |
442 expect(discardChangesCalled['bound'], 1); | |
443 expect(discardChangesCalled['predicate'], 1); | |
444 | |
445 expect(div.nodes.length, 1); | |
446 }); | |
447 }); | |
448 | |
449 | |
450 test('Empty-If', () { | |
451 var div = createTestHtml('<template if>{{ value }}</template>'); | |
452 var template = div.firstChild; | |
453 var m = toObservable({ 'value': 'foo' }); | |
454 templateBind(template).model = null; | |
455 return new Future(() { | |
456 expect(div.nodes.length, 1); | |
457 | |
458 templateBind(template).model = m; | |
459 }).then(endOfMicrotask).then((_) { | |
460 expect(div.nodes.length, 2); | |
461 expect(div.lastChild.text, 'foo'); | |
462 }); | |
463 }); | |
464 | |
465 test('OneTime - simple text', () { | |
466 var div = createTestHtml('<template bind>[[ value ]]</template>'); | |
467 var template = div.firstChild; | |
468 var m = toObservable({ 'value': 'foo' }); | |
469 templateBind(template).model = m; | |
470 return new Future(() { | |
471 expect(div.nodes.length, 2); | |
472 expect(div.lastChild.text, 'foo'); | |
473 | |
474 m['value'] = 'bar'; | |
475 | |
476 }).then(endOfMicrotask).then((_) { | |
477 // unchanged. | |
478 expect(div.lastChild.text, 'foo'); | |
479 }); | |
480 }); | |
481 | |
482 test('OneTime - compound text', () { | |
483 var div = createTestHtml( | |
484 '<template bind>[[ foo ]] bar [[ baz ]]</template>'); | |
485 var template = div.firstChild; | |
486 var m = toObservable({ 'foo': 'FOO', 'baz': 'BAZ' }); | |
487 templateBind(template).model = m; | |
488 return new Future(() { | |
489 expect(div.nodes.length, 2); | |
490 expect(div.lastChild.text, 'FOO bar BAZ'); | |
491 | |
492 m['foo'] = 'FI'; | |
493 m['baz'] = 'BA'; | |
494 | |
495 }).then(endOfMicrotask).then((_) { | |
496 // unchanged. | |
497 expect(div.nodes.length, 2); | |
498 expect(div.lastChild.text, 'FOO bar BAZ'); | |
499 }); | |
500 }); | |
501 | |
502 test('OneTime/Dynamic Mixed - compound text', () { | |
503 var div = createTestHtml( | |
504 '<template bind>[[ foo ]] bar {{ baz }}</template>'); | |
505 var template = div.firstChild; | |
506 var m = toObservable({ 'foo': 'FOO', 'baz': 'BAZ' }); | |
507 templateBind(template).model = m; | |
508 return new Future(() { | |
509 expect(div.nodes.length, 2); | |
510 expect(div.lastChild.text, 'FOO bar BAZ'); | |
511 | |
512 m['foo'] = 'FI'; | |
513 m['baz'] = 'BA'; | |
514 | |
515 }).then(endOfMicrotask).then((_) { | |
516 // unchanged [[ foo ]]. | |
517 expect(div.nodes.length, 2); | |
518 expect(div.lastChild.text, 'FOO bar BA'); | |
519 }); | |
520 }); | |
521 | |
522 test('OneTime - simple attribute', () { | |
523 var div = createTestHtml( | |
524 '<template bind><div foo="[[ value ]]"></div></template>'); | |
525 var template = div.firstChild; | |
526 var m = toObservable({ 'value': 'foo' }); | |
527 templateBind(template).model = m; | |
528 return new Future(() { | |
529 expect(div.nodes.length, 2); | |
530 expect(div.lastChild.attributes['foo'], 'foo'); | |
531 | |
532 m['value'] = 'bar'; | |
533 | |
534 }).then(endOfMicrotask).then((_) { | |
535 // unchanged. | |
536 expect(div.nodes.length, 2); | |
537 expect(div.lastChild.attributes['foo'], 'foo'); | |
538 }); | |
539 }); | |
540 | |
541 test('OneTime - compound attribute', () { | |
542 var div = createTestHtml( | |
543 '<template bind>' | |
544 '<div foo="[[ value ]]:[[ otherValue ]]"></div>' | |
545 '</template>'); | |
546 var template = div.firstChild; | |
547 var m = toObservable({ 'value': 'foo', 'otherValue': 'bar' }); | |
548 templateBind(template).model = m; | |
549 return new Future(() { | |
550 expect(div.nodes.length, 2); | |
551 expect(div.lastChild.attributes['foo'], 'foo:bar'); | |
552 | |
553 m['value'] = 'baz'; | |
554 m['otherValue'] = 'bot'; | |
555 | |
556 }).then(endOfMicrotask).then((_) { | |
557 // unchanged. | |
558 expect(div.lastChild.attributes['foo'], 'foo:bar'); | |
559 }); | |
560 }); | |
561 | |
562 test('OneTime/Dynamic mixed - compound attribute', () { | |
563 var div = createTestHtml( | |
564 '<template bind>' | |
565 '<div foo="{{ value }}:[[ otherValue ]]"></div>' | |
566 '</template>'); | |
567 var template = div.firstChild; | |
568 var m = toObservable({ 'value': 'foo', 'otherValue': 'bar' }); | |
569 templateBind(template).model = m; | |
570 return new Future(() { | |
571 expect(div.nodes.length, 2); | |
572 expect(div.lastChild.attributes['foo'], 'foo:bar'); | |
573 | |
574 m['value'] = 'baz'; | |
575 m['otherValue'] = 'bot'; | |
576 | |
577 }).then(endOfMicrotask).then((_) { | |
578 // unchanged [[ otherValue ]]. | |
579 expect(div.lastChild.attributes['foo'], 'baz:bar'); | |
580 }); | |
581 }); | |
582 | |
583 test('Repeat If', () { | |
584 var div = createTestHtml( | |
585 '<template repeat="{{ items }}" if="{{ predicate }}">{{}}</template>'); | |
586 // Dart note: predicate changed from 0->null because 0 isn't falsey in Dart. | |
587 // See https://code.google.com/p/dart/issues/detail?id=11956 | |
588 var m = toObservable({ 'predicate': null, 'items': [1] }); | |
589 var template = div.firstChild; | |
590 templateBind(template).model = m; | |
591 return new Future(() { | |
592 expect(div.nodes.length, 1); | |
593 | |
594 m['predicate'] = 1; | |
595 | |
596 }).then(endOfMicrotask).then((_) { | |
597 expect(div.nodes.length, 2); | |
598 expect(div.nodes[1].text, '1'); | |
599 | |
600 m['items']..add(2)..add(3); | |
601 | |
602 }).then(endOfMicrotask).then((_) { | |
603 expect(div.nodes.length, 4); | |
604 expect(div.nodes[1].text, '1'); | |
605 expect(div.nodes[2].text, '2'); | |
606 expect(div.nodes[3].text, '3'); | |
607 | |
608 m['items'] = [4]; | |
609 | |
610 }).then(endOfMicrotask).then((_) { | |
611 expect(div.nodes.length, 2); | |
612 expect(div.nodes[1].text, '4'); | |
613 | |
614 templateBind(template).model = null; | |
615 }).then(endOfMicrotask).then((_) { | |
616 expect(div.nodes.length, 1); | |
617 }); | |
618 }); | |
619 | |
620 test('Repeat oneTime-If (predicate false)', () { | |
621 var div = createTestHtml( | |
622 '<template repeat="{{ items }}" if="[[ predicate ]]">{{}}</template>'); | |
623 // Dart note: predicate changed from 0->null because 0 isn't falsey in Dart. | |
624 // See https://code.google.com/p/dart/issues/detail?id=11956 | |
625 var m = toObservable({ 'predicate': null, 'items': [1] }); | |
626 var template = div.firstChild; | |
627 templateBind(template).model = m; | |
628 return new Future(() { | |
629 expect(div.nodes.length, 1); | |
630 | |
631 m['predicate'] = 1; | |
632 | |
633 }).then(endOfMicrotask).then((_) { | |
634 expect(div.nodes.length, 1, reason: 'unchanged'); | |
635 | |
636 m['items']..add(2)..add(3); | |
637 | |
638 }).then(endOfMicrotask).then((_) { | |
639 expect(div.nodes.length, 1, reason: 'unchanged'); | |
640 | |
641 m['items'] = [4]; | |
642 | |
643 }).then(endOfMicrotask).then((_) { | |
644 expect(div.nodes.length, 1, reason: 'unchanged'); | |
645 | |
646 templateBind(template).model = null; | |
647 }).then(endOfMicrotask).then((_) { | |
648 expect(div.nodes.length, 1); | |
649 }); | |
650 }); | |
651 | |
652 test('Repeat oneTime-If (predicate true)', () { | |
653 var div = createTestHtml( | |
654 '<template repeat="{{ items }}" if="[[ predicate ]]">{{}}</template>'); | |
655 | |
656 var m = toObservable({ 'predicate': true, 'items': [1] }); | |
657 var template = div.firstChild; | |
658 templateBind(template).model = m; | |
659 return new Future(() { | |
660 expect(div.nodes.length, 2); | |
661 expect(div.nodes[1].text, '1'); | |
662 | |
663 m['items']..add(2)..add(3); | |
664 | |
665 }).then(endOfMicrotask).then((_) { | |
666 expect(div.nodes.length, 4); | |
667 expect(div.nodes[1].text, '1'); | |
668 expect(div.nodes[2].text, '2'); | |
669 expect(div.nodes[3].text, '3'); | |
670 | |
671 m['items'] = [4]; | |
672 | |
673 }).then(endOfMicrotask).then((_) { | |
674 expect(div.nodes.length, 2); | |
675 expect(div.nodes[1].text, '4'); | |
676 | |
677 m['predicate'] = false; | |
678 | |
679 }).then(endOfMicrotask).then((_) { | |
680 expect(div.nodes.length, 2, reason: 'unchanged'); | |
681 expect(div.nodes[1].text, '4', reason: 'unchanged'); | |
682 | |
683 templateBind(template).model = null; | |
684 }).then(endOfMicrotask).then((_) { | |
685 expect(div.nodes.length, 1); | |
686 }); | |
687 }); | |
688 | |
689 test('oneTime-Repeat If', () { | |
690 var div = createTestHtml( | |
691 '<template repeat="[[ items ]]" if="{{ predicate }}">{{}}</template>'); | |
692 | |
693 var m = toObservable({ 'predicate': false, 'items': [1] }); | |
694 var template = div.firstChild; | |
695 templateBind(template).model = m; | |
696 return new Future(() { | |
697 expect(div.nodes.length, 1); | |
698 | |
699 m['predicate'] = true; | |
700 | |
701 }).then(endOfMicrotask).then((_) { | |
702 expect(div.nodes.length, 2); | |
703 expect(div.nodes[1].text, '1'); | |
704 | |
705 m['items']..add(2)..add(3); | |
706 | |
707 }).then(endOfMicrotask).then((_) { | |
708 expect(div.nodes.length, 2); | |
709 expect(div.nodes[1].text, '1'); | |
710 | |
711 m['items'] = [4]; | |
712 | |
713 }).then(endOfMicrotask).then((_) { | |
714 expect(div.nodes.length, 2); | |
715 expect(div.nodes[1].text, '1'); | |
716 | |
717 templateBind(template).model = null; | |
718 }).then(endOfMicrotask).then((_) { | |
719 expect(div.nodes.length, 1); | |
720 }); | |
721 }); | |
722 | |
723 test('oneTime-Repeat oneTime-If', () { | |
724 var div = createTestHtml( | |
725 '<template repeat="[[ items ]]" if="[[ predicate ]]">{{}}</template>'); | |
726 | |
727 var m = toObservable({ 'predicate': true, 'items': [1] }); | |
728 var template = div.firstChild; | |
729 templateBind(template).model = m; | |
730 return new Future(() { | |
731 expect(div.nodes.length, 2); | |
732 expect(div.nodes[1].text, '1'); | |
733 | |
734 m['items']..add(2)..add(3); | |
735 | |
736 }).then(endOfMicrotask).then((_) { | |
737 expect(div.nodes.length, 2); | |
738 expect(div.nodes[1].text, '1'); | |
739 | |
740 m['items'] = [4]; | |
741 | |
742 }).then(endOfMicrotask).then((_) { | |
743 expect(div.nodes.length, 2); | |
744 expect(div.nodes[1].text, '1'); | |
745 | |
746 m['predicate'] = false; | |
747 | |
748 }).then(endOfMicrotask).then((_) { | |
749 expect(div.nodes.length, 2); | |
750 expect(div.nodes[1].text, '1'); | |
751 | |
752 templateBind(template).model = null; | |
753 }).then(endOfMicrotask).then((_) { | |
754 expect(div.nodes.length, 1); | |
755 }); | |
756 }); | |
757 | |
758 test('TextTemplateWithNullStringBinding', () { | |
759 var div = createTestHtml('<template bind={{}}>a{{b}}c</template>'); | |
760 var template = div.firstChild; | |
761 var model = toObservable({'b': 'B'}); | |
762 templateBind(template).model = model; | |
763 | |
764 return new Future(() { | |
765 expect(div.nodes.length, 2); | |
766 expect(div.nodes.last.text, 'aBc'); | |
767 | |
768 model['b'] = 'b'; | |
769 }).then(endOfMicrotask).then((_) { | |
770 expect(div.nodes.last.text, 'abc'); | |
771 | |
772 model['b'] = null; | |
773 }).then(endOfMicrotask).then((_) { | |
774 expect(div.nodes.last.text, 'ac'); | |
775 | |
776 model = null; | |
777 }).then(endOfMicrotask).then((_) { | |
778 // setting model isn't bindable. | |
779 expect(div.nodes.last.text, 'ac'); | |
780 }); | |
781 }); | |
782 | |
783 test('TextTemplateWithBindingPath', () { | |
784 var div = createTestHtml( | |
785 '<template bind="{{ data }}">a{{b}}c</template>'); | |
786 var model = toObservable({ 'data': {'b': 'B'} }); | |
787 var template = div.firstChild; | |
788 templateBind(template).model = model; | |
789 | |
790 return new Future(() { | |
791 expect(div.nodes.length, 2); | |
792 expect(div.nodes.last.text, 'aBc'); | |
793 | |
794 model['data']['b'] = 'b'; | |
795 }).then(endOfMicrotask).then((_) { | |
796 expect(div.nodes.last.text, 'abc'); | |
797 | |
798 model['data'] = toObservable({'b': 'X'}); | |
799 }).then(endOfMicrotask).then((_) { | |
800 expect(div.nodes.last.text, 'aXc'); | |
801 | |
802 // Dart note: changed from `null` since our null means don't render a mode
l. | |
803 model['data'] = toObservable({}); | |
804 }).then(endOfMicrotask).then((_) { | |
805 expect(div.nodes.last.text, 'ac'); | |
806 | |
807 model['data'] = null; | |
808 }).then(endOfMicrotask).then((_) { | |
809 expect(div.nodes.length, 1); | |
810 }); | |
811 }); | |
812 | |
813 test('TextTemplateWithBindingAndConditional', () { | |
814 var div = createTestHtml( | |
815 '<template bind="{{}}" if="{{ d }}">a{{b}}c</template>'); | |
816 var template = div.firstChild; | |
817 var model = toObservable({'b': 'B', 'd': 1}); | |
818 templateBind(template).model = model; | |
819 | |
820 return new Future(() { | |
821 expect(div.nodes.length, 2); | |
822 expect(div.nodes.last.text, 'aBc'); | |
823 | |
824 model['b'] = 'b'; | |
825 }).then(endOfMicrotask).then((_) { | |
826 expect(div.nodes.last.text, 'abc'); | |
827 | |
828 // TODO(jmesserly): MDV set this to empty string and relies on JS conversi
on | |
829 // rules. Is that intended? | |
830 // See https://github.com/Polymer/TemplateBinding/issues/59 | |
831 model['d'] = null; | |
832 }).then(endOfMicrotask).then((_) { | |
833 expect(div.nodes.length, 1); | |
834 | |
835 model['d'] = 'here'; | |
836 model['b'] = 'd'; | |
837 | |
838 }).then(endOfMicrotask).then((_) { | |
839 expect(div.nodes.length, 2); | |
840 expect(div.nodes.last.text, 'adc'); | |
841 }); | |
842 }); | |
843 | |
844 test('TemplateWithTextBinding2', () { | |
845 var div = createTestHtml( | |
846 '<template bind="{{ b }}">a{{value}}c</template>'); | |
847 expect(div.nodes.length, 1); | |
848 var template = div.firstChild; | |
849 var model = toObservable({'b': {'value': 'B'}}); | |
850 templateBind(template).model = model; | |
851 | |
852 return new Future(() { | |
853 expect(div.nodes.length, 2); | |
854 expect(div.nodes.last.text, 'aBc'); | |
855 | |
856 model['b'] = toObservable({'value': 'b'}); | |
857 }).then(endOfMicrotask).then((_) { | |
858 expect(div.nodes.last.text, 'abc'); | |
859 }); | |
860 }); | |
861 | |
862 test('TemplateWithAttributeBinding', () { | |
863 var div = createTestHtml( | |
864 '<template bind="{{}}">' | |
865 '<div foo="a{{b}}c"></div>' | |
866 '</template>'); | |
867 var template = div.firstChild; | |
868 var model = toObservable({'b': 'B'}); | |
869 templateBind(template).model = model; | |
870 | |
871 return new Future(() { | |
872 expect(div.nodes.length, 2); | |
873 expect(div.nodes.last.attributes['foo'], 'aBc'); | |
874 | |
875 model['b'] = 'b'; | |
876 }).then(endOfMicrotask).then((_) { | |
877 expect(div.nodes.last.attributes['foo'], 'abc'); | |
878 | |
879 model['b'] = 'X'; | |
880 }).then(endOfMicrotask).then((_) { | |
881 expect(div.nodes.last.attributes['foo'], 'aXc'); | |
882 }); | |
883 }); | |
884 | |
885 test('TemplateWithConditionalBinding', () { | |
886 var div = createTestHtml( | |
887 '<template bind="{{}}">' | |
888 '<div foo?="{{b}}"></div>' | |
889 '</template>'); | |
890 var template = div.firstChild; | |
891 var model = toObservable({'b': 'b'}); | |
892 templateBind(template).model = model; | |
893 | |
894 return new Future(() { | |
895 expect(div.nodes.length, 2); | |
896 expect(div.nodes.last.attributes['foo'], ''); | |
897 expect(div.nodes.last.attributes, isNot(contains('foo?'))); | |
898 | |
899 model['b'] = null; | |
900 }).then(endOfMicrotask).then((_) { | |
901 expect(div.nodes.last.attributes, isNot(contains('foo'))); | |
902 }); | |
903 }); | |
904 | |
905 test('Repeat', () { | |
906 var div = createTestHtml( | |
907 '<template repeat="{{ array }}">{{}},</template>'); | |
908 | |
909 var model = toObservable({'array': [0, 1, 2]}); | |
910 var template = templateBind(div.firstChild); | |
911 template.model = model; | |
912 | |
913 return new Future(() { | |
914 expect(div.nodes.length, 4); | |
915 expect(div.text, '0,1,2,'); | |
916 | |
917 model['array'].length = 1; | |
918 | |
919 }).then(endOfMicrotask).then((_) { | |
920 expect(div.nodes.length, 2); | |
921 expect(div.text, '0,'); | |
922 | |
923 model['array'].addAll([3, 4]); | |
924 | |
925 }).then(endOfMicrotask).then((_) { | |
926 expect(div.nodes.length, 4); | |
927 expect(div.text, '0,3,4,'); | |
928 | |
929 model['array'].removeRange(1, 2); | |
930 | |
931 }).then(endOfMicrotask).then((_) { | |
932 expect(div.nodes.length, 3); | |
933 expect(div.text, '0,4,'); | |
934 | |
935 model['array'].addAll([5, 6]); | |
936 model['array'] = toObservable(['x', 'y']); | |
937 | |
938 }).then(endOfMicrotask).then((_) { | |
939 expect(div.nodes.length, 3); | |
940 expect(div.text, 'x,y,'); | |
941 | |
942 template.model = null; | |
943 | |
944 }).then(endOfMicrotask).then((_) { | |
945 expect(div.nodes.length, 1); | |
946 expect(div.text, ''); | |
947 }); | |
948 }); | |
949 | |
950 test('Repeat - oneTime', () { | |
951 var div = createTestHtml('<template repeat="[[]]">text</template>'); | |
952 | |
953 var model = toObservable([0, 1, 2]); | |
954 var template = templateBind(div.firstChild); | |
955 template.model = model; | |
956 | |
957 return new Future(() { | |
958 expect(div.nodes.length, 4); | |
959 | |
960 model.length = 1; | |
961 }).then(endOfMicrotask).then((_) { | |
962 expect(div.nodes.length, 4); | |
963 | |
964 model.addAll([3, 4]); | |
965 }).then(endOfMicrotask).then((_) { | |
966 expect(div.nodes.length, 4); | |
967 | |
968 model.removeRange(1, 2); | |
969 }).then(endOfMicrotask).then((_) { | |
970 expect(div.nodes.length, 4); | |
971 | |
972 template.model = null; | |
973 }).then(endOfMicrotask).then((_) { | |
974 expect(div.nodes.length, 1); | |
975 }); | |
976 }); | |
977 | |
978 test('Repeat - Reuse Instances', () { | |
979 var div = createTestHtml('<template repeat>{{ val }}</template>'); | |
980 | |
981 var model = toObservable([ | |
982 {'val': 10}, | |
983 {'val': 5}, | |
984 {'val': 2}, | |
985 {'val': 8}, | |
986 {'val': 1} | |
987 ]); | |
988 var template = div.firstChild; | |
989 templateBind(template).model = model; | |
990 | |
991 return new Future(() { | |
992 expect(div.nodes.length, 6); | |
993 | |
994 addExpandos(template.nextNode); | |
995 checkExpandos(template.nextNode); | |
996 | |
997 model.sort((a, b) => a['val'] - b['val']); | |
998 }).then(endOfMicrotask).then((_) { | |
999 checkExpandos(template.nextNode); | |
1000 | |
1001 model = toObservable(model.reversed); | |
1002 templateBind(template).model = model; | |
1003 }).then(endOfMicrotask).then((_) { | |
1004 checkExpandos(template.nextNode); | |
1005 | |
1006 for (var item in model) { | |
1007 item['val'] += 1; | |
1008 } | |
1009 | |
1010 }).then(endOfMicrotask).then((_) { | |
1011 expect(div.nodes[1].text, "11"); | |
1012 expect(div.nodes[2].text, "9"); | |
1013 expect(div.nodes[3].text, "6"); | |
1014 expect(div.nodes[4].text, "3"); | |
1015 expect(div.nodes[5].text, "2"); | |
1016 }); | |
1017 }); | |
1018 | |
1019 test('Bind - Reuse Instance', () { | |
1020 var div = createTestHtml( | |
1021 '<template bind="{{ foo }}">{{ bar }}</template>'); | |
1022 | |
1023 var template = div.firstChild; | |
1024 var model = toObservable({ 'foo': { 'bar': 5 }}); | |
1025 templateBind(template).model = model; | |
1026 | |
1027 return new Future(() { | |
1028 expect(div.nodes.length, 2); | |
1029 | |
1030 addExpandos(template.nextNode); | |
1031 checkExpandos(template.nextNode); | |
1032 | |
1033 model = toObservable({'foo': model['foo']}); | |
1034 templateBind(template).model = model; | |
1035 }).then(endOfMicrotask).then((_) { | |
1036 checkExpandos(template.nextNode); | |
1037 }); | |
1038 }); | |
1039 | |
1040 test('Repeat-Empty', () { | |
1041 var div = createTestHtml( | |
1042 '<template repeat>text</template>'); | |
1043 | |
1044 var template = div.firstChild; | |
1045 var model = toObservable([0, 1, 2]); | |
1046 templateBind(template).model = model; | |
1047 | |
1048 return new Future(() { | |
1049 expect(div.nodes.length, 4); | |
1050 | |
1051 model.length = 1; | |
1052 }).then(endOfMicrotask).then((_) { | |
1053 expect(div.nodes.length, 2); | |
1054 | |
1055 model.addAll(toObservable([3, 4])); | |
1056 }).then(endOfMicrotask).then((_) { | |
1057 expect(div.nodes.length, 4); | |
1058 | |
1059 model.removeRange(1, 2); | |
1060 }).then(endOfMicrotask).then((_) { | |
1061 expect(div.nodes.length, 3); | |
1062 }); | |
1063 }); | |
1064 | |
1065 test('Removal from iteration needs to unbind', () { | |
1066 var div = createTestHtml( | |
1067 '<template repeat="{{}}"><a>{{v}}</a></template>'); | |
1068 var template = div.firstChild; | |
1069 var model = toObservable([{'v': 0}, {'v': 1}, {'v': 2}, {'v': 3}, | |
1070 {'v': 4}]); | |
1071 templateBind(template).model = model; | |
1072 | |
1073 var nodes, vs; | |
1074 return new Future(() { | |
1075 | |
1076 nodes = div.nodes.skip(1).toList(); | |
1077 vs = model.toList(); | |
1078 | |
1079 for (var i = 0; i < 5; i++) { | |
1080 expect(nodes[i].text, '$i'); | |
1081 } | |
1082 | |
1083 model.length = 3; | |
1084 }).then(endOfMicrotask).then((_) { | |
1085 for (var i = 0; i < 5; i++) { | |
1086 expect(nodes[i].text, '$i'); | |
1087 } | |
1088 | |
1089 vs[3]['v'] = 33; | |
1090 vs[4]['v'] = 44; | |
1091 }).then(endOfMicrotask).then((_) { | |
1092 for (var i = 0; i < 5; i++) { | |
1093 expect(nodes[i].text, '$i'); | |
1094 } | |
1095 }); | |
1096 }); | |
1097 | |
1098 test('Template.clear', () { | |
1099 var div = createTestHtml( | |
1100 '<template repeat>{{}}</template>'); | |
1101 var template = div.firstChild; | |
1102 templateBind(template).model = [0, 1, 2]; | |
1103 | |
1104 return new Future(() { | |
1105 expect(div.nodes.length, 4); | |
1106 expect(div.nodes[1].text, '0'); | |
1107 expect(div.nodes[2].text, '1'); | |
1108 expect(div.nodes[3].text, '2'); | |
1109 | |
1110 // clear() synchronously removes instances and clears the model. | |
1111 templateBind(div.firstChild).clear(); | |
1112 expect(div.nodes.length, 1); | |
1113 expect(templateBind(template).model, null); | |
1114 | |
1115 // test that template still works if new model assigned | |
1116 templateBind(template).model = [3, 4]; | |
1117 | |
1118 }).then(endOfMicrotask).then((_) { | |
1119 expect(div.nodes.length, 3); | |
1120 expect(div.nodes[1].text, '3'); | |
1121 expect(div.nodes[2].text, '4'); | |
1122 }); | |
1123 }); | |
1124 | |
1125 test('DOM Stability on Iteration', () { | |
1126 var div = createTestHtml( | |
1127 '<template repeat="{{}}">{{}}</template>'); | |
1128 var template = div.firstChild; | |
1129 var model = toObservable([1, 2, 3, 4, 5]); | |
1130 templateBind(template).model = model; | |
1131 | |
1132 var nodes; | |
1133 return new Future(() { | |
1134 // Note: the node at index 0 is the <template>. | |
1135 nodes = div.nodes.toList(); | |
1136 expect(nodes.length, 6, reason: 'list has 5 items'); | |
1137 | |
1138 model.removeAt(0); | |
1139 model.removeLast(); | |
1140 | |
1141 }).then(endOfMicrotask).then((_) { | |
1142 expect(div.nodes.length, 4, reason: 'list has 3 items'); | |
1143 expect(identical(div.nodes[1], nodes[2]), true, reason: '2 not removed'); | |
1144 expect(identical(div.nodes[2], nodes[3]), true, reason: '3 not removed'); | |
1145 expect(identical(div.nodes[3], nodes[4]), true, reason: '4 not removed'); | |
1146 | |
1147 model.insert(0, 5); | |
1148 model[2] = 6; | |
1149 model.add(7); | |
1150 | |
1151 }).then(endOfMicrotask).then((_) { | |
1152 | |
1153 expect(div.nodes.length, 6, reason: 'list has 5 items'); | |
1154 expect(nodes.contains(div.nodes[1]), false, reason: '5 is a new node'); | |
1155 expect(identical(div.nodes[2], nodes[2]), true); | |
1156 expect(nodes.contains(div.nodes[3]), false, reason: '6 is a new node'); | |
1157 expect(identical(div.nodes[4], nodes[4]), true); | |
1158 expect(nodes.contains(div.nodes[5]), false, reason: '7 is a new node'); | |
1159 | |
1160 nodes = div.nodes.toList(); | |
1161 | |
1162 model.insert(2, 8); | |
1163 | |
1164 }).then(endOfMicrotask).then((_) { | |
1165 | |
1166 expect(div.nodes.length, 7, reason: 'list has 6 items'); | |
1167 expect(identical(div.nodes[1], nodes[1]), true); | |
1168 expect(identical(div.nodes[2], nodes[2]), true); | |
1169 expect(nodes.contains(div.nodes[3]), false, reason: '8 is a new node'); | |
1170 expect(identical(div.nodes[4], nodes[3]), true); | |
1171 expect(identical(div.nodes[5], nodes[4]), true); | |
1172 expect(identical(div.nodes[6], nodes[5]), true); | |
1173 }); | |
1174 }); | |
1175 | |
1176 test('Repeat2', () { | |
1177 var div = createTestHtml( | |
1178 '<template repeat="{{}}">{{value}}</template>'); | |
1179 expect(div.nodes.length, 1); | |
1180 | |
1181 var template = div.firstChild; | |
1182 var model = toObservable([ | |
1183 {'value': 0}, | |
1184 {'value': 1}, | |
1185 {'value': 2} | |
1186 ]); | |
1187 templateBind(template).model = model; | |
1188 | |
1189 return new Future(() { | |
1190 expect(div.nodes.length, 4); | |
1191 expect(div.nodes[1].text, '0'); | |
1192 expect(div.nodes[2].text, '1'); | |
1193 expect(div.nodes[3].text, '2'); | |
1194 | |
1195 model[1]['value'] = 'One'; | |
1196 }).then(endOfMicrotask).then((_) { | |
1197 expect(div.nodes.length, 4); | |
1198 expect(div.nodes[1].text, '0'); | |
1199 expect(div.nodes[2].text, 'One'); | |
1200 expect(div.nodes[3].text, '2'); | |
1201 | |
1202 model.replaceRange(0, 1, toObservable([{'value': 'Zero'}])); | |
1203 }).then(endOfMicrotask).then((_) { | |
1204 expect(div.nodes.length, 4); | |
1205 expect(div.nodes[1].text, 'Zero'); | |
1206 expect(div.nodes[2].text, 'One'); | |
1207 expect(div.nodes[3].text, '2'); | |
1208 }); | |
1209 }); | |
1210 | |
1211 test('TemplateWithInputValue', () { | |
1212 var div = createTestHtml( | |
1213 '<template bind="{{}}">' | |
1214 '<input value="{{x}}">' | |
1215 '</template>'); | |
1216 var template = div.firstChild; | |
1217 var model = toObservable({'x': 'hi'}); | |
1218 templateBind(template).model = model; | |
1219 | |
1220 return new Future(() { | |
1221 expect(div.nodes.length, 2); | |
1222 expect(div.nodes.last.value, 'hi'); | |
1223 | |
1224 model['x'] = 'bye'; | |
1225 expect(div.nodes.last.value, 'hi'); | |
1226 }).then(endOfMicrotask).then((_) { | |
1227 expect(div.nodes.last.value, 'bye'); | |
1228 | |
1229 div.nodes.last.value = 'hello'; | |
1230 dispatchEvent('input', div.nodes.last); | |
1231 expect(model['x'], 'hello'); | |
1232 }).then(endOfMicrotask).then((_) { | |
1233 expect(div.nodes.last.value, 'hello'); | |
1234 }); | |
1235 }); | |
1236 | |
1237 ////////////////////////////////////////////////////////////////////////////// | |
1238 | |
1239 test('Decorated', () { | |
1240 var div = createTestHtml( | |
1241 '<template bind="{{ XX }}" id="t1">' | |
1242 '<p>Crew member: {{name}}, Job title: {{title}}</p>' | |
1243 '</template>' | |
1244 '<template bind="{{ XY }}" id="t2" ref="t1"></template>'); | |
1245 | |
1246 var t1 = document.getElementById('t1'); | |
1247 var t2 = document.getElementById('t2'); | |
1248 var model = toObservable({ | |
1249 'XX': {'name': 'Leela', 'title': 'Captain'}, | |
1250 'XY': {'name': 'Fry', 'title': 'Delivery boy'}, | |
1251 'XZ': {'name': 'Zoidberg', 'title': 'Doctor'} | |
1252 }); | |
1253 templateBind(t1).model = model; | |
1254 templateBind(t2).model = model; | |
1255 | |
1256 return new Future(() { | |
1257 var instance = t1.nextElementSibling; | |
1258 expect(instance.text, 'Crew member: Leela, Job title: Captain'); | |
1259 | |
1260 instance = t2.nextElementSibling; | |
1261 expect(instance.text, 'Crew member: Fry, Job title: Delivery boy'); | |
1262 | |
1263 expect(div.children.length, 4); | |
1264 expect(div.nodes.length, 4); | |
1265 | |
1266 expect(div.nodes[1].tagName, 'P'); | |
1267 expect(div.nodes[3].tagName, 'P'); | |
1268 }); | |
1269 }); | |
1270 | |
1271 test('DefaultStyles', () { | |
1272 var t = new Element.tag('template'); | |
1273 TemplateBindExtension.decorate(t); | |
1274 | |
1275 document.body.append(t); | |
1276 expect(t.getComputedStyle().display, 'none'); | |
1277 | |
1278 t.remove(); | |
1279 }); | |
1280 | |
1281 | |
1282 test('Bind', () { | |
1283 var div = createTestHtml('<template bind="{{}}">Hi {{ name }}</template>'); | |
1284 var template = div.firstChild; | |
1285 var model = toObservable({'name': 'Leela'}); | |
1286 templateBind(template).model = model; | |
1287 | |
1288 return new Future(() => expect(div.nodes[1].text, 'Hi Leela')); | |
1289 }); | |
1290 | |
1291 test('BindPlaceHolderHasNewLine', () { | |
1292 var div = createTestHtml( | |
1293 '<template bind="{{}}">Hi {{\nname\n}}</template>'); | |
1294 var template = div.firstChild; | |
1295 var model = toObservable({'name': 'Leela'}); | |
1296 templateBind(template).model = model; | |
1297 | |
1298 return new Future(() => expect(div.nodes[1].text, 'Hi Leela')); | |
1299 }); | |
1300 | |
1301 test('BindWithRef', () { | |
1302 var id = 't${new math.Random().nextInt(100)}'; | |
1303 var div = createTestHtml( | |
1304 '<template id="$id">' | |
1305 'Hi {{ name }}' | |
1306 '</template>' | |
1307 '<template ref="$id" bind="{{}}"></template>'); | |
1308 | |
1309 var t1 = div.nodes.first; | |
1310 var t2 = div.nodes[1]; | |
1311 | |
1312 var model = toObservable({'name': 'Fry'}); | |
1313 templateBind(t1).model = model; | |
1314 templateBind(t2).model = model; | |
1315 | |
1316 return new Future(() => expect(t2.nextNode.text, 'Hi Fry')); | |
1317 }); | |
1318 | |
1319 test('Ref at multiple', () { | |
1320 // Note: this test is asserting that template "ref"erences can be located | |
1321 // at various points. In particular: | |
1322 // -in the document (at large) (e.g. ref=doc) | |
1323 // -within template content referenced from sub-content | |
1324 // -both before and after the reference | |
1325 // The following asserts ensure that all referenced templates content is | |
1326 // found. | |
1327 var div = createTestHtml( | |
1328 '<template bind>' | |
1329 '<template bind ref=doc></template>' | |
1330 '<template id=elRoot>EL_ROOT</template>' | |
1331 '<template bind>' | |
1332 '<template bind ref=elRoot></template>' | |
1333 '<template bind>' | |
1334 '<template bind ref=subA></template>' | |
1335 '<template id=subB>SUB_B</template>' | |
1336 '<template bind>' | |
1337 '<template bind ref=subB></template>' | |
1338 '</template>' | |
1339 '</template>' | |
1340 '<template id=subA>SUB_A</template>' | |
1341 '</template>' | |
1342 '</template>' | |
1343 '<template id=doc>DOC</template>'); | |
1344 var t = div.firstChild; | |
1345 var fragment = templateBind(t).createInstance({}); | |
1346 expect(fragment.nodes.length, 14); | |
1347 expect(fragment.nodes[1].text, 'DOC'); | |
1348 expect(fragment.nodes[5].text, 'EL_ROOT'); | |
1349 expect(fragment.nodes[8].text, 'SUB_A'); | |
1350 expect(fragment.nodes[12].text, 'SUB_B'); | |
1351 div.append(fragment); | |
1352 }); | |
1353 | |
1354 test('Update Ref', () { | |
1355 // Updating ref by observing the attribute is dependent on MutationObserver | |
1356 var div = createTestHtml( | |
1357 '<template id=A>Hi, {{}}</template>' | |
1358 '<template id=B>Hola, {{}}</template>' | |
1359 '<template ref=A repeat></template>'); | |
1360 | |
1361 var template = div.nodes[2]; | |
1362 var model = new ObservableList.from(['Fry']); | |
1363 templateBind(template).model = model; | |
1364 | |
1365 return new Future(() { | |
1366 expect(div.nodes.length, 4); | |
1367 expect('Hi, Fry', div.nodes[3].text); | |
1368 | |
1369 // In IE 11, MutationObservers do not fire before setTimeout. | |
1370 // So rather than using "then" to queue up the next test, we use a | |
1371 // MutationObserver here to detect the change to "ref". | |
1372 var done = new Completer(); | |
1373 new MutationObserver((mutations, observer) { | |
1374 expect(div.nodes.length, 5); | |
1375 | |
1376 expect('Hola, Fry', div.nodes[3].text); | |
1377 expect('Hola, Leela', div.nodes[4].text); | |
1378 done.complete(); | |
1379 }).observe(template, attributes: true, attributeFilter: ['ref']); | |
1380 | |
1381 template.setAttribute('ref', 'B'); | |
1382 model.add('Leela'); | |
1383 | |
1384 return done.future; | |
1385 }); | |
1386 }); | |
1387 | |
1388 test('Bound Ref', () { | |
1389 var div = createTestHtml( | |
1390 '<template id=A>Hi, {{}}</template>' | |
1391 '<template id=B>Hola, {{}}</template>' | |
1392 '<template ref="{{ ref }}" repeat="{{ people }}"></template>'); | |
1393 | |
1394 var template = div.nodes[2]; | |
1395 var model = toObservable({'ref': 'A', 'people': ['Fry']}); | |
1396 templateBind(template).model = model; | |
1397 | |
1398 return new Future(() { | |
1399 expect(div.nodes.length, 4); | |
1400 expect('Hi, Fry', div.nodes[3].text); | |
1401 | |
1402 model['ref'] = 'B'; | |
1403 model['people'].add('Leela'); | |
1404 | |
1405 }).then(endOfMicrotask).then((x) { | |
1406 expect(div.nodes.length, 5); | |
1407 | |
1408 expect('Hola, Fry', div.nodes[3].text); | |
1409 expect('Hola, Leela', div.nodes[4].text); | |
1410 }); | |
1411 }); | |
1412 | |
1413 test('BindWithDynamicRef', () { | |
1414 var id = 't${new math.Random().nextInt(100)}'; | |
1415 var div = createTestHtml( | |
1416 '<template id="$id">' | |
1417 'Hi {{ name }}' | |
1418 '</template>' | |
1419 '<template ref="{{ id }}" bind="{{}}"></template>'); | |
1420 | |
1421 var t1 = div.firstChild; | |
1422 var t2 = div.nodes[1]; | |
1423 var model = toObservable({'name': 'Fry', 'id': id }); | |
1424 templateBind(t1).model = model; | |
1425 templateBind(t2).model = model; | |
1426 | |
1427 return new Future(() => expect(t2.nextNode.text, 'Hi Fry')); | |
1428 }); | |
1429 | |
1430 assertNodesAre(div, [arguments]) { | |
1431 var expectedLength = arguments.length; | |
1432 expect(div.nodes.length, expectedLength + 1); | |
1433 | |
1434 for (var i = 0; i < arguments.length; i++) { | |
1435 var targetNode = div.nodes[i + 1]; | |
1436 expect(targetNode.text, arguments[i]); | |
1437 } | |
1438 } | |
1439 | |
1440 test('Repeat3', () { | |
1441 var div = createTestHtml( | |
1442 '<template repeat="{{ contacts }}">Hi {{ name }}</template>'); | |
1443 var t = div.nodes.first; | |
1444 | |
1445 var m = toObservable({ | |
1446 'contacts': [ | |
1447 {'name': 'Raf'}, | |
1448 {'name': 'Arv'}, | |
1449 {'name': 'Neal'} | |
1450 ] | |
1451 }); | |
1452 | |
1453 templateBind(t).model = m; | |
1454 return new Future(() { | |
1455 | |
1456 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']); | |
1457 | |
1458 m['contacts'].add(toObservable({'name': 'Alex'})); | |
1459 }).then(endOfMicrotask).then((_) { | |
1460 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal', 'Hi Alex']); | |
1461 | |
1462 m['contacts'].replaceRange(0, 2, | |
1463 toObservable([{'name': 'Rafael'}, {'name': 'Erik'}])); | |
1464 }).then(endOfMicrotask).then((_) { | |
1465 assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Neal', 'Hi Alex']); | |
1466 | |
1467 m['contacts'].removeRange(1, 3); | |
1468 }).then(endOfMicrotask).then((_) { | |
1469 assertNodesAre(div, ['Hi Rafael', 'Hi Alex']); | |
1470 | |
1471 m['contacts'].insertAll(1, | |
1472 toObservable([{'name': 'Erik'}, {'name': 'Dimitri'}])); | |
1473 }).then(endOfMicrotask).then((_) { | |
1474 assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Dimitri', 'Hi Alex']); | |
1475 | |
1476 m['contacts'].replaceRange(0, 1, | |
1477 toObservable([{'name': 'Tab'}, {'name': 'Neal'}])); | |
1478 }).then(endOfMicrotask).then((_) { | |
1479 assertNodesAre(div, ['Hi Tab', 'Hi Neal', 'Hi Erik', 'Hi Dimitri', | |
1480 'Hi Alex']); | |
1481 | |
1482 m['contacts'] = toObservable([{'name': 'Alex'}]); | |
1483 }).then(endOfMicrotask).then((_) { | |
1484 assertNodesAre(div, ['Hi Alex']); | |
1485 | |
1486 m['contacts'].length = 0; | |
1487 }).then(endOfMicrotask).then((_) { | |
1488 assertNodesAre(div, []); | |
1489 }); | |
1490 }); | |
1491 | |
1492 test('RepeatModelSet', () { | |
1493 var div = createTestHtml( | |
1494 '<template repeat="{{ contacts }}">' | |
1495 'Hi {{ name }}' | |
1496 '</template>'); | |
1497 var template = div.firstChild; | |
1498 var m = toObservable({ | |
1499 'contacts': [ | |
1500 {'name': 'Raf'}, | |
1501 {'name': 'Arv'}, | |
1502 {'name': 'Neal'} | |
1503 ] | |
1504 }); | |
1505 templateBind(template).model = m; | |
1506 return new Future(() { | |
1507 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']); | |
1508 }); | |
1509 }); | |
1510 | |
1511 test('RepeatEmptyPath', () { | |
1512 var div = createTestHtml( | |
1513 '<template repeat="{{}}">Hi {{ name }}</template>'); | |
1514 var t = div.nodes.first; | |
1515 | |
1516 var m = toObservable([ | |
1517 {'name': 'Raf'}, | |
1518 {'name': 'Arv'}, | |
1519 {'name': 'Neal'} | |
1520 ]); | |
1521 templateBind(t).model = m; | |
1522 return new Future(() { | |
1523 | |
1524 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']); | |
1525 | |
1526 m.add(toObservable({'name': 'Alex'})); | |
1527 }).then(endOfMicrotask).then((_) { | |
1528 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal', 'Hi Alex']); | |
1529 | |
1530 m.replaceRange(0, 2, toObservable([{'name': 'Rafael'}, {'name': 'Erik'}]))
; | |
1531 }).then(endOfMicrotask).then((_) { | |
1532 assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Neal', 'Hi Alex']); | |
1533 | |
1534 m.removeRange(1, 3); | |
1535 }).then(endOfMicrotask).then((_) { | |
1536 assertNodesAre(div, ['Hi Rafael', 'Hi Alex']); | |
1537 | |
1538 m.insertAll(1, toObservable([{'name': 'Erik'}, {'name': 'Dimitri'}])); | |
1539 }).then(endOfMicrotask).then((_) { | |
1540 assertNodesAre(div, ['Hi Rafael', 'Hi Erik', 'Hi Dimitri', 'Hi Alex']); | |
1541 | |
1542 m.replaceRange(0, 1, toObservable([{'name': 'Tab'}, {'name': 'Neal'}])); | |
1543 }).then(endOfMicrotask).then((_) { | |
1544 assertNodesAre(div, ['Hi Tab', 'Hi Neal', 'Hi Erik', 'Hi Dimitri', | |
1545 'Hi Alex']); | |
1546 | |
1547 m.length = 0; | |
1548 m.add(toObservable({'name': 'Alex'})); | |
1549 }).then(endOfMicrotask).then((_) { | |
1550 assertNodesAre(div, ['Hi Alex']); | |
1551 }); | |
1552 }); | |
1553 | |
1554 test('RepeatNullModel', () { | |
1555 var div = createTestHtml( | |
1556 '<template repeat="{{}}">Hi {{ name }}</template>'); | |
1557 var t = div.nodes.first; | |
1558 | |
1559 var m = null; | |
1560 templateBind(t).model = m; | |
1561 | |
1562 expect(div.nodes.length, 1); | |
1563 | |
1564 t.attributes['iterate'] = ''; | |
1565 m = toObservable({}); | |
1566 templateBind(t).model = m; | |
1567 return new Future(() => expect(div.nodes.length, 1)); | |
1568 }); | |
1569 | |
1570 test('RepeatReuse', () { | |
1571 var div = createTestHtml( | |
1572 '<template repeat="{{}}">Hi {{ name }}</template>'); | |
1573 var t = div.nodes.first; | |
1574 | |
1575 var m = toObservable([ | |
1576 {'name': 'Raf'}, | |
1577 {'name': 'Arv'}, | |
1578 {'name': 'Neal'} | |
1579 ]); | |
1580 templateBind(t).model = m; | |
1581 | |
1582 var node1, node2, node3; | |
1583 return new Future(() { | |
1584 assertNodesAre(div, ['Hi Raf', 'Hi Arv', 'Hi Neal']); | |
1585 node1 = div.nodes[1]; | |
1586 node2 = div.nodes[2]; | |
1587 node3 = div.nodes[3]; | |
1588 | |
1589 m.replaceRange(1, 2, toObservable([{'name': 'Erik'}])); | |
1590 }).then(endOfMicrotask).then((_) { | |
1591 assertNodesAre(div, ['Hi Raf', 'Hi Erik', 'Hi Neal']); | |
1592 expect(div.nodes[1], node1, | |
1593 reason: 'model[0] did not change so the node should not have changed')
; | |
1594 expect(div.nodes[2], isNot(equals(node2)), | |
1595 reason: 'Should not reuse when replacing'); | |
1596 expect(div.nodes[3], node3, | |
1597 reason: 'model[2] did not change so the node should not have changed')
; | |
1598 | |
1599 node2 = div.nodes[2]; | |
1600 m.insert(0, toObservable({'name': 'Alex'})); | |
1601 }).then(endOfMicrotask).then((_) { | |
1602 assertNodesAre(div, ['Hi Alex', 'Hi Raf', 'Hi Erik', 'Hi Neal']); | |
1603 }); | |
1604 }); | |
1605 | |
1606 test('TwoLevelsDeepBug', () { | |
1607 var div = createTestHtml( | |
1608 '<template bind="{{}}"><span><span>{{ foo }}</span></span></template>'); | |
1609 var template = div.firstChild; | |
1610 var model = toObservable({'foo': 'bar'}); | |
1611 templateBind(template).model = model; | |
1612 return new Future(() { | |
1613 expect(div.nodes[1].nodes[0].nodes[0].text, 'bar'); | |
1614 }); | |
1615 }); | |
1616 | |
1617 test('Checked', () { | |
1618 var div = createTestHtml( | |
1619 '<template bind>' | |
1620 '<input type="checkbox" checked="{{a}}">' | |
1621 '</template>'); | |
1622 var t = div.nodes.first; | |
1623 templateBind(t).model = toObservable({'a': true }); | |
1624 | |
1625 return new Future(() { | |
1626 | |
1627 var instanceInput = t.nextNode; | |
1628 expect(instanceInput.checked, true); | |
1629 | |
1630 instanceInput.click(); | |
1631 expect(instanceInput.checked, false); | |
1632 | |
1633 instanceInput.click(); | |
1634 expect(instanceInput.checked, true); | |
1635 }); | |
1636 }); | |
1637 | |
1638 nestedHelper(s, start) { | |
1639 var div = createTestHtml(s); | |
1640 | |
1641 var m = toObservable({ | |
1642 'a': { | |
1643 'b': 1, | |
1644 'c': {'d': 2} | |
1645 }, | |
1646 }); | |
1647 | |
1648 recursivelySetTemplateModel(div, m); | |
1649 return new Future(() { | |
1650 | |
1651 var i = start; | |
1652 expect(div.nodes[i++].text, '1'); | |
1653 expect(div.nodes[i++].tagName, 'TEMPLATE'); | |
1654 expect(div.nodes[i++].text, '2'); | |
1655 | |
1656 m['a']['b'] = 11; | |
1657 }).then(endOfMicrotask).then((_) { | |
1658 expect(div.nodes[start].text, '11'); | |
1659 | |
1660 m['a']['c'] = toObservable({'d': 22}); | |
1661 }).then(endOfMicrotask).then((_) { | |
1662 expect(div.nodes[start + 2].text, '22'); | |
1663 | |
1664 //clearAllTemplates(div); | |
1665 }); | |
1666 } | |
1667 | |
1668 test('Nested', () => nestedHelper( | |
1669 '<template bind="{{a}}">' | |
1670 '{{b}}' | |
1671 '<template bind="{{c}}">' | |
1672 '{{d}}' | |
1673 '</template>' | |
1674 '</template>', 1)); | |
1675 | |
1676 test('NestedWithRef', () => nestedHelper( | |
1677 '<template id="inner">{{d}}</template>' | |
1678 '<template id="outer" bind="{{a}}">' | |
1679 '{{b}}' | |
1680 '<template ref="inner" bind="{{c}}"></template>' | |
1681 '</template>', 2)); | |
1682 | |
1683 nestedIterateInstantiateHelper(s, start) { | |
1684 var div = createTestHtml(s); | |
1685 | |
1686 var m = toObservable({ | |
1687 'a': [ | |
1688 { | |
1689 'b': 1, | |
1690 'c': {'d': 11} | |
1691 }, | |
1692 { | |
1693 'b': 2, | |
1694 'c': {'d': 22} | |
1695 } | |
1696 ] | |
1697 }); | |
1698 | |
1699 recursivelySetTemplateModel(div, m); | |
1700 return new Future(() { | |
1701 | |
1702 var i = start; | |
1703 expect(div.nodes[i++].text, '1'); | |
1704 expect(div.nodes[i++].tagName, 'TEMPLATE'); | |
1705 expect(div.nodes[i++].text, '11'); | |
1706 expect(div.nodes[i++].text, '2'); | |
1707 expect(div.nodes[i++].tagName, 'TEMPLATE'); | |
1708 expect(div.nodes[i++].text, '22'); | |
1709 | |
1710 m['a'][1] = toObservable({ | |
1711 'b': 3, | |
1712 'c': {'d': 33} | |
1713 }); | |
1714 | |
1715 }).then(endOfMicrotask).then((_) { | |
1716 expect(div.nodes[start + 3].text, '3'); | |
1717 expect(div.nodes[start + 5].text, '33'); | |
1718 }); | |
1719 } | |
1720 | |
1721 test('NestedRepeatBind', () => nestedIterateInstantiateHelper( | |
1722 '<template repeat="{{a}}">' | |
1723 '{{b}}' | |
1724 '<template bind="{{c}}">' | |
1725 '{{d}}' | |
1726 '</template>' | |
1727 '</template>', 1)); | |
1728 | |
1729 test('NestedRepeatBindWithRef', () => nestedIterateInstantiateHelper( | |
1730 '<template id="inner">' | |
1731 '{{d}}' | |
1732 '</template>' | |
1733 '<template repeat="{{a}}">' | |
1734 '{{b}}' | |
1735 '<template ref="inner" bind="{{c}}"></template>' | |
1736 '</template>', 2)); | |
1737 | |
1738 nestedIterateIterateHelper(s, start) { | |
1739 var div = createTestHtml(s); | |
1740 | |
1741 var m = toObservable({ | |
1742 'a': [ | |
1743 { | |
1744 'b': 1, | |
1745 'c': [{'d': 11}, {'d': 12}] | |
1746 }, | |
1747 { | |
1748 'b': 2, | |
1749 'c': [{'d': 21}, {'d': 22}] | |
1750 } | |
1751 ] | |
1752 }); | |
1753 | |
1754 recursivelySetTemplateModel(div, m); | |
1755 return new Future(() { | |
1756 | |
1757 var i = start; | |
1758 expect(div.nodes[i++].text, '1'); | |
1759 expect(div.nodes[i++].tagName, 'TEMPLATE'); | |
1760 expect(div.nodes[i++].text, '11'); | |
1761 expect(div.nodes[i++].text, '12'); | |
1762 expect(div.nodes[i++].text, '2'); | |
1763 expect(div.nodes[i++].tagName, 'TEMPLATE'); | |
1764 expect(div.nodes[i++].text, '21'); | |
1765 expect(div.nodes[i++].text, '22'); | |
1766 | |
1767 m['a'][1] = toObservable({ | |
1768 'b': 3, | |
1769 'c': [{'d': 31}, {'d': 32}, {'d': 33}] | |
1770 }); | |
1771 | |
1772 i = start + 4; | |
1773 }).then(endOfMicrotask).then((_) { | |
1774 expect(div.nodes[start + 4].text, '3'); | |
1775 expect(div.nodes[start + 6].text, '31'); | |
1776 expect(div.nodes[start + 7].text, '32'); | |
1777 expect(div.nodes[start + 8].text, '33'); | |
1778 }); | |
1779 } | |
1780 | |
1781 test('NestedRepeatBind', () => nestedIterateIterateHelper( | |
1782 '<template repeat="{{a}}">' | |
1783 '{{b}}' | |
1784 '<template repeat="{{c}}">' | |
1785 '{{d}}' | |
1786 '</template>' | |
1787 '</template>', 1)); | |
1788 | |
1789 test('NestedRepeatRepeatWithRef', () => nestedIterateIterateHelper( | |
1790 '<template id="inner">' | |
1791 '{{d}}' | |
1792 '</template>' | |
1793 '<template repeat="{{a}}">' | |
1794 '{{b}}' | |
1795 '<template ref="inner" repeat="{{c}}"></template>' | |
1796 '</template>', 2)); | |
1797 | |
1798 test('NestedRepeatSelfRef', () { | |
1799 var div = createTestHtml( | |
1800 '<template id="t" repeat="{{}}">' | |
1801 '{{name}}' | |
1802 '<template ref="t" repeat="{{items}}"></template>' | |
1803 '</template>'); | |
1804 | |
1805 var template = div.firstChild; | |
1806 | |
1807 var m = toObservable([ | |
1808 { | |
1809 'name': 'Item 1', | |
1810 'items': [ | |
1811 { | |
1812 'name': 'Item 1.1', | |
1813 'items': [ | |
1814 { | |
1815 'name': 'Item 1.1.1', | |
1816 'items': [] | |
1817 } | |
1818 ] | |
1819 }, | |
1820 { | |
1821 'name': 'Item 1.2' | |
1822 } | |
1823 ] | |
1824 }, | |
1825 { | |
1826 'name': 'Item 2', | |
1827 'items': [] | |
1828 }, | |
1829 ]); | |
1830 | |
1831 templateBind(template).model = m; | |
1832 | |
1833 int i = 1; | |
1834 return new Future(() { | |
1835 expect(div.nodes[i++].text, 'Item 1'); | |
1836 expect(div.nodes[i++].tagName, 'TEMPLATE'); | |
1837 expect(div.nodes[i++].text, 'Item 1.1'); | |
1838 expect(div.nodes[i++].tagName, 'TEMPLATE'); | |
1839 expect(div.nodes[i++].text, 'Item 1.1.1'); | |
1840 expect(div.nodes[i++].tagName, 'TEMPLATE'); | |
1841 expect(div.nodes[i++].text, 'Item 1.2'); | |
1842 expect(div.nodes[i++].tagName, 'TEMPLATE'); | |
1843 expect(div.nodes[i++].text, 'Item 2'); | |
1844 | |
1845 m[0] = toObservable({'name': 'Item 1 changed'}); | |
1846 | |
1847 i = 1; | |
1848 }).then(endOfMicrotask).then((_) { | |
1849 expect(div.nodes[i++].text, 'Item 1 changed'); | |
1850 expect(div.nodes[i++].tagName, 'TEMPLATE'); | |
1851 expect(div.nodes[i++].text, 'Item 2'); | |
1852 }); | |
1853 }); | |
1854 | |
1855 // Note: we don't need a zone for this test, and we don't want to alter timing | |
1856 // since we're testing a rather subtle relationship between select and option. | |
1857 test('Attribute Template Option/Optgroup', () { | |
1858 var div = createTestHtml( | |
1859 '<template bind>' | |
1860 '<select selectedIndex="{{ selected }}">' | |
1861 '<optgroup template repeat="{{ groups }}" label="{{ name }}">' | |
1862 '<option template repeat="{{ items }}">{{ val }}</option>' | |
1863 '</optgroup>' | |
1864 '</select>' | |
1865 '</template>'); | |
1866 | |
1867 var template = div.firstChild; | |
1868 var m = toObservable({ | |
1869 'selected': 1, | |
1870 'groups': [{ | |
1871 'name': 'one', 'items': [{ 'val': 0 }, { 'val': 1 }] | |
1872 }], | |
1873 }); | |
1874 | |
1875 templateBind(template).model = m; | |
1876 | |
1877 var completer = new Completer(); | |
1878 | |
1879 new MutationObserver((records, observer) { | |
1880 var select = div.nodes[0].nextNode; | |
1881 if (select == null || select.querySelector('option') == null) return; | |
1882 | |
1883 observer.disconnect(); | |
1884 new Future(() { | |
1885 expect(select.nodes.length, 2); | |
1886 | |
1887 expect(select.selectedIndex, 1, reason: 'selected index should update ' | |
1888 'after template expands.'); | |
1889 | |
1890 expect(select.nodes[0].tagName, 'TEMPLATE'); | |
1891 var optgroup = select.nodes[1]; | |
1892 expect(optgroup.nodes[0].tagName, 'TEMPLATE'); | |
1893 expect(optgroup.nodes[1].tagName, 'OPTION'); | |
1894 expect(optgroup.nodes[1].text, '0'); | |
1895 expect(optgroup.nodes[2].tagName, 'OPTION'); | |
1896 expect(optgroup.nodes[2].text, '1'); | |
1897 | |
1898 completer.complete(); | |
1899 }); | |
1900 })..observe(div, childList: true, subtree: true); | |
1901 | |
1902 Observable.dirtyCheck(); | |
1903 | |
1904 return completer.future; | |
1905 }); | |
1906 | |
1907 test('NestedIterateTableMixedSemanticNative', () { | |
1908 if (!parserHasNativeTemplate) return null; | |
1909 | |
1910 var div = createTestHtml( | |
1911 '<table><tbody>' | |
1912 '<template repeat="{{}}">' | |
1913 '<tr>' | |
1914 '<td template repeat="{{}}" class="{{ val }}">{{ val }}</td>' | |
1915 '</tr>' | |
1916 '</template>' | |
1917 '</tbody></table>'); | |
1918 var template = div.firstChild.firstChild.firstChild; | |
1919 | |
1920 var m = toObservable([ | |
1921 [{ 'val': 0 }, { 'val': 1 }], | |
1922 [{ 'val': 2 }, { 'val': 3 }] | |
1923 ]); | |
1924 | |
1925 templateBind(template).model = m; | |
1926 return new Future(() { | |
1927 var tbody = div.nodes[0].nodes[0]; | |
1928 | |
1929 // 1 for the <tr template>, 2 * (1 tr) | |
1930 expect(tbody.nodes.length, 3); | |
1931 | |
1932 // 1 for the <td template>, 2 * (1 td) | |
1933 expect(tbody.nodes[1].nodes.length, 3); | |
1934 | |
1935 expect(tbody.nodes[1].nodes[1].text, '0'); | |
1936 expect(tbody.nodes[1].nodes[2].text, '1'); | |
1937 | |
1938 // 1 for the <td template>, 2 * (1 td) | |
1939 expect(tbody.nodes[2].nodes.length, 3); | |
1940 expect(tbody.nodes[2].nodes[1].text, '2'); | |
1941 expect(tbody.nodes[2].nodes[2].text, '3'); | |
1942 | |
1943 // Asset the 'class' binding is retained on the semantic template (just | |
1944 // check the last one). | |
1945 expect(tbody.nodes[2].nodes[2].attributes["class"], '3'); | |
1946 }); | |
1947 }); | |
1948 | |
1949 test('NestedIterateTable', () { | |
1950 var div = createTestHtml( | |
1951 '<table><tbody>' | |
1952 '<tr template repeat="{{}}">' | |
1953 '<td template repeat="{{}}" class="{{ val }}">{{ val }}</td>' | |
1954 '</tr>' | |
1955 '</tbody></table>'); | |
1956 var template = div.firstChild.firstChild.firstChild; | |
1957 | |
1958 var m = toObservable([ | |
1959 [{ 'val': 0 }, { 'val': 1 }], | |
1960 [{ 'val': 2 }, { 'val': 3 }] | |
1961 ]); | |
1962 | |
1963 templateBind(template).model = m; | |
1964 return new Future(() { | |
1965 | |
1966 var i = 1; | |
1967 var tbody = div.nodes[0].nodes[0]; | |
1968 | |
1969 // 1 for the <tr template>, 2 * (1 tr) | |
1970 expect(tbody.nodes.length, 3); | |
1971 | |
1972 // 1 for the <td template>, 2 * (1 td) | |
1973 expect(tbody.nodes[1].nodes.length, 3); | |
1974 expect(tbody.nodes[1].nodes[1].text, '0'); | |
1975 expect(tbody.nodes[1].nodes[2].text, '1'); | |
1976 | |
1977 // 1 for the <td template>, 2 * (1 td) | |
1978 expect(tbody.nodes[2].nodes.length, 3); | |
1979 expect(tbody.nodes[2].nodes[1].text, '2'); | |
1980 expect(tbody.nodes[2].nodes[2].text, '3'); | |
1981 | |
1982 // Asset the 'class' binding is retained on the semantic template (just | |
1983 // check the last one). | |
1984 expect(tbody.nodes[2].nodes[2].attributes['class'], '3'); | |
1985 }); | |
1986 }); | |
1987 | |
1988 test('NestedRepeatDeletionOfMultipleSubTemplates', () { | |
1989 var div = createTestHtml( | |
1990 '<ul>' | |
1991 '<template repeat="{{}}" id=t1>' | |
1992 '<li>{{name}}' | |
1993 '<ul>' | |
1994 '<template ref=t1 repeat="{{items}}"></template>' | |
1995 '</ul>' | |
1996 '</li>' | |
1997 '</template>' | |
1998 '</ul>'); | |
1999 | |
2000 var m = toObservable([ | |
2001 { | |
2002 'name': 'Item 1', | |
2003 'items': [ | |
2004 { | |
2005 'name': 'Item 1.1' | |
2006 } | |
2007 ] | |
2008 } | |
2009 ]); | |
2010 var ul = div.firstChild; | |
2011 var t = ul.firstChild; | |
2012 | |
2013 templateBind(t).model = m; | |
2014 return new Future(() { | |
2015 expect(ul.nodes.length, 2); | |
2016 var ul2 = ul.nodes[1].nodes[1]; | |
2017 expect(ul2.nodes.length, 2); | |
2018 var ul3 = ul2.nodes[1].nodes[1]; | |
2019 expect(ul3.nodes.length, 1); | |
2020 | |
2021 m.removeAt(0); | |
2022 }).then(endOfMicrotask).then((_) { | |
2023 expect(ul.nodes.length, 1); | |
2024 }); | |
2025 }); | |
2026 | |
2027 test('DeepNested', () { | |
2028 var div = createTestHtml( | |
2029 '<template bind="{{a}}">' | |
2030 '<p>' | |
2031 '<template bind="{{b}}">' | |
2032 '{{ c }}' | |
2033 '</template>' | |
2034 '</p>' | |
2035 '</template>'); | |
2036 var template = div.firstChild; | |
2037 var m = toObservable({ | |
2038 'a': { | |
2039 'b': { | |
2040 'c': 42 | |
2041 } | |
2042 } | |
2043 }); | |
2044 templateBind(template).model = m; | |
2045 return new Future(() { | |
2046 expect(div.nodes[1].tagName, 'P'); | |
2047 expect(div.nodes[1].nodes.first.tagName, 'TEMPLATE'); | |
2048 expect(div.nodes[1].nodes[1].text, '42'); | |
2049 }); | |
2050 }); | |
2051 | |
2052 test('TemplateContentRemoved', () { | |
2053 var div = createTestHtml('<template bind="{{}}">{{ }}</template>'); | |
2054 var template = div.firstChild; | |
2055 var model = 42; | |
2056 | |
2057 templateBind(template).model = model; | |
2058 return new Future(() { | |
2059 expect(div.nodes[1].text, '42'); | |
2060 expect(div.nodes[0].text, ''); | |
2061 }); | |
2062 }); | |
2063 | |
2064 test('TemplateContentRemovedEmptyArray', () { | |
2065 var div = createTestHtml('<template iterate>Remove me</template>'); | |
2066 var template = div.firstChild; | |
2067 templateBind(template).model = []; | |
2068 return new Future(() { | |
2069 expect(div.nodes.length, 1); | |
2070 expect(div.nodes[0].text, ''); | |
2071 }); | |
2072 }); | |
2073 | |
2074 test('TemplateContentRemovedNested', () { | |
2075 var div = createTestHtml( | |
2076 '<template bind="{{}}">' | |
2077 '{{ a }}' | |
2078 '<template bind="{{}}">' | |
2079 '{{ b }}' | |
2080 '</template>' | |
2081 '</template>'); | |
2082 var template = div.firstChild; | |
2083 var model = toObservable({ | |
2084 'a': 1, | |
2085 'b': 2 | |
2086 }); | |
2087 templateBind(template).model = model; | |
2088 return new Future(() { | |
2089 expect(div.nodes[0].text, ''); | |
2090 expect(div.nodes[1].text, '1'); | |
2091 expect(div.nodes[2].text, ''); | |
2092 expect(div.nodes[3].text, '2'); | |
2093 }); | |
2094 }); | |
2095 | |
2096 test('BindWithUndefinedModel', () { | |
2097 var div = createTestHtml( | |
2098 '<template bind="{{}}" if="{{}}">{{ a }}</template>'); | |
2099 var template = div.firstChild; | |
2100 | |
2101 var model = toObservable({'a': 42}); | |
2102 templateBind(template).model = model; | |
2103 return new Future(() { | |
2104 expect(div.nodes[1].text, '42'); | |
2105 | |
2106 model = null; | |
2107 templateBind(template).model = model; | |
2108 }).then(endOfMicrotask).then((_) { | |
2109 expect(div.nodes.length, 1); | |
2110 | |
2111 model = toObservable({'a': 42}); | |
2112 templateBind(template).model = model; | |
2113 }).then(endOfMicrotask).then((_) { | |
2114 expect(div.nodes[1].text, '42'); | |
2115 }); | |
2116 }); | |
2117 | |
2118 test('BindNested', () { | |
2119 var div = createTestHtml( | |
2120 '<template bind="{{}}">' | |
2121 'Name: {{ name }}' | |
2122 '<template bind="{{wife}}" if="{{wife}}">' | |
2123 'Wife: {{ name }}' | |
2124 '</template>' | |
2125 '<template bind="{{child}}" if="{{child}}">' | |
2126 'Child: {{ name }}' | |
2127 '</template>' | |
2128 '</template>'); | |
2129 var template = div.firstChild; | |
2130 var m = toObservable({ | |
2131 'name': 'Hermes', | |
2132 'wife': { | |
2133 'name': 'LaBarbara' | |
2134 } | |
2135 }); | |
2136 templateBind(template).model = m; | |
2137 | |
2138 return new Future(() { | |
2139 expect(div.nodes.length, 5); | |
2140 expect(div.nodes[1].text, 'Name: Hermes'); | |
2141 expect(div.nodes[3].text, 'Wife: LaBarbara'); | |
2142 | |
2143 m['child'] = toObservable({'name': 'Dwight'}); | |
2144 | |
2145 }).then(endOfMicrotask).then((_) { | |
2146 expect(div.nodes.length, 6); | |
2147 expect(div.nodes[5].text, 'Child: Dwight'); | |
2148 | |
2149 m.remove('wife'); | |
2150 | |
2151 }).then(endOfMicrotask).then((_) { | |
2152 expect(div.nodes.length, 5); | |
2153 expect(div.nodes[4].text, 'Child: Dwight'); | |
2154 }); | |
2155 }); | |
2156 | |
2157 test('BindRecursive', () { | |
2158 var div = createTestHtml( | |
2159 '<template bind="{{}}" if="{{}}" id="t">' | |
2160 'Name: {{ name }}' | |
2161 '<template bind="{{friend}}" if="{{friend}}" ref="t"></template>' | |
2162 '</template>'); | |
2163 var template = div.firstChild; | |
2164 var m = toObservable({ | |
2165 'name': 'Fry', | |
2166 'friend': { | |
2167 'name': 'Bender' | |
2168 } | |
2169 }); | |
2170 templateBind(template).model = m; | |
2171 return new Future(() { | |
2172 expect(div.nodes.length, 5); | |
2173 expect(div.nodes[1].text, 'Name: Fry'); | |
2174 expect(div.nodes[3].text, 'Name: Bender'); | |
2175 | |
2176 m['friend']['friend'] = toObservable({'name': 'Leela'}); | |
2177 }).then(endOfMicrotask).then((_) { | |
2178 expect(div.nodes.length, 7); | |
2179 expect(div.nodes[5].text, 'Name: Leela'); | |
2180 | |
2181 m['friend'] = toObservable({'name': 'Leela'}); | |
2182 }).then(endOfMicrotask).then((_) { | |
2183 expect(div.nodes.length, 5); | |
2184 expect(div.nodes[3].text, 'Name: Leela'); | |
2185 }); | |
2186 }); | |
2187 | |
2188 test('Template - Self is terminator', () { | |
2189 var div = createTestHtml( | |
2190 '<template repeat>{{ foo }}' | |
2191 '<template bind></template>' | |
2192 '</template>'); | |
2193 var template = div.firstChild; | |
2194 | |
2195 var m = toObservable([{ 'foo': 'bar' }]); | |
2196 templateBind(template).model = m; | |
2197 return new Future(() { | |
2198 | |
2199 m.add(toObservable({ 'foo': 'baz' })); | |
2200 templateBind(template).model = m; | |
2201 }).then(endOfMicrotask).then((_) { | |
2202 | |
2203 expect(div.nodes.length, 5); | |
2204 expect(div.nodes[1].text, 'bar'); | |
2205 expect(div.nodes[3].text, 'baz'); | |
2206 }); | |
2207 }); | |
2208 | |
2209 test('Template - Same Contents, Different Array has no effect', () { | |
2210 if (!MutationObserver.supported) return null; | |
2211 | |
2212 var div = createTestHtml('<template repeat>{{ foo }}</template>'); | |
2213 var template = div.firstChild; | |
2214 | |
2215 var m = toObservable([{ 'foo': 'bar' }, { 'foo': 'bat'}]); | |
2216 templateBind(template).model = m; | |
2217 var observer = new MutationObserver((x, y) {}); | |
2218 return new Future(() { | |
2219 observer.observe(div, childList: true); | |
2220 | |
2221 var template = div.firstChild; | |
2222 templateBind(template).model = new ObservableList.from(m); | |
2223 }).then(endOfMicrotask).then((_) { | |
2224 var records = observer.takeRecords(); | |
2225 expect(records.length, 0); | |
2226 }); | |
2227 }); | |
2228 | |
2229 test('RecursiveRef', () { | |
2230 var div = createTestHtml( | |
2231 '<template bind>' | |
2232 '<template id=src>{{ foo }}</template>' | |
2233 '<template bind ref=src></template>' | |
2234 '</template>'); | |
2235 | |
2236 var m = toObservable({'foo': 'bar'}); | |
2237 templateBind(div.firstChild).model = m; | |
2238 return new Future(() { | |
2239 expect(div.nodes.length, 4); | |
2240 expect(div.nodes[3].text, 'bar'); | |
2241 }); | |
2242 }); | |
2243 | |
2244 test('baseURI', () { | |
2245 // TODO(jmesserly): Dart's setInnerHtml breaks this test -- the template | |
2246 // URL is created as blank despite the NullTreeSanitizer. | |
2247 // Use JS interop as a workaround. | |
2248 //var div = createTestHtml('<template bind>' | |
2249 // '<div style="background: url(foo.jpg)"></div></template>'); | |
2250 var div = new DivElement(); | |
2251 new JsObject.fromBrowserObject(div)['innerHTML'] = '<template bind>' | |
2252 '<div style="background: url(foo.jpg)"></div></template>'; | |
2253 testDiv.append(div); | |
2254 TemplateBindExtension.decorate(div.firstChild); | |
2255 | |
2256 var local = document.createElement('div'); | |
2257 local.attributes['style'] = 'background: url(foo.jpg)'; | |
2258 div.append(local); | |
2259 var template = div.firstChild; | |
2260 templateBind(template).model = {}; | |
2261 return new Future(() { | |
2262 expect(div.nodes[1].style.backgroundImage, local.style.backgroundImage); | |
2263 }); | |
2264 }); | |
2265 | |
2266 test('ChangeRefId', () { | |
2267 var div = createTestHtml( | |
2268 '<template id="a">a:{{ }}</template>' | |
2269 '<template id="b">b:{{ }}</template>' | |
2270 '<template repeat="{{}}">' | |
2271 '<template ref="a" bind="{{}}"></template>' | |
2272 '</template>'); | |
2273 var template = div.nodes[2]; | |
2274 var model = toObservable([]); | |
2275 templateBind(template).model = model; | |
2276 return new Future(() { | |
2277 expect(div.nodes.length, 3); | |
2278 | |
2279 document.getElementById('a').id = 'old-a'; | |
2280 document.getElementById('b').id = 'a'; | |
2281 | |
2282 model..add(1)..add(2); | |
2283 }).then(endOfMicrotask).then((_) { | |
2284 | |
2285 expect(div.nodes.length, 7); | |
2286 expect(div.nodes[4].text, 'b:1'); | |
2287 expect(div.nodes[6].text, 'b:2'); | |
2288 }); | |
2289 }); | |
2290 | |
2291 test('Content', () { | |
2292 var div = createTestHtml( | |
2293 '<template><a></a></template>' | |
2294 '<template><b></b></template>'); | |
2295 var templateA = div.nodes.first; | |
2296 var templateB = div.nodes.last; | |
2297 var contentA = templateBind(templateA).content; | |
2298 var contentB = templateBind(templateB).content; | |
2299 expect(contentA, isNotNull); | |
2300 | |
2301 expect(templateA.ownerDocument, isNot(equals(contentA.ownerDocument))); | |
2302 expect(templateB.ownerDocument, isNot(equals(contentB.ownerDocument))); | |
2303 | |
2304 expect(templateB.ownerDocument, templateA.ownerDocument); | |
2305 expect(contentB.ownerDocument, contentA.ownerDocument); | |
2306 | |
2307 // NOTE: these tests don't work under ShadowDOM polyfill. | |
2308 // Disabled for now. | |
2309 //expect(templateA.ownerDocument.window, window); | |
2310 //expect(templateB.ownerDocument.window, window); | |
2311 | |
2312 expect(contentA.ownerDocument.window, null); | |
2313 expect(contentB.ownerDocument.window, null); | |
2314 | |
2315 expect(contentA.nodes.last, contentA.nodes.first); | |
2316 expect(contentA.nodes.first.tagName, 'A'); | |
2317 | |
2318 expect(contentB.nodes.last, contentB.nodes.first); | |
2319 expect(contentB.nodes.first.tagName, 'B'); | |
2320 }); | |
2321 | |
2322 test('NestedContent', () { | |
2323 var div = createTestHtml( | |
2324 '<template>' | |
2325 '<template></template>' | |
2326 '</template>'); | |
2327 var templateA = div.nodes.first; | |
2328 var templateB = templateBind(templateA).content.nodes.first; | |
2329 | |
2330 expect(templateB.ownerDocument, templateBind(templateA) | |
2331 .content.ownerDocument); | |
2332 expect(templateBind(templateB).content.ownerDocument, | |
2333 templateBind(templateA).content.ownerDocument); | |
2334 }); | |
2335 | |
2336 test('BindShadowDOM', () { | |
2337 if (!ShadowRoot.supported) return null; | |
2338 | |
2339 var root = createShadowTestHtml( | |
2340 '<template bind="{{}}">Hi {{ name }}</template>'); | |
2341 var model = toObservable({'name': 'Leela'}); | |
2342 templateBind(root.firstChild).model = model; | |
2343 return new Future(() => expect(root.nodes[1].text, 'Hi Leela')); | |
2344 }); | |
2345 | |
2346 // Dart note: this test seems gone from JS. Keeping for posterity sake. | |
2347 test('BindShadowDOM createInstance', () { | |
2348 if (!ShadowRoot.supported) return null; | |
2349 | |
2350 var model = toObservable({'name': 'Leela'}); | |
2351 var template = new Element.html('<template>Hi {{ name }}</template>'); | |
2352 var root = createShadowTestHtml(''); | |
2353 root.nodes.add(templateBind(template).createInstance(model)); | |
2354 | |
2355 return new Future(() { | |
2356 expect(root.text, 'Hi Leela'); | |
2357 | |
2358 model['name'] = 'Fry'; | |
2359 }).then(endOfMicrotask).then((_) { | |
2360 expect(root.text, 'Hi Fry'); | |
2361 }); | |
2362 }); | |
2363 | |
2364 test('BindShadowDOM Template Ref', () { | |
2365 if (!ShadowRoot.supported) return null; | |
2366 var root = createShadowTestHtml( | |
2367 '<template id=foo>Hi</template><template bind ref=foo></template>'); | |
2368 var template = root.nodes[1]; | |
2369 templateBind(template).model = toObservable({}); | |
2370 return new Future(() { | |
2371 expect(root.nodes.length, 3); | |
2372 clearAllTemplates(root); | |
2373 }); | |
2374 }); | |
2375 | |
2376 // https://github.com/Polymer/TemplateBinding/issues/8 | |
2377 test('UnbindingInNestedBind', () { | |
2378 var div = createTestHtml( | |
2379 '<template bind="{{outer}}" if="{{outer}}" syntax="testHelper">' | |
2380 '<template bind="{{inner}}" if="{{inner}}">' | |
2381 '{{ age }}' | |
2382 '</template>' | |
2383 '</template>'); | |
2384 var template = div.firstChild; | |
2385 var syntax = new UnbindingInNestedBindSyntax(); | |
2386 var model = toObservable({'outer': {'inner': {'age': 42}}}); | |
2387 | |
2388 templateBind(template)..model = model..bindingDelegate = syntax; | |
2389 | |
2390 return new Future(() { | |
2391 expect(syntax.count, 1); | |
2392 | |
2393 var inner = model['outer']['inner']; | |
2394 model['outer'] = null; | |
2395 | |
2396 }).then(endOfMicrotask).then((_) { | |
2397 expect(syntax.count, 1); | |
2398 | |
2399 model['outer'] = toObservable({'inner': {'age': 2}}); | |
2400 syntax.expectedAge = 2; | |
2401 | |
2402 }).then(endOfMicrotask).then((_) { | |
2403 expect(syntax.count, 2); | |
2404 }); | |
2405 }); | |
2406 | |
2407 // https://github.com/toolkitchen/mdv/issues/8 | |
2408 test('DontCreateInstancesForAbandonedIterators', () { | |
2409 var div = createTestHtml( | |
2410 '<template bind="{{}} {{}}">' | |
2411 '<template bind="{{}}">Foo</template>' | |
2412 '</template>'); | |
2413 var template = div.firstChild; | |
2414 templateBind(template).model = null; | |
2415 return nextMicrotask; | |
2416 }); | |
2417 | |
2418 test('CreateInstance', () { | |
2419 var div = createTestHtml( | |
2420 '<template bind="{{a}}">' | |
2421 '<template bind="{{b}}">' | |
2422 '{{ foo }}:{{ replaceme }}' | |
2423 '</template>' | |
2424 '</template>'); | |
2425 var outer = templateBind(div.nodes.first); | |
2426 var model = toObservable({'b': {'foo': 'bar'}}); | |
2427 | |
2428 var instance = outer.createInstance(model, new TestBindingSyntax()); | |
2429 expect(instance.firstChild.nextNode.text, 'bar:replaced'); | |
2430 | |
2431 clearAllTemplates(instance); | |
2432 }); | |
2433 | |
2434 test('CreateInstance - sync error', () { | |
2435 var div = createTestHtml('<template>{{foo}}</template>'); | |
2436 var outer = templateBind(div.nodes.first); | |
2437 var model = 1; // model is missing 'foo' should throw. | |
2438 expect(() => outer.createInstance(model, new TestBindingSyntax()), | |
2439 throwsA(_isNoSuchMethodError)); | |
2440 }); | |
2441 | |
2442 test('CreateInstance - async error', () { | |
2443 var div = createTestHtml( | |
2444 '<template>' | |
2445 '<template bind="{{b}}">' | |
2446 '{{ foo }}:{{ replaceme }}' | |
2447 '</template>' | |
2448 '</template>'); | |
2449 var outer = templateBind(div.nodes.first); | |
2450 var model = toObservable({'b': 1}); // missing 'foo' should throw. | |
2451 | |
2452 bool seen = false; | |
2453 runZoned(() => outer.createInstance(model, new TestBindingSyntax()), | |
2454 onError: (e) { | |
2455 _expectNoSuchMethod(e); | |
2456 seen = true; | |
2457 }); | |
2458 return new Future(() { expect(seen, isTrue); }); | |
2459 }); | |
2460 | |
2461 test('Repeat - svg', () { | |
2462 var div = createTestHtml( | |
2463 '<svg width="400" height="110">' | |
2464 '<template repeat>' | |
2465 '<rect width="{{ width }}" height="{{ height }}" />' | |
2466 '</template>' | |
2467 '</svg>'); | |
2468 | |
2469 var model = toObservable([{ 'width': 10, 'height': 11 }, | |
2470 { 'width': 20, 'height': 21 }]); | |
2471 var svg = div.firstChild; | |
2472 var template = svg.firstChild; | |
2473 templateBind(template).model = model; | |
2474 | |
2475 return new Future(() { | |
2476 expect(svg.nodes.length, 3); | |
2477 expect(svg.nodes[1].attributes['width'], '10'); | |
2478 expect(svg.nodes[1].attributes['height'], '11'); | |
2479 expect(svg.nodes[2].attributes['width'], '20'); | |
2480 expect(svg.nodes[2].attributes['height'], '21'); | |
2481 }); | |
2482 }); | |
2483 | |
2484 test('Bootstrap', () { | |
2485 var div = new DivElement(); | |
2486 div.innerHtml = | |
2487 '<template>' | |
2488 '<div></div>' | |
2489 '<template>' | |
2490 'Hello' | |
2491 '</template>' | |
2492 '</template>'; | |
2493 | |
2494 TemplateBindExtension.bootstrap(div); | |
2495 var template = templateBind(div.nodes.first); | |
2496 expect(template.content.nodes.length, 2); | |
2497 var template2 = templateBind(template.content.nodes.first.nextNode); | |
2498 expect(template2.content.nodes.length, 1); | |
2499 expect(template2.content.nodes.first.text, 'Hello'); | |
2500 | |
2501 template = new Element.tag('template'); | |
2502 template.innerHtml = | |
2503 '<template>' | |
2504 '<div></div>' | |
2505 '<template>' | |
2506 'Hello' | |
2507 '</template>' | |
2508 '</template>'; | |
2509 | |
2510 TemplateBindExtension.bootstrap(template); | |
2511 template2 = templateBind(templateBind(template).content.nodes.first); | |
2512 expect(template2.content.nodes.length, 2); | |
2513 var template3 = templateBind(template2.content.nodes.first.nextNode); | |
2514 expect(template3.content.nodes.length, 1); | |
2515 expect(template3.content.nodes.first.text, 'Hello'); | |
2516 }); | |
2517 | |
2518 test('issue-285', () { | |
2519 var div = createTestHtml( | |
2520 '<template>' | |
2521 '<template bind if="{{show}}">' | |
2522 '<template id=del repeat="{{items}}">' | |
2523 '{{}}' | |
2524 '</template>' | |
2525 '</template>' | |
2526 '</template>'); | |
2527 | |
2528 var template = div.firstChild; | |
2529 | |
2530 var model = toObservable({ | |
2531 'show': true, | |
2532 'items': [1] | |
2533 }); | |
2534 | |
2535 div.append(templateBind(template).createInstance(model, | |
2536 new Issue285Syntax())); | |
2537 | |
2538 return new Future(() { | |
2539 expect(template.nextNode.nextNode.nextNode.text, '2'); | |
2540 model['show'] = false; | |
2541 }).then(endOfMicrotask).then((_) { | |
2542 model['show'] = true; | |
2543 }).then(endOfMicrotask).then((_) { | |
2544 expect(template.nextNode.nextNode.nextNode.text, '2'); | |
2545 }); | |
2546 }); | |
2547 | |
2548 test('Accessor value retrieval count', () { | |
2549 var div = createTestHtml( | |
2550 '<template bind>{{ prop }}</template>'); | |
2551 | |
2552 var model = new TestAccessorModel(); | |
2553 | |
2554 templateBind(div.firstChild).model = model; | |
2555 | |
2556 return new Future(() { | |
2557 expect(model.count, 1); | |
2558 | |
2559 model.value++; | |
2560 // Dart note: we don't handle getters in @observable, so we need to | |
2561 // notify regardless. | |
2562 model.notifyPropertyChange(#prop, 1, model.value); | |
2563 | |
2564 }).then(endOfMicrotask).then((_) { | |
2565 expect(model.count, 2); | |
2566 }); | |
2567 }); | |
2568 | |
2569 test('issue-141', () { | |
2570 var div = createTestHtml( | |
2571 '<template bind>' | |
2572 '<div foo="{{foo1}} {{foo2}}" bar="{{bar}}"></div>' | |
2573 '</template>'); | |
2574 | |
2575 var template = div.firstChild; | |
2576 var model = toObservable({ | |
2577 'foo1': 'foo1Value', | |
2578 'foo2': 'foo2Value', | |
2579 'bar': 'barValue' | |
2580 }); | |
2581 | |
2582 templateBind(template).model = model; | |
2583 return new Future(() { | |
2584 expect(div.lastChild.attributes['bar'], 'barValue'); | |
2585 }); | |
2586 }); | |
2587 | |
2588 test('issue-18', () { | |
2589 var delegate = new Issue18Syntax(); | |
2590 | |
2591 var div = createTestHtml( | |
2592 '<template bind>' | |
2593 '<div class="foo: {{ bar }}"></div>' | |
2594 '</template>'); | |
2595 | |
2596 var template = div.firstChild; | |
2597 var model = toObservable({'bar': 2}); | |
2598 | |
2599 templateBind(template)..model = model..bindingDelegate = delegate; | |
2600 | |
2601 return new Future(() { | |
2602 expect(div.lastChild.attributes['class'], 'foo: 2'); | |
2603 }); | |
2604 }); | |
2605 | |
2606 test('issue-152', () { | |
2607 var div = createTestHtml( | |
2608 '<template ref=notThere bind>XXX</template>'); | |
2609 | |
2610 var template = div.firstChild; | |
2611 templateBind(template).model = {}; | |
2612 | |
2613 return new Future(() { | |
2614 // if a ref cannot be located, a template will continue to use itself | |
2615 // as the source of template instances. | |
2616 expect(div.nodes[1].text, 'XXX'); | |
2617 }); | |
2618 }); | |
2619 } | |
2620 | |
2621 compatTests() { | |
2622 test('underbar bindings', () { | |
2623 var div = createTestHtml( | |
2624 '<template bind>' | |
2625 '<div _style="color: {{ color }};"></div>' | |
2626 '<img _src="{{ url }}">' | |
2627 '<a _href="{{ url2 }}">Link</a>' | |
2628 '<input type="number" _value="{{ number }}">' | |
2629 '</template>'); | |
2630 | |
2631 var template = div.firstChild; | |
2632 var model = toObservable({ | |
2633 'color': 'red', | |
2634 'url': 'pic.jpg', | |
2635 'url2': 'link.html', | |
2636 'number': 4 | |
2637 }); | |
2638 | |
2639 templateBind(template).model = model; | |
2640 return new Future(() { | |
2641 var subDiv = div.firstChild.nextNode; | |
2642 expect(subDiv.attributes['style'], 'color: red;'); | |
2643 | |
2644 var img = subDiv.nextNode; | |
2645 expect(img.attributes['src'], 'pic.jpg'); | |
2646 | |
2647 var a = img.nextNode; | |
2648 expect(a.attributes['href'], 'link.html'); | |
2649 | |
2650 var input = a.nextNode; | |
2651 expect(input.value, '4'); | |
2652 }); | |
2653 }); | |
2654 } | |
2655 | |
2656 // TODO(jmesserly): ideally we could test the type with isNoSuchMethodError, | |
2657 // however dart:js converts the nSM into a String at some point. | |
2658 // So for now we do string comparison. | |
2659 _isNoSuchMethodError(e) => '$e'.contains('NoSuchMethodError'); | |
2660 | |
2661 _expectNoSuchMethod(e) { | |
2662 // expect(e, isNoSuchMethodError); | |
2663 expect('$e', contains('NoSuchMethodError')); | |
2664 } | |
2665 | |
2666 class Issue285Syntax extends BindingDelegate { | |
2667 prepareInstanceModel(template) { | |
2668 if (template.id == 'del') return (val) => val * 2; | |
2669 } | |
2670 } | |
2671 | |
2672 class TestBindingSyntax extends BindingDelegate { | |
2673 prepareBinding(String path, name, node) { | |
2674 if (path.trim() == 'replaceme') { | |
2675 return (m, n, oneTime) => new PathObserver('replaced', ''); | |
2676 } | |
2677 return null; | |
2678 } | |
2679 } | |
2680 | |
2681 class UnbindingInNestedBindSyntax extends BindingDelegate { | |
2682 int expectedAge = 42; | |
2683 int count = 0; | |
2684 | |
2685 prepareBinding(path, name, node) { | |
2686 if (name != 'text' || path != 'age') return null; | |
2687 | |
2688 return (model, _, oneTime) { | |
2689 expect(model['age'], expectedAge); | |
2690 count++; | |
2691 return new PathObserver(model, path); | |
2692 }; | |
2693 } | |
2694 } | |
2695 | |
2696 class Issue18Syntax extends BindingDelegate { | |
2697 prepareBinding(path, name, node) { | |
2698 if (name != 'class') return null; | |
2699 | |
2700 return (model, _, oneTime) => new PathObserver(model, path); | |
2701 } | |
2702 } | |
2703 | |
2704 class BindIfMinimalDiscardChanges extends BindingDelegate { | |
2705 Map<String, int> discardChangesCalled; | |
2706 | |
2707 BindIfMinimalDiscardChanges(this.discardChangesCalled) : super() {} | |
2708 | |
2709 prepareBinding(path, name, node) { | |
2710 return (model, node, oneTime) => | |
2711 new DiscardCountingPathObserver(discardChangesCalled, model, path); | |
2712 } | |
2713 } | |
2714 | |
2715 class DiscardCountingPathObserver extends PathObserver { | |
2716 Map<String, int> discardChangesCalled; | |
2717 | |
2718 DiscardCountingPathObserver(this.discardChangesCalled, model, path) | |
2719 : super(model, path) {} | |
2720 | |
2721 get value { | |
2722 discardChangesCalled[path.toString()]++; | |
2723 return super.value; | |
2724 } | |
2725 } | |
2726 | |
2727 class TestAccessorModel extends Observable { | |
2728 @observable var value = 1; | |
2729 var count = 0; | |
2730 | |
2731 @reflectable | |
2732 get prop { | |
2733 count++; | |
2734 return value; | |
2735 } | |
2736 } | |
OLD | NEW |