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

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

Powered by Google App Engine
This is Rietveld 408576698