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 'package:observe/observe.dart'; | 6 import 'package:observe/observe.dart'; |
6 import 'package:unittest/unittest.dart'; | 7 import 'package:unittest/unittest.dart'; |
7 import 'observe_test_utils.dart'; | 8 import 'observe_test_utils.dart'; |
8 | 9 |
9 // This file contains code ported from: | 10 // This file contains code ported from: |
10 // https://github.com/rafaelw/ChangeSummary/blob/master/tests/test.js | 11 // https://github.com/rafaelw/ChangeSummary/blob/master/tests/test.js |
11 | 12 |
12 main() { | 13 main() => dirtyCheckZone().run(() { |
13 group('PathObserver', observePathTests); | 14 group('PathObserver', observePathTests); |
14 } | 15 |
15 | 16 group('PropertyPath', () { |
16 observePath(obj, path) => new PathObserver(obj, path); | 17 test('toString length', () { |
| 18 expectPath(p, str, len) { |
| 19 var path = new PropertyPath(p); |
| 20 expect(path.toString(), str); |
| 21 expect(path.length, len, reason: 'expected path length $len for $path'); |
| 22 } |
| 23 |
| 24 expectPath('/foo', '<invalid path>', 0); |
| 25 expectPath('abc', 'abc', 1); |
| 26 expectPath('a.b.c', 'a.b.c', 3); |
| 27 expectPath('a.b.c ', 'a.b.c', 3); |
| 28 expectPath(' a.b.c', 'a.b.c', 3); |
| 29 expectPath(' a.b.c ', 'a.b.c', 3); |
| 30 expectPath('1.abc', '1.abc', 2); |
| 31 expectPath([#qux], 'qux', 1); |
| 32 expectPath([1, #foo, #bar], '1.foo.bar', 3); |
| 33 }); |
| 34 |
| 35 test('caching and ==', () { |
| 36 var start = new PropertyPath('abc.0'); |
| 37 for (int i = 1; i <= 100; i++) { |
| 38 expect(identical(new PropertyPath('abc.0'), start), true, |
| 39 reason: 'should return identical path'); |
| 40 |
| 41 var p = new PropertyPath('abc.$i'); |
| 42 expect(identical(p, start), false, |
| 43 reason: 'different paths should not be merged'); |
| 44 } |
| 45 var end = new PropertyPath('abc.0'); |
| 46 expect(identical(end, start), false, |
| 47 reason: 'first entry expired'); |
| 48 expect(end, start, reason: 'different instances are equal'); |
| 49 }); |
| 50 |
| 51 test('hashCode equal', () { |
| 52 var a = new PropertyPath([#foo, 2, #bar]); |
| 53 var b = new PropertyPath('foo.2.bar'); |
| 54 expect(identical(a, b), false, reason: 'only strings cached'); |
| 55 expect(a, b, reason: 'same paths are equal'); |
| 56 expect(a.hashCode, b.hashCode, reason: 'equal hashCodes'); |
| 57 }); |
| 58 |
| 59 test('hashCode not equal', () { |
| 60 expect(2.hashCode, isNot(3.hashCode), |
| 61 reason: 'test depends on 2 and 3 having different hashcodes'); |
| 62 |
| 63 var a = new PropertyPath([2]); |
| 64 var b = new PropertyPath([3]); |
| 65 expect(a, isNot(b), reason: 'different paths'); |
| 66 expect(a.hashCode, isNot(b.hashCode), reason: 'different hashCodes'); |
| 67 }); |
| 68 }); |
| 69 }); |
| 70 |
17 | 71 |
18 observePathTests() { | 72 observePathTests() { |
19 observeTest('Degenerate Values', () { | 73 test('Degenerate Values', () { |
20 expect(observePath(null, '').value, null); | 74 expect(new PathObserver(null, '').value, null); |
21 expect(observePath(123, '').value, 123); | 75 expect(new PathObserver(123, '').value, 123); |
22 expect(observePath(123, 'foo.bar.baz').value, null); | 76 expect(new PathObserver(123, 'foo.bar.baz').value, null); |
23 | 77 |
24 // shouldn't throw: | 78 // shouldn't throw: |
25 observePath(123, '').changes.listen((_) {}).cancel(); | 79 new PathObserver(123, '')..open((_) {})..close(); |
26 observePath(null, '').value = null; | 80 new PropertyPath('').setValueFrom(null, null); |
27 observePath(123, '').value = 42; | 81 new PropertyPath('').setValueFrom(123, 42); |
28 observePath(123, 'foo.bar.baz').value = 42; | 82 new PropertyPath('foo.bar.baz').setValueFrom(123, 42); |
29 | 83 |
30 var foo = {}; | 84 var foo = {}; |
31 expect(observePath(foo, '').value, foo); | 85 expect(new PathObserver(foo, '').value, foo); |
32 | 86 |
33 foo = new Object(); | 87 foo = new Object(); |
34 expect(observePath(foo, '').value, foo); | 88 expect(new PathObserver(foo, '').value, foo); |
35 | 89 |
36 expect(observePath(foo, 'a/3!').value, null); | 90 expect(new PathObserver(foo, 'a/3!').value, null); |
37 }); | 91 }); |
38 | 92 |
39 observeTest('get value at path ObservableBox', () { | 93 test('get value at path ObservableBox', () { |
40 var obj = new ObservableBox(new ObservableBox(new ObservableBox(1))); | 94 var obj = new ObservableBox(new ObservableBox(new ObservableBox(1))); |
41 | 95 |
42 expect(observePath(obj, '').value, obj); | 96 expect(new PathObserver(obj, '').value, obj); |
43 expect(observePath(obj, 'value').value, obj.value); | 97 expect(new PathObserver(obj, 'value').value, obj.value); |
44 expect(observePath(obj, 'value.value').value, obj.value.value); | 98 expect(new PathObserver(obj, 'value.value').value, obj.value.value); |
45 expect(observePath(obj, 'value.value.value').value, 1); | 99 expect(new PathObserver(obj, 'value.value.value').value, 1); |
46 | 100 |
47 obj.value.value.value = 2; | 101 obj.value.value.value = 2; |
48 expect(observePath(obj, 'value.value.value').value, 2); | 102 expect(new PathObserver(obj, 'value.value.value').value, 2); |
49 | 103 |
50 obj.value.value = new ObservableBox(3); | 104 obj.value.value = new ObservableBox(3); |
51 expect(observePath(obj, 'value.value.value').value, 3); | 105 expect(new PathObserver(obj, 'value.value.value').value, 3); |
52 | 106 |
53 obj.value = new ObservableBox(4); | 107 obj.value = new ObservableBox(4); |
54 expect(observePath(obj, 'value.value.value').value, null); | 108 expect(new PathObserver(obj, 'value.value.value').value, null); |
55 expect(observePath(obj, 'value.value').value, 4); | 109 expect(new PathObserver(obj, 'value.value').value, 4); |
56 }); | 110 }); |
57 | 111 |
58 | 112 |
59 observeTest('get value at path ObservableMap', () { | 113 test('get value at path ObservableMap', () { |
60 var obj = toObservable({'a': {'b': {'c': 1}}}); | 114 var obj = toObservable({'a': {'b': {'c': 1}}}); |
61 | 115 |
62 expect(observePath(obj, '').value, obj); | 116 expect(new PathObserver(obj, '').value, obj); |
63 expect(observePath(obj, 'a').value, obj['a']); | 117 expect(new PathObserver(obj, 'a').value, obj['a']); |
64 expect(observePath(obj, 'a.b').value, obj['a']['b']); | 118 expect(new PathObserver(obj, 'a.b').value, obj['a']['b']); |
65 expect(observePath(obj, 'a.b.c').value, 1); | 119 expect(new PathObserver(obj, 'a.b.c').value, 1); |
66 | 120 |
67 obj['a']['b']['c'] = 2; | 121 obj['a']['b']['c'] = 2; |
68 expect(observePath(obj, 'a.b.c').value, 2); | 122 expect(new PathObserver(obj, 'a.b.c').value, 2); |
69 | 123 |
70 obj['a']['b'] = toObservable({'c': 3}); | 124 obj['a']['b'] = toObservable({'c': 3}); |
71 expect(observePath(obj, 'a.b.c').value, 3); | 125 expect(new PathObserver(obj, 'a.b.c').value, 3); |
72 | 126 |
73 obj['a'] = toObservable({'b': 4}); | 127 obj['a'] = toObservable({'b': 4}); |
74 expect(observePath(obj, 'a.b.c').value, null); | 128 expect(new PathObserver(obj, 'a.b.c').value, null); |
75 expect(observePath(obj, 'a.b').value, 4); | 129 expect(new PathObserver(obj, 'a.b').value, 4); |
76 }); | 130 }); |
77 | 131 |
78 observeTest('set value at path', () { | 132 test('set value at path', () { |
79 var obj = toObservable({}); | 133 var obj = toObservable({}); |
80 observePath(obj, 'foo').value = 3; | 134 new PropertyPath('foo').setValueFrom(obj, 3); |
81 expect(obj['foo'], 3); | 135 expect(obj['foo'], 3); |
82 | 136 |
83 var bar = toObservable({ 'baz': 3 }); | 137 var bar = toObservable({ 'baz': 3 }); |
84 observePath(obj, 'bar').value = bar; | 138 new PropertyPath('bar').setValueFrom(obj, bar); |
85 expect(obj['bar'], bar); | 139 expect(obj['bar'], bar); |
86 | 140 |
87 observePath(obj, 'bar.baz.bat').value = 'not here'; | 141 new PropertyPath('bar.baz.bat').setValueFrom(obj, 'not here'); |
88 expect(observePath(obj, 'bar.baz.bat').value, null); | 142 expect(new PathObserver(obj, 'bar.baz.bat').value, null); |
89 }); | 143 }); |
90 | 144 |
91 observeTest('set value back to same', () { | 145 test('set value back to same', () { |
92 var obj = toObservable({}); | 146 var obj = toObservable({}); |
93 var path = observePath(obj, 'foo'); | 147 var path = new PathObserver(obj, 'foo'); |
94 var values = []; | 148 var values = []; |
95 path.changes.listen((_) { values.add(path.value); }); | 149 path.open((x) { |
| 150 expect(x, path.value, reason: 'callback should get current value'); |
| 151 values.add(x); |
| 152 }); |
96 | 153 |
97 path.value = 3; | 154 path.value = 3; |
98 expect(obj['foo'], 3); | 155 expect(obj['foo'], 3); |
99 expect(path.value, 3); | 156 expect(path.value, 3); |
100 | 157 |
101 observePath(obj, 'foo').value = 2; | 158 new PropertyPath('foo').setValueFrom(obj, 2); |
102 performMicrotaskCheckpoint(); | 159 return new Future(() { |
103 expect(path.value, 2); | 160 expect(path.value, 2); |
104 expect(observePath(obj, 'foo').value, 2); | 161 expect(new PathObserver(obj, 'foo').value, 2); |
105 | 162 |
106 observePath(obj, 'foo').value = 3; | 163 new PropertyPath('foo').setValueFrom(obj, 3); |
107 performMicrotaskCheckpoint(); | 164 }).then(newMicrotask).then((_) { |
108 expect(path.value, 3); | 165 expect(path.value, 3); |
109 | 166 |
110 performMicrotaskCheckpoint(); | 167 }).then(newMicrotask).then((_) { |
111 expect(values, [2, 3]); | 168 expect(values, [2, 3]); |
112 }); | 169 }); |
113 | 170 }); |
114 observeTest('Observe and Unobserve - Paths', () { | 171 |
| 172 test('Observe and Unobserve - Paths', () { |
115 var arr = toObservable({}); | 173 var arr = toObservable({}); |
116 | 174 |
117 arr['foo'] = 'bar'; | 175 arr['foo'] = 'bar'; |
118 var fooValues = []; | 176 var fooValues = []; |
119 var fooPath = observePath(arr, 'foo'); | 177 var fooPath = new PathObserver(arr, 'foo'); |
120 var fooSub = fooPath.changes.listen((_) { | 178 fooPath.open(fooValues.add); |
121 fooValues.add(fooPath.value); | |
122 }); | |
123 arr['foo'] = 'baz'; | 179 arr['foo'] = 'baz'; |
124 arr['bat'] = 'bag'; | 180 arr['bat'] = 'bag'; |
125 var batValues = []; | 181 var batValues = []; |
126 var batPath = observePath(arr, 'bat'); | 182 var batPath = new PathObserver(arr, 'bat'); |
127 var batSub = batPath.changes.listen((_) { | 183 batPath.open(batValues.add); |
128 batValues.add(batPath.value); | 184 |
129 }); | 185 return new Future(() { |
130 | 186 expect(fooValues, ['baz']); |
131 performMicrotaskCheckpoint(); | 187 expect(batValues, []); |
132 expect(fooValues, ['baz']); | 188 |
133 expect(batValues, []); | 189 arr['foo'] = 'bar'; |
134 | 190 fooPath.close(); |
135 arr['foo'] = 'bar'; | 191 arr['bat'] = 'boo'; |
136 fooSub.cancel(); | 192 batPath.close(); |
137 arr['bat'] = 'boo'; | 193 arr['bat'] = 'boot'; |
138 batSub.cancel(); | 194 |
139 arr['bat'] = 'boot'; | 195 }).then(newMicrotask).then((_) { |
140 | 196 expect(fooValues, ['baz']); |
141 performMicrotaskCheckpoint(); | 197 expect(batValues, []); |
142 expect(fooValues, ['baz']); | 198 }); |
143 expect(batValues, []); | 199 }); |
144 }); | 200 |
145 | 201 test('Path Value With Indices', () { |
146 observeTest('Path Value With Indices', () { | |
147 var model = toObservable([]); | 202 var model = toObservable([]); |
148 var path = observePath(model, '0'); | 203 var path = new PathObserver(model, '0'); |
149 path.changes.listen(expectAsync1((_) { | 204 path.open(expectAsync1((x) { |
150 expect(path.value, 123); | 205 expect(path.value, 123); |
| 206 expect(x, 123); |
151 })); | 207 })); |
152 model.add(123); | 208 model.add(123); |
153 }); | 209 }); |
154 | 210 |
155 group('ObservableList', () { | 211 group('ObservableList', () { |
156 observeTest('isNotEmpty', () { | 212 test('isNotEmpty', () { |
157 var model = new ObservableList(); | 213 var model = new ObservableList(); |
158 var path = observePath(model, 'isNotEmpty'); | 214 var path = new PathObserver(model, 'isNotEmpty'); |
159 expect(path.value, false); | 215 expect(path.value, false); |
160 | 216 |
161 var future = path.changes.first.then((_) { | 217 path.open(expectAsync1((_) { |
162 expect(path.value, true); | 218 expect(path.value, true); |
163 }); | 219 })); |
164 model.add(123); | 220 model.add(123); |
165 | 221 }); |
166 return future; | 222 |
167 }); | 223 test('isEmpty', () { |
168 | |
169 observeTest('isEmpty', () { | |
170 var model = new ObservableList(); | 224 var model = new ObservableList(); |
171 var path = observePath(model, 'isEmpty'); | 225 var path = new PathObserver(model, 'isEmpty'); |
172 expect(path.value, true); | 226 expect(path.value, true); |
173 | 227 |
174 var future = path.changes.first.then((_) { | 228 path.open(expectAsync1((_) { |
175 expect(path.value, false); | 229 expect(path.value, false); |
176 }); | 230 })); |
177 model.add(123); | 231 model.add(123); |
178 | |
179 return future; | |
180 }); | 232 }); |
181 }); | 233 }); |
182 | 234 |
183 for (var createModel in [() => new TestModel(), () => new WatcherModel()]) { | 235 for (var createModel in [() => new TestModel(), () => new WatcherModel()]) { |
184 observeTest('Path Observation - ${createModel().runtimeType}', () { | 236 test('Path Observation - ${createModel().runtimeType}', () { |
185 var model = createModel()..a = | 237 var model = createModel()..a = |
186 (createModel()..b = (createModel()..c = 'hello, world')); | 238 (createModel()..b = (createModel()..c = 'hello, world')); |
187 | 239 |
188 var path = observePath(model, 'a.b.c'); | 240 var path = new PathObserver(model, 'a.b.c'); |
189 var lastValue = null; | 241 var lastValue = null; |
190 var sub = path.changes.listen((_) { lastValue = path.value; }); | 242 path.open((x) { lastValue = x; }); |
191 | 243 |
192 model.a.b.c = 'hello, mom'; | 244 model.a.b.c = 'hello, mom'; |
193 | 245 |
194 expect(lastValue, null); | 246 expect(lastValue, null); |
195 performMicrotaskCheckpoint(); | 247 return new Future(() { |
196 expect(lastValue, 'hello, mom'); | 248 expect(lastValue, 'hello, mom'); |
197 | 249 |
198 model.a.b = createModel()..c = 'hello, dad'; | 250 model.a.b = createModel()..c = 'hello, dad'; |
199 performMicrotaskCheckpoint(); | 251 }).then(newMicrotask).then((_) { |
200 expect(lastValue, 'hello, dad'); | 252 expect(lastValue, 'hello, dad'); |
201 | 253 |
202 model.a = createModel()..b = | 254 model.a = createModel()..b = |
203 (createModel()..c = 'hello, you'); | 255 (createModel()..c = 'hello, you'); |
204 performMicrotaskCheckpoint(); | 256 }).then(newMicrotask).then((_) { |
205 expect(lastValue, 'hello, you'); | 257 expect(lastValue, 'hello, you'); |
206 | 258 |
207 model.a.b = 1; | 259 model.a.b = 1; |
208 performMicrotaskCheckpoint(); | 260 }).then(newMicrotask).then((_) { |
209 expect(lastValue, null); | 261 expect(lastValue, null); |
210 | 262 |
211 // Stop observing | 263 // Stop observing |
212 sub.cancel(); | 264 path.close(); |
213 | 265 |
214 model.a.b = createModel()..c = 'hello, back again -- but not observing'; | 266 model.a.b = createModel()..c = 'hello, back again -- but not observing'; |
215 performMicrotaskCheckpoint(); | 267 }).then(newMicrotask).then((_) { |
216 expect(lastValue, null); | 268 expect(lastValue, null); |
217 | 269 |
218 // Resume observing | 270 // Resume observing |
219 sub = path.changes.listen((_) { lastValue = path.value; }); | 271 new PathObserver(model, 'a.b.c').open((x) { lastValue = x; }); |
220 | 272 |
221 model.a.b.c = 'hello. Back for reals'; | 273 model.a.b.c = 'hello. Back for reals'; |
222 performMicrotaskCheckpoint(); | 274 }).then(newMicrotask).then((_) { |
223 expect(lastValue, 'hello. Back for reals'); | 275 expect(lastValue, 'hello. Back for reals'); |
| 276 }); |
224 }); | 277 }); |
225 } | 278 } |
226 | 279 |
227 observeTest('observe map', () { | 280 test('observe map', () { |
228 var model = toObservable({'a': 1}); | 281 var model = toObservable({'a': 1}); |
229 var path = observePath(model, 'a'); | 282 var path = new PathObserver(model, 'a'); |
230 | 283 |
231 var values = [path.value]; | 284 var values = [path.value]; |
232 var sub = path.changes.listen((_) { values.add(path.value); }); | 285 path.open(values.add); |
233 expect(values, [1]); | 286 expect(values, [1]); |
234 | 287 |
235 model['a'] = 2; | 288 model['a'] = 2; |
236 performMicrotaskCheckpoint(); | 289 return new Future(() { |
237 expect(values, [1, 2]); | 290 expect(values, [1, 2]); |
238 | 291 |
239 sub.cancel(); | 292 path.close(); |
240 model['a'] = 3; | 293 model['a'] = 3; |
241 performMicrotaskCheckpoint(); | 294 }).then(newMicrotask).then((_) { |
242 expect(values, [1, 2]); | 295 expect(values, [1, 2]); |
243 }); | 296 }); |
244 | 297 }); |
245 observeTest('errors thrown from getter/setter', () { | 298 |
| 299 test('errors thrown from getter/setter', () { |
246 var model = new ObjectWithErrors(); | 300 var model = new ObjectWithErrors(); |
247 var observer = new PathObserver(model, 'foo'); | 301 var observer = new PathObserver(model, 'foo'); |
248 | 302 |
249 expect(() => observer.value, throws); | 303 expect(() => observer.value, throws); |
250 expect(model.getFooCalled, 1); | 304 expect(model.getFooCalled, 1); |
251 | 305 |
252 expect(() { observer.value = 123; }, throws); | 306 expect(() { observer.value = 123; }, throws); |
253 expect(model.setFooCalled, [123]); | 307 expect(model.setFooCalled, [123]); |
254 }); | 308 }); |
255 | 309 |
256 observeTest('object with noSuchMethod', () { | 310 test('object with noSuchMethod', () { |
257 var model = new NoSuchMethodModel(); | 311 var model = new NoSuchMethodModel(); |
258 var observer = new PathObserver(model, 'foo'); | 312 var observer = new PathObserver(model, 'foo'); |
259 | 313 |
260 expect(observer.value, 42); | 314 expect(observer.value, 42); |
261 observer.value = 'hi'; | 315 observer.value = 'hi'; |
262 expect(model._foo, 'hi'); | 316 expect(model._foo, 'hi'); |
263 expect(observer.value, 'hi'); | 317 expect(observer.value, 'hi'); |
264 | 318 |
265 expect(model.log, [#foo, const Symbol('foo='), #foo]); | 319 expect(model.log, [#foo, const Symbol('foo='), #foo]); |
266 | 320 |
267 // These shouldn't throw | 321 // These shouldn't throw |
268 observer = new PathObserver(model, 'bar'); | 322 observer = new PathObserver(model, 'bar'); |
269 expect(observer.value, null, reason: 'path not found'); | 323 expect(observer.value, null, reason: 'path not found'); |
270 observer.value = 42; | 324 observer.value = 42; |
271 expect(observer.value, null, reason: 'path not found'); | 325 expect(observer.value, null, reason: 'path not found'); |
272 }); | 326 }); |
273 | 327 |
274 observeTest('object with indexer', () { | 328 test('object with indexer', () { |
275 var model = new IndexerModel(); | 329 var model = new IndexerModel(); |
276 var observer = new PathObserver(model, 'foo'); | 330 var observer = new PathObserver(model, 'foo'); |
277 | 331 |
278 expect(observer.value, 42); | 332 expect(observer.value, 42); |
279 expect(model.log, ['[] foo']); | 333 expect(model.log, ['[] foo']); |
280 model.log.clear(); | 334 model.log.clear(); |
281 | 335 |
282 observer.value = 'hi'; | 336 observer.value = 'hi'; |
283 expect(model.log, ['[]= foo hi']); | 337 expect(model.log, ['[]= foo hi']); |
284 expect(model._foo, 'hi'); | 338 expect(model._foo, 'hi'); |
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
371 | 425 |
372 class WatcherModel extends Observable { | 426 class WatcherModel extends Observable { |
373 // 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: |
374 // @observable var a, b, c; | 428 // @observable var a, b, c; |
375 @observable var a; | 429 @observable var a; |
376 @observable var b; | 430 @observable var b; |
377 @observable var c; | 431 @observable var c; |
378 | 432 |
379 WatcherModel(); | 433 WatcherModel(); |
380 } | 434 } |
OLD | NEW |