OLD | NEW |
---|---|
(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 } | |
OLD | NEW |