OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 import 'dart:async'; | |
6 import 'package:observe/observe.dart'; | |
7 import 'package:unittest/unittest.dart'; | |
8 import 'package:observe/src/path_observer.dart' | |
9 show getSegmentsOfPropertyPathForTesting, | |
10 observerSentinelForTesting; | |
11 | |
12 import 'observe_test_utils.dart'; | |
13 | |
14 import 'package:observe/mirrors_used.dart'; // make test smaller. | |
15 import 'package:smoke/mirrors.dart'; | |
16 | |
17 // This file contains code ported from: | |
18 // https://github.com/rafaelw/ChangeSummary/blob/master/tests/test.js | |
19 // Dart note: getting invalid properties is an error, unlike in JS where it | |
20 // returns undefined. This difference comes up where we check for _throwsNSM in | |
21 // the tests below. | |
22 main() => dirtyCheckZone().run(() { | |
23 useMirrors(); | |
24 | |
25 group('PathObserver', observePathTests); | |
26 | |
27 group('PropertyPath', () { | |
28 test('toString length', () { | |
29 expectPath(p, str, len, [keys]) { | |
30 var path = new PropertyPath(p); | |
31 expect(path.toString(), str); | |
32 expect(path.length, len, reason: 'expected path length $len for $path'); | |
33 if (keys == null) { | |
34 expect(path.isValid, isFalse); | |
35 } else { | |
36 expect(path.isValid, isTrue); | |
37 expect(getSegmentsOfPropertyPathForTesting(path), keys); | |
38 } | |
39 } | |
40 | |
41 expectPath('/foo', '<invalid path>', 0); | |
42 expectPath('1.abc', '<invalid path>', 0); | |
43 expectPath('abc', 'abc', 1, [#abc]); | |
44 expectPath('a.b.c', 'a.b.c', 3, [#a, #b, #c]); | |
45 expectPath('a.b.c ', 'a.b.c', 3, [#a, #b, #c]); | |
46 expectPath(' a.b.c', 'a.b.c', 3, [#a, #b, #c]); | |
47 expectPath(' a.b.c ', 'a.b.c', 3, [#a, #b, #c]); | |
48 expectPath('[1].abc', '[1].abc', 2, [1, #abc]); | |
49 expectPath([#qux], 'qux', 1, [#qux]); | |
50 expectPath([1, #foo, #bar], '[1].foo.bar', 3, [1, #foo, #bar]); | |
51 expectPath([1, #foo, 'bar'], '[1].foo["bar"]', 3, [1, #foo, 'bar']); | |
52 | |
53 // From test.js: "path validity" test: | |
54 | |
55 expectPath('', '', 0, []); | |
56 expectPath(' ', '', 0, []); | |
57 expectPath(null, '', 0, []); | |
58 expectPath('a', 'a', 1, [#a]); | |
59 expectPath('a.b', 'a.b', 2, [#a, #b]); | |
60 expectPath('a. b', 'a.b', 2, [#a, #b]); | |
61 expectPath('a .b', 'a.b', 2, [#a, #b]); | |
62 expectPath('a . b', 'a.b', 2, [#a, #b]); | |
63 expectPath(' a . b ', 'a.b', 2, [#a, #b]); | |
64 expectPath('a[0]', 'a[0]', 2, [#a, 0]); | |
65 expectPath('a [0]', 'a[0]', 2, [#a, 0]); | |
66 expectPath('a[0][1]', 'a[0][1]', 3, [#a, 0, 1]); | |
67 expectPath('a [ 0 ] [ 1 ] ', 'a[0][1]', 3, [#a, 0, 1]); | |
68 expectPath('[1234567890] ', '[1234567890]', 1, [1234567890]); | |
69 expectPath(' [1234567890] ', '[1234567890]', 1, [1234567890]); | |
70 expectPath('opt0', 'opt0', 1, [#opt0]); | |
71 // Dart note: Modified to avoid a private Dart symbol: | |
72 expectPath(r'$foo.$bar.baz_', r'$foo.$bar.baz_', 3, | |
73 [#$foo, #$bar, #baz_]); | |
74 // Dart note: this test is different because we treat ["baz"] always as a | |
75 // indexing operation. | |
76 expectPath('foo["baz"]', 'foo.baz', 2, [#foo, #baz]); | |
77 expectPath('foo["b\\"az"]', 'foo["b\\"az"]', 2, [#foo, 'b"az']); | |
78 expectPath("foo['b\\'az']", 'foo["b\'az"]', 2, [#foo, "b'az"]); | |
79 expectPath([#a, #b], 'a.b', 2, [#a, #b]); | |
80 expectPath([], '', 0, []); | |
81 | |
82 expectPath('.', '<invalid path>', 0); | |
83 expectPath(' . ', '<invalid path>', 0); | |
84 expectPath('..', '<invalid path>', 0); | |
85 expectPath('a[4', '<invalid path>', 0); | |
86 expectPath('a.b.', '<invalid path>', 0); | |
87 expectPath('a,b', '<invalid path>', 0); | |
88 expectPath('a["foo]', '<invalid path>', 0); | |
89 expectPath('[0x04]', '<invalid path>', 0); | |
90 expectPath('[0foo]', '<invalid path>', 0); | |
91 expectPath('[foo-bar]', '<invalid path>', 0); | |
92 expectPath('foo-bar', '<invalid path>', 0); | |
93 expectPath('42', '<invalid path>', 0); | |
94 expectPath('a[04]', '<invalid path>', 0); | |
95 expectPath(' a [ 04 ]', '<invalid path>', 0); | |
96 expectPath(' 42 ', '<invalid path>', 0); | |
97 expectPath('foo["bar]', '<invalid path>', 0); | |
98 expectPath("foo['bar]", '<invalid path>', 0); | |
99 }); | |
100 | |
101 test('objects with toString are not supported', () { | |
102 // Dart note: this was intentionally not ported. See path_observer.dart. | |
103 expect(() => new PropertyPath([new Foo('a'), new Foo('b')]), throws); | |
104 }); | |
105 | |
106 test('invalid path returns null value', () { | |
107 var path = new PropertyPath('a b'); | |
108 expect(path.isValid, isFalse); | |
109 expect(path.getValueFrom({'a': {'b': 2}}), isNull); | |
110 }); | |
111 | |
112 | |
113 test('caching and ==', () { | |
114 var start = new PropertyPath('abc[0]'); | |
115 for (int i = 1; i <= 100; i++) { | |
116 expect(identical(new PropertyPath('abc[0]'), start), true, | |
117 reason: 'should return identical path'); | |
118 | |
119 var p = new PropertyPath('abc[$i]'); | |
120 expect(identical(p, start), false, | |
121 reason: 'different paths should not be merged'); | |
122 } | |
123 var end = new PropertyPath('abc[0]'); | |
124 expect(identical(end, start), false, | |
125 reason: 'first entry expired'); | |
126 expect(end, start, reason: 'different instances are equal'); | |
127 }); | |
128 | |
129 test('hashCode equal', () { | |
130 var a = new PropertyPath([#foo, 2, #bar]); | |
131 var b = new PropertyPath('foo[2].bar'); | |
132 expect(identical(a, b), false, reason: 'only strings cached'); | |
133 expect(a, b, reason: 'same paths are equal'); | |
134 expect(a.hashCode, b.hashCode, reason: 'equal hashCodes'); | |
135 }); | |
136 | |
137 test('hashCode not equal', () { | |
138 expect(2.hashCode, isNot(3.hashCode), | |
139 reason: 'test depends on 2 and 3 having different hashcodes'); | |
140 | |
141 var a = new PropertyPath([2]); | |
142 var b = new PropertyPath([3]); | |
143 expect(a, isNot(b), reason: 'different paths'); | |
144 expect(a.hashCode, isNot(b.hashCode), reason: 'different hashCodes'); | |
145 }); | |
146 }); | |
147 | |
148 group('CompoundObserver', compoundObserverTests); | |
149 }); | |
150 | |
151 observePathTests() { | |
152 test('Degenerate Values', () { | |
153 expect(new PathObserver(null, '').value, null); | |
154 expect(new PathObserver(123, '').value, 123); | |
155 expect(() => new PathObserver(123, 'foo.bar.baz').value, _throwsNSM('foo')); | |
156 | |
157 // shouldn't throw: | |
158 new PathObserver(123, '')..open((_) {})..close(); | |
159 new PropertyPath('').setValueFrom(null, null); | |
160 new PropertyPath('').setValueFrom(123, 42); | |
161 expect(() => new PropertyPath('foo.bar.baz').setValueFrom(123, 42), | |
162 _throwsNSM('foo')); | |
163 var foo = {}; | |
164 expect(new PathObserver(foo, '').value, foo); | |
165 | |
166 foo = new Object(); | |
167 expect(new PathObserver(foo, '').value, foo); | |
168 | |
169 expect(new PathObserver(foo, 'a/3!').value, null); | |
170 }); | |
171 | |
172 test('get value at path ObservableBox', () { | |
173 var obj = new ObservableBox(new ObservableBox(new ObservableBox(1))); | |
174 | |
175 expect(new PathObserver(obj, '').value, obj); | |
176 expect(new PathObserver(obj, 'value').value, obj.value); | |
177 expect(new PathObserver(obj, 'value.value').value, obj.value.value); | |
178 expect(new PathObserver(obj, 'value.value.value').value, 1); | |
179 | |
180 obj.value.value.value = 2; | |
181 expect(new PathObserver(obj, 'value.value.value').value, 2); | |
182 | |
183 obj.value.value = new ObservableBox(3); | |
184 expect(new PathObserver(obj, 'value.value.value').value, 3); | |
185 | |
186 obj.value = new ObservableBox(4); | |
187 expect(() => new PathObserver(obj, 'value.value.value').value, | |
188 _throwsNSM('value')); | |
189 expect(new PathObserver(obj, 'value.value').value, 4); | |
190 }); | |
191 | |
192 | |
193 test('get value at path ObservableMap', () { | |
194 var obj = toObservable({'a': {'b': {'c': 1}}}); | |
195 | |
196 expect(new PathObserver(obj, '').value, obj); | |
197 expect(new PathObserver(obj, 'a').value, obj['a']); | |
198 expect(new PathObserver(obj, 'a.b').value, obj['a']['b']); | |
199 expect(new PathObserver(obj, 'a.b.c').value, 1); | |
200 | |
201 obj['a']['b']['c'] = 2; | |
202 expect(new PathObserver(obj, 'a.b.c').value, 2); | |
203 | |
204 obj['a']['b'] = toObservable({'c': 3}); | |
205 expect(new PathObserver(obj, 'a.b.c').value, 3); | |
206 | |
207 obj['a'] = toObservable({'b': 4}); | |
208 expect(() => new PathObserver(obj, 'a.b.c').value, _throwsNSM('c')); | |
209 expect(new PathObserver(obj, 'a.b').value, 4); | |
210 }); | |
211 | |
212 test('set value at path', () { | |
213 var obj = toObservable({}); | |
214 new PropertyPath('foo').setValueFrom(obj, 3); | |
215 expect(obj['foo'], 3); | |
216 | |
217 var bar = toObservable({ 'baz': 3 }); | |
218 new PropertyPath('bar').setValueFrom(obj, bar); | |
219 expect(obj['bar'], bar); | |
220 | |
221 expect(() => new PropertyPath('bar.baz.bat').setValueFrom(obj, 'not here'), | |
222 _throwsNSM('bat=')); | |
223 expect(() => new PathObserver(obj, 'bar.baz.bat').value, _throwsNSM('bat')); | |
224 }); | |
225 | |
226 test('set value back to same', () { | |
227 var obj = toObservable({}); | |
228 var path = new PathObserver(obj, 'foo'); | |
229 var values = []; | |
230 path.open((x) { | |
231 expect(x, path.value, reason: 'callback should get current value'); | |
232 values.add(x); | |
233 }); | |
234 | |
235 path.value = 3; | |
236 expect(obj['foo'], 3); | |
237 expect(path.value, 3); | |
238 | |
239 new PropertyPath('foo').setValueFrom(obj, 2); | |
240 return new Future(() { | |
241 expect(path.value, 2); | |
242 expect(new PathObserver(obj, 'foo').value, 2); | |
243 | |
244 new PropertyPath('foo').setValueFrom(obj, 3); | |
245 }).then(newMicrotask).then((_) { | |
246 expect(path.value, 3); | |
247 | |
248 }).then(newMicrotask).then((_) { | |
249 expect(values, [2, 3]); | |
250 }); | |
251 }); | |
252 | |
253 test('Observe and Unobserve - Paths', () { | |
254 var arr = toObservable({}); | |
255 | |
256 arr['foo'] = 'bar'; | |
257 var fooValues = []; | |
258 var fooPath = new PathObserver(arr, 'foo'); | |
259 fooPath.open(fooValues.add); | |
260 arr['foo'] = 'baz'; | |
261 arr['bat'] = 'bag'; | |
262 var batValues = []; | |
263 var batPath = new PathObserver(arr, 'bat'); | |
264 batPath.open(batValues.add); | |
265 | |
266 return new Future(() { | |
267 expect(fooValues, ['baz']); | |
268 expect(batValues, []); | |
269 | |
270 arr['foo'] = 'bar'; | |
271 fooPath.close(); | |
272 arr['bat'] = 'boo'; | |
273 batPath.close(); | |
274 arr['bat'] = 'boot'; | |
275 | |
276 }).then(newMicrotask).then((_) { | |
277 expect(fooValues, ['baz']); | |
278 expect(batValues, []); | |
279 }); | |
280 }); | |
281 | |
282 test('Path Value With Indices', () { | |
283 var model = toObservable([]); | |
284 var path = new PathObserver(model, '[0]'); | |
285 path.open(expectAsync((x) { | |
286 expect(path.value, 123); | |
287 expect(x, 123); | |
288 })); | |
289 model.add(123); | |
290 }); | |
291 | |
292 group('ObservableList', () { | |
293 test('isNotEmpty', () { | |
294 var model = new ObservableList(); | |
295 var path = new PathObserver(model, 'isNotEmpty'); | |
296 expect(path.value, false); | |
297 | |
298 path.open(expectAsync((_) { | |
299 expect(path.value, true); | |
300 })); | |
301 model.add(123); | |
302 }); | |
303 | |
304 test('isEmpty', () { | |
305 var model = new ObservableList(); | |
306 var path = new PathObserver(model, 'isEmpty'); | |
307 expect(path.value, true); | |
308 | |
309 path.open(expectAsync((_) { | |
310 expect(path.value, false); | |
311 })); | |
312 model.add(123); | |
313 }); | |
314 }); | |
315 | |
316 for (var createModel in [() => new TestModel(), () => new WatcherModel()]) { | |
317 test('Path Observation - ${createModel().runtimeType}', () { | |
318 var model = createModel()..a = | |
319 (createModel()..b = (createModel()..c = 'hello, world')); | |
320 | |
321 var path = new PathObserver(model, 'a.b.c'); | |
322 var lastValue = null; | |
323 var errorSeen = false; | |
324 runZoned(() { | |
325 path.open((x) { lastValue = x; }); | |
326 }, onError: (e) { | |
327 expect(e, _isNoSuchMethodOf('c')); | |
328 errorSeen = true; | |
329 }); | |
330 | |
331 model.a.b.c = 'hello, mom'; | |
332 | |
333 expect(lastValue, null); | |
334 return new Future(() { | |
335 expect(lastValue, 'hello, mom'); | |
336 | |
337 model.a.b = createModel()..c = 'hello, dad'; | |
338 }).then(newMicrotask).then((_) { | |
339 expect(lastValue, 'hello, dad'); | |
340 | |
341 model.a = createModel()..b = | |
342 (createModel()..c = 'hello, you'); | |
343 }).then(newMicrotask).then((_) { | |
344 expect(lastValue, 'hello, you'); | |
345 | |
346 model.a.b = 1; | |
347 expect(errorSeen, isFalse); | |
348 }).then(newMicrotask).then((_) { | |
349 expect(errorSeen, isTrue); | |
350 expect(lastValue, 'hello, you'); | |
351 | |
352 // Stop observing | |
353 path.close(); | |
354 | |
355 model.a.b = createModel()..c = 'hello, back again -- but not observing'; | |
356 }).then(newMicrotask).then((_) { | |
357 expect(lastValue, 'hello, you'); | |
358 | |
359 // Resume observing | |
360 new PathObserver(model, 'a.b.c').open((x) { lastValue = x; }); | |
361 | |
362 model.a.b.c = 'hello. Back for reals'; | |
363 }).then(newMicrotask).then((_) { | |
364 expect(lastValue, 'hello. Back for reals'); | |
365 }); | |
366 }); | |
367 } | |
368 | |
369 test('observe map', () { | |
370 var model = toObservable({'a': 1}); | |
371 var path = new PathObserver(model, 'a'); | |
372 | |
373 var values = [path.value]; | |
374 path.open(values.add); | |
375 expect(values, [1]); | |
376 | |
377 model['a'] = 2; | |
378 return new Future(() { | |
379 expect(values, [1, 2]); | |
380 | |
381 path.close(); | |
382 model['a'] = 3; | |
383 }).then(newMicrotask).then((_) { | |
384 expect(values, [1, 2]); | |
385 }); | |
386 }); | |
387 | |
388 test('errors thrown from getter/setter', () { | |
389 var model = new ObjectWithErrors(); | |
390 var observer = new PathObserver(model, 'foo'); | |
391 | |
392 expect(() => observer.value, _throwsNSM('bar')); | |
393 expect(model.getFooCalled, 1); | |
394 | |
395 expect(() { observer.value = 123; }, _throwsNSM('bar=')); | |
396 expect(model.setFooCalled, [123]); | |
397 }); | |
398 | |
399 test('object with noSuchMethod', () { | |
400 var model = new NoSuchMethodModel(); | |
401 var observer = new PathObserver(model, 'foo'); | |
402 | |
403 expect(observer.value, 42); | |
404 observer.value = 'hi'; | |
405 expect(model._foo, 'hi'); | |
406 expect(observer.value, 'hi'); | |
407 | |
408 expect(model.log, [#foo, const Symbol('foo='), #foo]); | |
409 | |
410 // These shouldn't throw | |
411 observer = new PathObserver(model, 'bar'); | |
412 expect(observer.value, null, reason: 'path not found'); | |
413 observer.value = 42; | |
414 expect(observer.value, null, reason: 'path not found'); | |
415 }); | |
416 | |
417 test('object with indexer', () { | |
418 var model = new IndexerModel(); | |
419 var observer = new PathObserver(model, 'foo'); | |
420 | |
421 expect(observer.value, 42); | |
422 expect(model.log, ['[] foo']); | |
423 model.log.clear(); | |
424 | |
425 observer.value = 'hi'; | |
426 expect(model.log, ['[]= foo hi']); | |
427 expect(model._foo, 'hi'); | |
428 | |
429 expect(observer.value, 'hi'); | |
430 | |
431 // These shouldn't throw | |
432 model.log.clear(); | |
433 observer = new PathObserver(model, 'bar'); | |
434 expect(observer.value, null, reason: 'path not found'); | |
435 expect(model.log, ['[] bar']); | |
436 model.log.clear(); | |
437 | |
438 observer.value = 42; | |
439 expect(model.log, ['[]= bar 42']); | |
440 model.log.clear(); | |
441 }); | |
442 | |
443 test('regression for TemplateBinding#161', () { | |
444 var model = toObservable({'obj': toObservable({'bar': false})}); | |
445 var ob1 = new PathObserver(model, 'obj.bar'); | |
446 var called = false; | |
447 ob1.open(() { called = true; }); | |
448 | |
449 var obj2 = new PathObserver(model, 'obj'); | |
450 obj2.open(() { model['obj']['bar'] = true; }); | |
451 | |
452 model['obj'] = toObservable({ 'obj': 'obj' }); | |
453 | |
454 return new Future(() {}) | |
455 .then((_) => expect(called, true)); | |
456 }); | |
457 } | |
458 | |
459 compoundObserverTests() { | |
460 var model; | |
461 var observer; | |
462 bool called; | |
463 var newValues; | |
464 var oldValues; | |
465 var observed; | |
466 | |
467 setUp(() { | |
468 model = new TestModel(1, 2, 3); | |
469 called = false; | |
470 }); | |
471 | |
472 callback(a, b, c) { | |
473 called = true; | |
474 newValues = a; | |
475 oldValues = b; | |
476 observed = c; | |
477 } | |
478 | |
479 reset() { | |
480 called = false; | |
481 newValues = null; | |
482 oldValues = null; | |
483 observed = null; | |
484 } | |
485 | |
486 expectNoChanges() { | |
487 observer.deliver(); | |
488 expect(called, isFalse); | |
489 expect(newValues, isNull); | |
490 expect(oldValues, isNull); | |
491 expect(observed, isNull); | |
492 } | |
493 | |
494 expectCompoundPathChanges(expectedNewValues, | |
495 expectedOldValues, expectedObserved, {deliver: true}) { | |
496 if (deliver) observer.deliver(); | |
497 expect(called, isTrue); | |
498 | |
499 expect(newValues, expectedNewValues); | |
500 var oldValuesAsMap = {}; | |
501 for (int i = 0; i < expectedOldValues.length; i++) { | |
502 if (expectedOldValues[i] != null) { | |
503 oldValuesAsMap[i] = expectedOldValues[i]; | |
504 } | |
505 } | |
506 expect(oldValues, oldValuesAsMap); | |
507 expect(observed, expectedObserved); | |
508 | |
509 reset(); | |
510 } | |
511 | |
512 tearDown(() { | |
513 observer.close(); | |
514 reset(); | |
515 }); | |
516 | |
517 _path(s) => new PropertyPath(s); | |
518 | |
519 test('simple', () { | |
520 observer = new CompoundObserver(); | |
521 observer.addPath(model, 'a'); | |
522 observer.addPath(model, 'b'); | |
523 observer.addPath(model, _path('c')); | |
524 observer.open(callback); | |
525 expectNoChanges(); | |
526 | |
527 var expectedObs = [model, _path('a'), model, _path('b'), model, _path('c')]; | |
528 model.a = -10; | |
529 model.b = 20; | |
530 model.c = 30; | |
531 expectCompoundPathChanges([-10, 20, 30], [1, 2, 3], expectedObs); | |
532 | |
533 model.a = 'a'; | |
534 model.c = 'c'; | |
535 expectCompoundPathChanges(['a', 20, 'c'], [-10, null, 30], expectedObs); | |
536 | |
537 model.a = 2; | |
538 model.b = 3; | |
539 model.c = 4; | |
540 expectCompoundPathChanges([2, 3, 4], ['a', 20, 'c'], expectedObs); | |
541 | |
542 model.a = 'z'; | |
543 model.b = 'y'; | |
544 model.c = 'x'; | |
545 expect(observer.value, ['z', 'y', 'x']); | |
546 expectNoChanges(); | |
547 | |
548 expect(model.a, 'z'); | |
549 expect(model.b, 'y'); | |
550 expect(model.c, 'x'); | |
551 expectNoChanges(); | |
552 }); | |
553 | |
554 test('reportChangesOnOpen', () { | |
555 observer = new CompoundObserver(true); | |
556 observer.addPath(model, 'a'); | |
557 observer.addPath(model, 'b'); | |
558 observer.addPath(model, _path('c')); | |
559 | |
560 model.a = -10; | |
561 model.b = 20; | |
562 observer.open(callback); | |
563 var expectedObs = [model, _path('a'), model, _path('b'), model, _path('c')]; | |
564 expectCompoundPathChanges([-10, 20, 3], [1, 2, null], expectedObs, | |
565 deliver: false); | |
566 }); | |
567 | |
568 test('All Observers', () { | |
569 observer = new CompoundObserver(); | |
570 var pathObserver1 = new PathObserver(model, 'a'); | |
571 var pathObserver2 = new PathObserver(model, 'b'); | |
572 var pathObserver3 = new PathObserver(model, _path('c')); | |
573 | |
574 observer.addObserver(pathObserver1); | |
575 observer.addObserver(pathObserver2); | |
576 observer.addObserver(pathObserver3); | |
577 observer.open(callback); | |
578 | |
579 var expectedObs = [observerSentinelForTesting, pathObserver1, | |
580 observerSentinelForTesting, pathObserver2, | |
581 observerSentinelForTesting, pathObserver3]; | |
582 model.a = -10; | |
583 model.b = 20; | |
584 model.c = 30; | |
585 expectCompoundPathChanges([-10, 20, 30], [1, 2, 3], expectedObs); | |
586 | |
587 model.a = 'a'; | |
588 model.c = 'c'; | |
589 expectCompoundPathChanges(['a', 20, 'c'], [-10, null, 30], expectedObs); | |
590 }); | |
591 | |
592 test('Degenerate Values', () { | |
593 observer = new CompoundObserver(); | |
594 observer.addPath(model, '.'); // invalid path | |
595 observer.addPath('obj-value', ''); // empty path | |
596 // Dart note: we don't port these two tests because in Dart we produce | |
597 // exceptions for these invalid paths. | |
598 // observer.addPath(model, 'foo'); // unreachable | |
599 // observer.addPath(3, 'bar'); // non-object with non-empty path | |
600 var values = observer.open(callback); | |
601 expect(values.length, 2); | |
602 expect(values[0], null); | |
603 expect(values[1], 'obj-value'); | |
604 observer.close(); | |
605 }); | |
606 | |
607 test('Heterogeneous', () { | |
608 model.c = null; | |
609 var otherModel = new TestModel(null, null, 3); | |
610 | |
611 twice(value) => value * 2; | |
612 half(value) => value ~/ 2; | |
613 | |
614 var compound = new CompoundObserver(); | |
615 compound.addPath(model, 'a'); | |
616 compound.addObserver(new ObserverTransform(new PathObserver(model, 'b'), | |
617 twice, setValue: half)); | |
618 compound.addObserver(new PathObserver(otherModel, 'c')); | |
619 | |
620 combine(values) => values[0] + values[1] + values[2]; | |
621 observer = new ObserverTransform(compound, combine); | |
622 | |
623 var newValue; | |
624 transformCallback(v) { | |
625 newValue = v; | |
626 called = true; | |
627 } | |
628 expect(observer.open(transformCallback), 8); | |
629 | |
630 model.a = 2; | |
631 model.b = 4; | |
632 observer.deliver(); | |
633 expect(called, isTrue); | |
634 expect(newValue, 13); | |
635 called = false; | |
636 | |
637 model.b = 10; | |
638 otherModel.c = 5; | |
639 observer.deliver(); | |
640 expect(called, isTrue); | |
641 expect(newValue, 27); | |
642 called = false; | |
643 | |
644 model.a = 20; | |
645 model.b = 1; | |
646 otherModel.c = 5; | |
647 observer.deliver(); | |
648 expect(called, isFalse); | |
649 expect(newValue, 27); | |
650 }); | |
651 } | |
652 | |
653 /// A matcher that checks that a closure throws a NoSuchMethodError matching the | |
654 /// given [name]. | |
655 _throwsNSM(String name) => throwsA(_isNoSuchMethodOf(name)); | |
656 | |
657 /// A matcher that checkes whether an exception is a NoSuchMethodError matching | |
658 /// the given [name]. | |
659 _isNoSuchMethodOf(String name) => predicate((e) => | |
660 e is NoSuchMethodError && | |
661 // Dart2js and VM error messages are a bit different, but they both contain | |
662 // the name. | |
663 ('$e'.contains("'$name'") || // VM error | |
664 '$e'.contains('\'Symbol("$name")\''))); // dart2js error | |
665 | |
666 class ObjectWithErrors { | |
667 int getFooCalled = 0; | |
668 List setFooCalled = []; | |
669 @reflectable get foo { | |
670 getFooCalled++; | |
671 (this as dynamic).bar; | |
672 } | |
673 @reflectable set foo(value) { | |
674 setFooCalled.add(value); | |
675 (this as dynamic).bar = value; | |
676 } | |
677 } | |
678 | |
679 class NoSuchMethodModel { | |
680 var _foo = 42; | |
681 List log = []; | |
682 | |
683 // TODO(ahe): Remove @reflectable from here (once either of | |
684 // http://dartbug.com/15408 or http://dartbug.com/15409 are fixed). | |
685 @reflectable noSuchMethod(Invocation invocation) { | |
686 final name = invocation.memberName; | |
687 log.add(name); | |
688 if (name == #foo && invocation.isGetter) return _foo; | |
689 if (name == const Symbol('foo=')) { | |
690 _foo = invocation.positionalArguments[0]; | |
691 return null; | |
692 } | |
693 return super.noSuchMethod(invocation); | |
694 } | |
695 } | |
696 | |
697 class IndexerModel implements Indexable<String, dynamic> { | |
698 var _foo = 42; | |
699 List log = []; | |
700 | |
701 operator [](index) { | |
702 log.add('[] $index'); | |
703 if (index == 'foo') return _foo; | |
704 } | |
705 | |
706 operator []=(index, value) { | |
707 log.add('[]= $index $value'); | |
708 if (index == 'foo') _foo = value; | |
709 } | |
710 } | |
711 | |
712 @reflectable | |
713 class TestModel extends ChangeNotifier { | |
714 var _a, _b, _c; | |
715 | |
716 TestModel([this._a, this._b, this._c]); | |
717 | |
718 get a => _a; | |
719 | |
720 void set a(newValue) { | |
721 _a = notifyPropertyChange(#a, _a, newValue); | |
722 } | |
723 | |
724 get b => _b; | |
725 | |
726 void set b(newValue) { | |
727 _b = notifyPropertyChange(#b, _b, newValue); | |
728 } | |
729 | |
730 get c => _c; | |
731 | |
732 void set c(newValue) { | |
733 _c = notifyPropertyChange(#c, _c, newValue); | |
734 } | |
735 } | |
736 | |
737 class WatcherModel extends Observable { | |
738 // TODO(jmesserly): dart2js does not let these be on the same line: | |
739 // @observable var a, b, c; | |
740 @observable var a; | |
741 @observable var b; | |
742 @observable var c; | |
743 | |
744 WatcherModel(); | |
745 } | |
746 | |
747 class Foo { | |
748 var value; | |
749 Foo(this.value); | |
750 String toString() => 'Foo$value'; | |
751 } | |
OLD | NEW |