Index: third_party/pkg/angular/test/change_detection/dirty_checking_change_detector_spec.dart |
diff --git a/third_party/pkg/angular/test/change_detection/dirty_checking_change_detector_spec.dart b/third_party/pkg/angular/test/change_detection/dirty_checking_change_detector_spec.dart |
index 0d732f2dc0b0c22c4d16522fff8d0303f49c64a9..011f59a022e5033b8490276f91832a4682a7c92e 100644 |
--- a/third_party/pkg/angular/test/change_detection/dirty_checking_change_detector_spec.dart |
+++ b/third_party/pkg/angular/test/change_detection/dirty_checking_change_detector_spec.dart |
@@ -3,477 +3,810 @@ library dirty_chekcing_change_detector_spec; |
import '../_specs.dart'; |
import 'package:angular/change_detection/change_detection.dart'; |
import 'package:angular/change_detection/dirty_checking_change_detector.dart'; |
+import 'package:angular/change_detection/dirty_checking_change_detector_static.dart'; |
+import 'package:angular/change_detection/dirty_checking_change_detector_dynamic.dart'; |
import 'dart:collection'; |
- |
-main() => describe('DirtyCheckingChangeDetector', () { |
- DirtyCheckingChangeDetector<String> detector; |
- |
- beforeEach(() { |
- GetterCache getterCache = new GetterCache({ |
- "first": (o) => o.first, |
- "age": (o) => o.age |
- }); |
- detector = new DirtyCheckingChangeDetector<String>(getterCache); |
- }); |
- |
- describe('object field', () { |
- it('should detect nothing', () { |
- var changes = detector.collectChanges(); |
- expect(changes).toEqual(null); |
- }); |
- |
- it('should detect field changes', () { |
- var user = new _User('', ''); |
- var change; |
- |
- detector |
- ..watch(user, 'first', null) |
- ..watch(user, 'last', null) |
- ..collectChanges(); // throw away first set |
- |
- change = detector.collectChanges(); |
- expect(change).toEqual(null); |
- user..first = 'misko' |
- ..last = 'hevery'; |
- |
- change = detector.collectChanges(); |
- expect(change.currentValue).toEqual('misko'); |
- expect(change.previousValue).toEqual(''); |
- expect(change.nextChange.currentValue).toEqual('hevery'); |
- expect(change.nextChange.previousValue).toEqual(''); |
- expect(change.nextChange.nextChange).toEqual(null); |
- |
- // force different instance |
- user.first = 'mis'; |
- user.first += 'ko'; |
- |
- change = detector.collectChanges(); |
- expect(change).toEqual(null); |
- |
- user.last = 'Hevery'; |
- change = detector.collectChanges(); |
- expect(change.currentValue).toEqual('Hevery'); |
- expect(change.previousValue).toEqual('hevery'); |
- expect(change.nextChange).toEqual(null); |
- }); |
- |
- it('should ignore NaN != NaN', () { |
- var user = new _User(); |
- user.age = double.NAN; |
- detector..watch(user, 'age', null)..collectChanges(); // throw away first set |
- |
- var changes = detector.collectChanges(); |
- expect(changes).toEqual(null); |
- |
- user.age = 123; |
- changes = detector.collectChanges(); |
- expect(changes.currentValue).toEqual(123); |
- expect(changes.previousValue.isNaN).toEqual(true); |
- expect(changes.nextChange).toEqual(null); |
- }); |
- |
- it('should treat map field dereference as []', () { |
- var obj = {'name':'misko'}; |
- detector.watch(obj, 'name', null); |
- detector.collectChanges(); // throw away first set |
- |
- obj['name'] = 'Misko'; |
- var changes = detector.collectChanges(); |
- expect(changes.currentValue).toEqual('Misko'); |
- expect(changes.previousValue).toEqual('misko'); |
- }); |
- }); |
- |
- describe('insertions / removals', () { |
- it('should insert at the end of list', () { |
- var obj = {}; |
- var a = detector.watch(obj, 'a', 'a'); |
- var b = detector.watch(obj, 'b', 'b'); |
- |
- obj['a'] = obj['b'] = 1; |
- var changes = detector.collectChanges(); |
- expect(changes.handler).toEqual('a'); |
- expect(changes.nextChange.handler).toEqual('b'); |
- expect(changes.nextChange.nextChange).toEqual(null); |
- |
- obj['a'] = obj['b'] = 2; |
- a.remove(); |
- changes = detector.collectChanges(); |
- expect(changes.handler).toEqual('b'); |
- expect(changes.nextChange).toEqual(null); |
- |
- obj['a'] = obj['b'] = 3; |
- b.remove(); |
- changes = detector.collectChanges(); |
- expect(changes).toEqual(null); |
- }); |
- |
- it('should remove all watches in group and group\'s children', () { |
- var obj = {}; |
- detector.watch(obj, 'a', '0a'); |
- var child1a = detector.newGroup(); |
- var child1b = detector.newGroup(); |
- var child2 = child1a.newGroup(); |
- child1a.watch(obj,'a', '1a'); |
- child1b.watch(obj,'a', '1b'); |
- detector.watch(obj, 'a', '0A'); |
- child1a.watch(obj,'a', '1A'); |
- child2.watch(obj,'a', '2A'); |
- |
- obj['a'] = 1; |
- expect(detector.collectChanges(), |
- toEqualChanges(['0a', '0A', '1a', '1A', '2A', '1b'])); |
- |
- obj['a'] = 2; |
- child1a.remove(); // should also remove child2 |
- expect(detector.collectChanges(), toEqualChanges(['0a', '0A', '1b'])); |
+import 'dart:math'; |
+ |
+void main() { |
+ describe('DirtyCheckingChangeDetector', () { |
+ DirtyCheckingChangeDetector<String> detector; |
+ FieldGetterFactory getterFactory = new StaticFieldGetterFactory({ |
+ "first": (o) => o.first, |
+ "age": (o) => o.age, |
+ "last": (o) => o.last, |
+ "toString": (o) => o.toString, |
+ "isUnderAge": (o) => o.isUnderAge, |
+ "isUnderAgeAsVariable": (o) => o.isUnderAgeAsVariable |
}); |
- it('should add watches within its own group', () { |
- var obj = {}; |
- var ra = detector.watch(obj, 'a', 'a'); |
- var child = detector.newGroup(); |
- var cb = child.watch(obj,'b', 'b'); |
- |
- obj['a'] = obj['b'] = 1; |
- expect(detector.collectChanges(), toEqualChanges(['a', 'b'])); |
- |
- obj['a'] = obj['b'] = 2; |
- ra.remove(); |
- expect(detector.collectChanges(), toEqualChanges(['b'])); |
- |
- obj['a'] = obj['b'] = 3; |
- cb.remove(); |
- expect(detector.collectChanges(), toEqualChanges([])); |
- |
- // TODO: add them back in wrong order, assert events in right order |
- cb = child.watch(obj,'b', 'b'); |
- ra = detector.watch(obj, 'a', 'a'); |
- obj['a'] = obj['b'] = 4; |
- expect(detector.collectChanges(), toEqualChanges(['a', 'b'])); |
- }); |
- |
- it('should properly add children', () { |
- var a = detector.newGroup(); |
- var aChild = a.newGroup(); |
- var b = detector.newGroup(); |
- expect(detector.collectChanges).not.toThrow(); |
+ beforeEach(() { |
+ detector = new DirtyCheckingChangeDetector<String>(getterFactory); |
}); |
- }); |
- describe('list watching', () { |
- it('should detect changes in list', () { |
- var list = []; |
- var record = detector.watch(list, null, 'handler'); |
- expect(detector.collectChanges()).toEqual(null); |
- |
- list.add('a'); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['a[null -> 0]'], |
- additions: ['a[null -> 0]'], |
- moves: [], |
- removals: [])); |
- |
- list.add('b'); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['a', 'b[null -> 1]'], |
- additions: ['b[null -> 1]'], |
- moves: [], |
- removals: [])); |
- |
- list.add('c'); |
- list.add('d'); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['a', 'b', 'c[null -> 2]', 'd[null -> 3]'], |
- additions: ['c[null -> 2]', 'd[null -> 3]'], |
- moves: [], |
- removals: [])); |
- |
- list.remove('c'); |
- expect(list).toEqual(['a', 'b', 'd']); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['a', 'b', 'd[3 -> 2]'], |
- additions: [], |
- moves: ['d[3 -> 2]'], |
- removals: ['c[2 -> null]'])); |
- |
- list.clear(); |
- list.addAll(['d', 'c', 'b', 'a']); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['d[2 -> 0]', 'c[null -> 1]', 'b[1 -> 2]', 'a[0 -> 3]'], |
- additions: ['c[null -> 1]'], |
- moves: ['d[2 -> 0]', 'b[1 -> 2]', 'a[0 -> 3]'], |
- removals: [])); |
+ describe('StaticFieldGetterFactory', () { |
+ DirtyCheckingChangeDetector<String> detector; |
+ var user = new _User('Marko', 'Vuksanovic', 30); |
+ FieldGetterFactory getterFactory = new StaticFieldGetterFactory({ |
+ "first": (o) => o.first, |
+ "age": (o) => o.age, |
+ "last": (o) => o.last, |
+ "toString": (o) => o.toString, |
+ "isUnderAge": (o) => o.isUnderAge, |
+ "isUnderAgeAsVariable": (o) => o.isUnderAgeAsVariable, |
+ "list": (o) => o.list, |
+ "map": (o) => o.map |
+ }); |
+ |
+ beforeEach(() { |
+ detector = new DirtyCheckingChangeDetector<String>(getterFactory); |
+ }); |
+ |
+ it('should detect methods', () { |
+ var obj = new _User(); |
+ expect(getterFactory.isMethod(obj, 'toString')).toEqual(true); |
+ expect(getterFactory.isMethod(obj, 'age')).toEqual(false); |
+ }); |
+ |
+ it('should return true is method is real method', () { |
+ expect(getterFactory.isMethod(user, 'isUnderAge')).toEqual(true); |
+ }); |
+ |
+ it('should return false is field is a function', () { |
+ expect(getterFactory.isMethod(user, 'isUnderAgeAsVariable')).toEqual(false); |
+ }); |
+ |
+ it('should return false is field is a list', () { |
+ expect(getterFactory.isMethod(user, 'list')).toEqual(false); |
+ }); |
+ |
+ it('should return false is field is a map', () { |
+ expect(getterFactory.isMethod(user, 'map')).toEqual(false); |
+ }); |
}); |
- it('should detect changes in list', () { |
- var list = []; |
- var record = detector.watch(list.map((i) => i), null, 'handler'); |
- expect(detector.collectChanges()).toEqual(null); |
- |
- list.add('a'); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['a[null -> 0]'], |
- additions: ['a[null -> 0]'], |
- moves: [], |
- removals: [])); |
- |
- list.add('b'); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['a', 'b[null -> 1]'], |
- additions: ['b[null -> 1]'], |
- moves: [], |
- removals: [])); |
- |
- list.add('c'); |
- list.add('d'); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['a', 'b', 'c[null -> 2]', 'd[null -> 3]'], |
- additions: ['c[null -> 2]', 'd[null -> 3]'], |
- moves: [], |
- removals: [])); |
- |
- list.remove('c'); |
- expect(list).toEqual(['a', 'b', 'd']); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['a', 'b', 'd[3 -> 2]'], |
- additions: [], |
- moves: ['d[3 -> 2]'], |
- removals: ['c[2 -> null]'])); |
- |
- list.clear(); |
- list.addAll(['d', 'c', 'b', 'a']); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['d[2 -> 0]', 'c[null -> 1]', 'b[1 -> 2]', 'a[0 -> 3]'], |
- additions: ['c[null -> 1]'], |
- moves: ['d[2 -> 0]', 'b[1 -> 2]', 'a[0 -> 3]'], |
- removals: [])); |
- }); |
+ describe('Dynamic GetterFactory', () { |
+ DirtyCheckingChangeDetector<String> detector; |
+ var user = new _User('Marko', 'Vuksanovic', 30); |
+ FieldGetterFactory getterFactory = new DynamicFieldGetterFactory(); |
- it('should test string by value rather than by reference', () { |
- var list = ['a', 'boo']; |
- detector..watch(list, null, null)..collectChanges(); |
+ beforeEach(() { |
+ detector = new DirtyCheckingChangeDetector<String>(getterFactory); |
+ }); |
- list[1] = 'b' + 'oo'; |
+ it('should return true is method is real method', () { |
+ expect(getterFactory.isMethod(user, 'isUnderAge')).toEqual(true); |
+ }); |
- expect(detector.collectChanges()).toEqual(null); |
- }); |
+ it('should return false is field is a function', () { |
+ expect(getterFactory.isMethod(user, 'isUnderAgeAsVariable')).toEqual(false); |
+ }); |
- it('should ignore [NaN] != [NaN]', () { |
- var list = [double.NAN]; |
- var record = detector..watch(list, null, null)..collectChanges(); |
+ it('should return false is field is a list', () { |
+ expect(getterFactory.isMethod(user, 'list')).toEqual(false); |
+ }); |
- expect(detector.collectChanges()).toEqual(null); |
+ it('should return false is field is a map', () { |
+ expect(getterFactory.isMethod(user, 'map')).toEqual(false); |
+ }); |
}); |
- it('should remove and add same item', () { |
- var list = ['a', 'b', 'c']; |
- var record = detector.watch(list, null, 'handler'); |
- detector.collectChanges(); |
- |
- list.remove('b'); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['a', 'c[2 -> 1]'], |
- additions: [], |
- moves: ['c[2 -> 1]'], |
- removals: ['b[1 -> null]'])); |
- |
- list.insert(1, 'b'); |
- expect(list).toEqual(['a', 'b', 'c']); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['a', 'b[null -> 1]', 'c[1 -> 2]'], |
- additions: ['b[null -> 1]'], |
- moves: ['c[1 -> 2]'], |
- removals: [])); |
+ describe('object field', () { |
+ it('should detect nothing', () { |
+ var changes = detector.collectChanges(); |
+ expect(changes.moveNext()).toEqual(false); |
+ }); |
+ |
+ it('should detect field changes', () { |
+ var user = new _User('', ''); |
+ Iterator changeIterator; |
+ |
+ detector..watch(user, 'first', null) |
+ ..watch(user, 'last', null) |
+ ..collectChanges(); // throw away first set |
+ |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(false); |
+ user..first = 'misko' |
+ ..last = 'hevery'; |
+ |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.current.currentValue).toEqual('misko'); |
+ expect(changeIterator.current.previousValue).toEqual(''); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.current.currentValue).toEqual('hevery'); |
+ expect(changeIterator.current.previousValue).toEqual(''); |
+ expect(changeIterator.moveNext()).toEqual(false); |
+ |
+ // force different instance |
+ user.first = 'mis'; |
+ user.first += 'ko'; |
+ |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(false); |
+ |
+ user.last = 'Hevery'; |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.current.currentValue).toEqual('Hevery'); |
+ expect(changeIterator.current.previousValue).toEqual('hevery'); |
+ expect(changeIterator.moveNext()).toEqual(false); |
+ }); |
+ |
+ it('should ignore NaN != NaN', () { |
+ var user = new _User(); |
+ user.age = double.NAN; |
+ detector..watch(user, 'age', null)..collectChanges(); // throw away first set |
+ |
+ var changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(false); |
+ |
+ user.age = 123; |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.current.currentValue).toEqual(123); |
+ expect(changeIterator.current.previousValue.isNaN).toEqual(true); |
+ expect(changeIterator.moveNext()).toEqual(false); |
+ }); |
+ |
+ it('should treat map field dereference as []', () { |
+ var obj = {'name':'misko'}; |
+ detector.watch(obj, 'name', null); |
+ detector.collectChanges(); // throw away first set |
+ |
+ obj['name'] = 'Misko'; |
+ var changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.current.currentValue).toEqual('Misko'); |
+ expect(changeIterator.current.previousValue).toEqual('misko'); |
+ }); |
}); |
- it('should support duplicates', () { |
- var list = ['a', 'a', 'a', 'b', 'b']; |
- var record = detector.watch(list, null, 'handler'); |
- detector.collectChanges(); |
- |
- list.removeAt(0); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['a', 'a', 'b[3 -> 2]', 'b[4 -> 3]'], |
- additions: [], |
- moves: ['b[3 -> 2]', 'b[4 -> 3]'], |
- removals: ['a[2 -> null]'])); |
- }); |
- |
- |
- it('should support insertions/moves', () { |
- var list = ['a', 'a', 'b', 'b']; |
- var record = detector.watch(list, null, 'handler'); |
- detector.collectChanges(); |
- list.insert(0, 'b'); |
- expect(list).toEqual(['b', 'a', 'a', 'b', 'b']); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['b[2 -> 0]', 'a[0 -> 1]', 'a[1 -> 2]', 'b', 'b[null -> 4]'], |
- additions: ['b[null -> 4]'], |
- moves: ['b[2 -> 0]', 'a[0 -> 1]', 'a[1 -> 2]'], |
- removals: [])); |
- }); |
- |
- it('should support UnmodifiableListView', () { |
- var hiddenList = [1]; |
- var list = new UnmodifiableListView(hiddenList); |
- var record = detector.watch(list, null, 'handler'); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['1[null -> 0]'], |
- additions: ['1[null -> 0]'], |
- moves: [], |
- removals: [])); |
- |
- // assert no changes detected |
- expect(detector.collectChanges()).toEqual(null); |
- |
- // change the hiddenList normally this should trigger change detection |
- // but because we are wrapped in UnmodifiableListView we see nothing. |
- hiddenList[0] = 2; |
- expect(detector.collectChanges()).toEqual(null); |
- }); |
- |
- it('should bug', () { |
- var list = [1, 2, 3, 4]; |
- var record = detector.watch(list, null, 'handler'); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['1[null -> 0]', '2[null -> 1]', '3[null -> 2]', '4[null -> 3]'], |
- additions: ['1[null -> 0]', '2[null -> 1]', '3[null -> 2]', '4[null -> 3]'], |
- moves: [], |
- removals: [])); |
- detector.collectChanges(); |
- |
- list.removeRange(0, 1); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['2[1 -> 0]', '3[2 -> 1]', '4[3 -> 2]'], |
- additions: [], |
- moves: ['2[1 -> 0]', '3[2 -> 1]', '4[3 -> 2]'], |
- removals: ['1[0 -> null]'])); |
- |
- list.insert(0, 1); |
- expect(detector.collectChanges().currentValue, toEqualCollectionRecord( |
- collection: ['1[null -> 0]', '2[0 -> 1]', '3[1 -> 2]', '4[2 -> 3]'], |
- additions: ['1[null -> 0]'], |
- moves: ['2[0 -> 1]', '3[1 -> 2]', '4[2 -> 3]'], |
- removals: [])); |
- }); |
- }); |
- |
- describe('map watching', () { |
- it('should do basic map watching', () { |
- var map = {}; |
- var record = detector.watch(map, null, 'handler'); |
- expect(detector.collectChanges()).toEqual(null); |
- |
- map['a'] = 'A'; |
- expect(detector.collectChanges().currentValue, toEqualMapRecord( |
- map: ['a[null -> A]'], |
- additions: ['a[null -> A]'], |
- changes: [], |
- removals: [])); |
- |
- map['b'] = 'B'; |
- expect(detector.collectChanges().currentValue, toEqualMapRecord( |
- map: ['a', 'b[null -> B]'], |
- additions: ['b[null -> B]'], |
- changes: [], |
- removals: [])); |
- |
- map['b'] = 'BB'; |
- map['d'] = 'D'; |
- expect(detector.collectChanges().currentValue, toEqualMapRecord( |
- map: ['a', 'b[B -> BB]', 'd[null -> D]'], |
- additions: ['d[null -> D]'], |
- changes: ['b[B -> BB]'], |
- removals: [])); |
- |
- map.remove('b'); |
- expect(map).toEqual({'a': 'A', 'd':'D'}); |
- expect(detector.collectChanges().currentValue, toEqualMapRecord( |
- map: ['a', 'd'], |
- additions: [], |
- changes: [], |
- removals: ['b[BB -> null]'])); |
- |
- map.clear(); |
- expect(detector.collectChanges().currentValue, toEqualMapRecord( |
- map: [], |
- additions: [], |
- changes: [], |
- removals: ['a[A -> null]', 'd[D -> null]'])); |
- }); |
- |
- it('should test string keys by value rather than by reference', () { |
- var map = {'foo': 0}; |
- detector..watch(map, null, null)..collectChanges(); |
- |
- map['f' + 'oo'] = 0; |
+ describe('insertions / removals', () { |
+ it('should insert at the end of list', () { |
+ var obj = {}; |
+ var a = detector.watch(obj, 'a', 'a'); |
+ var b = detector.watch(obj, 'b', 'b'); |
+ |
+ obj['a'] = obj['b'] = 1; |
+ var changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.current.handler).toEqual('a'); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.current.handler).toEqual('b'); |
+ expect(changeIterator.moveNext()).toEqual(false); |
+ |
+ obj['a'] = obj['b'] = 2; |
+ a.remove(); |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.current.handler).toEqual('b'); |
+ expect(changeIterator.moveNext()).toEqual(false); |
+ |
+ obj['a'] = obj['b'] = 3; |
+ b.remove(); |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(false); |
+ }); |
+ |
+ it('should remove all watches in group and group\'s children', () { |
+ var obj = {}; |
+ detector.watch(obj, 'a', '0a'); |
+ var child1a = detector.newGroup(); |
+ var child1b = detector.newGroup(); |
+ var child2 = child1a.newGroup(); |
+ child1a.watch(obj,'a', '1a'); |
+ child1b.watch(obj,'a', '1b'); |
+ detector.watch(obj, 'a', '0A'); |
+ child1a.watch(obj,'a', '1A'); |
+ child2.watch(obj,'a', '2A'); |
+ |
+ var iterator; |
+ obj['a'] = 1; |
+ expect(detector.collectChanges(), |
+ toEqualChanges(['0a', '0A', '1a', '1A', '2A', '1b'])); |
+ |
+ obj['a'] = 2; |
+ child1a.remove(); // should also remove child2 |
+ expect(detector.collectChanges(), toEqualChanges(['0a', '0A', '1b'])); |
+ }); |
+ |
+ it('should add watches within its own group', () { |
+ var obj = {}; |
+ var ra = detector.watch(obj, 'a', 'a'); |
+ var child = detector.newGroup(); |
+ var cb = child.watch(obj,'b', 'b'); |
+ var iterotar; |
+ |
+ obj['a'] = obj['b'] = 1; |
+ expect(detector.collectChanges(), toEqualChanges(['a', 'b'])); |
+ |
+ obj['a'] = obj['b'] = 2; |
+ ra.remove(); |
+ expect(detector.collectChanges(), toEqualChanges(['b'])); |
+ |
+ obj['a'] = obj['b'] = 3; |
+ cb.remove(); |
+ expect(detector.collectChanges(), toEqualChanges([])); |
+ |
+ // TODO: add them back in wrong order, assert events in right order |
+ cb = child.watch(obj,'b', 'b'); |
+ ra = detector.watch(obj, 'a', 'a'); |
+ obj['a'] = obj['b'] = 4; |
+ expect(detector.collectChanges(), toEqualChanges(['a', 'b'])); |
+ }); |
+ |
+ it('should properly add children', () { |
+ var a = detector.newGroup(); |
+ var aChild = a.newGroup(); |
+ var b = detector.newGroup(); |
+ expect(detector.collectChanges).not.toThrow(); |
+ }); |
+ |
+ it('should properly disconnect group in case watch is removed in disconected group', () { |
+ var map = {}; |
+ var detector0 = new DirtyCheckingChangeDetector<String>(getterFactory); |
+ var detector1 = detector0.newGroup(); |
+ var detector2 = detector1.newGroup(); |
+ var watch2 = detector2.watch(map, 'f1', null); |
+ var detector3 = detector0.newGroup(); |
+ detector1.remove(); |
+ watch2.remove(); // removing a dead record |
+ detector3.watch(map, 'f2', null); |
+ }); |
+ |
+ it('should find random bugs', () { |
+ List detectors; |
+ List records; |
+ List steps; |
+ var field = 'someField'; |
+ step(text) { |
+ //print(text); |
+ steps.add(text); |
+ } |
+ Map map = {}; |
+ var random = new Random(); |
+ try { |
+ for (var i = 0; i < 100000; i++) { |
+ if (i % 50 == 0) { |
+ //print(steps); |
+ //print('==================================='); |
+ records = []; |
+ steps = []; |
+ detectors = [new DirtyCheckingChangeDetector<String>(getterFactory)]; |
+ } |
+ switch (random.nextInt(4)) { |
+ case 0: // new child detector |
+ if (detectors.length > 10) break; |
+ var index = random.nextInt(detectors.length); |
+ ChangeDetectorGroup detector = detectors[index]; |
+ step('detectors[$index].newGroup()'); |
+ var child = detector.newGroup(); |
+ detectors.add(child); |
+ break; |
+ case 1: // add watch |
+ var index = random.nextInt(detectors.length); |
+ ChangeDetectorGroup detector = detectors[index]; |
+ step('detectors[$index].watch(map, field, null)'); |
+ WatchRecord record = detector.watch(map, field, null); |
+ records.add(record); |
+ break; |
+ case 2: // destroy watch group |
+ if (detectors.length == 1) break; |
+ var index = random.nextInt(detectors.length - 1) + 1; |
+ ChangeDetectorGroup detector = detectors[index]; |
+ step('detectors[$index].remove()'); |
+ detector.remove(); |
+ detectors = detectors |
+ .where((s) => s.isAttached) |
+ .toList(); |
+ break; |
+ case 3: // remove watch on watch group |
+ if (records.length == 0) break; |
+ var index = random.nextInt(records.length); |
+ WatchRecord record = records.removeAt(index); |
+ step('records.removeAt($index).remove()'); |
+ record.remove(); |
+ break; |
+ } |
+ } |
+ } catch(e) { |
+ print(steps); |
+ rethrow; |
+ } |
+ }); |
- expect(detector.collectChanges()).toEqual(null); |
}); |
- it('should test string values by value rather than by reference', () { |
- var map = {'foo': 'bar'}; |
- detector..watch(map, null, null)..collectChanges(); |
- |
- map['foo'] = 'b' + 'ar'; |
- |
- expect(detector.collectChanges()).toEqual(null); |
+ describe('list watching', () { |
+ describe('previous state', () { |
+ it('should store on addition', () { |
+ var list = []; |
+ var record = detector.watch(list, null, null); |
+ expect(detector.collectChanges().moveNext()).toEqual(false); |
+ var iterator; |
+ |
+ list.add('a'); |
+ iterator = detector.collectChanges(); |
+ expect(iterator.moveNext()).toEqual(true); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['a[null -> 0]'], |
+ previous: [], |
+ additions: ['a[null -> 0]'], |
+ moves: [], |
+ removals: [])); |
+ |
+ list.add('b'); |
+ iterator = detector.collectChanges(); |
+ expect(iterator.moveNext()).toEqual(true); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['a', 'b[null -> 1]'], |
+ previous: ['a'], |
+ additions: ['b[null -> 1]'], |
+ moves: [], |
+ removals: [])); |
+ }); |
+ |
+ it('should handle swapping elements correctly', () { |
+ var list = [1, 2]; |
+ var record = detector.watch(list, null, null); |
+ detector.collectChanges().moveNext(); |
+ var iterator; |
+ |
+ // reverse the list. |
+ list.setAll(0, list.reversed.toList()); |
+ iterator = detector.collectChanges(); |
+ expect(iterator.moveNext()).toEqual(true); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['2[1 -> 0]', '1[0 -> 1]'], |
+ previous: ['1[0 -> 1]', '2[1 -> 0]'], |
+ additions: [], |
+ moves: ['2[1 -> 0]', '1[0 -> 1]'], |
+ removals: [])); |
+ }); |
+ }); |
+ |
+ it('should detect changes in list', () { |
+ var list = []; |
+ var record = detector.watch(list, null, 'handler'); |
+ expect(detector.collectChanges().moveNext()).toEqual(false); |
+ var iterator; |
+ |
+ list.add('a'); |
+ iterator = detector.collectChanges(); |
+ expect(iterator.moveNext()).toEqual(true); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['a[null -> 0]'], |
+ additions: ['a[null -> 0]'], |
+ moves: [], |
+ removals: [])); |
+ |
+ list.add('b'); |
+ iterator = detector.collectChanges(); |
+ expect(iterator.moveNext()).toEqual(true); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['a', 'b[null -> 1]'], |
+ previous: ['a'], |
+ additions: ['b[null -> 1]'], |
+ moves: [], |
+ removals: [])); |
+ |
+ list.add('c'); |
+ list.add('d'); |
+ iterator = detector.collectChanges(); |
+ expect(iterator.moveNext()).toEqual(true); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['a', 'b', 'c[null -> 2]', 'd[null -> 3]'], |
+ previous: ['a', 'b'], |
+ additions: ['c[null -> 2]', 'd[null -> 3]'], |
+ moves: [], |
+ removals: [])); |
+ |
+ list.remove('c'); |
+ expect(list).toEqual(['a', 'b', 'd']); |
+ iterator = detector.collectChanges(); |
+ expect(iterator.moveNext()).toEqual(true); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['a', 'b', 'd[3 -> 2]'], |
+ previous: ['a', 'b', 'c[2 -> null]', 'd[3 -> 2]'], |
+ additions: [], |
+ moves: ['d[3 -> 2]'], |
+ removals: ['c[2 -> null]'])); |
+ |
+ list.clear(); |
+ list.addAll(['d', 'c', 'b', 'a']); |
+ iterator = detector.collectChanges(); |
+ expect(iterator.moveNext()).toEqual(true); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['d[2 -> 0]', 'c[null -> 1]', 'b[1 -> 2]', 'a[0 -> 3]'], |
+ previous: ['a[0 -> 3]', 'b[1 -> 2]', 'd[2 -> 0]'], |
+ additions: ['c[null -> 1]'], |
+ moves: ['d[2 -> 0]', 'b[1 -> 2]', 'a[0 -> 3]'], |
+ removals: [])); |
+ }); |
+ |
+ it('should test string by value rather than by reference', () { |
+ var list = ['a', 'boo']; |
+ detector..watch(list, null, null)..collectChanges(); |
+ |
+ list[1] = 'b' + 'oo'; |
+ |
+ expect(detector.collectChanges().moveNext()).toEqual(false); |
+ }); |
+ |
+ it('should ignore [NaN] != [NaN]', () { |
+ var list = [double.NAN]; |
+ var record = detector..watch(list, null, null)..collectChanges(); |
+ |
+ expect(detector.collectChanges().moveNext()).toEqual(false); |
+ }); |
+ |
+ it('should remove and add same item', () { |
+ var list = ['a', 'b', 'c']; |
+ var record = detector.watch(list, null, 'handler'); |
+ var iterator; |
+ detector.collectChanges(); |
+ |
+ list.remove('b'); |
+ iterator = detector.collectChanges()..moveNext(); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['a', 'c[2 -> 1]'], |
+ previous: ['a', 'b[1 -> null]', 'c[2 -> 1]'], |
+ additions: [], |
+ moves: ['c[2 -> 1]'], |
+ removals: ['b[1 -> null]'])); |
+ |
+ list.insert(1, 'b'); |
+ expect(list).toEqual(['a', 'b', 'c']); |
+ iterator = detector.collectChanges()..moveNext(); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['a', 'b[null -> 1]', 'c[1 -> 2]'], |
+ previous: ['a', 'c[1 -> 2]'], |
+ additions: ['b[null -> 1]'], |
+ moves: ['c[1 -> 2]'], |
+ removals: [])); |
+ }); |
+ |
+ it('should support duplicates', () { |
+ var list = ['a', 'a', 'a', 'b', 'b']; |
+ var record = detector.watch(list, null, 'handler'); |
+ detector.collectChanges(); |
+ |
+ list.removeAt(0); |
+ var iterator = detector.collectChanges()..moveNext(); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['a', 'a', 'b[3 -> 2]', 'b[4 -> 3]'], |
+ previous: ['a', 'a', 'a[2 -> null]', 'b[3 -> 2]', 'b[4 -> 3]'], |
+ additions: [], |
+ moves: ['b[3 -> 2]', 'b[4 -> 3]'], |
+ removals: ['a[2 -> null]'])); |
+ }); |
+ |
+ |
+ it('should support insertions/moves', () { |
+ var list = ['a', 'a', 'b', 'b']; |
+ var record = detector.watch(list, null, 'handler'); |
+ var iterator; |
+ detector.collectChanges(); |
+ list.insert(0, 'b'); |
+ expect(list).toEqual(['b', 'a', 'a', 'b', 'b']); |
+ iterator = detector.collectChanges()..moveNext(); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['b[2 -> 0]', 'a[0 -> 1]', 'a[1 -> 2]', 'b', 'b[null -> 4]'], |
+ previous: ['a[0 -> 1]', 'a[1 -> 2]', 'b[2 -> 0]', 'b'], |
+ additions: ['b[null -> 4]'], |
+ moves: ['b[2 -> 0]', 'a[0 -> 1]', 'a[1 -> 2]'], |
+ removals: [])); |
+ }); |
+ |
+ it('should support UnmodifiableListView', () { |
+ var hiddenList = [1]; |
+ var list = new UnmodifiableListView(hiddenList); |
+ var record = detector.watch(list, null, 'handler'); |
+ var iterator = detector.collectChanges()..moveNext(); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['1[null -> 0]'], |
+ additions: ['1[null -> 0]'], |
+ moves: [], |
+ removals: [])); |
+ |
+ // assert no changes detected |
+ expect(detector.collectChanges().moveNext()).toEqual(false); |
+ |
+ // change the hiddenList normally this should trigger change detection |
+ // but because we are wrapped in UnmodifiableListView we see nothing. |
+ hiddenList[0] = 2; |
+ expect(detector.collectChanges().moveNext()).toEqual(false); |
+ }); |
+ |
+ it('should bug', () { |
+ var list = [1, 2, 3, 4]; |
+ var record = detector.watch(list, null, 'handler'); |
+ var iterator; |
+ |
+ iterator = detector.collectChanges()..moveNext(); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['1[null -> 0]', '2[null -> 1]', '3[null -> 2]', '4[null -> 3]'], |
+ additions: ['1[null -> 0]', '2[null -> 1]', '3[null -> 2]', '4[null -> 3]'], |
+ moves: [], |
+ removals: [])); |
+ detector.collectChanges(); |
+ |
+ list.removeRange(0, 1); |
+ iterator = detector.collectChanges()..moveNext(); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['2[1 -> 0]', '3[2 -> 1]', '4[3 -> 2]'], |
+ previous: ['1[0 -> null]', '2[1 -> 0]', '3[2 -> 1]', '4[3 -> 2]'], |
+ additions: [], |
+ moves: ['2[1 -> 0]', '3[2 -> 1]', '4[3 -> 2]'], |
+ removals: ['1[0 -> null]'])); |
+ |
+ list.insert(0, 1); |
+ iterator = detector.collectChanges()..moveNext(); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['1[null -> 0]', '2[0 -> 1]', '3[1 -> 2]', '4[2 -> 3]'], |
+ previous: ['2[0 -> 1]', '3[1 -> 2]', '4[2 -> 3]'], |
+ additions: ['1[null -> 0]'], |
+ moves: ['2[0 -> 1]', '3[1 -> 2]', '4[2 -> 3]'], |
+ removals: [])); |
+ }); |
+ |
+ it('should properly support objects with equality', () { |
+ FooBar.fooIds = 0; |
+ var list = [new FooBar('a', 'a'), new FooBar('a', 'a')]; |
+ var record = detector.watch(list, null, 'handler'); |
+ var iterator; |
+ |
+ iterator = detector.collectChanges()..moveNext(); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['(0)a-a[null -> 0]', '(1)a-a[null -> 1]'], |
+ additions: ['(0)a-a[null -> 0]', '(1)a-a[null -> 1]'], |
+ moves: [], |
+ removals: [])); |
+ detector.collectChanges(); |
+ |
+ list.removeRange(0, 1); |
+ iterator = detector.collectChanges()..moveNext(); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['(1)a-a[1 -> 0]'], |
+ previous: ['(0)a-a[0 -> null]', '(1)a-a[1 -> 0]'], |
+ additions: [], |
+ moves: ['(1)a-a[1 -> 0]'], |
+ removals: ['(0)a-a[0 -> null]'])); |
+ |
+ list.insert(0, new FooBar('a', 'a')); |
+ iterator = detector.collectChanges()..moveNext(); |
+ expect(iterator.current.currentValue, toEqualCollectionRecord( |
+ collection: ['(2)a-a[null -> 0]', '(1)a-a[0 -> 1]'], |
+ previous: ['(1)a-a[0 -> 1]'], |
+ additions: ['(2)a-a[null -> 0]'], |
+ moves: ['(1)a-a[0 -> 1]'], |
+ removals: [])); |
+ }); |
}); |
- it('should not see a NaN value as a change', () { |
- var map = {'foo': double.NAN}; |
- var record = detector..watch(map, null, null)..collectChanges(); |
- |
- expect(detector.collectChanges()).toEqual(null); |
+ describe('map watching', () { |
+ describe('previous state', () { |
+ it('should store on insertion', () { |
+ var map = {}; |
+ var record = detector.watch(map, null, null); |
+ expect(detector.collectChanges().moveNext()).toEqual(false); |
+ var iterator; |
+ |
+ map['a'] = 1; |
+ iterator = detector.collectChanges(); |
+ expect(iterator.moveNext()).toEqual(true); |
+ expect(iterator.current.currentValue, toEqualMapRecord( |
+ map: ['a[null -> 1]'], |
+ previous: [], |
+ additions: ['a[null -> 1]'], |
+ changes: [], |
+ removals: [])); |
+ |
+ map['b'] = 2; |
+ iterator = detector.collectChanges(); |
+ expect(iterator.moveNext()).toEqual(true); |
+ expect(iterator.current.currentValue, toEqualMapRecord( |
+ map: ['a', 'b[null -> 2]'], |
+ previous: ['a'], |
+ additions: ['b[null -> 2]'], |
+ changes: [], |
+ removals: [])); |
+ }); |
+ |
+ it('should handle changing key/values correctly', () { |
+ var map = {1: 10, 2: 20}; |
+ var record = detector.watch(map, null, null); |
+ detector.collectChanges().moveNext(); |
+ var iterator; |
+ |
+ map[1] = 20; |
+ map[2] = 10; |
+ iterator = detector.collectChanges(); |
+ expect(iterator.moveNext()).toEqual(true); |
+ expect(iterator.current.currentValue, toEqualMapRecord( |
+ map: ['1[10 -> 20]', '2[20 -> 10]'], |
+ previous: ['1[10 -> 20]', '2[20 -> 10]'], |
+ additions: [], |
+ changes: ['1[10 -> 20]', '2[20 -> 10]'], |
+ removals: [])); |
+ }); |
+ }); |
+ |
+ it('should do basic map watching', () { |
+ var map = {}; |
+ var record = detector.watch(map, null, 'handler'); |
+ expect(detector.collectChanges().moveNext()).toEqual(false); |
+ |
+ var changeIterator; |
+ map['a'] = 'A'; |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.current.currentValue, toEqualMapRecord( |
+ map: ['a[null -> A]'], |
+ previous: [], |
+ additions: ['a[null -> A]'], |
+ changes: [], |
+ removals: [])); |
+ |
+ map['b'] = 'B'; |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.current.currentValue, toEqualMapRecord( |
+ map: ['a', 'b[null -> B]'], |
+ previous: ['a'], |
+ additions: ['b[null -> B]'], |
+ changes: [], |
+ removals: [])); |
+ |
+ map['b'] = 'BB'; |
+ map['d'] = 'D'; |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.current.currentValue, toEqualMapRecord( |
+ map: ['a', 'b[B -> BB]', 'd[null -> D]'], |
+ previous: ['a', 'b[B -> BB]'], |
+ additions: ['d[null -> D]'], |
+ changes: ['b[B -> BB]'], |
+ removals: [])); |
+ |
+ map.remove('b'); |
+ expect(map).toEqual({'a': 'A', 'd':'D'}); |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.current.currentValue, toEqualMapRecord( |
+ map: ['a', 'd'], |
+ previous: ['a', 'b[BB -> null]', 'd'], |
+ additions: [], |
+ changes: [], |
+ removals: ['b[BB -> null]'])); |
+ |
+ map.clear(); |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.current.currentValue, toEqualMapRecord( |
+ map: [], |
+ previous: ['a[A -> null]', 'd[D -> null]'], |
+ additions: [], |
+ changes: [], |
+ removals: ['a[A -> null]', 'd[D -> null]'])); |
+ }); |
+ |
+ it('should test string keys by value rather than by reference', () { |
+ var map = {'foo': 0}; |
+ detector..watch(map, null, null)..collectChanges(); |
+ |
+ map['f' + 'oo'] = 0; |
+ |
+ expect(detector.collectChanges().moveNext()).toEqual(false); |
+ }); |
+ |
+ it('should test string values by value rather than by reference', () { |
+ var map = {'foo': 'bar'}; |
+ detector..watch(map, null, null)..collectChanges(); |
+ |
+ map['foo'] = 'b' + 'ar'; |
+ |
+ expect(detector.collectChanges().moveNext()).toEqual(false); |
+ }); |
+ |
+ it('should not see a NaN value as a change', () { |
+ var map = {'foo': double.NAN}; |
+ var record = detector..watch(map, null, null)..collectChanges(); |
+ |
+ expect(detector.collectChanges().moveNext()).toEqual(false); |
+ }); |
}); |
- }); |
- describe('DuplicateMap', () { |
- DuplicateMap map; |
- beforeEach(() => map = new DuplicateMap()); |
- |
- it('should do basic operations', () { |
- var k1 = 'a'; |
- var r1 = new ItemRecord(k1)..currentIndex = 1; |
- map.put(r1); |
- expect(map.get(k1, 2)).toEqual(null); |
- expect(map.get(k1, 1)).toEqual(null); |
- expect(map.get(k1, 0)).toEqual(r1); |
- expect(map.remove(r1)).toEqual(r1); |
- expect(map.get(k1, -1)).toEqual(null); |
+ describe('function watching', () { |
+ it('should detect no changes when watching a function', () { |
+ var user = new _User('marko', 'vuksanovic', 15); |
+ Iterator changeIterator; |
+ |
+ detector..watch(user, 'isUnderAge', null); |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ expect(changeIterator.moveNext()).toEqual(false); |
+ |
+ user.age = 17; |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(false); |
+ |
+ user.age = 30; |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(false); |
+ }); |
+ |
+ it('should detect change when watching a property function', () { |
+ var user = new _User('marko', 'vuksanovic', 30); |
+ Iterator changeIterator; |
+ |
+ detector..watch(user, 'isUnderAgeAsVariable', null); |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(false); |
+ |
+ user.isUnderAgeAsVariable = () => false; |
+ changeIterator = detector.collectChanges(); |
+ expect(changeIterator.moveNext()).toEqual(true); |
+ }); |
}); |
- it('should do basic operations on duplicate keys', () { |
- var k1 = 'a'; |
- var r1 = new ItemRecord(k1)..currentIndex = 1; |
- var r2 = new ItemRecord(k1)..currentIndex = 2; |
- map..put(r1)..put(r2); |
- expect(map.get(k1, 0)).toEqual(r1); |
- expect(map.get(k1, 1)).toEqual(r2); |
- expect(map.get(k1, 2)).toEqual(null); |
- expect(map.remove(r2)).toEqual(r2); |
- expect(map.get(k1, 0)).toEqual(r1); |
- expect(map.remove(r1)).toEqual(r1); |
- expect(map.get(k1, 0)).toEqual(null); |
+ describe('DuplicateMap', () { |
+ DuplicateMap map; |
+ beforeEach(() => map = new DuplicateMap()); |
+ |
+ it('should do basic operations', () { |
+ var k1 = 'a'; |
+ var r1 = new ItemRecord(k1)..currentIndex = 1; |
+ map.put(r1); |
+ expect(map.get(k1, 2)).toEqual(null); |
+ expect(map.get(k1, 1)).toEqual(null); |
+ expect(map.get(k1, 0)).toEqual(r1); |
+ expect(map.remove(r1)).toEqual(r1); |
+ expect(map.get(k1, -1)).toEqual(null); |
+ }); |
+ |
+ it('should do basic operations on duplicate keys', () { |
+ var k1 = 'a'; |
+ var r1 = new ItemRecord(k1)..currentIndex = 1; |
+ var r2 = new ItemRecord(k1)..currentIndex = 2; |
+ map..put(r1)..put(r2); |
+ expect(map.get(k1, 0)).toEqual(r1); |
+ expect(map.get(k1, 1)).toEqual(r2); |
+ expect(map.get(k1, 2)).toEqual(null); |
+ expect(map.remove(r2)).toEqual(r2); |
+ expect(map.get(k1, 0)).toEqual(r1); |
+ expect(map.remove(r1)).toEqual(r1); |
+ expect(map.get(k1, 0)).toEqual(null); |
+ }); |
}); |
}); |
-}); |
+} |
class _User { |
String first; |
String last; |
num age; |
+ var isUnderAgeAsVariable; |
+ List<String> list = ['foo', 'bar', 'baz']; |
+ Map map = {'foo': 'bar', 'baz': 'cux'}; |
+ |
+ _User([this.first, this.last, this.age]) { |
+ isUnderAgeAsVariable = isUnderAge; |
+ } |
- _User([this.first, this.last, this.age]); |
+ bool isUnderAge() { |
+ return age != null ? age < 18 : false; |
+ } |
} |
-Matcher toEqualCollectionRecord({collection, additions, moves, removals}) => |
- new CollectionRecordMatcher(collection:collection, additions:additions, |
- moves:moves, removals:removals); |
-Matcher toEqualMapRecord({map, additions, changes, removals}) => |
- new MapRecordMatcher(map:map, additions:additions, |
- changes:changes, removals:removals); |
+Matcher toEqualCollectionRecord({collection, previous, additions, moves, removals}) => |
+ new CollectionRecordMatcher(collection:collection, previous: previous, |
+ additions:additions, moves:moves, removals:removals); |
+Matcher toEqualMapRecord({map, previous, additions, changes, removals}) => |
+ new MapRecordMatcher(map:map, previous: previous, |
+ additions:additions, changes:changes, removals:removals); |
Matcher toEqualChanges(List changes) => new ChangeMatcher(changes); |
class ChangeMatcher extends Matcher { |
@@ -484,38 +817,77 @@ class ChangeMatcher extends Matcher { |
Description describe(Description description) => |
description..add(expected.toString()); |
- Description describeMismatch(changes, Description mismatchDescription, |
+ Description describeMismatch(Iterator<Record> changes, |
+ Description mismatchDescription, |
Map matchState, bool verbose) { |
List list = []; |
- while(changes != null) { |
- list.add(changes.handler); |
- changes = changes.nextChange; |
+ while(changes.moveNext()) { |
+ list.add(changes.current.handler); |
} |
return mismatchDescription..add(list.toString()); |
} |
- bool matches(changes, Map matchState) { |
+ bool matches(Iterator<Record> changes, Map matchState) { |
int count = 0; |
- while(changes != null) { |
- if (changes.handler != expected[count++]) return false; |
- changes = changes.nextChange; |
+ while(changes.moveNext()) { |
+ if (changes.current.handler != expected[count++]) return false; |
} |
return count == expected.length; |
} |
} |
-class CollectionRecordMatcher extends Matcher { |
+abstract class _CollectionMatcher<T> extends Matcher { |
+ List<T> _getList(Function it) { |
+ var result = <T>[]; |
+ it((item) { |
+ result.add(item); |
+ }); |
+ return result; |
+ } |
+ |
+ bool _compareLists(String tag, List expected, List actual, List diffs) { |
+ var equals = true; |
+ Iterator iActual = actual.iterator; |
+ iActual.moveNext(); |
+ T actualItem = iActual.current; |
+ if (expected == null) { |
+ expected = []; |
+ } |
+ for (String expectedItem in expected) { |
+ if (actualItem == null) { |
+ equals = false; |
+ diffs.add('$tag too short: $expectedItem'); |
+ } else { |
+ if ("$actualItem" != expectedItem) { |
+ equals = false; |
+ diffs.add('$tag mismatch: $actualItem != $expectedItem'); |
+ } |
+ iActual.moveNext(); |
+ actualItem = iActual.current; |
+ } |
+ } |
+ if (actualItem != null) { |
+ diffs.add('$tag too long: $actualItem'); |
+ equals = false; |
+ } |
+ return equals; |
+ } |
+} |
+ |
+class CollectionRecordMatcher extends _CollectionMatcher<ItemRecord> { |
final List collection; |
+ final List previous; |
final List additions; |
final List moves; |
final List removals; |
- CollectionRecordMatcher({this.collection, this.additions, this.moves, |
- this.removals}); |
+ CollectionRecordMatcher({this.collection, this.previous, |
+ this.additions, this.moves, this.removals}); |
Description describeMismatch(changes, Description mismatchDescription, |
Map matchState, bool verbose) { |
List diffs = matchState['diffs']; |
+ if (diffs == null) return mismatchDescription; |
return mismatchDescription..add(diffs.join('\n')); |
} |
@@ -527,6 +899,7 @@ class CollectionRecordMatcher extends Matcher { |
} |
add('collection', collection); |
+ add('previous', previous); |
add('additions', additions); |
add('moves', moves); |
add('removals', removals); |
@@ -536,119 +909,57 @@ class CollectionRecordMatcher extends Matcher { |
bool matches(CollectionChangeRecord changeRecord, Map matchState) { |
var diffs = matchState['diffs'] = []; |
return checkCollection(changeRecord, diffs) && |
+ checkPrevious(changeRecord, diffs) && |
checkAdditions(changeRecord, diffs) && |
checkMoves(changeRecord, diffs) && |
checkRemovals(changeRecord, diffs); |
} |
bool checkCollection(CollectionChangeRecord changeRecord, List diffs) { |
- var equals = true; |
- if (collection != null) { |
- CollectionItem collectionItem = changeRecord.collectionHead; |
- for (var item in collection) { |
- if (collectionItem == null) { |
- equals = false; |
- diffs.add('collection too short: $item'); |
- } else { |
- if (collectionItem.toString() != item) { |
- equals = false; |
- diffs.add('collection mismatch: $collectionItem != $item'); |
- } |
- collectionItem = collectionItem.nextCollectionItem; |
- } |
- } |
- if (collectionItem != null) { |
- diffs.add('collection too long: $collectionItem'); |
- equals = false; |
- } |
+ List items = _getList((fn) => changeRecord.forEachItem(fn)); |
+ bool equals = _compareLists("collection", collection, items, diffs); |
+ int iterableLength = changeRecord.iterable.toList().length; |
+ if (iterableLength != items.length) { |
+ diffs.add('collection length mismatched: $iterableLength != ${items.length}'); |
+ equals = false; |
} |
return equals; |
} |
+ bool checkPrevious(CollectionChangeRecord changeRecord, List diffs) { |
+ List items = _getList((fn) => changeRecord.forEachPreviousItem(fn)); |
+ return _compareLists("previous", previous, items, diffs); |
+ } |
+ |
bool checkAdditions(CollectionChangeRecord changeRecord, List diffs) { |
- var equals = true; |
- if (additions != null) { |
- AddedItem addedItem = changeRecord.additionsHead; |
- for (var item in additions) { |
- if (addedItem == null) { |
- equals = false; |
- diffs.add('additions too short: $item'); |
- } else { |
- if (addedItem.toString() != item) { |
- equals = false; |
- diffs.add('additions mismatch: $addedItem != $item'); |
- } |
- addedItem = addedItem.nextAddedItem; |
- } |
- } |
- if (addedItem != null) { |
- equals = false; |
- diffs.add('additions too long: $addedItem'); |
- } |
- } |
- return equals; |
+ List items = _getList((fn) => changeRecord.forEachAddition(fn)); |
+ return _compareLists("additions", additions, items, diffs); |
} |
bool checkMoves(CollectionChangeRecord changeRecord, List diffs) { |
- var equals = true; |
- if (moves != null) { |
- MovedItem movedItem = changeRecord.movesHead; |
- for (var item in moves) { |
- if (movedItem == null) { |
- equals = false; |
- diffs.add('moves too short: $item'); |
- } else { |
- if (movedItem.toString() != item) { |
- equals = false; |
- diffs.add('moves too mismatch: $movedItem != $item'); |
- } |
- movedItem = movedItem.nextMovedItem; |
- } |
- } |
- if (movedItem != null) { |
- equals = false; |
- diffs.add('moves too long: $movedItem'); |
- } |
- } |
- return equals; |
+ List items = _getList((fn) => changeRecord.forEachMove(fn)); |
+ return _compareLists("moves", moves, items, diffs); |
} |
bool checkRemovals(CollectionChangeRecord changeRecord, List diffs) { |
- var equals = true; |
- if (removals != null) { |
- RemovedItem removedItem = changeRecord.removalsHead; |
- for (var item in removals) { |
- if (removedItem == null) { |
- equals = false; |
- diffs.add('removes too short: $item'); |
- } else { |
- if (removedItem.toString() != item) { |
- equals = false; |
- diffs.add('removes too mismatch: $removedItem != $item'); |
- } |
- removedItem = removedItem.nextRemovedItem; |
- } |
- } |
- if (removedItem != null) { |
- equals = false; |
- diffs.add('removes too long: $removedItem'); |
- } |
- } |
- return equals; |
+ List items = _getList((fn) => changeRecord.forEachRemoval(fn)); |
+ return _compareLists("removes", removals, items, diffs); |
} |
} |
-class MapRecordMatcher extends Matcher { |
+class MapRecordMatcher extends _CollectionMatcher<KeyValueRecord> { |
final List map; |
+ final List previous; |
final List additions; |
final List changes; |
final List removals; |
- MapRecordMatcher({this.map, this.additions, this.changes, this.removals}); |
+ MapRecordMatcher({this.map, this.previous, this.additions, this.changes, this.removals}); |
Description describeMismatch(changes, Description mismatchDescription, |
Map matchState, bool verbose) { |
List diffs = matchState['diffs']; |
+ if (diffs == null) return mismatchDescription; |
return mismatchDescription..add(diffs.join('\n')); |
} |
@@ -660,6 +971,7 @@ class MapRecordMatcher extends Matcher { |
} |
add('map', map); |
+ add('previous', previous); |
add('additions', additions); |
add('changes', changes); |
add('removals', removals); |
@@ -669,104 +981,59 @@ class MapRecordMatcher extends Matcher { |
bool matches(MapChangeRecord changeRecord, Map matchState) { |
var diffs = matchState['diffs'] = []; |
return checkMap(changeRecord, diffs) && |
+ checkPrevious(changeRecord, diffs) && |
checkAdditions(changeRecord, diffs) && |
checkChanges(changeRecord, diffs) && |
checkRemovals(changeRecord, diffs); |
} |
bool checkMap(MapChangeRecord changeRecord, List diffs) { |
- var equals = true; |
- if (map != null) { |
- KeyValue mapKeyValue = changeRecord.mapHead; |
- for (var item in map) { |
- if (mapKeyValue == null) { |
- equals = false; |
- diffs.add('map too short: $item'); |
- } else { |
- if (mapKeyValue.toString() != item) { |
- equals = false; |
- diffs.add('map mismatch: $mapKeyValue != $item'); |
- } |
- mapKeyValue = mapKeyValue.nextKeyValue; |
- } |
- } |
- if (mapKeyValue != null) { |
- diffs.add('map too long: $mapKeyValue'); |
- equals = false; |
- } |
+ List items = _getList((fn) => changeRecord.forEachItem(fn)); |
+ bool equals = _compareLists("map", map, items, diffs); |
+ int mapLength = changeRecord.map.length; |
+ if (mapLength != items.length) { |
+ diffs.add('map length mismatched: $mapLength != ${items.length}'); |
+ equals = false; |
} |
return equals; |
} |
+ bool checkPrevious(MapChangeRecord changeRecord, List diffs) { |
+ List items = _getList((fn) => changeRecord.forEachPreviousItem(fn)); |
+ return _compareLists("previous", previous, items, diffs); |
+ } |
+ |
bool checkAdditions(MapChangeRecord changeRecord, List diffs) { |
- var equals = true; |
- if (additions != null) { |
- AddedKeyValue addedKeyValue = changeRecord.additionsHead; |
- for (var item in additions) { |
- if (addedKeyValue == null) { |
- equals = false; |
- diffs.add('additions too short: $item'); |
- } else { |
- if (addedKeyValue.toString() != item) { |
- equals = false; |
- diffs.add('additions mismatch: $addedKeyValue != $item'); |
- } |
- addedKeyValue = addedKeyValue.nextAddedKeyValue; |
- } |
- } |
- if (addedKeyValue != null) { |
- equals = false; |
- diffs.add('additions too long: $addedKeyValue'); |
- } |
- } |
- return equals; |
+ List items = _getList((fn) => changeRecord.forEachAddition(fn)); |
+ return _compareLists("additions", additions, items, diffs); |
} |
bool checkChanges(MapChangeRecord changeRecord, List diffs) { |
- var equals = true; |
- if (changes != null) { |
- ChangedKeyValue movedKeyValue = changeRecord.changesHead; |
- for (var item in changes) { |
- if (movedKeyValue == null) { |
- equals = false; |
- diffs.add('changes too short: $item'); |
- } else { |
- if (movedKeyValue.toString() != item) { |
- equals = false; |
- diffs.add('changes too mismatch: $movedKeyValue != $item'); |
- } |
- movedKeyValue = movedKeyValue.nextChangedKeyValue; |
- } |
- } |
- if (movedKeyValue != null) { |
- equals = false; |
- diffs.add('changes too long: $movedKeyValue'); |
- } |
- } |
- return equals; |
+ List items = _getList((fn) => changeRecord.forEachChange(fn)); |
+ return _compareLists("changes", changes, items, diffs); |
} |
bool checkRemovals(MapChangeRecord changeRecord, List diffs) { |
- var equals = true; |
- if (removals != null) { |
- RemovedKeyValue removedKeyValue = changeRecord.removalsHead; |
- for (var item in removals) { |
- if (removedKeyValue == null) { |
- equals = false; |
- diffs.add('rechanges too short: $item'); |
- } else { |
- if (removedKeyValue.toString() != item) { |
- equals = false; |
- diffs.add('rechanges too mismatch: $removedKeyValue != $item'); |
- } |
- removedKeyValue = removedKeyValue.nextRemovedKeyValue; |
- } |
- } |
- if (removedKeyValue != null) { |
- equals = false; |
- diffs.add('rechanges too long: $removedKeyValue'); |
- } |
- } |
- return equals; |
+ List items = _getList((fn) => changeRecord.forEachRemoval(fn)); |
+ return _compareLists("removals", removals, items, diffs); |
} |
} |
+ |
+ |
+class FooBar { |
+ static int fooIds = 0; |
+ |
+ int id; |
+ String foo, bar; |
+ |
+ FooBar(this.foo, this.bar) { |
+ id = fooIds++; |
+ } |
+ |
+ bool operator==(other) => |
+ other is FooBar && foo == other.foo && bar == other.bar; |
+ |
+ int get hashCode => foo.hashCode ^ bar.hashCode; |
+ |
+ String toString() => '($id)$foo-$bar'; |
+} |