| 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 import 'dart:async'; | |
| 6 import 'dart:html'; | |
| 7 | |
| 8 import 'package:observe/observe.dart'; | |
| 9 import 'package:observe/mirrors_used.dart'; // make test smaller. | |
| 10 import 'package:polymer_expressions/polymer_expressions.dart'; | |
| 11 import 'package:polymer_expressions/eval.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:smoke/mirrors.dart' as smoke; | |
| 16 | |
| 17 class TestScopeFactory implements ScopeFactory { | |
| 18 int scopeCount = 0; | |
| 19 | |
| 20 modelScope({Object model, Map<String, Object> variables}) { | |
| 21 scopeCount++; | |
| 22 return new Scope(model: model, variables: variables); | |
| 23 } | |
| 24 | |
| 25 childScope(Scope parent, String name, Object value) { | |
| 26 scopeCount++; | |
| 27 return parent.childScope(name, value); | |
| 28 } | |
| 29 } | |
| 30 | |
| 31 main() { | |
| 32 useHtmlConfiguration(); | |
| 33 smoke.useMirrors(); | |
| 34 | |
| 35 group('PolymerExpressions', () { | |
| 36 DivElement testDiv; | |
| 37 TestScopeFactory testScopeFactory; | |
| 38 | |
| 39 setUp(() { | |
| 40 document.body.append(testDiv = new DivElement()); | |
| 41 testScopeFactory = new TestScopeFactory(); | |
| 42 }); | |
| 43 | |
| 44 tearDown(() { | |
| 45 testDiv.children.clear(); | |
| 46 testDiv = null; | |
| 47 }); | |
| 48 | |
| 49 Future<Element> setUpTest(String html, {model, Map globals}) { | |
| 50 var tag = new Element.html(html, | |
| 51 treeSanitizer: new NullNodeTreeSanitizer()); | |
| 52 | |
| 53 // make sure templates behave in the polyfill | |
| 54 TemplateBindExtension.bootstrap(tag); | |
| 55 | |
| 56 templateBind(tag) | |
| 57 ..bindingDelegate = new PolymerExpressions(globals: globals, | |
| 58 scopeFactory: testScopeFactory) | |
| 59 ..model = model; | |
| 60 testDiv.children.clear(); | |
| 61 testDiv.append(tag); | |
| 62 return waitForChange(testDiv); | |
| 63 } | |
| 64 | |
| 65 group('scope creation', () { | |
| 66 // These tests are sensitive to some internals of the implementation that | |
| 67 // might not be visible to applications, but are useful for verifying that | |
| 68 // that we're not creating too many Scopes. | |
| 69 | |
| 70 // The reason that we create two Scopes in the cases with one binding is | |
| 71 // that <template bind> has one scope for the context to evaluate the bind | |
| 72 // binding in, and another scope for the bindings inside the template. | |
| 73 | |
| 74 // We could try to optimize the outer scope away in cases where the | |
| 75 // expression is empty, but there are a lot of special cases in the | |
| 76 // syntax code already. | |
| 77 test('should create one scope for a single binding', () => | |
| 78 setUpTest(''' | |
| 79 <template id="test" bind> | |
| 80 <div>{{ data }}</div> | |
| 81 </template>''', | |
| 82 model: new Model('a')) | |
| 83 .then((_) { | |
| 84 expect(testDiv.children.length, 2); | |
| 85 expect(testDiv.children[1].text, 'a'); | |
| 86 expect(testScopeFactory.scopeCount, 1); | |
| 87 })); | |
| 88 | |
| 89 test('should only create a single scope for two bindings', () => | |
| 90 setUpTest(''' | |
| 91 <template id="test" bind> | |
| 92 <div>{{ data }}</div> | |
| 93 <div>{{ data }}</div> | |
| 94 </template>''', | |
| 95 model: new Model('a')) | |
| 96 .then((_) { | |
| 97 expect(testDiv.children.length, 3); | |
| 98 expect(testDiv.children[1].text, 'a'); | |
| 99 expect(testDiv.children[2].text, 'a'); | |
| 100 expect(testScopeFactory.scopeCount, 1); | |
| 101 })); | |
| 102 | |
| 103 test('should create a new scope for a bind/as binding', () { | |
| 104 return setUpTest(''' | |
| 105 <template id="test" bind> | |
| 106 <div>{{ data }}</div> | |
| 107 <template bind="{{ data as a }}" id="inner"> | |
| 108 <div>{{ a }}</div> | |
| 109 <div>{{ data }}</div> | |
| 110 </template> | |
| 111 </template>''', | |
| 112 model: new Model('foo')) | |
| 113 .then((_) { | |
| 114 expect(testDiv.children.length, 5); | |
| 115 expect(testDiv.children[1].text, 'foo'); | |
| 116 expect(testDiv.children[3].text, 'foo'); | |
| 117 expect(testDiv.children[4].text, 'foo'); | |
| 118 expect(testScopeFactory.scopeCount, 2); | |
| 119 }); | |
| 120 }); | |
| 121 | |
| 122 test('should create scopes for a repeat/in binding', () { | |
| 123 return setUpTest(''' | |
| 124 <template id="test" bind> | |
| 125 <div>{{ data }}</div> | |
| 126 <template repeat="{{ i in items }}" id="inner"> | |
| 127 <div>{{ i }}</div> | |
| 128 <div>{{ data }}</div> | |
| 129 </template> | |
| 130 </template>''', | |
| 131 model: new Model('foo'), globals: {'items': ['a', 'b', 'c']}) | |
| 132 .then((_) { | |
| 133 expect(testDiv.children.length, 9); | |
| 134 expect(testDiv.children[1].text, 'foo'); | |
| 135 expect(testDiv.children[3].text, 'a'); | |
| 136 expect(testDiv.children[4].text, 'foo'); | |
| 137 expect(testDiv.children[5].text, 'b'); | |
| 138 expect(testDiv.children[6].text, 'foo'); | |
| 139 expect(testDiv.children[7].text, 'c'); | |
| 140 expect(testDiv.children[8].text, 'foo'); | |
| 141 // 1 scopes for <template bind>, 1 for each repeat | |
| 142 expect(testScopeFactory.scopeCount, 4); | |
| 143 }); | |
| 144 }); | |
| 145 | |
| 146 | |
| 147 }); | |
| 148 | |
| 149 group('with template bind', () { | |
| 150 | |
| 151 test('should show a simple binding on the model', () => | |
| 152 setUpTest(''' | |
| 153 <template id="test" bind> | |
| 154 <div>{{ data }}</div> | |
| 155 </template>''', | |
| 156 model: new Model('a')) | |
| 157 .then((_) { | |
| 158 expect(testDiv.children.length, 2); | |
| 159 expect(testDiv.children[1].text, 'a'); | |
| 160 })); | |
| 161 | |
| 162 test('should handle an empty binding on the model', () => | |
| 163 setUpTest(''' | |
| 164 <template id="test" bind> | |
| 165 <div>{{ }}</div> | |
| 166 </template>''', | |
| 167 model: 'a') | |
| 168 .then((_) { | |
| 169 expect(testDiv.children.length, 2); | |
| 170 expect(testDiv.children[1].text, 'a'); | |
| 171 })); | |
| 172 | |
| 173 test('should show a simple binding to a global', () => | |
| 174 setUpTest(''' | |
| 175 <template id="test" bind> | |
| 176 <div>{{ a }}</div> | |
| 177 </template>''', | |
| 178 globals: {'a': '123'}) | |
| 179 .then((_) { | |
| 180 expect(testDiv.children.length, 2); | |
| 181 expect(testDiv.children[1].text, '123'); | |
| 182 })); | |
| 183 | |
| 184 test('should show an expression binding', () => | |
| 185 setUpTest(''' | |
| 186 <template id="test" bind> | |
| 187 <div>{{ data + 'b' }}</div> | |
| 188 </template>''', | |
| 189 model: new Model('a')) | |
| 190 .then((_) { | |
| 191 expect(testDiv.children.length, 2); | |
| 192 expect(testDiv.children[1].text, 'ab'); | |
| 193 })); | |
| 194 | |
| 195 test('should handle an expression in the bind attribute', () => | |
| 196 setUpTest(''' | |
| 197 <template id="test" bind="{{ data }}"> | |
| 198 <div>{{ this }}</div> | |
| 199 </template>''', | |
| 200 model: new Model('a')) | |
| 201 .then((_) { | |
| 202 expect(testDiv.children.length, 2); | |
| 203 expect(testDiv.children[1].text, 'a'); | |
| 204 })); | |
| 205 | |
| 206 test('should handle a nested template with an expression in the bind ' | |
| 207 'attribute', () => | |
| 208 setUpTest(''' | |
| 209 <template id="test" bind> | |
| 210 <template id="inner" bind="{{ data }}"> | |
| 211 <div>{{ this }}</div> | |
| 212 </template> | |
| 213 </template>''', | |
| 214 model: new Model('a')) | |
| 215 .then((_) { | |
| 216 expect(testDiv.children.length, 3); | |
| 217 expect(testDiv.children[2].text, 'a'); | |
| 218 })); | |
| 219 | |
| 220 | |
| 221 test('should handle an "as" expression in the bind attribute', () => | |
| 222 setUpTest(''' | |
| 223 <template id="test" bind="{{ data as a }}"> | |
| 224 <div>{{ data }}b</div> | |
| 225 <div>{{ a }}c</div> | |
| 226 </template>''', | |
| 227 model: new Model('a')) | |
| 228 .then((_) { | |
| 229 expect(testDiv.children.length, 3); | |
| 230 expect(testDiv.children[1].text, 'ab'); | |
| 231 expect(testDiv.children[2].text, 'ac'); | |
| 232 })); | |
| 233 | |
| 234 // passes safari | |
| 235 test('should not resolve names in the outer template from within a nested' | |
| 236 ' template with a bind binding', () { | |
| 237 var completer = new Completer(); | |
| 238 var bindingErrorHappened = false; | |
| 239 var templateRendered = false; | |
| 240 maybeComplete() { | |
| 241 if (bindingErrorHappened && templateRendered) { | |
| 242 completer.complete(true); | |
| 243 } | |
| 244 } | |
| 245 runZoned(() { | |
| 246 setUpTest(''' | |
| 247 <template id="test" bind> | |
| 248 <div>{{ data }}</div> | |
| 249 <div>{{ b }}</div> | |
| 250 <template id="inner" bind="{{ b }}"> | |
| 251 <div>{{ data }}</div> | |
| 252 <div>{{ b }}</div> | |
| 253 <div>{{ this }}</div> | |
| 254 </template> | |
| 255 </template>''', | |
| 256 model: new Model('foo'), globals: {'b': 'bbb'}) | |
| 257 .then((_) { | |
| 258 expect(testDiv.children[0].text, ''); | |
| 259 expect(testDiv.children[1].text, 'foo'); | |
| 260 expect(testDiv.children[2].text, 'bbb'); | |
| 261 // Something very strage is happening in the template bindings | |
| 262 // polyfill, and the template is being stamped out, inside the | |
| 263 // template tag (which shouldn't happen), and outside the template | |
| 264 //expect(testDiv.children[3].text, ''); | |
| 265 expect(testDiv.children[3].tagName.toLowerCase(), 'template'); | |
| 266 expect(testDiv.children[4].text, ''); | |
| 267 expect(testDiv.children[5].text, 'bbb'); | |
| 268 expect(testDiv.children[6].text, 'bbb'); | |
| 269 templateRendered = true; | |
| 270 maybeComplete(); | |
| 271 }); | |
| 272 }, onError: (e, s) { | |
| 273 expect('$e', contains('data')); | |
| 274 bindingErrorHappened = true; | |
| 275 maybeComplete(); | |
| 276 }); | |
| 277 return completer.future; | |
| 278 }); | |
| 279 | |
| 280 // passes safari | |
| 281 test('should shadow names in the outer template from within a nested ' | |
| 282 'template', () => | |
| 283 setUpTest(''' | |
| 284 <template id="test" bind> | |
| 285 <div>{{ a }}</div> | |
| 286 <div>{{ b }}</div> | |
| 287 <template bind="{{ b as a }}"> | |
| 288 <div>{{ a }}</div> | |
| 289 <div>{{ b }}</div> | |
| 290 </template> | |
| 291 </template>''', | |
| 292 globals: {'a': 'aaa', 'b': 'bbb'}) | |
| 293 .then((_) { | |
| 294 expect(testDiv.children[0].text, ''); | |
| 295 expect(testDiv.children[1].text, 'aaa'); | |
| 296 expect(testDiv.children[2].text, 'bbb'); | |
| 297 expect(testDiv.children[3].tagName.toLowerCase(), 'template'); | |
| 298 expect(testDiv.children[4].text, 'bbb'); | |
| 299 expect(testDiv.children[5].text, 'bbb'); | |
| 300 })); | |
| 301 | |
| 302 }); | |
| 303 | |
| 304 group('with template repeat', () { | |
| 305 | |
| 306 // passes safari | |
| 307 test('should not resolve names in the outer template from within a nested' | |
| 308 ' template with a repeat binding', () { | |
| 309 var completer = new Completer(); | |
| 310 var bindingErrorHappened = false; | |
| 311 var templateRendered = false; | |
| 312 maybeComplete() { | |
| 313 if (bindingErrorHappened && templateRendered) { | |
| 314 completer.complete(true); | |
| 315 } | |
| 316 } | |
| 317 runZoned(() { | |
| 318 setUpTest(''' | |
| 319 <template id="test" bind> | |
| 320 <div>{{ data }}</div> | |
| 321 <template repeat="{{ items }}"> | |
| 322 <div>{{ }}{{ data }}</div> | |
| 323 </template> | |
| 324 </template>''', | |
| 325 globals: {'items': [1, 2, 3]}, | |
| 326 model: new Model('a')) | |
| 327 .then((_) { | |
| 328 expect(testDiv.children[0].text, ''); | |
| 329 expect(testDiv.children[1].text, 'a'); | |
| 330 expect(testDiv.children[2].tagName.toLowerCase(), 'template'); | |
| 331 expect(testDiv.children[3].text, '1'); | |
| 332 expect(testDiv.children[4].text, '2'); | |
| 333 expect(testDiv.children[5].text, '3'); | |
| 334 templateRendered = true; | |
| 335 maybeComplete(); | |
| 336 }); | |
| 337 }, onError: (e, s) { | |
| 338 expect('$e', contains('data')); | |
| 339 bindingErrorHappened = true; | |
| 340 maybeComplete(); | |
| 341 }); | |
| 342 return completer.future; | |
| 343 }); | |
| 344 | |
| 345 test('should handle repeat/in bindings', () => | |
| 346 setUpTest(''' | |
| 347 <template id="test" bind> | |
| 348 <div>{{ data }}</div> | |
| 349 <template repeat="{{ item in items }}"> | |
| 350 <div>{{ item }}{{ data }}</div> | |
| 351 </template> | |
| 352 </template>''', | |
| 353 globals: {'items': [1, 2, 3]}, | |
| 354 model: new Model('a')) | |
| 355 .then((_) { | |
| 356 // expect 6 children: two templates, a div and three instances | |
| 357 expect(testDiv.children[0].text, ''); | |
| 358 expect(testDiv.children[1].text, 'a'); | |
| 359 expect(testDiv.children[2].tagName.toLowerCase(), 'template'); | |
| 360 expect(testDiv.children[3].text, '1a'); | |
| 361 expect(testDiv.children[4].text, '2a'); | |
| 362 expect(testDiv.children[5].text, '3a'); | |
| 363 // expect(testDiv.children.map((c) => c.text), | |
| 364 // ['', 'a', '', '1a', '2a', '3a']); | |
| 365 })); | |
| 366 | |
| 367 test('should observe changes to lists in repeat bindings', () { | |
| 368 var items = new ObservableList.from([1, 2, 3]); | |
| 369 return setUpTest(''' | |
| 370 <template id="test" bind> | |
| 371 <template repeat="{{ items }}"> | |
| 372 <div>{{ }}</div> | |
| 373 </template> | |
| 374 </template>''', | |
| 375 globals: {'items': items}, | |
| 376 model: new Model('a')) | |
| 377 .then((_) { | |
| 378 expect(testDiv.children[0].text, ''); | |
| 379 expect(testDiv.children[1].tagName.toLowerCase(), 'template'); | |
| 380 expect(testDiv.children[2].text, '1'); | |
| 381 expect(testDiv.children[3].text, '2'); | |
| 382 expect(testDiv.children[4].text, '3'); | |
| 383 // expect(testDiv.children.map((c) => c.text), | |
| 384 // ['', '', '1', '2', '3']); | |
| 385 items.add(4); | |
| 386 return waitForChange(testDiv); | |
| 387 }).then((_) { | |
| 388 expect(testDiv.children[0].text, ''); | |
| 389 expect(testDiv.children[1].tagName.toLowerCase(), 'template'); | |
| 390 expect(testDiv.children[2].text, '1'); | |
| 391 expect(testDiv.children[3].text, '2'); | |
| 392 expect(testDiv.children[4].text, '3'); | |
| 393 expect(testDiv.children[5].text, '4'); | |
| 394 // expect(testDiv.children.map((c) => c.text), | |
| 395 // ['', '', '1', '2', '3', '4']); | |
| 396 }); | |
| 397 }); | |
| 398 | |
| 399 test('should observe changes to lists in repeat/in bindings', () { | |
| 400 var items = new ObservableList.from([1, 2, 3]); | |
| 401 return setUpTest(''' | |
| 402 <template id="test" bind> | |
| 403 <template repeat="{{ item in items }}"> | |
| 404 <div>{{ item }}</div> | |
| 405 </template> | |
| 406 </template>''', | |
| 407 globals: {'items': items}, | |
| 408 model: new Model('a')) | |
| 409 .then((_) { | |
| 410 expect(testDiv.children[0].text, ''); | |
| 411 expect(testDiv.children[1].tagName.toLowerCase(), 'template'); | |
| 412 expect(testDiv.children[2].text, '1'); | |
| 413 expect(testDiv.children[3].text, '2'); | |
| 414 expect(testDiv.children[4].text, '3'); | |
| 415 // expect(testDiv.children.map((c) => c.text), | |
| 416 // ['', '', '1', '2', '3']); | |
| 417 items.add(4); | |
| 418 return waitForChange(testDiv); | |
| 419 }).then((_) { | |
| 420 expect(testDiv.children[0].text, ''); | |
| 421 expect(testDiv.children[1].tagName.toLowerCase(), 'template'); | |
| 422 expect(testDiv.children[2].text, '1'); | |
| 423 expect(testDiv.children[3].text, '2'); | |
| 424 expect(testDiv.children[4].text, '3'); | |
| 425 expect(testDiv.children[5].text, '4'); | |
| 426 // expect(testDiv.children.map((c) => c.text), | |
| 427 // ['', '', '1', '2', '3', '4']); | |
| 428 }); | |
| 429 }); | |
| 430 }); | |
| 431 | |
| 432 group('with template if', () { | |
| 433 | |
| 434 Future doTest(value, bool shouldRender) => | |
| 435 setUpTest(''' | |
| 436 <template id="test" bind> | |
| 437 <div>{{ data }}</div> | |
| 438 <template if="{{ show }}"> | |
| 439 <div>{{ data }}</div> | |
| 440 </template> | |
| 441 </template>''', | |
| 442 globals: {'show': value}, | |
| 443 model: new Model('a')) | |
| 444 .then((_) { | |
| 445 if (shouldRender) { | |
| 446 expect(testDiv.children.length, 4); | |
| 447 expect(testDiv.children[1].text, 'a'); | |
| 448 expect(testDiv.children[3].text, 'a'); | |
| 449 } else { | |
| 450 expect(testDiv.children.length, 3); | |
| 451 expect(testDiv.children[1].text, 'a'); | |
| 452 } | |
| 453 }); | |
| 454 | |
| 455 test('should render for a true expression', | |
| 456 () => doTest(true, true)); | |
| 457 | |
| 458 test('should treat a non-null expression as truthy', | |
| 459 () => doTest('a', true)); | |
| 460 | |
| 461 test('should treat an empty list as truthy', | |
| 462 () => doTest([], true)); | |
| 463 | |
| 464 test('should handle a false expression', | |
| 465 () => doTest(false, false)); | |
| 466 | |
| 467 test('should treat null as falsey', | |
| 468 () => doTest(null, false)); | |
| 469 }); | |
| 470 | |
| 471 group('error handling', () { | |
| 472 | |
| 473 test('should silently handle bad variable names', () { | |
| 474 var completer = new Completer(); | |
| 475 runZoned(() { | |
| 476 testDiv.nodes.add(new Element.html(''' | |
| 477 <template id="test" bind>{{ foo }}</template>''')); | |
| 478 templateBind(query('#test')) | |
| 479 ..bindingDelegate = new PolymerExpressions() | |
| 480 ..model = []; | |
| 481 return new Future(() {}); | |
| 482 }, onError: (e, s) { | |
| 483 expect('$e', contains('foo')); | |
| 484 completer.complete(true); | |
| 485 }); | |
| 486 return completer.future; | |
| 487 }); | |
| 488 | |
| 489 test('should handle null collections in "in" expressions', () => | |
| 490 setUpTest(''' | |
| 491 <template id="test" bind> | |
| 492 <template repeat="{{ item in items }}"> | |
| 493 {{ item }} | |
| 494 </template> | |
| 495 </template>''', | |
| 496 globals: {'items': null}) | |
| 497 .then((_) { | |
| 498 expect(testDiv.children.length, 2); | |
| 499 expect(testDiv.children[0].id, 'test'); | |
| 500 })); | |
| 501 | |
| 502 }); | |
| 503 | |
| 504 group('special bindings', () { | |
| 505 | |
| 506 test('should handle class attributes with lists', () => | |
| 507 setUpTest(''' | |
| 508 <template id="test" bind> | |
| 509 <div class="{{ classes }}"> | |
| 510 </template>''', | |
| 511 globals: {'classes': ['a', 'b']}) | |
| 512 .then((_) { | |
| 513 expect(testDiv.children.length, 2); | |
| 514 expect(testDiv.children[1].attributes['class'], 'a b'); | |
| 515 expect(testDiv.children[1].classes, ['a', 'b']); | |
| 516 })); | |
| 517 | |
| 518 test('should handle class attributes with maps', () => | |
| 519 setUpTest(''' | |
| 520 <template id="test" bind> | |
| 521 <div class="{{ classes }}"> | |
| 522 </template>''', | |
| 523 globals: {'classes': {'a': true, 'b': false, 'c': true}}) | |
| 524 .then((_) { | |
| 525 expect(testDiv.children.length, 2); | |
| 526 expect(testDiv.children[1].attributes['class'], 'a c'); | |
| 527 expect(testDiv.children[1].classes, ['a', 'c']); | |
| 528 })); | |
| 529 | |
| 530 test('should handle style attributes with lists', () => | |
| 531 setUpTest(''' | |
| 532 <template id="test" bind> | |
| 533 <div style="{{ styles }}"> | |
| 534 </template>''', | |
| 535 globals: {'styles': ['display: none', 'color: black']}) | |
| 536 .then((_) { | |
| 537 expect(testDiv.children.length, 2); | |
| 538 expect(testDiv.children[1].attributes['style'], | |
| 539 'display: none;color: black'); | |
| 540 })); | |
| 541 | |
| 542 test('should handle style attributes with maps', () => | |
| 543 setUpTest(''' | |
| 544 <template id="test" bind> | |
| 545 <div style="{{ styles }}"> | |
| 546 </template>''', | |
| 547 globals: {'styles': {'display': 'none', 'color': 'black'}}) | |
| 548 .then((_) { | |
| 549 expect(testDiv.children.length, 2); | |
| 550 expect(testDiv.children[1].attributes['style'], | |
| 551 'display: none;color: black'); | |
| 552 })); | |
| 553 }); | |
| 554 | |
| 555 group('regression tests', () { | |
| 556 | |
| 557 test('should bind to literals', () => | |
| 558 setUpTest(''' | |
| 559 <template id="test" bind> | |
| 560 <div>{{ 123 }}</div> | |
| 561 <div>{{ 123.456 }}</div> | |
| 562 <div>{{ "abc" }}</div> | |
| 563 <div>{{ true }}</div> | |
| 564 <div>{{ null }}</div> | |
| 565 </template>''', | |
| 566 globals: {'items': null}) | |
| 567 .then((_) { | |
| 568 expect(testDiv.children.length, 6); | |
| 569 expect(testDiv.children[1].text, '123'); | |
| 570 expect(testDiv.children[2].text, '123.456'); | |
| 571 expect(testDiv.children[3].text, 'abc'); | |
| 572 expect(testDiv.children[4].text, 'true'); | |
| 573 expect(testDiv.children[5].text, ''); | |
| 574 })); | |
| 575 | |
| 576 }); | |
| 577 | |
| 578 }); | |
| 579 } | |
| 580 | |
| 581 Future<Element> waitForChange(Element e) { | |
| 582 var completer = new Completer<Element>(); | |
| 583 new MutationObserver((mutations, observer) { | |
| 584 observer.disconnect(); | |
| 585 completer.complete(e); | |
| 586 }).observe(e, childList: true); | |
| 587 return completer.future.timeout(new Duration(seconds: 1)); | |
| 588 } | |
| 589 | |
| 590 @reflectable | |
| 591 class Model extends ChangeNotifier { | |
| 592 String _data; | |
| 593 | |
| 594 Model(this._data); | |
| 595 | |
| 596 String get data => _data; | |
| 597 | |
| 598 void set data(String value) { | |
| 599 _data = notifyPropertyChange(#data, _data, value); | |
| 600 } | |
| 601 | |
| 602 String toString() => "Model(data: $_data)"; | |
| 603 } | |
| 604 | |
| 605 class NullNodeTreeSanitizer implements NodeTreeSanitizer { | |
| 606 | |
| 607 @override | |
| 608 void sanitizeTree(Node node) {} | |
| 609 } | |
| OLD | NEW |