diff --git a/lib/change_detection/dirty_checking_change_detector.dart b/lib/change_detection/dirty_checking_change_detector.dart index 3236122da..e5eb196bd 100644 --- a/lib/change_detection/dirty_checking_change_detector.dart +++ b/lib/change_detection/dirty_checking_change_detector.dart @@ -462,6 +462,7 @@ class DirtyCheckingRecord implements Record, WatchRecord { } else { _mode = _MODE_GETTER_; _getter = _fieldGetterFactory.getter(obj, field); + currentValue = _getter(obj); } } @@ -489,7 +490,11 @@ class DirtyCheckingRecord implements Record, WatchRecord { } var last = currentValue; - if (!identical(last, current)) { + // We use == to check if previous and current value are equal in case we're dealing with + // methods because + // var a = method1(); var b = method1(); + // (a == b) == true, while identical(a, b) == false + if (current is Function ? last != current : !identical(last, current)) { if (last is String && current is String && last == current) { // This is false change in strings we need to recover, and pretend it diff --git a/lib/change_detection/dirty_checking_change_detector_dynamic.dart b/lib/change_detection/dirty_checking_change_detector_dynamic.dart index fefdfd589..bcb60a34a 100644 --- a/lib/change_detection/dirty_checking_change_detector_dynamic.dart +++ b/lib/change_detection/dirty_checking_change_detector_dynamic.dart @@ -14,11 +14,12 @@ class DynamicFieldGetterFactory implements FieldGetterFactory { final isMethodInvoke = true; bool isMethod(Object object, String name) { - try { - return method(object, name) != null; - } catch (e, s) { - return false; - } + InstanceMirror im = reflect(object); + Map methods = {}; + im.type.instanceMembers.forEach((Symbol symbol, MethodMirror methodMirror) { + if(methodMirror.isRegularMethod) methods[symbol] = methodMirror; + }); + return methods.containsKey(new Symbol(name)); } Function method(Object object, String name) { diff --git a/test/change_detection/dirty_checking_change_detector_spec.dart b/test/change_detection/dirty_checking_change_detector_spec.dart index 3ef529ae4..a4cf5e271 100644 --- a/test/change_detection/dirty_checking_change_detector_spec.dart +++ b/test/change_detection/dirty_checking_change_detector_spec.dart @@ -4,6 +4,7 @@ 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'; import 'dart:math'; @@ -14,7 +15,9 @@ void main() { "first": (o) => o.first, "age": (o) => o.age, "last": (o) => o.last, - "toString": (o) => o.toString + "toString": (o) => o.toString, + "isUnderAge": (o) => o.isUnderAge, + "isUnderAgeAsVariable": (o) => o.isUnderAgeAsVariable }); beforeEach(() { @@ -22,11 +25,70 @@ void main() { }); describe('StaticFieldGetterFactory', () { + DirtyCheckingChangeDetector 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(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); + }); + }); + + describe('Dynamic GetterFactory', () { + DirtyCheckingChangeDetector detector; + var user = new _User('Marko', 'Vuksanovic', 30); + FieldGetterFactory getterFactory = new DynamicFieldGetterFactory(); + + beforeEach(() { + detector = new DirtyCheckingChangeDetector(getterFactory); + }); + + 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); + }); }); describe('object field', () { @@ -657,6 +719,38 @@ void main() { }); }); + 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(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); + }); + }); + describe('DuplicateMap', () { DuplicateMap map; beforeEach(() => map = new DuplicateMap()); @@ -693,8 +787,17 @@ class _User { String first; String last; num age; + var isUnderAgeAsVariable; + List list = ['foo', 'bar', 'baz']; + Map map = {'foo': 'bar', 'baz': 'cux'}; - _User([this.first, this.last, this.age]); + _User([this.first, this.last, this.age]) { + isUnderAgeAsVariable = isUnderAge; + } + + bool isUnderAge() { + return age != null ? age < 18 : false; + } } Matcher toEqualCollectionRecord({collection, previous, additions, moves, removals}) => diff --git a/test/directive/ng_repeat_spec.dart b/test/directive/ng_repeat_spec.dart index 74d3eb366..e90ab55ee 100644 --- a/test/directive/ng_repeat_spec.dart +++ b/test/directive/ng_repeat_spec.dart @@ -116,6 +116,16 @@ main() { expect(element.querySelectorAll('span').length).toEqual(2); }); + it('should support function as a formatter', () { + scope.context['isEven'] = (num) => num % 2 == 0; + var element = compile( + '
' + '{{r}}' + '
'); + scope.apply(); + expect(element.text).toEqual('24'); + }); + describe('track by', () { it(r'should track using expression function', () {