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 |