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

Unified 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 side-by-side diff with in-line comments
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 »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: test/observe_test.dart
diff --git a/test/observe_test.dart b/test/observe_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..77c602ffd72de113ce8044a625fdbaac079ed456
--- /dev/null
+++ b/test/observe_test.dart
@@ -0,0 +1,611 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/** Tests for some of the utility helper functions used by the compiler. */
+library observe_test;
+
+import 'dart:collection' show LinkedHashMap;
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+import 'package:web_ui/observe.dart';
+import 'package:web_ui/src/utils.dart' show setImmediate;
+
+main() {
+ useCompactVMConfiguration();
+
+ group('TestObservable', () {
+ test('no observers', () {
+ var t = new TestObservable<int>(123);
+ expect(t.value, 123);
+ expect(t.rawValue, 123);
+ t.value = 42;
+ expect(t.value, 42);
+ expect(t.rawValue, 42);
+ expect(t.observers, null);
+ });
+
+ test('observe', () {
+ var t = new TestObservable<int>(123);
+ int called = 0;
+ observe(() => t.value, expectAsync1((ChangeNotification n) {
+ called++;
+ expect(n.oldValue, 123);
+ expect(n.newValue, 42);
+ }));
+ t.value = 41;
+ t.value = 42;
+ expect(called, 0, reason: 'changes delived async');
+ });
+
+ test('observe multiple changes', () {
+ var t = new TestObservable<int>(123);
+ observe(() => t.value, expectAsync1((ChangeNotification n) {
+ if (n.oldValue == 123) {
+ expect(n.newValue, 42);
+ // Cause another change
+ t.value = 777;
+ } else {
+ expect(n.oldValue, 42);
+ expect(n.newValue, 777);
+ }
+ }, count: 2));
+ t.value = 42;
+ });
+
+ test('multiple observers', () {
+ var t = new TestObservable<int>(123);
+ observe(() => t.value, expectAsync1((ChangeNotification n) {
+ expect(n.oldValue, 123);
+ expect(n.newValue, 42);
+ }));
+ observe(() => t.value + 1, expectAsync1((ChangeNotification n) {
+ expect(n.oldValue, 124);
+ expect(n.newValue, 43);
+ }));
+ t.value = 41;
+ t.value = 42;
+ });
+
+ test('deliverChangesSync', () {
+ var t = new TestObservable<int>(123);
+ var notifications = [];
+ observe(() => t.value, notifications.add);
+ t.value = 41;
+ t.value = 42;
+ expect(notifications, [], reason: 'changes delived async');
+
+ deliverChangesSync();
+ expect(notifications, [_change(123, 42)]);
+ t.value = 777;
+ expect(notifications.length, 1, reason: 'changes delived async');
+
+ deliverChangesSync();
+ expect(notifications, [_change(123, 42), _change(42, 777)]);
+
+ // Has no effect if there are no changes
+ deliverChangesSync();
+ expect(notifications, [_change(123, 42), _change(42, 777)]);
+ });
+
+ test('unobserve', () {
+ var t = new TestObservable<int>(123);
+ ChangeUnobserver unobserve;
+ unobserve = observe(() => t.value, expectAsync1((n) {
+ expect(n.oldValue, 123);
+ expect(n.newValue, 42);
+ unobserve();
+ t.value = 777;
+ }));
+ t.value = 42;
+ });
+
+ test('observers fired in order', () {
+ var t = new TestObservable<int>(123);
+ int expectOldValue = 123;
+ int expectNewValue = 42;
+ observe(() => t.value, expectAsync1((n) {
+ expect(n.oldValue, expectOldValue);
+ expect(n.newValue, expectNewValue);
+
+ // The second observer will see this change already, and only be called
+ // once. However we'll be called a second time.
+ t.value = 777;
+ expectNewValue = 777;
+ expectOldValue = 42;
+ }, count: 2));
+
+ observe(() => t.value + 1000, expectAsync1((n) {
+ expect(n.oldValue, 1123);
+ expect(n.newValue, 1777);
+ }));
+
+ // Make the initial change
+ t.value = 42;
+ });
+
+ test('unobserve one of two observers', () {
+ var t = new TestObservable<int>(123);
+ ChangeUnobserver unobserve;
+ unobserve = observe(() => t.value, expectAsync1((n) {
+ expect(n.oldValue, 123);
+ expect(n.newValue, 42);
+
+ // This will not affect the other observer, so it still gets the event.
+ unobserve();
+ setImmediate(() => t.value = 777);
+ }));
+ int count = 0;
+ observe(() => t.value + 1000, expectAsync1((n) {
+ if (++count == 1) {
+ expect(n.oldValue, 1123);
+ expect(n.newValue, 1042);
+ } else {
+ expect(n.oldValue, 1042);
+ expect(n.newValue, 1777);
+ }
+ }, count: 2));
+
+ // Make the initial change
+ t.value = 42;
+ });
+
+ test('notifyRead in getter', () {
+ var t = new TestObservable<int>(123);
+
+ observe(() {
+ expect(observeReads, true);
+ expect(t.observers, null);
+ return t.value;
+ }, (n) {});
+
+ expect(observeReads, false);
+ expect(t.observers, isNotNull);
+ });
+
+ test('notifyWrite in setter', () {
+ var t = new TestObservable<int>(123);
+ observe(() => t.value, (n) {});
+
+ t.value = 42;
+ expect(observeReads, false);
+ expect(t.observers, null);
+
+ // This will re-observe the expression.
+ deliverChangesSync();
+
+ expect(observeReads, false);
+ expect(t.observers, isNotNull);
+ });
+
+ test('observe conditional async', () {
+ var t = new TestObservable<bool>(false);
+ var a = new TestObservable<int>(123);
+ var b = new TestObservable<String>('hi');
+
+ int count = 0;
+ var oldValue = 'hi';
+ observe(() => t.value ? a.value : b.value, expectAsync1((n) {
+ expect(n.oldValue, oldValue);
+ oldValue = t.value ? a.value : b.value;
+ expect(n.newValue, oldValue);
+
+ switch (++count) {
+ case 1:
+ // We are observing "a", change it
+ a.value = 42;
+ break;
+ case 2:
+ // Switch to observing "b"
+ t.value = false;
+ break;
+ case 3:
+ // Change "a", this should have no effect and will not fire a 4th
+ // change event.
+ a.value = 777;
+ expect(a.observers, null);
+ expect(b.observers, isNotNull);
+ break;
+ default:
+ // Should not be able to reach this because of the "count" argument
+ // to expectAsync1
+ throw new StateError('unreachable');
+ }
+ }, count: 3));
+
+ expect(t.observers, isNotNull);
+ expect(a.observers, null);
+ expect(b.observers, isNotNull);
+
+ // Start off by changing "t" to true.
+ t.value = true;
+ });
+
+ test('change limit set to null (unbounded)', () {
+ const BIG_LIMIT = 1000;
+ expect(circularNotifyLimit, lessThan(BIG_LIMIT));
+
+ int oldLimit = circularNotifyLimit;
+ circularNotifyLimit = null;
+ try {
+ var x = new TestObservable(false);
+ var y = new TestObservable(false);
+
+ int xCount = 0, yCount = 0;
+ int limit = BIG_LIMIT;
+ observe(() => x.value, (n) {
+ if (++xCount < limit) y.value = x.value;
+ });
+ observe(() => y.value, (n) {
+ if (++yCount < limit) x.value = !y.value;
+ });
+
+ // Kick off the cascading changes
+ x.value = true;
+
+ deliverChangesSync();
+
+ expect(xCount, limit);
+ expect(yCount, limit - 1);
+ } finally {
+ circularNotifyLimit = oldLimit;
+ }
+ });
+ });
+
+ group('ObservableReference', () {
+ test('observe conditional sync', () {
+ var t = new ObservableReference<bool>(false);
+ var a = new ObservableReference<int>(123);
+ var b = new ObservableReference<String>('hi');
+
+ var notifications = [];
+ observe(() => t.value ? a.value : b.value, notifications.add);
+
+ // Start off by changing "t" to true, so we evaluate "a".
+ t.value = true;
+ deliverChangesSync();
+
+ // This changes "a" which we should be observing.
+ a.value = 42;
+ deliverChangesSync();
+
+ // This has no effect because we aren't using "b" yet.
+ b.value = 'universe';
+ deliverChangesSync();
+
+ // Switch to use "b".
+ t.value = false;
+ deliverChangesSync();
+
+ // This has no effect because we aren't using "a" anymore.
+ a.value = 777;
+ deliverChangesSync();
+
+ expect(notifications, [
+ _change('hi', 123),
+ _change(123, 42),
+ _change(42, 'universe')]);
+ });
+ });
+
+
+ group('ObservableList', () {
+ // TODO(jmesserly): need all standard List tests.
+
+ test('observe length', () {
+ var list = new ObservableList();
+ var notification = null;
+ observe(() => list.length, (n) { notification = n; });
+
+ list.addAll([1, 2, 3]);
+ expect(list, [1, 2, 3]);
+ deliverChangesSync();
+ expect(notification, _change(0, 3), reason: 'addAll changes length');
+
+ list.add(4);
+ expect(list, [1, 2, 3, 4]);
+ deliverChangesSync();
+ expect(notification, _change(3, 4), reason: 'add changes length');
+
+ list.removeRange(1, 2);
+ expect(list, [1, 4]);
+ deliverChangesSync();
+ expect(notification, _change(4, 2), reason: 'removeRange changes length');
+
+ list.length = 5;
+ expect(list, [1, 4, null, null, null]);
+ deliverChangesSync();
+ expect(notification, _change(2, 5), reason: 'length= changes length');
+ notification = null;
+
+ list[2] = 9000;
+ expect(list, [1, 4, 9000, null, null]);
+ deliverChangesSync();
+ expect(notification, null, reason: '[]= does not change length');
+
+ list.clear();
+ expect(list, []);
+ deliverChangesSync();
+ expect(notification, _change(5, 0), reason: 'clear changes length');
+ });
+
+ test('observe index', () {
+ var list = new ObservableList.from([1, 2, 3]);
+ var notification = null;
+ observe(() => list[1], (n) { notification = n; });
+
+ list.add(4);
+ expect(list, [1, 2, 3, 4]);
+ deliverChangesSync();
+ expect(notification, null,
+ reason: 'add does not change existing items');
+
+ list[1] = 777;
+ expect(list, [1, 777, 3, 4]);
+ deliverChangesSync();
+ expect(notification, _change(2, 777));
+
+ notification = null;
+ list[2] = 9000;
+ expect(list, [1, 777, 9000, 4]);
+ deliverChangesSync();
+ expect(notification, null,
+ reason: 'setting a different index should not fire change');
+
+ list[1] = 44;
+ list[1] = 43;
+ list[1] = 42;
+ expect(list, [1, 42, 9000, 4]);
+ deliverChangesSync();
+ expect(notification, _change(777, 42));
+
+ notification = null;
+ list.length = 2;
+ expect(list, [1, 42]);
+ deliverChangesSync();
+ expect(notification, null,
+ reason: 'did not truncate the observed item');
+
+ list.length = 1; // truncate
+ list.add(2);
+ expect(list, [1, 2]);
+ deliverChangesSync();
+ expect(notification, _change(42, 2),
+ reason: 'item truncated and added back');
+
+ notification = null;
+ list.length = 1; // truncate
+ list.add(2);
+ expect(list, [1, 2]);
+ deliverChangesSync();
+ expect(notification, null,
+ reason: 'truncated but added same item back');
+ });
+
+ test('toString', () {
+ var list = new ObservableList.from([1, 2, 3]);
+ var notification = null;
+ observe(() => list.toString(), (n) { notification = n; });
+ list[2] = 4;
+ deliverChangesSync();
+ expect(notification, _change('[1, 2, 3]', '[1, 2, 4]'));
+ });
+ });
+
+
+ group('ObservableSet', () {
+ // TODO(jmesserly): need all standard Set tests.
+
+ test('observe length', () {
+ var set = new ObservableSet();
+ var notification = null;
+ observe(() => set.length, (n) { notification = n; });
+
+ set.addAll([1, 2, 3]);
+ expect(set, [1, 2, 3]);
+ deliverChangesSync();
+ expect(notification, _change(0, 3), reason: 'addAll changes length');
+
+ set.add(4);
+ expect(set, [1, 2, 3, 4]);
+ deliverChangesSync();
+ expect(notification, _change(3, 4), reason: 'add changes length');
+
+ set.removeAll([2, 3]);
+ expect(set, [1, 4]);
+ deliverChangesSync();
+ expect(notification, _change(4, 2), reason: 'removeAll changes length');
+
+ set.remove(1);
+ expect(set, [4]);
+ deliverChangesSync();
+ expect(notification, _change(2, 1), reason: 'remove changes length');
+
+ notification = null;
+ set.add(4);
+ expect(set, [4]);
+ deliverChangesSync();
+ expect(notification, null, reason: 'item already exists');
+
+ set.clear();
+ expect(set, []);
+ deliverChangesSync();
+ expect(notification, _change(1, 0), reason: 'clear changes length');
+ });
+
+ test('observe item', () {
+ var set = new ObservableSet.from([1, 2, 3]);
+ var notification = null;
+ observe(() => set.contains(2), (n) { notification = n; });
+
+ set.add(4);
+ expect(set, [1, 2, 3, 4]);
+ deliverChangesSync();
+ expect(notification, null, reason: 'add does not change existing items');
+
+ set.remove(3);
+ expect(set, [1, 2, 4]);
+ expect(notification, null,
+ reason: 'removing an item does not change other items');
+
+ set.remove(2);
+ expect(set, [1, 4]);
+ deliverChangesSync();
+ expect(notification, _change(true, false));
+
+ notification = null;
+ set.removeAll([2, 3]);
+ expect(set, [1, 4]);
+ deliverChangesSync();
+ expect(notification, null, reason: 'item already removed');
+
+ set.add(2);
+ expect(set, [1, 2, 4]);
+ deliverChangesSync();
+ expect(notification, _change(false, true), reason: 'item added again');
+ });
+
+ test('toString', () {
+ var original = new Set.from([1, 2, 3]);
+ var set = new ObservableSet.from(original);
+ var notification = null;
+ observe(() => set.toString(), (n) { notification = n; });
+ set.add(4);
+ deliverChangesSync();
+ var updated = new Set.from([1, 2, 3, 4]);
+
+ // Note: using Set.toString as the exectation, so the order is the same
+ // as with ObservableSet, regardless of how hashCode is implemented.
+ expect(notification, _change('$original', '$updated'));
+ });
+ });
+
+
+ group('ObservableMap', () {
+ // TODO(jmesserly): need all standard Map tests.
+
+ test('observe length', () {
+ var map = new ObservableMap();
+ var notification = null;
+ observe(() => map.length, (n) { notification = n; });
+
+ map['a'] = 1;
+ map.putIfAbsent('b', () => 2);
+ map['c'] = 3;
+ expect(map, {'a': 1, 'b': 2, 'c': 3});
+ deliverChangesSync();
+ expect(notification, _change(0, 3), reason: 'adding changes length');
+
+ map['d'] = 4;
+ expect(map, {'a': 1, 'b': 2, 'c': 3, 'd': 4});
+ deliverChangesSync();
+ expect(notification, _change(3, 4), reason: 'add changes length');
+
+ map.remove('b');
+ map.remove('c');
+ expect(map, {'a': 1, 'd': 4});
+ deliverChangesSync();
+ expect(notification, _change(4, 2), reason: 'removeRange changes length');
+
+ notification = null;
+ map['d'] = 9000;
+ expect(map, {'a': 1, 'd': 9000});
+ deliverChangesSync();
+ expect(notification, null, reason: 'update item does not change length');
+
+ map.clear();
+ expect(map, {});
+ deliverChangesSync();
+ expect(notification, _change(2, 0), reason: 'clear changes length');
+ });
+
+ test('observe index', () {
+ var map = new ObservableMap.from({'a': 1, 'b': 2, 'c': 3});
+ var notification = null;
+ observe(() => map['b'], (n) { notification = n; });
+
+ map.putIfAbsent('d', () => 4);
+ expect(map, {'a': 1, 'b': 2, 'c': 3, 'd': 4});
+ deliverChangesSync();
+ expect(notification, null, reason: 'add does not change existing items');
+
+ map['b'] = null;
+ expect(map, {'a': 1, 'b': null, 'c': 3, 'd': 4});
+ deliverChangesSync();
+ expect(notification, _change(2, null));
+
+ map['b'] = 777;
+ expect(map, {'a': 1, 'b': 777, 'c': 3, 'd': 4});
+ deliverChangesSync();
+ expect(notification, _change(null, 777));
+
+ notification = null;
+ map.putIfAbsent('b', () => 1234);
+ expect(map, {'a': 1, 'b': 777, 'c': 3, 'd': 4});
+ deliverChangesSync();
+ expect(notification, null, reason: 'item already there');
+
+ map['c'] = 9000;
+ expect(map, {'a': 1, 'b': 777, 'c': 9000, 'd': 4});
+ deliverChangesSync();
+ expect(notification, null, reason: 'setting a different item');
+
+ map['b'] = 44;
+ map['b'] = 43;
+ map['b'] = 42;
+ expect(map, {'a': 1, 'b': 42, 'c': 9000, 'd': 4});
+ deliverChangesSync();
+ expect(notification, _change(777, 42));
+
+ notification = null;
+ map.remove('a');
+ map.remove('d');
+ expect(map, {'b': 42, 'c': 9000});
+ deliverChangesSync();
+ expect(notification, null, reason: 'did not remove the observed item');
+
+ map.remove('b');
+ map['b'] = 2;
+ expect(map, {'b': 2, 'c': 9000});
+ deliverChangesSync();
+ expect(notification, _change(42, 2), reason: 'removed and added back');
+ });
+
+ test('toString', () {
+ var map = new ObservableMap.from({'a': 1, 'b': 2},
+ createMap: () => new LinkedHashMap());
+
+ var notification = null;
+ observe(() => map.toString(), (n) { notification = n; });
+ map.remove('b');
+ map['c'] = 3;
+ deliverChangesSync();
+
+ expect(notification, _change('{a: 1, b: 2}', '{a: 1, c: 3}'));
+ });
+ });
+
+}
+
+_change(oldValue, newValue) => new ChangeNotification(oldValue, newValue);
+
+/**
+ * This is similar to ObservableReference, but with fields public for testing.
+ */
+class TestObservable<T> {
+ var observers;
+ T rawValue;
+
+ TestObservable([T initialValue]) : rawValue = initialValue;
+
+ T get value {
+ if (observeReads) observers = notifyRead(observers);
+ return rawValue;
+ }
+
+ void set value(T newValue) {
+ if (observers != null) observers = notifyWrite(observers);
+ rawValue = newValue;
+ }
+}
« 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