| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 import 'dart:async'; | 5 import 'dart:async'; |
| 6 import 'package:observe/observe.dart'; | 6 import 'package:observe/observe.dart'; |
| 7 import 'package:unittest/unittest.dart'; | 7 import 'package:unittest/unittest.dart'; |
| 8 import 'observe_test_utils.dart'; | 8 import 'observe_test_utils.dart'; |
| 9 | 9 |
| 10 // This file contains code ported from: | 10 // This file contains code ported from: |
| 11 // https://github.com/rafaelw/ChangeSummary/blob/master/tests/test.js | 11 // https://github.com/rafaelw/ChangeSummary/blob/master/tests/test.js |
| 12 // Dart note: getting invalid properties is an error, unlike in JS where it | 12 |
| 13 // returns undefined. This difference comes up where we check for _throwsNSM in | |
| 14 // the tests below. | |
| 15 main() => dirtyCheckZone().run(() { | 13 main() => dirtyCheckZone().run(() { |
| 16 group('PathObserver', observePathTests); | 14 group('PathObserver', observePathTests); |
| 17 | 15 |
| 18 group('PropertyPath', () { | 16 group('PropertyPath', () { |
| 19 test('toString length', () { | 17 test('toString length', () { |
| 20 expectPath(p, str, len) { | 18 expectPath(p, str, len) { |
| 21 var path = new PropertyPath(p); | 19 var path = new PropertyPath(p); |
| 22 expect(path.toString(), str); | 20 expect(path.toString(), str); |
| 23 expect(path.length, len, reason: 'expected path length $len for $path'); | 21 expect(path.length, len, reason: 'expected path length $len for $path'); |
| 24 } | 22 } |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 63 reason: 'test depends on 2 and 3 having different hashcodes'); | 61 reason: 'test depends on 2 and 3 having different hashcodes'); |
| 64 | 62 |
| 65 var a = new PropertyPath([2]); | 63 var a = new PropertyPath([2]); |
| 66 var b = new PropertyPath([3]); | 64 var b = new PropertyPath([3]); |
| 67 expect(a, isNot(b), reason: 'different paths'); | 65 expect(a, isNot(b), reason: 'different paths'); |
| 68 expect(a.hashCode, isNot(b.hashCode), reason: 'different hashCodes'); | 66 expect(a.hashCode, isNot(b.hashCode), reason: 'different hashCodes'); |
| 69 }); | 67 }); |
| 70 }); | 68 }); |
| 71 }); | 69 }); |
| 72 | 70 |
| 71 |
| 73 observePathTests() { | 72 observePathTests() { |
| 74 test('Degenerate Values', () { | 73 test('Degenerate Values', () { |
| 75 expect(new PathObserver(null, '').value, null); | 74 expect(new PathObserver(null, '').value, null); |
| 76 expect(new PathObserver(123, '').value, 123); | 75 expect(new PathObserver(123, '').value, 123); |
| 77 expect(() => new PathObserver(123, 'foo.bar.baz').value, _throwsNSM('foo')); | 76 expect(new PathObserver(123, 'foo.bar.baz').value, null); |
| 78 | 77 |
| 79 // shouldn't throw: | 78 // shouldn't throw: |
| 80 new PathObserver(123, '')..open((_) {})..close(); | 79 new PathObserver(123, '')..open((_) {})..close(); |
| 81 new PropertyPath('').setValueFrom(null, null); | 80 new PropertyPath('').setValueFrom(null, null); |
| 82 new PropertyPath('').setValueFrom(123, 42); | 81 new PropertyPath('').setValueFrom(123, 42); |
| 83 expect(() => new PropertyPath('foo.bar.baz').setValueFrom(123, 42), | 82 new PropertyPath('foo.bar.baz').setValueFrom(123, 42); |
| 84 _throwsNSM('foo')); | 83 |
| 85 var foo = {}; | 84 var foo = {}; |
| 86 expect(new PathObserver(foo, '').value, foo); | 85 expect(new PathObserver(foo, '').value, foo); |
| 87 | 86 |
| 88 foo = new Object(); | 87 foo = new Object(); |
| 89 expect(new PathObserver(foo, '').value, foo); | 88 expect(new PathObserver(foo, '').value, foo); |
| 90 | 89 |
| 91 expect(new PathObserver(foo, 'a/3!').value, null); | 90 expect(new PathObserver(foo, 'a/3!').value, null); |
| 92 }); | 91 }); |
| 93 | 92 |
| 94 test('get value at path ObservableBox', () { | 93 test('get value at path ObservableBox', () { |
| 95 var obj = new ObservableBox(new ObservableBox(new ObservableBox(1))); | 94 var obj = new ObservableBox(new ObservableBox(new ObservableBox(1))); |
| 96 | 95 |
| 97 expect(new PathObserver(obj, '').value, obj); | 96 expect(new PathObserver(obj, '').value, obj); |
| 98 expect(new PathObserver(obj, 'value').value, obj.value); | 97 expect(new PathObserver(obj, 'value').value, obj.value); |
| 99 expect(new PathObserver(obj, 'value.value').value, obj.value.value); | 98 expect(new PathObserver(obj, 'value.value').value, obj.value.value); |
| 100 expect(new PathObserver(obj, 'value.value.value').value, 1); | 99 expect(new PathObserver(obj, 'value.value.value').value, 1); |
| 101 | 100 |
| 102 obj.value.value.value = 2; | 101 obj.value.value.value = 2; |
| 103 expect(new PathObserver(obj, 'value.value.value').value, 2); | 102 expect(new PathObserver(obj, 'value.value.value').value, 2); |
| 104 | 103 |
| 105 obj.value.value = new ObservableBox(3); | 104 obj.value.value = new ObservableBox(3); |
| 106 expect(new PathObserver(obj, 'value.value.value').value, 3); | 105 expect(new PathObserver(obj, 'value.value.value').value, 3); |
| 107 | 106 |
| 108 obj.value = new ObservableBox(4); | 107 obj.value = new ObservableBox(4); |
| 109 expect(() => new PathObserver(obj, 'value.value.value').value, | 108 expect(new PathObserver(obj, 'value.value.value').value, null); |
| 110 _throwsNSM('value')); | |
| 111 expect(new PathObserver(obj, 'value.value').value, 4); | 109 expect(new PathObserver(obj, 'value.value').value, 4); |
| 112 }); | 110 }); |
| 113 | 111 |
| 114 | 112 |
| 115 test('get value at path ObservableMap', () { | 113 test('get value at path ObservableMap', () { |
| 116 var obj = toObservable({'a': {'b': {'c': 1}}}); | 114 var obj = toObservable({'a': {'b': {'c': 1}}}); |
| 117 | 115 |
| 118 expect(new PathObserver(obj, '').value, obj); | 116 expect(new PathObserver(obj, '').value, obj); |
| 119 expect(new PathObserver(obj, 'a').value, obj['a']); | 117 expect(new PathObserver(obj, 'a').value, obj['a']); |
| 120 expect(new PathObserver(obj, 'a.b').value, obj['a']['b']); | 118 expect(new PathObserver(obj, 'a.b').value, obj['a']['b']); |
| 121 expect(new PathObserver(obj, 'a.b.c').value, 1); | 119 expect(new PathObserver(obj, 'a.b.c').value, 1); |
| 122 | 120 |
| 123 obj['a']['b']['c'] = 2; | 121 obj['a']['b']['c'] = 2; |
| 124 expect(new PathObserver(obj, 'a.b.c').value, 2); | 122 expect(new PathObserver(obj, 'a.b.c').value, 2); |
| 125 | 123 |
| 126 obj['a']['b'] = toObservable({'c': 3}); | 124 obj['a']['b'] = toObservable({'c': 3}); |
| 127 expect(new PathObserver(obj, 'a.b.c').value, 3); | 125 expect(new PathObserver(obj, 'a.b.c').value, 3); |
| 128 | 126 |
| 129 obj['a'] = toObservable({'b': 4}); | 127 obj['a'] = toObservable({'b': 4}); |
| 130 expect(() => new PathObserver(obj, 'a.b.c').value, _throwsNSM('c')); | 128 expect(new PathObserver(obj, 'a.b.c').value, null); |
| 131 expect(new PathObserver(obj, 'a.b').value, 4); | 129 expect(new PathObserver(obj, 'a.b').value, 4); |
| 132 }); | 130 }); |
| 133 | 131 |
| 134 test('set value at path', () { | 132 test('set value at path', () { |
| 135 var obj = toObservable({}); | 133 var obj = toObservable({}); |
| 136 new PropertyPath('foo').setValueFrom(obj, 3); | 134 new PropertyPath('foo').setValueFrom(obj, 3); |
| 137 expect(obj['foo'], 3); | 135 expect(obj['foo'], 3); |
| 138 | 136 |
| 139 var bar = toObservable({ 'baz': 3 }); | 137 var bar = toObservable({ 'baz': 3 }); |
| 140 new PropertyPath('bar').setValueFrom(obj, bar); | 138 new PropertyPath('bar').setValueFrom(obj, bar); |
| 141 expect(obj['bar'], bar); | 139 expect(obj['bar'], bar); |
| 142 | 140 |
| 143 expect(() => new PropertyPath('bar.baz.bat').setValueFrom(obj, 'not here'), | 141 new PropertyPath('bar.baz.bat').setValueFrom(obj, 'not here'); |
| 144 _throwsNSM('bat=')); | 142 expect(new PathObserver(obj, 'bar.baz.bat').value, null); |
| 145 expect(() => new PathObserver(obj, 'bar.baz.bat').value, _throwsNSM('bat')); | |
| 146 }); | 143 }); |
| 147 | 144 |
| 148 test('set value back to same', () { | 145 test('set value back to same', () { |
| 149 var obj = toObservable({}); | 146 var obj = toObservable({}); |
| 150 var path = new PathObserver(obj, 'foo'); | 147 var path = new PathObserver(obj, 'foo'); |
| 151 var values = []; | 148 var values = []; |
| 152 path.open((x) { | 149 path.open((x) { |
| 153 expect(x, path.value, reason: 'callback should get current value'); | 150 expect(x, path.value, reason: 'callback should get current value'); |
| 154 values.add(x); | 151 values.add(x); |
| 155 }); | 152 }); |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 235 }); | 232 }); |
| 236 }); | 233 }); |
| 237 | 234 |
| 238 for (var createModel in [() => new TestModel(), () => new WatcherModel()]) { | 235 for (var createModel in [() => new TestModel(), () => new WatcherModel()]) { |
| 239 test('Path Observation - ${createModel().runtimeType}', () { | 236 test('Path Observation - ${createModel().runtimeType}', () { |
| 240 var model = createModel()..a = | 237 var model = createModel()..a = |
| 241 (createModel()..b = (createModel()..c = 'hello, world')); | 238 (createModel()..b = (createModel()..c = 'hello, world')); |
| 242 | 239 |
| 243 var path = new PathObserver(model, 'a.b.c'); | 240 var path = new PathObserver(model, 'a.b.c'); |
| 244 var lastValue = null; | 241 var lastValue = null; |
| 245 var errorSeen = false; | 242 path.open((x) { lastValue = x; }); |
| 246 runZoned(() { | |
| 247 path.open((x) { lastValue = x; }); | |
| 248 }, onError: (e) { | |
| 249 expect(e, _isNoSuchMethodOf('c')); | |
| 250 errorSeen = true; | |
| 251 }); | |
| 252 | 243 |
| 253 model.a.b.c = 'hello, mom'; | 244 model.a.b.c = 'hello, mom'; |
| 254 | 245 |
| 255 expect(lastValue, null); | 246 expect(lastValue, null); |
| 256 return new Future(() { | 247 return new Future(() { |
| 257 expect(lastValue, 'hello, mom'); | 248 expect(lastValue, 'hello, mom'); |
| 258 | 249 |
| 259 model.a.b = createModel()..c = 'hello, dad'; | 250 model.a.b = createModel()..c = 'hello, dad'; |
| 260 }).then(newMicrotask).then((_) { | 251 }).then(newMicrotask).then((_) { |
| 261 expect(lastValue, 'hello, dad'); | 252 expect(lastValue, 'hello, dad'); |
| 262 | 253 |
| 263 model.a = createModel()..b = | 254 model.a = createModel()..b = |
| 264 (createModel()..c = 'hello, you'); | 255 (createModel()..c = 'hello, you'); |
| 265 }).then(newMicrotask).then((_) { | 256 }).then(newMicrotask).then((_) { |
| 266 expect(lastValue, 'hello, you'); | 257 expect(lastValue, 'hello, you'); |
| 267 | 258 |
| 268 model.a.b = 1; | 259 model.a.b = 1; |
| 269 expect(errorSeen, isFalse); | |
| 270 }).then(newMicrotask).then((_) { | 260 }).then(newMicrotask).then((_) { |
| 271 expect(errorSeen, isTrue); | 261 expect(lastValue, null); |
| 272 expect(lastValue, 'hello, you'); | |
| 273 | 262 |
| 274 // Stop observing | 263 // Stop observing |
| 275 path.close(); | 264 path.close(); |
| 276 | 265 |
| 277 model.a.b = createModel()..c = 'hello, back again -- but not observing'; | 266 model.a.b = createModel()..c = 'hello, back again -- but not observing'; |
| 278 }).then(newMicrotask).then((_) { | 267 }).then(newMicrotask).then((_) { |
| 279 expect(lastValue, 'hello, you'); | 268 expect(lastValue, null); |
| 280 | 269 |
| 281 // Resume observing | 270 // Resume observing |
| 282 new PathObserver(model, 'a.b.c').open((x) { lastValue = x; }); | 271 new PathObserver(model, 'a.b.c').open((x) { lastValue = x; }); |
| 283 | 272 |
| 284 model.a.b.c = 'hello. Back for reals'; | 273 model.a.b.c = 'hello. Back for reals'; |
| 285 }).then(newMicrotask).then((_) { | 274 }).then(newMicrotask).then((_) { |
| 286 expect(lastValue, 'hello. Back for reals'); | 275 expect(lastValue, 'hello. Back for reals'); |
| 287 }); | 276 }); |
| 288 }); | 277 }); |
| 289 } | 278 } |
| (...skipping 14 matching lines...) Expand all Loading... |
| 304 model['a'] = 3; | 293 model['a'] = 3; |
| 305 }).then(newMicrotask).then((_) { | 294 }).then(newMicrotask).then((_) { |
| 306 expect(values, [1, 2]); | 295 expect(values, [1, 2]); |
| 307 }); | 296 }); |
| 308 }); | 297 }); |
| 309 | 298 |
| 310 test('errors thrown from getter/setter', () { | 299 test('errors thrown from getter/setter', () { |
| 311 var model = new ObjectWithErrors(); | 300 var model = new ObjectWithErrors(); |
| 312 var observer = new PathObserver(model, 'foo'); | 301 var observer = new PathObserver(model, 'foo'); |
| 313 | 302 |
| 314 expect(() => observer.value, _throwsNSM('bar')); | 303 expect(() => observer.value, throws); |
| 315 expect(model.getFooCalled, 1); | 304 expect(model.getFooCalled, 1); |
| 316 | 305 |
| 317 expect(() { observer.value = 123; }, _throwsNSM('bar=')); | 306 expect(() { observer.value = 123; }, throws); |
| 318 expect(model.setFooCalled, [123]); | 307 expect(model.setFooCalled, [123]); |
| 319 }); | 308 }); |
| 320 | 309 |
| 321 test('object with noSuchMethod', () { | 310 test('object with noSuchMethod', () { |
| 322 var model = new NoSuchMethodModel(); | 311 var model = new NoSuchMethodModel(); |
| 323 var observer = new PathObserver(model, 'foo'); | 312 var observer = new PathObserver(model, 'foo'); |
| 324 | 313 |
| 325 expect(observer.value, 42); | 314 expect(observer.value, 42); |
| 326 observer.value = 'hi'; | 315 observer.value = 'hi'; |
| 327 expect(model._foo, 'hi'); | 316 expect(model._foo, 'hi'); |
| (...skipping 28 matching lines...) Expand all Loading... |
| 356 expect(observer.value, null, reason: 'path not found'); | 345 expect(observer.value, null, reason: 'path not found'); |
| 357 expect(model.log, ['[] bar']); | 346 expect(model.log, ['[] bar']); |
| 358 model.log.clear(); | 347 model.log.clear(); |
| 359 | 348 |
| 360 observer.value = 42; | 349 observer.value = 42; |
| 361 expect(model.log, ['[]= bar 42']); | 350 expect(model.log, ['[]= bar 42']); |
| 362 model.log.clear(); | 351 model.log.clear(); |
| 363 }); | 352 }); |
| 364 } | 353 } |
| 365 | 354 |
| 366 /// A matcher that checks that a closure throws a NoSuchMethodError matching the | |
| 367 /// given [name]. | |
| 368 _throwsNSM(String name) => throwsA(_isNoSuchMethodOf(name)); | |
| 369 | |
| 370 /// A matcher that checkes whether an exception is a NoSuchMethodError matching | |
| 371 /// the given [name]. | |
| 372 _isNoSuchMethodOf(String name) => predicate((e) => | |
| 373 e is NoSuchMethodError && | |
| 374 // Dart2js and VM error messages are a bit different, but they both contain | |
| 375 // the name. | |
| 376 ('$e'.contains("'$name'") || // VM error | |
| 377 '$e'.contains('\'Symbol("$name")\''))); // dart2js error | |
| 378 | |
| 379 class ObjectWithErrors { | 355 class ObjectWithErrors { |
| 380 int getFooCalled = 0; | 356 int getFooCalled = 0; |
| 381 List setFooCalled = []; | 357 List setFooCalled = []; |
| 382 @reflectable get foo { | 358 @reflectable get foo { |
| 383 getFooCalled++; | 359 getFooCalled++; |
| 384 (this as dynamic).bar; | 360 (this as dynamic).bar; |
| 385 } | 361 } |
| 386 @reflectable set foo(value) { | 362 @reflectable set foo(value) { |
| 387 setFooCalled.add(value); | 363 setFooCalled.add(value); |
| 388 (this as dynamic).bar = value; | 364 (this as dynamic).bar = value; |
| (...skipping 11 matching lines...) Expand all Loading... |
| 400 log.add(name); | 376 log.add(name); |
| 401 if (name == #foo && invocation.isGetter) return _foo; | 377 if (name == #foo && invocation.isGetter) return _foo; |
| 402 if (name == const Symbol('foo=')) { | 378 if (name == const Symbol('foo=')) { |
| 403 _foo = invocation.positionalArguments[0]; | 379 _foo = invocation.positionalArguments[0]; |
| 404 return null; | 380 return null; |
| 405 } | 381 } |
| 406 return super.noSuchMethod(invocation); | 382 return super.noSuchMethod(invocation); |
| 407 } | 383 } |
| 408 } | 384 } |
| 409 | 385 |
| 410 class IndexerModel implements Indexable<String, dynamic> { | 386 class IndexerModel { |
| 411 var _foo = 42; | 387 var _foo = 42; |
| 412 List log = []; | 388 List log = []; |
| 413 | 389 |
| 414 operator [](index) { | 390 operator [](index) { |
| 415 log.add('[] $index'); | 391 log.add('[] $index'); |
| 416 if (index == 'foo') return _foo; | 392 if (index == 'foo') return _foo; |
| 417 } | 393 } |
| 418 | 394 |
| 419 operator []=(index, value) { | 395 operator []=(index, value) { |
| 420 log.add('[]= $index $value'); | 396 log.add('[]= $index $value'); |
| (...skipping 28 matching lines...) Expand all Loading... |
| 449 | 425 |
| 450 class WatcherModel extends Observable { | 426 class WatcherModel extends Observable { |
| 451 // TODO(jmesserly): dart2js does not let these be on the same line: | 427 // TODO(jmesserly): dart2js does not let these be on the same line: |
| 452 // @observable var a, b, c; | 428 // @observable var a, b, c; |
| 453 @observable var a; | 429 @observable var a; |
| 454 @observable var b; | 430 @observable var b; |
| 455 @observable var c; | 431 @observable var c; |
| 456 | 432 |
| 457 WatcherModel(); | 433 WatcherModel(); |
| 458 } | 434 } |
| OLD | NEW |