Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(680)

Side by Side Diff: pkg/observe/test/path_observer_test.dart

Issue 211763002: Change path-observer to lookup properties aggressively and report errors (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698