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