Index: pkg/observe/test/path_observer_test.dart |
diff --git a/pkg/observe/test/path_observer_test.dart b/pkg/observe/test/path_observer_test.dart |
index dcb8853ba963622cbeffef0c4d567caac3e6b956..0993c69754db0a21d82695618f0181038f38649d 100644 |
--- a/pkg/observe/test/path_observer_test.dart |
+++ b/pkg/observe/test/path_observer_test.dart |
@@ -2,6 +2,7 @@ |
// for details. All rights reserved. Use of this source code is governed by a |
// BSD-style license that can be found in the LICENSE file. |
+import 'dart:async'; |
import 'package:observe/observe.dart'; |
import 'package:unittest/unittest.dart'; |
import 'observe_test_utils.dart'; |
@@ -9,240 +10,293 @@ import 'observe_test_utils.dart'; |
// This file contains code ported from: |
// https://github.com/rafaelw/ChangeSummary/blob/master/tests/test.js |
-main() { |
+main() => dirtyCheckZone().run(() { |
group('PathObserver', observePathTests); |
-} |
-observePath(obj, path) => new PathObserver(obj, path); |
+ group('PropertyPath', () { |
+ test('toString length', () { |
+ expectPath(p, str, len) { |
+ var path = new PropertyPath(p); |
+ expect(path.toString(), str); |
+ expect(path.length, len, reason: 'expected path length $len for $path'); |
+ } |
+ |
+ expectPath('/foo', '<invalid path>', 0); |
+ expectPath('abc', 'abc', 1); |
+ expectPath('a.b.c', 'a.b.c', 3); |
+ expectPath('a.b.c ', 'a.b.c', 3); |
+ expectPath(' a.b.c', 'a.b.c', 3); |
+ expectPath(' a.b.c ', 'a.b.c', 3); |
+ expectPath('1.abc', '1.abc', 2); |
+ expectPath([#qux], 'qux', 1); |
+ expectPath([1, #foo, #bar], '1.foo.bar', 3); |
+ }); |
+ |
+ test('caching and ==', () { |
+ var start = new PropertyPath('abc.0'); |
+ for (int i = 1; i <= 100; i++) { |
+ expect(identical(new PropertyPath('abc.0'), start), true, |
+ reason: 'should return identical path'); |
+ |
+ var p = new PropertyPath('abc.$i'); |
+ expect(identical(p, start), false, |
+ reason: 'different paths should not be merged'); |
+ } |
+ var end = new PropertyPath('abc.0'); |
+ expect(identical(end, start), false, |
+ reason: 'first entry expired'); |
+ expect(end, start, reason: 'different instances are equal'); |
+ }); |
+ |
+ test('hashCode equal', () { |
+ var a = new PropertyPath([#foo, 2, #bar]); |
+ var b = new PropertyPath('foo.2.bar'); |
+ expect(identical(a, b), false, reason: 'only strings cached'); |
+ expect(a, b, reason: 'same paths are equal'); |
+ expect(a.hashCode, b.hashCode, reason: 'equal hashCodes'); |
+ }); |
+ |
+ test('hashCode not equal', () { |
+ expect(2.hashCode, isNot(3.hashCode), |
+ reason: 'test depends on 2 and 3 having different hashcodes'); |
+ |
+ var a = new PropertyPath([2]); |
+ var b = new PropertyPath([3]); |
+ expect(a, isNot(b), reason: 'different paths'); |
+ expect(a.hashCode, isNot(b.hashCode), reason: 'different hashCodes'); |
+ }); |
+ }); |
+}); |
+ |
observePathTests() { |
- observeTest('Degenerate Values', () { |
- expect(observePath(null, '').value, null); |
- expect(observePath(123, '').value, 123); |
- expect(observePath(123, 'foo.bar.baz').value, null); |
+ test('Degenerate Values', () { |
+ expect(new PathObserver(null, '').value, null); |
+ expect(new PathObserver(123, '').value, 123); |
+ expect(new PathObserver(123, 'foo.bar.baz').value, null); |
// shouldn't throw: |
- observePath(123, '').changes.listen((_) {}).cancel(); |
- observePath(null, '').value = null; |
- observePath(123, '').value = 42; |
- observePath(123, 'foo.bar.baz').value = 42; |
+ new PathObserver(123, '')..open((_) {})..close(); |
+ new PropertyPath('').setValueFrom(null, null); |
+ new PropertyPath('').setValueFrom(123, 42); |
+ new PropertyPath('foo.bar.baz').setValueFrom(123, 42); |
var foo = {}; |
- expect(observePath(foo, '').value, foo); |
+ expect(new PathObserver(foo, '').value, foo); |
foo = new Object(); |
- expect(observePath(foo, '').value, foo); |
+ expect(new PathObserver(foo, '').value, foo); |
- expect(observePath(foo, 'a/3!').value, null); |
+ expect(new PathObserver(foo, 'a/3!').value, null); |
}); |
- observeTest('get value at path ObservableBox', () { |
+ test('get value at path ObservableBox', () { |
var obj = new ObservableBox(new ObservableBox(new ObservableBox(1))); |
- expect(observePath(obj, '').value, obj); |
- expect(observePath(obj, 'value').value, obj.value); |
- expect(observePath(obj, 'value.value').value, obj.value.value); |
- expect(observePath(obj, 'value.value.value').value, 1); |
+ expect(new PathObserver(obj, '').value, obj); |
+ expect(new PathObserver(obj, 'value').value, obj.value); |
+ expect(new PathObserver(obj, 'value.value').value, obj.value.value); |
+ expect(new PathObserver(obj, 'value.value.value').value, 1); |
obj.value.value.value = 2; |
- expect(observePath(obj, 'value.value.value').value, 2); |
+ expect(new PathObserver(obj, 'value.value.value').value, 2); |
obj.value.value = new ObservableBox(3); |
- expect(observePath(obj, 'value.value.value').value, 3); |
+ expect(new PathObserver(obj, 'value.value.value').value, 3); |
obj.value = new ObservableBox(4); |
- expect(observePath(obj, 'value.value.value').value, null); |
- expect(observePath(obj, 'value.value').value, 4); |
+ expect(new PathObserver(obj, 'value.value.value').value, null); |
+ expect(new PathObserver(obj, 'value.value').value, 4); |
}); |
- observeTest('get value at path ObservableMap', () { |
+ test('get value at path ObservableMap', () { |
var obj = toObservable({'a': {'b': {'c': 1}}}); |
- expect(observePath(obj, '').value, obj); |
- expect(observePath(obj, 'a').value, obj['a']); |
- expect(observePath(obj, 'a.b').value, obj['a']['b']); |
- expect(observePath(obj, 'a.b.c').value, 1); |
+ expect(new PathObserver(obj, '').value, obj); |
+ expect(new PathObserver(obj, 'a').value, obj['a']); |
+ expect(new PathObserver(obj, 'a.b').value, obj['a']['b']); |
+ expect(new PathObserver(obj, 'a.b.c').value, 1); |
obj['a']['b']['c'] = 2; |
- expect(observePath(obj, 'a.b.c').value, 2); |
+ expect(new PathObserver(obj, 'a.b.c').value, 2); |
obj['a']['b'] = toObservable({'c': 3}); |
- expect(observePath(obj, 'a.b.c').value, 3); |
+ expect(new PathObserver(obj, 'a.b.c').value, 3); |
obj['a'] = toObservable({'b': 4}); |
- expect(observePath(obj, 'a.b.c').value, null); |
- expect(observePath(obj, 'a.b').value, 4); |
+ expect(new PathObserver(obj, 'a.b.c').value, null); |
+ expect(new PathObserver(obj, 'a.b').value, 4); |
}); |
- observeTest('set value at path', () { |
+ test('set value at path', () { |
var obj = toObservable({}); |
- observePath(obj, 'foo').value = 3; |
+ new PropertyPath('foo').setValueFrom(obj, 3); |
expect(obj['foo'], 3); |
var bar = toObservable({ 'baz': 3 }); |
- observePath(obj, 'bar').value = bar; |
+ new PropertyPath('bar').setValueFrom(obj, bar); |
expect(obj['bar'], bar); |
- observePath(obj, 'bar.baz.bat').value = 'not here'; |
- expect(observePath(obj, 'bar.baz.bat').value, null); |
+ new PropertyPath('bar.baz.bat').setValueFrom(obj, 'not here'); |
+ expect(new PathObserver(obj, 'bar.baz.bat').value, null); |
}); |
- observeTest('set value back to same', () { |
+ test('set value back to same', () { |
var obj = toObservable({}); |
- var path = observePath(obj, 'foo'); |
+ var path = new PathObserver(obj, 'foo'); |
var values = []; |
- path.changes.listen((_) { values.add(path.value); }); |
+ path.open((x) { |
+ expect(x, path.value, reason: 'callback should get current value'); |
+ values.add(x); |
+ }); |
path.value = 3; |
expect(obj['foo'], 3); |
expect(path.value, 3); |
- observePath(obj, 'foo').value = 2; |
- performMicrotaskCheckpoint(); |
- expect(path.value, 2); |
- expect(observePath(obj, 'foo').value, 2); |
+ new PropertyPath('foo').setValueFrom(obj, 2); |
+ return new Future(() { |
+ expect(path.value, 2); |
+ expect(new PathObserver(obj, 'foo').value, 2); |
- observePath(obj, 'foo').value = 3; |
- performMicrotaskCheckpoint(); |
- expect(path.value, 3); |
+ new PropertyPath('foo').setValueFrom(obj, 3); |
+ }).then(newMicrotask).then((_) { |
+ expect(path.value, 3); |
- performMicrotaskCheckpoint(); |
- expect(values, [2, 3]); |
+ }).then(newMicrotask).then((_) { |
+ expect(values, [2, 3]); |
+ }); |
}); |
- observeTest('Observe and Unobserve - Paths', () { |
+ test('Observe and Unobserve - Paths', () { |
var arr = toObservable({}); |
arr['foo'] = 'bar'; |
var fooValues = []; |
- var fooPath = observePath(arr, 'foo'); |
- var fooSub = fooPath.changes.listen((_) { |
- fooValues.add(fooPath.value); |
- }); |
+ var fooPath = new PathObserver(arr, 'foo'); |
+ fooPath.open(fooValues.add); |
arr['foo'] = 'baz'; |
arr['bat'] = 'bag'; |
var batValues = []; |
- var batPath = observePath(arr, 'bat'); |
- var batSub = batPath.changes.listen((_) { |
- batValues.add(batPath.value); |
+ var batPath = new PathObserver(arr, 'bat'); |
+ batPath.open(batValues.add); |
+ |
+ return new Future(() { |
+ expect(fooValues, ['baz']); |
+ expect(batValues, []); |
+ |
+ arr['foo'] = 'bar'; |
+ fooPath.close(); |
+ arr['bat'] = 'boo'; |
+ batPath.close(); |
+ arr['bat'] = 'boot'; |
+ |
+ }).then(newMicrotask).then((_) { |
+ expect(fooValues, ['baz']); |
+ expect(batValues, []); |
}); |
- |
- performMicrotaskCheckpoint(); |
- expect(fooValues, ['baz']); |
- expect(batValues, []); |
- |
- arr['foo'] = 'bar'; |
- fooSub.cancel(); |
- arr['bat'] = 'boo'; |
- batSub.cancel(); |
- arr['bat'] = 'boot'; |
- |
- performMicrotaskCheckpoint(); |
- expect(fooValues, ['baz']); |
- expect(batValues, []); |
}); |
- observeTest('Path Value With Indices', () { |
+ test('Path Value With Indices', () { |
var model = toObservable([]); |
- var path = observePath(model, '0'); |
- path.changes.listen(expectAsync1((_) { |
+ var path = new PathObserver(model, '0'); |
+ path.open(expectAsync1((x) { |
expect(path.value, 123); |
+ expect(x, 123); |
})); |
model.add(123); |
}); |
group('ObservableList', () { |
- observeTest('isNotEmpty', () { |
+ test('isNotEmpty', () { |
var model = new ObservableList(); |
- var path = observePath(model, 'isNotEmpty'); |
+ var path = new PathObserver(model, 'isNotEmpty'); |
expect(path.value, false); |
- var future = path.changes.first.then((_) { |
+ path.open(expectAsync1((_) { |
expect(path.value, true); |
- }); |
+ })); |
model.add(123); |
- |
- return future; |
}); |
- observeTest('isEmpty', () { |
+ test('isEmpty', () { |
var model = new ObservableList(); |
- var path = observePath(model, 'isEmpty'); |
+ var path = new PathObserver(model, 'isEmpty'); |
expect(path.value, true); |
- var future = path.changes.first.then((_) { |
+ path.open(expectAsync1((_) { |
expect(path.value, false); |
- }); |
+ })); |
model.add(123); |
- |
- return future; |
}); |
}); |
for (var createModel in [() => new TestModel(), () => new WatcherModel()]) { |
- observeTest('Path Observation - ${createModel().runtimeType}', () { |
+ test('Path Observation - ${createModel().runtimeType}', () { |
var model = createModel()..a = |
(createModel()..b = (createModel()..c = 'hello, world')); |
- var path = observePath(model, 'a.b.c'); |
+ var path = new PathObserver(model, 'a.b.c'); |
var lastValue = null; |
- var sub = path.changes.listen((_) { lastValue = path.value; }); |
+ path.open((x) { lastValue = x; }); |
model.a.b.c = 'hello, mom'; |
expect(lastValue, null); |
- performMicrotaskCheckpoint(); |
- expect(lastValue, 'hello, mom'); |
+ return new Future(() { |
+ expect(lastValue, 'hello, mom'); |
- model.a.b = createModel()..c = 'hello, dad'; |
- performMicrotaskCheckpoint(); |
- expect(lastValue, 'hello, dad'); |
+ model.a.b = createModel()..c = 'hello, dad'; |
+ }).then(newMicrotask).then((_) { |
+ expect(lastValue, 'hello, dad'); |
- model.a = createModel()..b = |
- (createModel()..c = 'hello, you'); |
- performMicrotaskCheckpoint(); |
- expect(lastValue, 'hello, you'); |
+ model.a = createModel()..b = |
+ (createModel()..c = 'hello, you'); |
+ }).then(newMicrotask).then((_) { |
+ expect(lastValue, 'hello, you'); |
- model.a.b = 1; |
- performMicrotaskCheckpoint(); |
- expect(lastValue, null); |
+ model.a.b = 1; |
+ }).then(newMicrotask).then((_) { |
+ expect(lastValue, null); |
- // Stop observing |
- sub.cancel(); |
+ // Stop observing |
+ path.close(); |
- model.a.b = createModel()..c = 'hello, back again -- but not observing'; |
- performMicrotaskCheckpoint(); |
- expect(lastValue, null); |
+ model.a.b = createModel()..c = 'hello, back again -- but not observing'; |
+ }).then(newMicrotask).then((_) { |
+ expect(lastValue, null); |
- // Resume observing |
- sub = path.changes.listen((_) { lastValue = path.value; }); |
+ // Resume observing |
+ new PathObserver(model, 'a.b.c').open((x) { lastValue = x; }); |
- model.a.b.c = 'hello. Back for reals'; |
- performMicrotaskCheckpoint(); |
- expect(lastValue, 'hello. Back for reals'); |
+ model.a.b.c = 'hello. Back for reals'; |
+ }).then(newMicrotask).then((_) { |
+ expect(lastValue, 'hello. Back for reals'); |
+ }); |
}); |
} |
- observeTest('observe map', () { |
+ test('observe map', () { |
var model = toObservable({'a': 1}); |
- var path = observePath(model, 'a'); |
+ var path = new PathObserver(model, 'a'); |
var values = [path.value]; |
- var sub = path.changes.listen((_) { values.add(path.value); }); |
+ path.open(values.add); |
expect(values, [1]); |
model['a'] = 2; |
- performMicrotaskCheckpoint(); |
- expect(values, [1, 2]); |
+ return new Future(() { |
+ expect(values, [1, 2]); |
- sub.cancel(); |
- model['a'] = 3; |
- performMicrotaskCheckpoint(); |
- expect(values, [1, 2]); |
+ path.close(); |
+ model['a'] = 3; |
+ }).then(newMicrotask).then((_) { |
+ expect(values, [1, 2]); |
+ }); |
}); |
- observeTest('errors thrown from getter/setter', () { |
+ test('errors thrown from getter/setter', () { |
var model = new ObjectWithErrors(); |
var observer = new PathObserver(model, 'foo'); |
@@ -253,7 +307,7 @@ observePathTests() { |
expect(model.setFooCalled, [123]); |
}); |
- observeTest('object with noSuchMethod', () { |
+ test('object with noSuchMethod', () { |
var model = new NoSuchMethodModel(); |
var observer = new PathObserver(model, 'foo'); |
@@ -271,7 +325,7 @@ observePathTests() { |
expect(observer.value, null, reason: 'path not found'); |
}); |
- observeTest('object with indexer', () { |
+ test('object with indexer', () { |
var model = new IndexerModel(); |
var observer = new PathObserver(model, 'foo'); |