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

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

Issue 19771010: implement dirty checking for @observable objects (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: logging for loops in dirty checking Created 7 years, 5 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 'package:observe/observe.dart'; 6 import 'package:observe/observe.dart';
6 import 'package:unittest/unittest.dart'; 7 import 'package:unittest/unittest.dart';
8 import 'observe_test_utils.dart';
7 9
8 // This file contains code ported from: 10 // This file contains code ported from:
9 // https://github.com/rafaelw/ChangeSummary/blob/master/tests/test.js 11 // https://github.com/rafaelw/ChangeSummary/blob/master/tests/test.js
10 12
11 main() { 13 main() {
12 group('PathObserver', observePathTests); 14 group('PathObserver', observePathTests);
13 } 15 }
14 16
15 observePath(obj, path) => new PathObserver(obj, path); 17 observePath(obj, path) => new PathObserver(obj, path);
16 18
17 sym(x) => new Symbol(x); 19 sym(x) => new Symbol(x);
18 20
19 toSymbolMap(Map map) { 21 toSymbolMap(Map map) {
20 var result = new ObservableMap.linked(); 22 var result = new ObservableMap.linked();
21 map.forEach((key, value) { 23 map.forEach((key, value) {
22 if (value is Map) value = toSymbolMap(value); 24 if (value is Map) value = toSymbolMap(value);
23 result[new Symbol(key)] = value; 25 result[new Symbol(key)] = value;
24 }); 26 });
25 return result; 27 return result;
26 } 28 }
27 29
28 observePathTests() { 30 observePathTests() {
29 31 observeTest('Degenerate Values', () {
30 test('Degenerate Values', () {
31 expect(observePath(null, '').value, null); 32 expect(observePath(null, '').value, null);
32 expect(observePath(123, '').value, 123); 33 expect(observePath(123, '').value, 123);
33 expect(observePath(123, 'foo.bar.baz').value, null); 34 expect(observePath(123, 'foo.bar.baz').value, null);
34 35
35 // shouldn't throw: 36 // shouldn't throw:
36 observePath(123, '').values.listen((_) {}).cancel(); 37 observePath(123, '').changes.listen((_) {}).cancel();
37 observePath(null, '').value = null; 38 observePath(null, '').value = null;
38 observePath(123, '').value = 42; 39 observePath(123, '').value = 42;
39 observePath(123, 'foo.bar.baz').value = 42; 40 observePath(123, 'foo.bar.baz').value = 42;
40 41
41 var foo = {}; 42 var foo = {};
42 expect(observePath(foo, '').value, foo); 43 expect(observePath(foo, '').value, foo);
43 44
44 foo = new Object(); 45 foo = new Object();
45 expect(observePath(foo, '').value, foo); 46 expect(observePath(foo, '').value, foo);
46 47
47 expect(observePath(foo, 'a/3!').value, null); 48 expect(observePath(foo, 'a/3!').value, null);
48 }); 49 });
49 50
50 test('get value at path ObservableBox', () { 51 observeTest('get value at path ObservableBox', () {
51 var obj = new ObservableBox(new ObservableBox(new ObservableBox(1))); 52 var obj = new ObservableBox(new ObservableBox(new ObservableBox(1)));
52 53
53 expect(observePath(obj, '').value, obj); 54 expect(observePath(obj, '').value, obj);
54 expect(observePath(obj, 'value').value, obj.value); 55 expect(observePath(obj, 'value').value, obj.value);
55 expect(observePath(obj, 'value.value').value, obj.value.value); 56 expect(observePath(obj, 'value.value').value, obj.value.value);
56 expect(observePath(obj, 'value.value.value').value, 1); 57 expect(observePath(obj, 'value.value.value').value, 1);
57 58
58 obj.value.value.value = 2; 59 obj.value.value.value = 2;
59 expect(observePath(obj, 'value.value.value').value, 2); 60 expect(observePath(obj, 'value.value.value').value, 2);
60 61
61 obj.value.value = new ObservableBox(3); 62 obj.value.value = new ObservableBox(3);
62 expect(observePath(obj, 'value.value.value').value, 3); 63 expect(observePath(obj, 'value.value.value').value, 3);
63 64
64 obj.value = new ObservableBox(4); 65 obj.value = new ObservableBox(4);
65 expect(observePath(obj, 'value.value.value').value, null); 66 expect(observePath(obj, 'value.value.value').value, null);
66 expect(observePath(obj, 'value.value').value, 4); 67 expect(observePath(obj, 'value.value').value, 4);
67 }); 68 });
68 69
69 70
70 test('get value at path ObservableMap', () { 71 observeTest('get value at path ObservableMap', () {
71 var obj = toSymbolMap({'a': {'b': {'c': 1}}}); 72 var obj = toSymbolMap({'a': {'b': {'c': 1}}});
72 73
73 expect(observePath(obj, '').value, obj); 74 expect(observePath(obj, '').value, obj);
74 expect(observePath(obj, 'a').value, obj[sym('a')]); 75 expect(observePath(obj, 'a').value, obj[sym('a')]);
75 expect(observePath(obj, 'a.b').value, obj[sym('a')][sym('b')]); 76 expect(observePath(obj, 'a.b').value, obj[sym('a')][sym('b')]);
76 expect(observePath(obj, 'a.b.c').value, 1); 77 expect(observePath(obj, 'a.b.c').value, 1);
77 78
78 obj[sym('a')][sym('b')][sym('c')] = 2; 79 obj[sym('a')][sym('b')][sym('c')] = 2;
79 expect(observePath(obj, 'a.b.c').value, 2); 80 expect(observePath(obj, 'a.b.c').value, 2);
80 81
81 obj[sym('a')][sym('b')] = toSymbolMap({'c': 3}); 82 obj[sym('a')][sym('b')] = toSymbolMap({'c': 3});
82 expect(observePath(obj, 'a.b.c').value, 3); 83 expect(observePath(obj, 'a.b.c').value, 3);
83 84
84 obj[sym('a')] = toSymbolMap({'b': 4}); 85 obj[sym('a')] = toSymbolMap({'b': 4});
85 expect(observePath(obj, 'a.b.c').value, null); 86 expect(observePath(obj, 'a.b.c').value, null);
86 expect(observePath(obj, 'a.b').value, 4); 87 expect(observePath(obj, 'a.b').value, 4);
87 }); 88 });
88 89
89 test('set value at path', () { 90 observeTest('set value at path', () {
90 var obj = toSymbolMap({}); 91 var obj = toSymbolMap({});
91 observePath(obj, 'foo').value = 3; 92 observePath(obj, 'foo').value = 3;
92 expect(obj[sym('foo')], 3); 93 expect(obj[sym('foo')], 3);
93 94
94 var bar = toSymbolMap({ 'baz': 3 }); 95 var bar = toSymbolMap({ 'baz': 3 });
95 observePath(obj, 'bar').value = bar; 96 observePath(obj, 'bar').value = bar;
96 expect(obj[sym('bar')], bar); 97 expect(obj[sym('bar')], bar);
97 98
98 observePath(obj, 'bar.baz.bat').value = 'not here'; 99 observePath(obj, 'bar.baz.bat').value = 'not here';
99 expect(observePath(obj, 'bar.baz.bat').value, null); 100 expect(observePath(obj, 'bar.baz.bat').value, null);
100 }); 101 });
101 102
102 test('set value back to same', () { 103 observeTest('set value back to same', () {
103 var obj = toSymbolMap({}); 104 var obj = toSymbolMap({});
104 var path = observePath(obj, 'foo'); 105 var path = observePath(obj, 'foo');
105 var values = []; 106 var values = [];
106 path.values.listen((v) { values.add(v); }); 107 path.changes.listen((_) { values.add(path.value); });
107 108
108 path.value = 3; 109 path.value = 3;
109 expect(obj[sym('foo')], 3); 110 expect(obj[sym('foo')], 3);
110 expect(path.value, 3); 111 expect(path.value, 3);
111 112
112 observePath(obj, 'foo').value = 2; 113 observePath(obj, 'foo').value = 2;
113 deliverChangeRecords(); 114 performMicrotaskCheckpoint();
114 expect(path.value, 2); 115 expect(path.value, 2);
115 expect(observePath(obj, 'foo').value, 2); 116 expect(observePath(obj, 'foo').value, 2);
116 117
117 observePath(obj, 'foo').value = 3; 118 observePath(obj, 'foo').value = 3;
118 deliverChangeRecords(); 119 performMicrotaskCheckpoint();
119 expect(path.value, 3); 120 expect(path.value, 3);
120 121
121 deliverChangeRecords(); 122 performMicrotaskCheckpoint();
122 expect(values, [2, 3]); 123 expect(values, [2, 3]);
123 }); 124 });
124 125
125 test('Observe and Unobserve - Paths', () { 126 observeTest('Observe and Unobserve - Paths', () {
126 var arr = toSymbolMap({}); 127 var arr = toSymbolMap({});
127 128
128 arr[sym('foo')] = 'bar'; 129 arr[sym('foo')] = 'bar';
129 var fooValues = []; 130 var fooValues = [];
130 var fooPath = observePath(arr, 'foo'); 131 var fooPath = observePath(arr, 'foo');
131 var fooSub = fooPath.values.listen((v) { 132 var fooSub = fooPath.changes.listen((_) {
132 fooValues.add(v); 133 fooValues.add(fooPath.value);
133 }); 134 });
134 arr[sym('foo')] = 'baz'; 135 arr[sym('foo')] = 'baz';
135 arr[sym('bat')] = 'bag'; 136 arr[sym('bat')] = 'bag';
136 var batValues = []; 137 var batValues = [];
137 var batPath = observePath(arr, 'bat'); 138 var batPath = observePath(arr, 'bat');
138 var batSub = batPath.values.listen((v) { 139 var batSub = batPath.changes.listen((_) {
139 batValues.add(v); 140 batValues.add(batPath.value);
140 }); 141 });
141 142
142 deliverChangeRecords(); 143 performMicrotaskCheckpoint();
143 expect(fooValues, ['baz']); 144 expect(fooValues, ['baz']);
144 expect(batValues, []); 145 expect(batValues, []);
145 146
146 arr[sym('foo')] = 'bar'; 147 arr[sym('foo')] = 'bar';
147 fooSub.cancel(); 148 fooSub.cancel();
148 arr[sym('bat')] = 'boo'; 149 arr[sym('bat')] = 'boo';
149 batSub.cancel(); 150 batSub.cancel();
150 arr[sym('bat')] = 'boot'; 151 arr[sym('bat')] = 'boot';
151 152
152 deliverChangeRecords(); 153 performMicrotaskCheckpoint();
153 expect(fooValues, ['baz']); 154 expect(fooValues, ['baz']);
154 expect(batValues, []); 155 expect(batValues, []);
155 }); 156 });
156 157
157 test('Path Value With Indices', () { 158 observeTest('Path Value With Indices', () {
158 var model = toObservable([]); 159 var model = toObservable([]);
159 observePath(model, '0').values.listen(expectAsync1((v) { 160 var path = observePath(model, '0');
160 expect(v, 123); 161 path.changes.listen(expectAsync1((_) {
162 expect(path.value, 123);
161 })); 163 }));
162 model.add(123); 164 model.add(123);
163 }); 165 });
164 166
165 test('Path Observation', () { 167 for (var createModel in [() => new TestModel(), () => new WatcherModel()]) {
166 var model = new TestModel()..a = 168 observeTest('Path Observation - ${createModel().runtimeType}', () {
167 (new TestModel()..b = (new TestModel()..c = 'hello, world')); 169 var model = createModel()..a =
170 (createModel()..b = (createModel()..c = 'hello, world'));
168 171
169 var path = observePath(model, 'a.b.c'); 172 var path = observePath(model, 'a.b.c');
170 var lastValue = null; 173 var lastValue = null;
171 var sub = path.values.listen((v) { lastValue = v; }); 174 var sub = path.changes.listen((_) { lastValue = path.value; });
172 175
173 model.a.b.c = 'hello, mom'; 176 model.a.b.c = 'hello, mom';
174 177
175 expect(lastValue, null); 178 expect(lastValue, null);
176 deliverChangeRecords(); 179 performMicrotaskCheckpoint();
177 expect(lastValue, 'hello, mom'); 180 expect(lastValue, 'hello, mom');
178 181
179 model.a.b = new TestModel()..c = 'hello, dad'; 182 model.a.b = createModel()..c = 'hello, dad';
180 deliverChangeRecords(); 183 performMicrotaskCheckpoint();
181 expect(lastValue, 'hello, dad'); 184 expect(lastValue, 'hello, dad');
182 185
183 model.a = new TestModel()..b = 186 model.a = createModel()..b =
184 (new TestModel()..c = 'hello, you'); 187 (createModel()..c = 'hello, you');
185 deliverChangeRecords(); 188 performMicrotaskCheckpoint();
186 expect(lastValue, 'hello, you'); 189 expect(lastValue, 'hello, you');
187 190
188 model.a.b = 1; 191 model.a.b = 1;
189 deliverChangeRecords(); 192 performMicrotaskCheckpoint();
190 expect(lastValue, null); 193 expect(lastValue, null);
191 194
192 // Stop observing 195 // Stop observing
193 sub.cancel(); 196 sub.cancel();
194 197
195 model.a.b = new TestModel()..c = 'hello, back again -- but not observing'; 198 model.a.b = createModel()..c = 'hello, back again -- but not observing';
196 deliverChangeRecords(); 199 performMicrotaskCheckpoint();
197 expect(lastValue, null); 200 expect(lastValue, null);
198 201
199 // Resume observing 202 // Resume observing
200 sub = path.values.listen((v) { lastValue = v; }); 203 sub = path.changes.listen((_) { lastValue = path.value; });
201 204
202 model.a.b.c = 'hello. Back for reals'; 205 model.a.b.c = 'hello. Back for reals';
203 deliverChangeRecords(); 206 performMicrotaskCheckpoint();
204 expect(lastValue, 'hello. Back for reals'); 207 expect(lastValue, 'hello. Back for reals');
205 }); 208 });
209 }
206 210
207 test('observe map', () { 211 observeTest('observe map', () {
208 var model = toSymbolMap({'a': 1}); 212 var model = toSymbolMap({'a': 1});
209 var path = observePath(model, 'a'); 213 var path = observePath(model, 'a');
210 214
211 var values = [path.value]; 215 var values = [path.value];
212 var sub = path.values.listen((v) { values.add(v); }); 216 var sub = path.changes.listen((_) { values.add(path.value); });
213 expect(values, [1]); 217 expect(values, [1]);
214 218
215 model[sym('a')] = 2; 219 model[sym('a')] = 2;
216 deliverChangeRecords(); 220 performMicrotaskCheckpoint();
217 expect(values, [1, 2]); 221 expect(values, [1, 2]);
218 222
219 sub.cancel(); 223 sub.cancel();
220 model[sym('a')] = 3; 224 model[sym('a')] = 3;
221 deliverChangeRecords(); 225 performMicrotaskCheckpoint();
222 expect(values, [1, 2]); 226 expect(values, [1, 2]);
223 }); 227 });
224 } 228 }
225 229
226 class TestModel extends ObservableBase { 230 class TestModel extends ChangeNotifierBase {
227 var _a, _b, _c; 231 var _a, _b, _c;
228 232
229 TestModel(); 233 TestModel();
230 234
231 get a => _a; 235 get a => _a;
232 236
233 void set a(newValue) { 237 void set a(newValue) {
234 _a = notifyPropertyChange(const Symbol('a'), _a, newValue); 238 _a = notifyPropertyChange(const Symbol('a'), _a, newValue);
235 } 239 }
236 240
237 get b => _b; 241 get b => _b;
238 242
239 void set b(newValue) { 243 void set b(newValue) {
240 _b = notifyPropertyChange(const Symbol('b'), _b, newValue); 244 _b = notifyPropertyChange(const Symbol('b'), _b, newValue);
241 } 245 }
242 246
243 get c => _c; 247 get c => _c;
244 248
245 void set c(newValue) { 249 void set c(newValue) {
246 _c = notifyPropertyChange(const Symbol('c'), _c, newValue); 250 _c = notifyPropertyChange(const Symbol('c'), _c, newValue);
247 } 251 }
248 } 252 }
253
254 class WatcherModel extends ObservableBase {
255 // TODO(jmesserly): dart2js does not let these be on the same line:
256 // @observable var a, b, c;
257 @observable var a;
258 @observable var b;
259 @observable var c;
260
261 WatcherModel();
262 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698