OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012, 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 /** Tests for some of the utility helper functions used by the compiler. */ |
| 6 library observe_test; |
| 7 |
| 8 import 'package:unittest/compact_vm_config.dart'; |
| 9 import 'package:unittest/unittest.dart'; |
| 10 import 'package:web_ui/observe.dart'; |
| 11 import 'package:web_ui/src/utils.dart' show setImmediate; |
| 12 |
| 13 main() { |
| 14 useCompactVMConfiguration(); |
| 15 |
| 16 group('ObservableReference', () { |
| 17 test('no observers', () { |
| 18 var t = new ObservableReference<int>(123); |
| 19 expect(t.value, 123); |
| 20 t.value = 42; |
| 21 expect(t.value, 42); |
| 22 expect(t.hasObservers, false); |
| 23 }); |
| 24 |
| 25 test('observe', () { |
| 26 var t = new ObservableReference<int>(123); |
| 27 int called = 0; |
| 28 observe(() => t.value, expectAsync1((ExpressionChange n) { |
| 29 expect(n.oldValue, 123); |
| 30 expect(n.newValue, 42); |
| 31 })); |
| 32 t.value = 41; |
| 33 t.value = 42; |
| 34 expect(called, 0, reason: 'changes delived async'); |
| 35 }); |
| 36 |
| 37 test('observe multiple changes', () { |
| 38 var t = new ObservableReference<int>(123); |
| 39 observe(() => t.value, expectAsync1((ExpressionChange n) { |
| 40 if (n.oldValue == 123) { |
| 41 expect(n.newValue, 42); |
| 42 // Cause another change |
| 43 t.value = 777; |
| 44 } else { |
| 45 expect(n.oldValue, 42); |
| 46 expect(n.newValue, 777); |
| 47 } |
| 48 }, count: 2)); |
| 49 t.value = 42; |
| 50 }); |
| 51 |
| 52 test('multiple observers', () { |
| 53 var t = new ObservableReference<int>(123); |
| 54 observe(() => t.value, expectAsync1((ExpressionChange n) { |
| 55 expect(n.oldValue, 123); |
| 56 expect(n.newValue, 42); |
| 57 })); |
| 58 observe(() => t.value + 1, expectAsync1((ExpressionChange n) { |
| 59 expect(n.oldValue, 124); |
| 60 expect(n.newValue, 43); |
| 61 })); |
| 62 t.value = 41; |
| 63 t.value = 42; |
| 64 }); |
| 65 |
| 66 test('deliverChangesSync', () { |
| 67 var t = new ObservableReference<int>(123); |
| 68 var notifications = []; |
| 69 observe(() => t.value, notifications.add); |
| 70 t.value = 41; |
| 71 t.value = 42; |
| 72 expect(notifications, [], reason: 'changes delived async'); |
| 73 |
| 74 deliverChangesSync(); |
| 75 expect(notifications, [_change(123, 42)]); |
| 76 t.value = 777; |
| 77 expect(notifications.length, 1, reason: 'changes delived async'); |
| 78 |
| 79 deliverChangesSync(); |
| 80 expect(notifications, [_change(123, 42), _change(42, 777)]); |
| 81 |
| 82 // Has no effect if there are no changes |
| 83 deliverChangesSync(); |
| 84 expect(notifications, [_change(123, 42), _change(42, 777)]); |
| 85 }); |
| 86 |
| 87 test('unobserve', () { |
| 88 var t = new ObservableReference<int>(123); |
| 89 ChangeUnobserver unobserve; |
| 90 unobserve = observe(() => t.value, expectAsync1((n) { |
| 91 expect(n.oldValue, 123); |
| 92 expect(n.newValue, 42); |
| 93 unobserve(); |
| 94 t.value = 777; |
| 95 })); |
| 96 t.value = 42; |
| 97 }); |
| 98 |
| 99 test('observers fired in order', () { |
| 100 var t = new ObservableReference<int>(123); |
| 101 int expectOldValue = 123; |
| 102 int expectNewValue = 42; |
| 103 observe(() => t.value, expectAsync1((n) { |
| 104 expect(n.oldValue, expectOldValue); |
| 105 expect(n.newValue, expectNewValue); |
| 106 |
| 107 // The second observer will see this change already, and only be called |
| 108 // once. However we'll be called a second time. |
| 109 t.value = 777; |
| 110 expectNewValue = 777; |
| 111 expectOldValue = 42; |
| 112 }, count: 2)); |
| 113 int count = 0; |
| 114 observe(() => t.value + 1000, expectAsync1((n) { |
| 115 expect(n.oldValue, 1123); |
| 116 expect(n.newValue, 1777); |
| 117 })); |
| 118 |
| 119 // Make the initial change |
| 120 t.value = 42; |
| 121 }); |
| 122 |
| 123 test('unobserve one of two observers', () { |
| 124 var t = new ObservableReference<int>(123); |
| 125 ChangeUnobserver unobserve; |
| 126 unobserve = observe(() => t.value, expectAsync1((n) { |
| 127 expect(n.oldValue, 123); |
| 128 expect(n.newValue, 42); |
| 129 |
| 130 // This will not affect the other observer, so it still gets the event. |
| 131 unobserve(); |
| 132 setImmediate(() => t.value = 777); |
| 133 })); |
| 134 int count = 0; |
| 135 observe(() => t.value + 1000, expectAsync1((n) { |
| 136 if (++count == 1) { |
| 137 expect(n.oldValue, 1123); |
| 138 expect(n.newValue, 1042); |
| 139 } else { |
| 140 expect(n.oldValue, 1042); |
| 141 expect(n.newValue, 1777); |
| 142 } |
| 143 }, count: 2)); |
| 144 |
| 145 // Make the initial change |
| 146 t.value = 42; |
| 147 }); |
| 148 |
| 149 test('notifyRead in getter', () { |
| 150 var t = new ObservableReference<int>(123); |
| 151 |
| 152 observe(() { |
| 153 expect(observeReads, true); |
| 154 expect(t.hasObservers, false); |
| 155 return t.value; |
| 156 }, (n) {}); |
| 157 |
| 158 expect(observeReads, false); |
| 159 expect(t.hasObservers, true); |
| 160 }); |
| 161 |
| 162 test('notifyWrite in setter', () { |
| 163 var t = new ObservableReference<int>(123); |
| 164 observe(() => t.value, (n) {}); |
| 165 |
| 166 t.value = 42; |
| 167 expect(observeReads, false); |
| 168 expect(t.hasObservers, true); |
| 169 |
| 170 // This will re-observe the expression. |
| 171 deliverChangesSync(); |
| 172 |
| 173 expect(observeReads, false); |
| 174 expect(t.hasObservers, true); |
| 175 }); |
| 176 |
| 177 test('observe conditional async', () { |
| 178 var t = new ObservableReference<bool>(false); |
| 179 var a = new ObservableReference<int>(123); |
| 180 var b = new ObservableReference<String>('hi'); |
| 181 |
| 182 int count = 0; |
| 183 var oldValue = 'hi'; |
| 184 observe(() => t.value ? a.value : b.value, expectAsync1((n) { |
| 185 expect(n.oldValue, oldValue); |
| 186 oldValue = t.value ? a.value : b.value; |
| 187 expect(n.newValue, oldValue); |
| 188 |
| 189 switch (++count) { |
| 190 case 1: |
| 191 // We are observing "a", change it |
| 192 a.value = 42; |
| 193 break; |
| 194 case 2: |
| 195 // Switch to observing "b" |
| 196 t.value = false; |
| 197 break; |
| 198 case 3: |
| 199 // Change "a", this should have no effect and will not fire a 4th |
| 200 // change event. |
| 201 a.value = 777; |
| 202 break; |
| 203 default: |
| 204 // Should not be able to reach this because of the "count" argument |
| 205 // to expectAsync1 |
| 206 throw new StateError('unreachable'); |
| 207 } |
| 208 }, count: 3)); |
| 209 |
| 210 expect(t.hasObservers, true); |
| 211 expect(a.hasObservers, false); |
| 212 expect(b.hasObservers, true); |
| 213 |
| 214 // Start off by changing "t" to true. |
| 215 t.value = true; |
| 216 }); |
| 217 |
| 218 test('change limit', () { |
| 219 var x = new ObservableReference(false); |
| 220 var y = new ObservableReference(false); |
| 221 |
| 222 int xCount = 0, yCount = 0; |
| 223 int limit = 1000; |
| 224 observe(() => x.value, (n) { |
| 225 if (++xCount < limit) y.value = x.value; |
| 226 }); |
| 227 observe(() => y.value, (n) { |
| 228 if (++yCount < limit) x.value = !y.value; |
| 229 }); |
| 230 |
| 231 // Kick off the cascading changes |
| 232 x.value = true; |
| 233 |
| 234 deliverChangesSync(); |
| 235 |
| 236 expect(xCount, limit); |
| 237 expect(yCount, limit - 1); |
| 238 }); |
| 239 |
| 240 test('observe conditional sync', () { |
| 241 var t = new ObservableReference<bool>(false); |
| 242 var a = new ObservableReference<int>(123); |
| 243 var b = new ObservableReference<String>('hi'); |
| 244 |
| 245 var notifications = []; |
| 246 observe(() => t.value ? a.value : b.value, notifications.add); |
| 247 |
| 248 // Start off by changing "t" to true, so we evaluate "a". |
| 249 t.value = true; |
| 250 deliverChangesSync(); |
| 251 |
| 252 // This changes "a" which we should be observing. |
| 253 a.value = 42; |
| 254 deliverChangesSync(); |
| 255 |
| 256 // This has no effect because we aren't using "b" yet. |
| 257 b.value = 'universe'; |
| 258 deliverChangesSync(); |
| 259 |
| 260 // Switch to use "b". |
| 261 t.value = false; |
| 262 deliverChangesSync(); |
| 263 |
| 264 // This has no effect because we aren't using "a" anymore. |
| 265 a.value = 777; |
| 266 deliverChangesSync(); |
| 267 |
| 268 expect(notifications, [ |
| 269 _change('hi', 123), |
| 270 _change(123, 42), |
| 271 _change(42, 'universe')]); |
| 272 }); |
| 273 }); |
| 274 |
| 275 |
| 276 group('ObservableList', () { |
| 277 // TODO(jmesserly): need all standard List tests. |
| 278 |
| 279 test('observe length', () { |
| 280 var list = new ObservableList(); |
| 281 var notification = null; |
| 282 observe(() => list.length, (n) { notification = n; }); |
| 283 |
| 284 list.addAll([1, 2, 3]); |
| 285 expect(list, [1, 2, 3]); |
| 286 deliverChangesSync(); |
| 287 expect(notification, _change(0, 3), reason: 'addAll changes length'); |
| 288 |
| 289 list.add(4); |
| 290 expect(list, [1, 2, 3, 4]); |
| 291 deliverChangesSync(); |
| 292 expect(notification, _change(3, 4), reason: 'add changes length'); |
| 293 |
| 294 list.removeRange(1, 2); |
| 295 expect(list, [1, 4]); |
| 296 deliverChangesSync(); |
| 297 expect(notification, _change(4, 2), reason: 'removeRange changes length'); |
| 298 |
| 299 list.length = 5; |
| 300 expect(list, [1, 4, null, null, null]); |
| 301 deliverChangesSync(); |
| 302 expect(notification, _change(2, 5), reason: 'length= changes length'); |
| 303 notification = null; |
| 304 |
| 305 list[2] = 9000; |
| 306 expect(list, [1, 4, 9000, null, null]); |
| 307 deliverChangesSync(); |
| 308 expect(notification, null, reason: '[]= does not change length'); |
| 309 |
| 310 list.clear(); |
| 311 expect(list, []); |
| 312 deliverChangesSync(); |
| 313 expect(notification, _change(5, 0), reason: 'clear changes length'); |
| 314 }); |
| 315 |
| 316 test('observe index', () { |
| 317 var list = new ObservableList.from([1, 2, 3]); |
| 318 var notification = null; |
| 319 observe(() => list[1], (n) { notification = n; }); |
| 320 |
| 321 list.add(4); |
| 322 expect(list, [1, 2, 3, 4]); |
| 323 deliverChangesSync(); |
| 324 expect(notification, null, |
| 325 reason: 'add does not change existing items'); |
| 326 |
| 327 list[1] = 777; |
| 328 expect(list, [1, 777, 3, 4]); |
| 329 deliverChangesSync(); |
| 330 expect(notification, _change(2, 777)); |
| 331 |
| 332 notification = null; |
| 333 list[2] = 9000; |
| 334 expect(list, [1, 777, 9000, 4]); |
| 335 deliverChangesSync(); |
| 336 expect(notification, null, |
| 337 reason: 'setting a different index should not fire change'); |
| 338 |
| 339 list[1] = 44; |
| 340 list[1] = 43; |
| 341 list[1] = 42; |
| 342 expect(list, [1, 42, 9000, 4]); |
| 343 deliverChangesSync(); |
| 344 expect(notification, _change(777, 42)); |
| 345 |
| 346 notification = null; |
| 347 list.length = 2; |
| 348 expect(list, [1, 42]); |
| 349 deliverChangesSync(); |
| 350 expect(notification, null, |
| 351 reason: 'did not truncate the observed item'); |
| 352 |
| 353 list.length = 1; // truncate |
| 354 list.add(2); |
| 355 expect(list, [1, 2]); |
| 356 deliverChangesSync(); |
| 357 expect(notification, _change(42, 2), |
| 358 reason: 'item truncated and added back'); |
| 359 }); |
| 360 |
| 361 test('toString', () { |
| 362 var list = new ObservableList.from([1, 2, 3]); |
| 363 var notification = null; |
| 364 observe(() => list.toString(), (n) { notification = n; }); |
| 365 list[2] = 4; |
| 366 deliverChangesSync(); |
| 367 expect(notification, _change('[1, 2, 3]', '[1, 2, 4]')); |
| 368 }); |
| 369 }); |
| 370 |
| 371 |
| 372 group('ObservableSet', () { |
| 373 // TODO(jmesserly): need all standard Set tests. |
| 374 |
| 375 test('observe length', () { |
| 376 var set = new ObservableSet(); |
| 377 var notification = null; |
| 378 observe(() => set.length, (n) { notification = n; }); |
| 379 |
| 380 set.addAll([1, 2, 3]); |
| 381 expect(set, [1, 2, 3]); |
| 382 deliverChangesSync(); |
| 383 expect(notification, _change(0, 3), reason: 'addAll changes length'); |
| 384 |
| 385 set.add(4); |
| 386 expect(set, [1, 2, 3, 4]); |
| 387 deliverChangesSync(); |
| 388 expect(notification, _change(3, 4), reason: 'add changes length'); |
| 389 |
| 390 set.removeAll([2, 3]); |
| 391 expect(set, [1, 4]); |
| 392 deliverChangesSync(); |
| 393 expect(notification, _change(4, 2), reason: 'removeAll changes length'); |
| 394 |
| 395 set.remove(1); |
| 396 expect(set, [4]); |
| 397 deliverChangesSync(); |
| 398 expect(notification, _change(2, 1), reason: 'remove changes length'); |
| 399 |
| 400 notification = null; |
| 401 set.add(4); |
| 402 expect(set, [4]); |
| 403 deliverChangesSync(); |
| 404 expect(notification, null, reason: 'item already exists'); |
| 405 |
| 406 set.clear(); |
| 407 expect(set, []); |
| 408 deliverChangesSync(); |
| 409 expect(notification, _change(1, 0), reason: 'clear changes length'); |
| 410 }); |
| 411 |
| 412 test('observe item', () { |
| 413 var set = new ObservableSet.from([1, 2, 3]); |
| 414 var notification = null; |
| 415 observe(() => set.contains(2), (n) { notification = n; }); |
| 416 |
| 417 set.add(4); |
| 418 expect(set, [1, 2, 3, 4]); |
| 419 deliverChangesSync(); |
| 420 expect(notification, null, reason: 'add does not change existing items'); |
| 421 |
| 422 set.remove(3); |
| 423 expect(set, [1, 2, 4]); |
| 424 expect(notification, null, |
| 425 reason: 'removing an item does not change other items'); |
| 426 |
| 427 set.remove(2); |
| 428 expect(set, [1, 4]); |
| 429 deliverChangesSync(); |
| 430 expect(notification, _change(true, false)); |
| 431 |
| 432 notification = null; |
| 433 set.removeAll([2, 3]); |
| 434 expect(set, [1, 4]); |
| 435 deliverChangesSync(); |
| 436 expect(notification, null, reason: 'item already removed'); |
| 437 |
| 438 set.add(2); |
| 439 expect(set, [1, 2, 4]); |
| 440 deliverChangesSync(); |
| 441 expect(notification, _change(false, true), reason: 'item added again'); |
| 442 }); |
| 443 |
| 444 test('toString', () { |
| 445 var original = new Set.from([1, 2, 3]); |
| 446 var set = new ObservableSet.from(original); |
| 447 var notification = null; |
| 448 observe(() => set.toString(), (n) { notification = n; }); |
| 449 set.add(4); |
| 450 deliverChangesSync(); |
| 451 var updated = new Set.from([1, 2, 3, 4]); |
| 452 |
| 453 // Note: using Set.toString as the exectation, so the order is the same |
| 454 // as with ObservableSet, regardless of how hashCode is implemented. |
| 455 expect(notification, _change('$original', '$updated')); |
| 456 }); |
| 457 }); |
| 458 |
| 459 |
| 460 group('ObservableMap', () { |
| 461 // TODO(jmesserly): need all standard Map tests. |
| 462 |
| 463 test('observe length', () { |
| 464 var map = new ObservableMap(); |
| 465 var notification = null; |
| 466 observe(() => map.length, (n) { notification = n; }); |
| 467 |
| 468 map['a'] = 1; |
| 469 map.putIfAbsent('b', () => 2); |
| 470 map['c'] = 3; |
| 471 expect(map, {'a': 1, 'b': 2, 'c': 3}); |
| 472 deliverChangesSync(); |
| 473 expect(notification, _change(0, 3), reason: 'adding changes length'); |
| 474 |
| 475 map['d'] = 4; |
| 476 expect(map, {'a': 1, 'b': 2, 'c': 3, 'd': 4}); |
| 477 deliverChangesSync(); |
| 478 expect(notification, _change(3, 4), reason: 'add changes length'); |
| 479 |
| 480 map.remove('b'); |
| 481 map.remove('c'); |
| 482 expect(map, {'a': 1, 'd': 4}); |
| 483 deliverChangesSync(); |
| 484 expect(notification, _change(4, 2), reason: 'removeRange changes length'); |
| 485 |
| 486 notification = null; |
| 487 map['d'] = 9000; |
| 488 expect(map, {'a': 1, 'd': 9000}); |
| 489 deliverChangesSync(); |
| 490 expect(notification, null, reason: 'update item does not change length'); |
| 491 |
| 492 map.clear(); |
| 493 expect(map, {}); |
| 494 deliverChangesSync(); |
| 495 expect(notification, _change(2, 0), reason: 'clear changes length'); |
| 496 }); |
| 497 |
| 498 test('observe index', () { |
| 499 var map = new ObservableMap.from({'a': 1, 'b': 2, 'c': 3}); |
| 500 var notification = null; |
| 501 observe(() => map['b'], (n) { notification = n; }); |
| 502 |
| 503 map.putIfAbsent('d', () => 4); |
| 504 expect(map, {'a': 1, 'b': 2, 'c': 3, 'd': 4}); |
| 505 deliverChangesSync(); |
| 506 expect(notification, null, reason: 'add does not change existing items'); |
| 507 |
| 508 map['b'] = 777; |
| 509 expect(map, {'a': 1, 'b': 777, 'c': 3, 'd': 4}); |
| 510 deliverChangesSync(); |
| 511 expect(notification, _change(2, 777)); |
| 512 |
| 513 notification = null; |
| 514 map.putIfAbsent('b', () => 1234); |
| 515 expect(map, {'a': 1, 'b': 777, 'c': 3, 'd': 4}); |
| 516 deliverChangesSync(); |
| 517 expect(notification, null, reason: 'item already there'); |
| 518 |
| 519 map['c'] = 9000; |
| 520 expect(map, {'a': 1, 'b': 777, 'c': 9000, 'd': 4}); |
| 521 deliverChangesSync(); |
| 522 expect(notification, null, reason: 'setting a different item'); |
| 523 |
| 524 map['b'] = 44; |
| 525 map['b'] = 43; |
| 526 map['b'] = 42; |
| 527 expect(map, {'a': 1, 'b': 42, 'c': 9000, 'd': 4}); |
| 528 deliverChangesSync(); |
| 529 expect(notification, _change(777, 42)); |
| 530 |
| 531 notification = null; |
| 532 map.remove('a'); |
| 533 map.remove('d'); |
| 534 expect(map, {'b': 42, 'c': 9000}); |
| 535 deliverChangesSync(); |
| 536 expect(notification, null, reason: 'did not remove the observed item'); |
| 537 |
| 538 map.remove('b'); |
| 539 map['b'] = 2; |
| 540 expect(map, {'b': 2, 'c': 9000}); |
| 541 deliverChangesSync(); |
| 542 expect(notification, _change(42, 2), reason: 'removed and added back'); |
| 543 }); |
| 544 |
| 545 test('toString', () { |
| 546 var hashMap = new Map.from({'a': 1, 'b': 2}); |
| 547 var expect1 = hashMap.toString(); |
| 548 var map = new ObservableMap.from(hashMap); |
| 549 |
| 550 var notification = null; |
| 551 observe(() => map.toString(), (n) { notification = n; }); |
| 552 map.remove('b'); |
| 553 map['c'] = 3; |
| 554 |
| 555 hashMap.remove('b'); |
| 556 hashMap['c'] = 3; |
| 557 var expect2 = hashMap.toString(); |
| 558 |
| 559 deliverChangesSync(); |
| 560 |
| 561 expect(notification, _change(expect1, expect2)); |
| 562 }); |
| 563 }); |
| 564 |
| 565 } |
| 566 |
| 567 _change(oldValue, newValue) => new ExpressionChange(oldValue, newValue); |
OLD | NEW |