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

Side by Side Diff: test/observe_test.dart

Issue 12225039: Support for observable models, fixes #259 (Closed) Base URL: https://github.com/dart-lang/web-ui.git@master
Patch Set: Created 7 years, 10 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
OLDNEW
(Empty)
1 // Copyright (c) 2012, 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 /** Tests for some of the utility helper functions used by the compiler. */
6 library observe_test;
7
8 import 'dart:collection' show LinkedHashMap;
9 import 'package:unittest/compact_vm_config.dart';
10 import 'package:unittest/unittest.dart';
11 import 'package:web_ui/observe.dart';
12 import 'package:web_ui/src/utils.dart' show setImmediate;
13
14 main() {
15 useCompactVMConfiguration();
16
17 group('TestObservable', () {
18 test('no observers', () {
19 var t = new TestObservable<int>(123);
20 expect(t.value, 123);
21 expect(t.rawValue, 123);
22 t.value = 42;
23 expect(t.value, 42);
24 expect(t.rawValue, 42);
25 expect(t.observers, null);
26 });
27
28 test('observe', () {
29 var t = new TestObservable<int>(123);
30 int called = 0;
31 observe(() => t.value, expectAsync1((ChangeNotification n) {
32 expect(n.oldValue, 123);
33 expect(n.newValue, 42);
34 }));
35 t.value = 41;
36 t.value = 42;
37 expect(called, 0, reason: 'changes delived async');
Siggi Cherem (dart-lang) 2013/02/13 19:28:54 mmm. called is never updated?
Jennifer Messerly 2013/02/14 00:38:09 Done.
38 });
39
40 test('observe multiple changes', () {
41 var t = new TestObservable<int>(123);
42 observe(() => t.value, expectAsync1((ChangeNotification n) {
43 if (n.oldValue == 123) {
44 expect(n.newValue, 42);
45 // Cause another change
46 t.value = 777;
47 } else {
48 expect(n.oldValue, 42);
49 expect(n.newValue, 777);
50 }
51 }, count: 2));
52 t.value = 42;
53 });
54
55 test('multiple observers', () {
56 var t = new TestObservable<int>(123);
57 observe(() => t.value, expectAsync1((ChangeNotification n) {
58 expect(n.oldValue, 123);
59 expect(n.newValue, 42);
60 }));
61 observe(() => t.value + 1, expectAsync1((ChangeNotification n) {
62 expect(n.oldValue, 124);
63 expect(n.newValue, 43);
64 }));
65 t.value = 41;
66 t.value = 42;
67 });
68
69 test('deliverChangesSync', () {
70 var t = new TestObservable<int>(123);
71 var notifications = [];
72 observe(() => t.value, notifications.add);
73 t.value = 41;
74 t.value = 42;
75 expect(notifications, [], reason: 'changes delived async');
76
77 deliverChangesSync();
78 expect(notifications, [_change(123, 42)]);
79 t.value = 777;
80 expect(notifications.length, 1, reason: 'changes delived async');
81
82 deliverChangesSync();
83 expect(notifications, [_change(123, 42), _change(42, 777)]);
84
85 // Has no effect if there are no changes
86 deliverChangesSync();
87 expect(notifications, [_change(123, 42), _change(42, 777)]);
88 });
89
90 test('unobserve', () {
91 var t = new TestObservable<int>(123);
92 ChangeUnobserver unobserve;
93 unobserve = observe(() => t.value, expectAsync1((n) {
94 expect(n.oldValue, 123);
95 expect(n.newValue, 42);
96 unobserve();
97 t.value = 777;
98 }));
99 t.value = 42;
100 });
101
102 test('observers fired in order', () {
103 var t = new TestObservable<int>(123);
104 int expectOldValue = 123;
105 int expectNewValue = 42;
106 observe(() => t.value, expectAsync1((n) {
107 expect(n.oldValue, expectOldValue);
108 expect(n.newValue, expectNewValue);
109
110 // The second observer will see this change already, and only be called
111 // once. However we'll be called a second time.
112 t.value = 777;
113 expectNewValue = 777;
114 expectOldValue = 42;
115 }, count: 2));
116 int count = 0;
Siggi Cherem (dart-lang) 2013/02/13 19:28:54 unused variable
Jennifer Messerly 2013/02/14 00:38:09 Done.
117 observe(() => t.value + 1000, expectAsync1((n) {
118 expect(n.oldValue, 1123);
119 expect(n.newValue, 1777);
120 }));
121
122 // Make the initial change
123 t.value = 42;
124 });
125
126 test('unobserve one of two observers', () {
127 var t = new TestObservable<int>(123);
128 ChangeUnobserver unobserve;
129 unobserve = observe(() => t.value, expectAsync1((n) {
130 expect(n.oldValue, 123);
131 expect(n.newValue, 42);
132
133 // This will not affect the other observer, so it still gets the event.
134 unobserve();
135 setImmediate(() => t.value = 777);
136 }));
137 int count = 0;
138 observe(() => t.value + 1000, expectAsync1((n) {
139 if (++count == 1) {
140 expect(n.oldValue, 1123);
141 expect(n.newValue, 1042);
142 } else {
143 expect(n.oldValue, 1042);
144 expect(n.newValue, 1777);
145 }
146 }, count: 2));
147
148 // Make the initial change
149 t.value = 42;
150 });
151
152 test('notifyRead in getter', () {
153 var t = new TestObservable<int>(123);
154
155 observe(() {
156 expect(observeReads, true);
157 expect(t.observers, null);
158 return t.value;
159 }, (n) {});
160
161 expect(observeReads, false);
162 expect(t.observers, isNotNull);
163 });
164
165 test('notifyWrite in setter', () {
166 var t = new TestObservable<int>(123);
167 observe(() => t.value, (n) {});
168
169 t.value = 42;
170 expect(observeReads, false);
171 expect(t.observers, null);
172
173 // This will re-observe the expression.
174 deliverChangesSync();
175
176 expect(observeReads, false);
177 expect(t.observers, isNotNull);
178 });
179
180 test('observe conditional async', () {
181 var t = new TestObservable<bool>(false);
182 var a = new TestObservable<int>(123);
183 var b = new TestObservable<String>('hi');
184
185 int count = 0;
186 var oldValue = 'hi';
187 observe(() => t.value ? a.value : b.value, expectAsync1((n) {
188 expect(n.oldValue, oldValue);
189 oldValue = t.value ? a.value : b.value;
190 expect(n.newValue, oldValue);
191
192 switch (++count) {
193 case 1:
194 // We are observing "a", change it
195 a.value = 42;
196 break;
197 case 2:
198 // Switch to observing "b"
199 t.value = false;
200 break;
201 case 3:
202 // Change "a", this should have no effect and will not fire a 4th
203 // change event.
204 a.value = 777;
Siggi Cherem (dart-lang) 2013/02/13 19:28:54 it's redundant, but maybe add expect(a.observers,
Jennifer Messerly 2013/02/14 00:38:09 Added for "a.observers". Testing for "b" doesn't
205 break;
206 default:
207 // Should not be able to reach this because of the "count" argument
208 // to expectAsync1
209 throw new StateError('unreachable');
210 }
211 }, count: 3));
212
213 expect(t.observers, isNotNull);
214 expect(a.observers, null);
215 expect(b.observers, isNotNull);
216
217 // Start off by changing "t" to true.
218 t.value = true;
219 });
220
221 test('change limit set to null', () {
Siggi Cherem (dart-lang) 2013/02/13 19:28:54 '... to null (unbounded)'
Jennifer Messerly 2013/02/14 00:38:09 Done.
222 const BIG_LIMIT = 1000;
223 expect(circularNotifyLimit, lessThan(BIG_LIMIT));
224
225 int oldLimit = circularNotifyLimit;
226 circularNotifyLimit = null;
227 try {
228 var x = new TestObservable(false);
229 var y = new TestObservable(false);
230
231 int xCount = 0, yCount = 0;
232 int limit = BIG_LIMIT;
233 observe(() => x.value, (n) {
234 if (++xCount < limit) y.value = x.value;
235 });
236 observe(() => y.value, (n) {
237 if (++yCount < limit) x.value = !y.value;
238 });
239
240 // Kick off the cascading changes
241 x.value = true;
242
243 deliverChangesSync();
244
245 expect(xCount, limit);
246 expect(yCount, limit - 1);
247 } finally {
Siggi Cherem (dart-lang) 2013/02/13 19:28:54 alternatively, use setUp/tearTown?
Jennifer Messerly 2013/02/14 00:38:09 As far as I understand setUp is only for a group:
Siggi Cherem (dart-lang) 2013/02/14 00:59:25 sounds good
248 circularNotifyLimit = oldLimit;
249 }
250 });
251 });
252
253 group('ObservableReference', () {
254 test('observe conditional sync', () {
255 var t = new ObservableReference<bool>(false);
256 var a = new ObservableReference<int>(123);
257 var b = new ObservableReference<String>('hi');
258
259 var notifications = [];
260 observe(() => t.value ? a.value : b.value, notifications.add);
261
262 // Start off by changing "t" to true, so we evaluate "a".
263 t.value = true;
264 deliverChangesSync();
265
266 // This changes "a" which we should be observing.
267 a.value = 42;
268 deliverChangesSync();
269
270 // This has no effect because we aren't using "b" yet.
271 b.value = 'universe';
272 deliverChangesSync();
273
274 // Switch to use "b".
275 t.value = false;
276 deliverChangesSync();
277
278 // This has no effect because we aren't using "a" anymore.
279 a.value = 777;
280 deliverChangesSync();
281
282 expect(notifications, [
283 _change('hi', 123),
284 _change(123, 42),
285 _change(42, 'universe')]);
286 });
287 });
288
289
290 group('ObservableList', () {
291 // TODO(jmesserly): need all standard List tests.
292
293 test('observe length', () {
294 var list = new ObservableList();
295 var notification = null;
296 observe(() => list.length, (n) { notification = n; });
297
298 list.addAll([1, 2, 3]);
299 expect(list, [1, 2, 3]);
300 deliverChangesSync();
301 expect(notification, _change(0, 3), reason: 'addAll changes length');
302
303 list.add(4);
304 expect(list, [1, 2, 3, 4]);
305 deliverChangesSync();
306 expect(notification, _change(3, 4), reason: 'add changes length');
307
308 list.removeRange(1, 2);
309 expect(list, [1, 4]);
310 deliverChangesSync();
311 expect(notification, _change(4, 2), reason: 'removeRange changes length');
312
313 list.length = 5;
314 expect(list, [1, 4, null, null, null]);
315 deliverChangesSync();
316 expect(notification, _change(2, 5), reason: 'length= changes length');
317 notification = null;
318
319 list[2] = 9000;
320 expect(list, [1, 4, 9000, null, null]);
321 deliverChangesSync();
322 expect(notification, null, reason: '[]= does not change length');
323
324 list.clear();
325 expect(list, []);
326 deliverChangesSync();
327 expect(notification, _change(5, 0), reason: 'clear changes length');
328 });
329
330 test('observe index', () {
331 var list = new ObservableList.from([1, 2, 3]);
332 var notification = null;
333 observe(() => list[1], (n) { notification = n; });
334
335 list.add(4);
336 expect(list, [1, 2, 3, 4]);
337 deliverChangesSync();
338 expect(notification, null,
339 reason: 'add does not change existing items');
340
341 list[1] = 777;
342 expect(list, [1, 777, 3, 4]);
343 deliverChangesSync();
344 expect(notification, _change(2, 777));
345
346 notification = null;
347 list[2] = 9000;
348 expect(list, [1, 777, 9000, 4]);
349 deliverChangesSync();
350 expect(notification, null,
351 reason: 'setting a different index should not fire change');
352
353 list[1] = 44;
354 list[1] = 43;
355 list[1] = 42;
356 expect(list, [1, 42, 9000, 4]);
357 deliverChangesSync();
358 expect(notification, _change(777, 42));
359
360 notification = null;
361 list.length = 2;
362 expect(list, [1, 42]);
363 deliverChangesSync();
364 expect(notification, null,
365 reason: 'did not truncate the observed item');
366
367 list.length = 1; // truncate
368 list.add(2);
369 expect(list, [1, 2]);
370 deliverChangesSync();
371 expect(notification, _change(42, 2),
372 reason: 'item truncated and added back');
Siggi Cherem (dart-lang) 2013/02/13 19:28:54 maybe add one where it's truncated but not added b
Jennifer Messerly 2013/02/14 00:38:09 The problem is our list[1] will throw in that case
373 });
374
375 test('toString', () {
376 var list = new ObservableList.from([1, 2, 3]);
377 var notification = null;
378 observe(() => list.toString(), (n) { notification = n; });
379 list[2] = 4;
380 deliverChangesSync();
381 expect(notification, _change('[1, 2, 3]', '[1, 2, 4]'));
382 });
383 });
384
385
386 group('ObservableSet', () {
387 // TODO(jmesserly): need all standard Set tests.
388
389 test('observe length', () {
390 var set = new ObservableSet();
391 var notification = null;
392 observe(() => set.length, (n) { notification = n; });
393
394 set.addAll([1, 2, 3]);
395 expect(set, [1, 2, 3]);
396 deliverChangesSync();
397 expect(notification, _change(0, 3), reason: 'addAll changes length');
398
399 set.add(4);
400 expect(set, [1, 2, 3, 4]);
401 deliverChangesSync();
402 expect(notification, _change(3, 4), reason: 'add changes length');
403
404 set.removeAll([2, 3]);
405 expect(set, [1, 4]);
406 deliverChangesSync();
407 expect(notification, _change(4, 2), reason: 'removeAll changes length');
408
409 set.remove(1);
410 expect(set, [4]);
411 deliverChangesSync();
412 expect(notification, _change(2, 1), reason: 'remove changes length');
413
414 notification = null;
415 set.add(4);
416 expect(set, [4]);
417 deliverChangesSync();
418 expect(notification, null, reason: 'item already exists');
419
420 set.clear();
421 expect(set, []);
422 deliverChangesSync();
423 expect(notification, _change(1, 0), reason: 'clear changes length');
424 });
425
426 test('observe item', () {
427 var set = new ObservableSet.from([1, 2, 3]);
428 var notification = null;
429 observe(() => set.contains(2), (n) { notification = n; });
430
431 set.add(4);
432 expect(set, [1, 2, 3, 4]);
433 deliverChangesSync();
434 expect(notification, null, reason: 'add does not change existing items');
435
436 set.remove(3);
437 expect(set, [1, 2, 4]);
438 expect(notification, null,
439 reason: 'removing an item does not change other items');
440
441 set.remove(2);
442 expect(set, [1, 4]);
443 deliverChangesSync();
444 expect(notification, _change(true, false));
445
446 notification = null;
447 set.removeAll([2, 3]);
448 expect(set, [1, 4]);
449 deliverChangesSync();
450 expect(notification, null, reason: 'item already removed');
451
452 set.add(2);
453 expect(set, [1, 2, 4]);
454 deliverChangesSync();
455 expect(notification, _change(false, true), reason: 'item added again');
456 });
457
458 test('toString', () {
459 var original = new Set.from([1, 2, 3]);
460 var set = new ObservableSet.from(original);
461 var notification = null;
462 observe(() => set.toString(), (n) { notification = n; });
463 set.add(4);
464 deliverChangesSync();
465 var updated = new Set.from([1, 2, 3, 4]);
466
467 // Note: using Set.toString as the exectation, so the order is the same
468 // as with ObservableSet, regardless of how hashCode is implemented.
469 expect(notification, _change('$original', '$updated'));
470 });
471 });
472
473
474 group('ObservableMap', () {
475 // TODO(jmesserly): need all standard Map tests.
476
477 test('observe length', () {
478 var map = new ObservableMap();
479 var notification = null;
480 observe(() => map.length, (n) { notification = n; });
481
482 map['a'] = 1;
483 map.putIfAbsent('b', () => 2);
484 map['c'] = 3;
485 expect(map, {'a': 1, 'b': 2, 'c': 3});
486 deliverChangesSync();
487 expect(notification, _change(0, 3), reason: 'adding changes length');
488
489 map['d'] = 4;
490 expect(map, {'a': 1, 'b': 2, 'c': 3, 'd': 4});
491 deliverChangesSync();
492 expect(notification, _change(3, 4), reason: 'add changes length');
493
494 map.remove('b');
495 map.remove('c');
496 expect(map, {'a': 1, 'd': 4});
497 deliverChangesSync();
498 expect(notification, _change(4, 2), reason: 'removeRange changes length');
499
500 notification = null;
501 map['d'] = 9000;
502 expect(map, {'a': 1, 'd': 9000});
503 deliverChangesSync();
504 expect(notification, null, reason: 'update item does not change length');
505
506 map.clear();
507 expect(map, {});
508 deliverChangesSync();
509 expect(notification, _change(2, 0), reason: 'clear changes length');
510 });
511
512 test('observe index', () {
513 var map = new ObservableMap.from({'a': 1, 'b': 2, 'c': 3});
514 var notification = null;
515 observe(() => map['b'], (n) { notification = n; });
516
517 map.putIfAbsent('d', () => 4);
518 expect(map, {'a': 1, 'b': 2, 'c': 3, 'd': 4});
519 deliverChangesSync();
520 expect(notification, null, reason: 'add does not change existing items');
521
522 map['b'] = 777;
523 expect(map, {'a': 1, 'b': 777, 'c': 3, 'd': 4});
524 deliverChangesSync();
525 expect(notification, _change(2, 777));
526
527 notification = null;
528 map.putIfAbsent('b', () => 1234);
529 expect(map, {'a': 1, 'b': 777, 'c': 3, 'd': 4});
530 deliverChangesSync();
531 expect(notification, null, reason: 'item already there');
532
533 map['c'] = 9000;
534 expect(map, {'a': 1, 'b': 777, 'c': 9000, 'd': 4});
535 deliverChangesSync();
536 expect(notification, null, reason: 'setting a different item');
537
538 map['b'] = 44;
539 map['b'] = 43;
540 map['b'] = 42;
541 expect(map, {'a': 1, 'b': 42, 'c': 9000, 'd': 4});
542 deliverChangesSync();
543 expect(notification, _change(777, 42));
544
545 notification = null;
546 map.remove('a');
547 map.remove('d');
548 expect(map, {'b': 42, 'c': 9000});
549 deliverChangesSync();
550 expect(notification, null, reason: 'did not remove the observed item');
551
552 map.remove('b');
553 map['b'] = 2;
554 expect(map, {'b': 2, 'c': 9000});
555 deliverChangesSync();
556 expect(notification, _change(42, 2), reason: 'removed and added back');
557 });
Siggi Cherem (dart-lang) 2013/02/13 19:28:54 consider a couple test adding a new key with a 'nu
Jennifer Messerly 2013/02/14 00:38:09 I don't think Maps allow null keys: https://code.
Siggi Cherem (dart-lang) 2013/02/14 00:59:25 yeah, the one for value is what I wanted, thanks =
558
559 test('toString', () {
560 var map = new ObservableMap.from({'a': 1, 'b': 2},
561 createMap: () => new LinkedHashMap());
562
563 var notification = null;
564 observe(() => map.toString(), (n) { notification = n; });
565 map.remove('b');
566 map['c'] = 3;
567 deliverChangesSync();
568
569 expect(notification, _change('{a: 1, b: 2}', '{a: 1, c: 3}'));
570 });
571 });
572
573 }
574
575 _change(oldValue, newValue) => new ChangeNotification(oldValue, newValue);
576
577 /**
578 * This is similar to ObservableReference, but with fields public for testing.
579 */
580 class TestObservable<T> {
581 var observers;
582 T rawValue;
583
584 TestObservable([T initialValue]) : rawValue = initialValue;
585
586 T get value {
587 if (observeReads) observers = notifyRead(observers);
588 return rawValue;
589 }
590
591 void set value(T newValue) {
592 if (observers != null) observers = notifyWrite(observers);
593 rawValue = newValue;
594 }
595 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698