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 | |
13 main() => dirtyCheckZone().run(() { | 12 main() => dirtyCheckZone().run(() { |
14 group('PathObserver', observePathTests); | 13 group('PathObserver', observePathTests); |
15 | 14 |
16 group('PropertyPath', () { | 15 group('PropertyPath', () { |
17 test('toString length', () { | 16 test('toString length', () { |
18 expectPath(p, str, len) { | 17 expectPath(p, str, len) { |
19 var path = new PropertyPath(p); | 18 var path = new PropertyPath(p); |
20 expect(path.toString(), str); | 19 expect(path.toString(), str); |
21 expect(path.length, len, reason: 'expected path length $len for $path'); | 20 expect(path.length, len, reason: 'expected path length $len for $path'); |
22 } | 21 } |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
61 reason: 'test depends on 2 and 3 having different hashcodes'); | 60 reason: 'test depends on 2 and 3 having different hashcodes'); |
62 | 61 |
63 var a = new PropertyPath([2]); | 62 var a = new PropertyPath([2]); |
64 var b = new PropertyPath([3]); | 63 var b = new PropertyPath([3]); |
65 expect(a, isNot(b), reason: 'different paths'); | 64 expect(a, isNot(b), reason: 'different paths'); |
66 expect(a.hashCode, isNot(b.hashCode), reason: 'different hashCodes'); | 65 expect(a.hashCode, isNot(b.hashCode), reason: 'different hashCodes'); |
67 }); | 66 }); |
68 }); | 67 }); |
69 }); | 68 }); |
70 | 69 |
71 | |
72 observePathTests() { | 70 observePathTests() { |
73 test('Degenerate Values', () { | 71 test('Degenerate Values', () { |
74 expect(new PathObserver(null, '').value, null); | 72 expect(new PathObserver(null, '').value, null); |
75 expect(new PathObserver(123, '').value, 123); | 73 expect(new PathObserver(123, '').value, 123); |
76 expect(new PathObserver(123, 'foo.bar.baz').value, null); | 74 new Future(() {}).then((_) { |
Jennifer Messerly
2014/03/25 22:32:44
return the Future?
also does it work to start wit
Siggi Cherem (dart-lang)
2014/03/26 00:32:07
(obsolete)
| |
75 // should throw: | |
76 return _asyncError('foo', | |
77 () => expect(new PathObserver(123, 'foo.bar.baz').value, null)); | |
Jennifer Messerly
2014/03/25 22:32:44
as noted earlier, I'd expect this error to be sync
Siggi Cherem (dart-lang)
2014/03/26 00:32:07
Done.
| |
78 }).then((_) { | |
79 // shouldn't throw: | |
80 new PathObserver(123, '')..open((_) {})..close(); | |
81 new PropertyPath('').setValueFrom(null, null); | |
82 new PropertyPath('').setValueFrom(123, 42); | |
77 | 83 |
78 // shouldn't throw: | 84 // should throw: |
79 new PathObserver(123, '')..open((_) {})..close(); | 85 return _asyncError('foo', |
Jennifer Messerly
2014/03/25 22:32:44
when i'm changing behavior w.r.t. JS I usually put
Siggi Cherem (dart-lang)
2014/03/26 00:32:07
Done (made the note on the source code too)
| |
80 new PropertyPath('').setValueFrom(null, null); | 86 () => new PropertyPath('foo.bar.baz').setValueFrom(123, 42)); |
81 new PropertyPath('').setValueFrom(123, 42); | 87 }).then((_) { |
82 new PropertyPath('foo.bar.baz').setValueFrom(123, 42); | 88 var foo = {}; |
89 expect(new PathObserver(foo, '').value, foo); | |
83 | 90 |
84 var foo = {}; | 91 foo = new Object(); |
85 expect(new PathObserver(foo, '').value, foo); | 92 expect(new PathObserver(foo, '').value, foo); |
86 | 93 |
87 foo = new Object(); | 94 expect(new PathObserver(foo, 'a/3!').value, null); |
88 expect(new PathObserver(foo, '').value, foo); | 95 }); |
89 | |
90 expect(new PathObserver(foo, 'a/3!').value, null); | |
91 }); | 96 }); |
92 | 97 |
93 test('get value at path ObservableBox', () { | 98 test('get value at path ObservableBox', () { |
94 var obj = new ObservableBox(new ObservableBox(new ObservableBox(1))); | 99 var obj = new ObservableBox(new ObservableBox(new ObservableBox(1))); |
95 | 100 |
96 expect(new PathObserver(obj, '').value, obj); | 101 expect(new PathObserver(obj, '').value, obj); |
97 expect(new PathObserver(obj, 'value').value, obj.value); | 102 expect(new PathObserver(obj, 'value').value, obj.value); |
98 expect(new PathObserver(obj, 'value.value').value, obj.value.value); | 103 expect(new PathObserver(obj, 'value.value').value, obj.value.value); |
99 expect(new PathObserver(obj, 'value.value.value').value, 1); | 104 expect(new PathObserver(obj, 'value.value.value').value, 1); |
100 | 105 |
101 obj.value.value.value = 2; | 106 obj.value.value.value = 2; |
102 expect(new PathObserver(obj, 'value.value.value').value, 2); | 107 expect(new PathObserver(obj, 'value.value.value').value, 2); |
103 | 108 |
104 obj.value.value = new ObservableBox(3); | 109 obj.value.value = new ObservableBox(3); |
105 expect(new PathObserver(obj, 'value.value.value').value, 3); | 110 expect(new PathObserver(obj, 'value.value.value').value, 3); |
106 | 111 |
107 obj.value = new ObservableBox(4); | 112 obj.value = new ObservableBox(4); |
108 expect(new PathObserver(obj, 'value.value.value').value, null); | |
109 expect(new PathObserver(obj, 'value.value').value, 4); | 113 expect(new PathObserver(obj, 'value.value').value, 4); |
114 return _asyncError('value', | |
115 () => expect(new PathObserver(obj, 'value.value.value').value, null)); | |
110 }); | 116 }); |
111 | 117 |
112 | 118 |
113 test('get value at path ObservableMap', () { | 119 test('get value at path ObservableMap', () { |
114 var obj = toObservable({'a': {'b': {'c': 1}}}); | 120 var obj = toObservable({'a': {'b': {'c': 1}}}); |
115 | 121 |
116 expect(new PathObserver(obj, '').value, obj); | 122 expect(new PathObserver(obj, '').value, obj); |
117 expect(new PathObserver(obj, 'a').value, obj['a']); | 123 expect(new PathObserver(obj, 'a').value, obj['a']); |
118 expect(new PathObserver(obj, 'a.b').value, obj['a']['b']); | 124 expect(new PathObserver(obj, 'a.b').value, obj['a']['b']); |
119 expect(new PathObserver(obj, 'a.b.c').value, 1); | 125 expect(new PathObserver(obj, 'a.b.c').value, 1); |
120 | 126 |
121 obj['a']['b']['c'] = 2; | 127 obj['a']['b']['c'] = 2; |
122 expect(new PathObserver(obj, 'a.b.c').value, 2); | 128 expect(new PathObserver(obj, 'a.b.c').value, 2); |
123 | 129 |
124 obj['a']['b'] = toObservable({'c': 3}); | 130 obj['a']['b'] = toObservable({'c': 3}); |
125 expect(new PathObserver(obj, 'a.b.c').value, 3); | 131 expect(new PathObserver(obj, 'a.b.c').value, 3); |
126 | 132 |
127 obj['a'] = toObservable({'b': 4}); | 133 obj['a'] = toObservable({'b': 4}); |
128 expect(new PathObserver(obj, 'a.b.c').value, null); | |
129 expect(new PathObserver(obj, 'a.b').value, 4); | 134 expect(new PathObserver(obj, 'a.b').value, 4); |
135 return _asyncError('c', | |
136 () => expect(new PathObserver(obj, 'a.b.c').value, null)); | |
130 }); | 137 }); |
131 | 138 |
132 test('set value at path', () { | 139 test('set value at path', () { |
133 var obj = toObservable({}); | 140 var obj = toObservable({}); |
134 new PropertyPath('foo').setValueFrom(obj, 3); | 141 new PropertyPath('foo').setValueFrom(obj, 3); |
135 expect(obj['foo'], 3); | 142 expect(obj['foo'], 3); |
136 | 143 |
137 var bar = toObservable({ 'baz': 3 }); | 144 var bar = toObservable({ 'baz': 3 }); |
138 new PropertyPath('bar').setValueFrom(obj, bar); | 145 new PropertyPath('bar').setValueFrom(obj, bar); |
139 expect(obj['bar'], bar); | 146 expect(obj['bar'], bar); |
140 | 147 |
141 new PropertyPath('bar.baz.bat').setValueFrom(obj, 'not here'); | 148 new Future(() {}).then((_) { |
142 expect(new PathObserver(obj, 'bar.baz.bat').value, null); | 149 return _asyncError('bat=', |
150 () => new PropertyPath('bar.baz.bat').setValueFrom(obj, 'not here')); | |
151 }).then((_) { | |
152 return _asyncError('bat', | |
153 () => expect(new PathObserver(obj, 'bar.baz.bat').value, null)); | |
154 }); | |
143 }); | 155 }); |
144 | 156 |
145 test('set value back to same', () { | 157 test('set value back to same', () { |
146 var obj = toObservable({}); | 158 var obj = toObservable({}); |
147 var path = new PathObserver(obj, 'foo'); | 159 var path = new PathObserver(obj, 'foo'); |
148 var values = []; | 160 var values = []; |
149 path.open((x) { | 161 path.open((x) { |
150 expect(x, path.value, reason: 'callback should get current value'); | 162 expect(x, path.value, reason: 'callback should get current value'); |
151 values.add(x); | 163 values.add(x); |
152 }); | 164 }); |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
232 }); | 244 }); |
233 }); | 245 }); |
234 | 246 |
235 for (var createModel in [() => new TestModel(), () => new WatcherModel()]) { | 247 for (var createModel in [() => new TestModel(), () => new WatcherModel()]) { |
236 test('Path Observation - ${createModel().runtimeType}', () { | 248 test('Path Observation - ${createModel().runtimeType}', () { |
237 var model = createModel()..a = | 249 var model = createModel()..a = |
238 (createModel()..b = (createModel()..c = 'hello, world')); | 250 (createModel()..b = (createModel()..c = 'hello, world')); |
239 | 251 |
240 var path = new PathObserver(model, 'a.b.c'); | 252 var path = new PathObserver(model, 'a.b.c'); |
241 var lastValue = null; | 253 var lastValue = null; |
242 path.open((x) { lastValue = x; }); | 254 var errorSeen = false; |
255 var future = _asyncError('c', () => path.open((x) { lastValue = x; })) | |
256 .then((_) { errorSeen = true; }); | |
243 | 257 |
244 model.a.b.c = 'hello, mom'; | 258 model.a.b.c = 'hello, mom'; |
245 | 259 |
246 expect(lastValue, null); | 260 expect(lastValue, null); |
247 return new Future(() { | 261 return new Future(() { |
248 expect(lastValue, 'hello, mom'); | 262 expect(lastValue, 'hello, mom'); |
249 | 263 |
250 model.a.b = createModel()..c = 'hello, dad'; | 264 model.a.b = createModel()..c = 'hello, dad'; |
251 }).then(newMicrotask).then((_) { | 265 }).then(newMicrotask).then((_) { |
252 expect(lastValue, 'hello, dad'); | 266 expect(lastValue, 'hello, dad'); |
253 | 267 |
254 model.a = createModel()..b = | 268 model.a = createModel()..b = |
255 (createModel()..c = 'hello, you'); | 269 (createModel()..c = 'hello, you'); |
256 }).then(newMicrotask).then((_) { | 270 }).then(newMicrotask).then((_) { |
257 expect(lastValue, 'hello, you'); | 271 expect(lastValue, 'hello, you'); |
258 | 272 |
259 model.a.b = 1; | 273 model.a.b = 1; |
260 }).then(newMicrotask).then((_) { | 274 expect(errorSeen, false); // update will be seen on next micro task |
275 }).then((_) => future).then((_) { | |
276 expect(errorSeen, true); | |
261 expect(lastValue, null); | 277 expect(lastValue, null); |
262 | 278 |
263 // Stop observing | 279 // Stop observing |
264 path.close(); | 280 path.close(); |
265 | 281 |
266 model.a.b = createModel()..c = 'hello, back again -- but not observing'; | 282 model.a.b = createModel()..c = 'hello, back again -- but not observing'; |
267 }).then(newMicrotask).then((_) { | 283 }).then(newMicrotask).then((_) { |
268 expect(lastValue, null); | 284 expect(lastValue, null); |
269 | 285 |
270 // Resume observing | 286 // Resume observing |
(...skipping 22 matching lines...) Expand all Loading... | |
293 model['a'] = 3; | 309 model['a'] = 3; |
294 }).then(newMicrotask).then((_) { | 310 }).then(newMicrotask).then((_) { |
295 expect(values, [1, 2]); | 311 expect(values, [1, 2]); |
296 }); | 312 }); |
297 }); | 313 }); |
298 | 314 |
299 test('errors thrown from getter/setter', () { | 315 test('errors thrown from getter/setter', () { |
300 var model = new ObjectWithErrors(); | 316 var model = new ObjectWithErrors(); |
301 var observer = new PathObserver(model, 'foo'); | 317 var observer = new PathObserver(model, 'foo'); |
302 | 318 |
303 expect(() => observer.value, throws); | 319 new Future(() {}).then((_) { |
304 expect(model.getFooCalled, 1); | 320 return _asyncError('bar', () => observer.value); |
305 | 321 }).then((_) { |
306 expect(() { observer.value = 123; }, throws); | 322 expect(model.getFooCalled, 1); |
307 expect(model.setFooCalled, [123]); | 323 return _asyncError('bar=', () { observer.value = 123; }); |
324 }).then((_) { | |
325 expect(model.setFooCalled, [123]); | |
326 }); | |
308 }); | 327 }); |
309 | 328 |
310 test('object with noSuchMethod', () { | 329 test('object with noSuchMethod', () { |
311 var model = new NoSuchMethodModel(); | 330 var model = new NoSuchMethodModel(); |
312 var observer = new PathObserver(model, 'foo'); | 331 var observer = new PathObserver(model, 'foo'); |
313 | 332 |
314 expect(observer.value, 42); | 333 expect(observer.value, 42); |
315 observer.value = 'hi'; | 334 observer.value = 'hi'; |
316 expect(model._foo, 'hi'); | 335 expect(model._foo, 'hi'); |
317 expect(observer.value, 'hi'); | 336 expect(observer.value, 'hi'); |
(...skipping 27 matching lines...) Expand all Loading... | |
345 expect(observer.value, null, reason: 'path not found'); | 364 expect(observer.value, null, reason: 'path not found'); |
346 expect(model.log, ['[] bar']); | 365 expect(model.log, ['[] bar']); |
347 model.log.clear(); | 366 model.log.clear(); |
348 | 367 |
349 observer.value = 42; | 368 observer.value = 42; |
350 expect(model.log, ['[]= bar 42']); | 369 expect(model.log, ['[]= bar 42']); |
351 model.log.clear(); | 370 model.log.clear(); |
352 }); | 371 }); |
353 } | 372 } |
354 | 373 |
374 _asyncError(String missingPropertyName, Function code) { | |
375 var completer = new Completer(); | |
376 runZoned(() { | |
377 code(); | |
378 return new Future(() {}); | |
379 }, onError: (e) { | |
380 completer.complete(true); | |
381 expect(e, predicate((e) => e is NoSuchMethodError)); | |
382 | |
383 // Dart2js and VM error messages are a bit different, but they are both | |
384 // contain the missingPropertyName. | |
385 expect('$e', predicate((message) => | |
386 message.contains("'$missingPropertyName'") || // VM error | |
387 message.contains('\'Symbol("$missingPropertyName")\''))); // dart2js error | |
388 }); | |
389 return completer.future; | |
390 } | |
391 | |
355 class ObjectWithErrors { | 392 class ObjectWithErrors { |
356 int getFooCalled = 0; | 393 int getFooCalled = 0; |
357 List setFooCalled = []; | 394 List setFooCalled = []; |
358 @reflectable get foo { | 395 @reflectable get foo { |
359 getFooCalled++; | 396 getFooCalled++; |
360 (this as dynamic).bar; | 397 (this as dynamic).bar; |
361 } | 398 } |
362 @reflectable set foo(value) { | 399 @reflectable set foo(value) { |
363 setFooCalled.add(value); | 400 setFooCalled.add(value); |
364 (this as dynamic).bar = value; | 401 (this as dynamic).bar = value; |
(...skipping 11 matching lines...) Expand all Loading... | |
376 log.add(name); | 413 log.add(name); |
377 if (name == #foo && invocation.isGetter) return _foo; | 414 if (name == #foo && invocation.isGetter) return _foo; |
378 if (name == const Symbol('foo=')) { | 415 if (name == const Symbol('foo=')) { |
379 _foo = invocation.positionalArguments[0]; | 416 _foo = invocation.positionalArguments[0]; |
380 return null; | 417 return null; |
381 } | 418 } |
382 return super.noSuchMethod(invocation); | 419 return super.noSuchMethod(invocation); |
383 } | 420 } |
384 } | 421 } |
385 | 422 |
386 class IndexerModel { | 423 class IndexerModel implements StringIndexer { |
387 var _foo = 42; | 424 var _foo = 42; |
388 List log = []; | 425 List log = []; |
389 | 426 |
390 operator [](index) { | 427 operator [](index) { |
391 log.add('[] $index'); | 428 log.add('[] $index'); |
392 if (index == 'foo') return _foo; | 429 if (index == 'foo') return _foo; |
393 } | 430 } |
394 | 431 |
395 operator []=(index, value) { | 432 operator []=(index, value) { |
396 log.add('[]= $index $value'); | 433 log.add('[]= $index $value'); |
(...skipping 28 matching lines...) Expand all Loading... | |
425 | 462 |
426 class WatcherModel extends Observable { | 463 class WatcherModel extends Observable { |
427 // TODO(jmesserly): dart2js does not let these be on the same line: | 464 // TODO(jmesserly): dart2js does not let these be on the same line: |
428 // @observable var a, b, c; | 465 // @observable var a, b, c; |
429 @observable var a; | 466 @observable var a; |
430 @observable var b; | 467 @observable var b; |
431 @observable var c; | 468 @observable var c; |
432 | 469 |
433 WatcherModel(); | 470 WatcherModel(); |
434 } | 471 } |
OLD | NEW |