Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit 4c3d7ee

Browse files
committed
fix(Dirty Checking): fix watching methods/closures
- BUG: DynamicFieldGetterFactory::isMethod did not handle methods defined in superclasses. - BUG: Upon detecting a method, the code assumed that you would only invoke it. This broke application code that watched a method (e.g. by way of Component mapping) just to get the closurized value and store it somewhere to invoke later (test case and stack trace at end of this commit message.) - BUG: StaticFieldGetterFactory::method() and DynamicFieldGetterFactory::method() differed.  There was no difference between StaticFieldGetterFactory::method() and StaticFieldGetterFactory::getter().  DynamicFieldGetterFactory::method(), as mentioned before, assumed that the only thing you could do with it was to invoke it (i.e. not a leaf watch.) - There was very little testing for StaticFieldGetterFactory.  This meant that, though it was out of sync with DynamicFieldGetterFactory, no tests were failing. Changes in this commit: - run the same tests against StaticFieldGetterFactory that are run against DynamicFieldGetterFactory - do not call the result of GetterFactory.method() in "set object(value)" - reduce the difference between the two different factories.  GetterFactory now only has one method in it's interface definition - the getter function.  `_MODE_METHOD_INVOKE_`, `isMethod`, `isMethodInvoke`, etc. are gone. **Bug Details:** Refer to the repro case at chirayuk/angular.dart@issue_999^...issue_999 ```dart // Given this object. class Foo { bar(x) => x+1; } // This test case (in an appropriate file like `scope_spec.dart`) fails // with a traceback it('should watch closures', (RootScope rootScope, Logger log) { rootScope.context['foo'] = new Foo(); rootScope.context['func'] = null; rootScope.watch('foo.bar', (v, _) { rootScope.context['func'] = v; }); rootScope.watch('func(1)', (v, o) => log([v, o])); rootScope.apply(); expect(log).toEqual([[null, null], [2, null]]); }); ``` **Stack Trace:** Chrome 34.0.1847 (Mac OS X 10.9.2) scope watch/digest should watch closures FAILED Test failed: Caught Closure call with mismatched arguments: function 'DynamicFieldGetterFactory.method.<anonymous closure>' NoSuchMethodError: incorrect number of arguments passed to method named 'DynamicFieldGetterFactory.method.<anonymous closure>' Receiver: Closure: (List, Map) => dynamic Tried calling: DynamicFieldGetterFactory.method.<anonymous closure>(Instance of 'Foo') Found: DynamicFieldGetterFactory.method.<anonymous closure>(args, namedArgs) #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:45) #1 DirtyCheckingRecord.object= (package:angular/change_detection/dirty_checking_change_detector.dart:465:78) #2 _FieldHandler.acceptValue (package:angular/change_detection/watch_group.dart:630:17) #3 WatchGroup.addFieldWatch (package:angular/change_detection/watch_group.dart:171:29) #4 FieldReadAST.setupWatch (package:angular/change_detection/ast.dart:67:31) #5 WatchGroup.watch.<anonymous closure> (package:angular/change_detection/watch_group.dart:144:40) #6 _HashMap.putIfAbsent (dart:collection-patch/collection_patch.dart:124) #7 WatchGroup.watch (package:angular/change_detection/watch_group.dart:143:27) #8 Scope.watch (package:angular/core/scope.dart:240:31) #9 main.<anonymous closure>.<anonymous closure>.<anonymous closure> (/Users/chirayu/work/angular.dart/test/core/scope_spec.dart:1308:24) #10 _LocalInstanceMirror._invoke (dart:mirrors-patch/mirrors_impl.dart:440) #11 _LocalInstanceMirror.invoke (dart:mirrors-patch/mirrors_impl.dart:436) #12 _LocalClosureMirror.apply (dart:mirrors-patch/mirrors_impl.dart:466) #13 DynamicInjector.invoke (package:di/dynamic_injector.dart:97:20) #14 _SpecInjector.inject (package:angular/mock/test_injection.dart:58:22) #15 inject.<anonymous closure> (package:angular/mock/test_injection.dart:100:44) #16 _rootRun (dart:async/zone.dart:723) #17 _ZoneDelegate.run (dart:async/zone.dart:453) #18 _CustomizedZone.run (dart:async/zone.dart:663) #19 runZoned (dart:async/zone.dart:954) #20 _syncOuter.<anonymous closure> (package:angular/mock/zone.dart:227:22) #21 _withSetup.<anonymous closure> (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:13:14) #22 _run.<anonymous closure> (package:unittest/src/test_case.dart:102:27) #23 _rootRunUnary (dart:async/zone.dart:730) #24 _ZoneDelegate.runUnary (dart:async/zone.dart:462) #25 _CustomizedZone.runUnary (dart:async/zone.dart:667) #26 _Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:488) #27 _Future._propagateToListeners (dart:async/future_impl.dart:571) #28 _Future._completeWithValue (dart:async/future_impl.dart:331) #29 _Future._asyncComplete.<anonymous closure> (dart:async/future_impl.dart:393) #30 _rootRun (dart:async/zone.dart:723) #31 _ZoneDelegate.run (dart:async/zone.dart:453) #32 _CustomizedZone.run (dart:async/zone.dart:663) #33 _BaseZone.runGuarded (dart:async/zone.dart:574) #34 _BaseZone.bindCallback.<anonymous closure> (dart:async/zone.dart:599) #35 _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:23) #36 _asyncRunCallback (dart:async/schedule_microtask.dart:32) #37 _handleMutation (file:///Volumes/data/b/build/slave/dartium-mac-full-dev/build/src/dart/tools/dom/src/native_DOMImplementation.dart:588) DECLARED AT:#0 inject (package:angular/mock/test_injection.dart:97:5) #1 _injectify (/Users/chirayu/work/angular.dart/test/_specs.dart:236:25) #2 iit (/Users/chirayu/work/angular.dart/test/_specs.dart:244:53) #3 main.<anonymous closure>.<anonymous closure> (/Users/chirayu/work/angular.dart/test/core/scope_spec.dart:1305:10) #4 describe.<anonymous closure> (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:62:9) #5 group (package:unittest/unittest.dart:396:9) #6 describe (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:60:15) #7 describe (/Users/chirayu/work/angular.dart/test/_specs.dart:248:46) #8 main.<anonymous closure> (/Users/chirayu/work/angular.dart/test/core/scope_spec.dart:1009:13) #9 describe.<anonymous closure> (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:62:9) #10 group (package:unittest/unittest.dart:396:9) #11 describe (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:60:15) #12 describe (/Users/chirayu/work/angular.dart/test/_specs.dart:248:46) #13 main (/Users/chirayu/work/angular.dart/test/core/scope_spec.dart:14:11) #14 main.<anonymous closure> (http://localhost:8765/__adapter_dart_unittest.dart:169:15) #15 _rootRun (dart:async/zone.dart:723) #16 _ZoneDelegate.run (dart:async/zone.dart:453) #17 _CustomizedZone.run (dart:async/zone.dart:663) #18 runZoned (dart:async/zone.dart:954) #19 main (http://localhost:8765/__adapter_dart_unittest.dart:146:11) #0 _SpecInjector.inject (package:angular/mock/test_injection.dart:60:7) #1 inject.<anonymous closure> (package:angular/mock/test_injection.dart:100:44) #2 _rootRun (dart:async/zone.dart:723) #3 _rootRun (dart:async/zone.dart:724) #4 _rootRun (dart:async/zone.dart:724) #5 _ZoneDelegate.run (dart:async/zone.dart:453) #6 _CustomizedZone.run (dart:async/zone.dart:663) #7 runZoned (dart:async/zone.dart:954) #8 _syncOuter.<anonymous closure> (package:angular/mock/zone.dart:227:22) #9 _withSetup.<anonymous closure> (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:13:14) #10 _withSetup.<anonymous closure> (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:14:5) #11 _withSetup.<anonymous closure> (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:14:5) #12 _run.<anonymous closure> (package:unittest/src/test_case.dart:102:27) #13 _rootRunUnary (dart:async/zone.dart:730) #14 _ZoneDelegate.runUnary (dart:async/zone.dart:462) #15 _CustomizedZone.runUnary (dart:async/zone.dart:667) #16 _Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:488) #17 _Future._propagateToListeners (dart:async/future_impl.dart:571) #18 _Future._completeWithValue (dart:async/future_impl.dart:331) #19 _Future._asyncComplete.<anonymous closure> (dart:async/future_impl.dart:393) #20 _rootRun (dart:async/zone.dart:723) #21 _ZoneDelegate.run (dart:async/zone.dart:453) #22 _CustomizedZone.run (dart:async/zone.dart:663) #23 _BaseZone.runGuarded (dart:async/zone.dart:574) #24 _BaseZone.bindCallback.<anonymous closure> (dart:async/zone.dart:599) #25 _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:23) #26 _asyncRunCallback (dart:async/schedule_microtask.dart:32) #27 _handleMutation (file:///Volumes/data/b/build/slave/dartium-mac-full-dev/build/src/dart/tools/dom/src/native_DOMImplementation.dart:588) Closes #999
1 parent 50fc615 commit 4c3d7ee

7 files changed

+87
-150
lines changed

lib/change_detection/change_detection.dart

-3
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,6 @@ typedef dynamic FieldGetter(object);
174174
typedef void FieldSetter(object, value);
175175

176176
abstract class FieldGetterFactory {
177-
get isMethodInvoke;
178-
bool isMethod(Object object, String name);
179-
Function method(Object object, String name);
180177
FieldGetter getter(Object object, String name);
181178
}
182179

lib/change_detection/dirty_checking_change_detector.dart

+20-9
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,8 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
392392
DirtyCheckingRecord<H> _prevRecord;
393393
Record<H> _nextChange;
394394
var _object;
395+
var _identityValue;
396+
bool _checkForVaryingClosure = false;
395397
FieldGetter _getter;
396398

397399
DirtyCheckingRecord(this._group, this._fieldGetterFactory, this.handler,
@@ -418,6 +420,7 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
418420
_object = obj;
419421
if (obj == null) {
420422
_mode = _MODE_IDENTITY_;
423+
_identityValue = null;
421424
_getter = null;
422425
return;
423426
}
@@ -451,6 +454,7 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
451454
}
452455
} else {
453456
_mode = _MODE_IDENTITY_;
457+
_identityValue = obj;
454458
}
455459

456460
return;
@@ -460,14 +464,9 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
460464
_mode = _MODE_MAP_FIELD_;
461465
_getter = null;
462466
} else {
463-
if (_fieldGetterFactory.isMethod(obj, field)) {
464-
_mode = _MODE_IDENTITY_;
465-
previousValue = currentValue = _fieldGetterFactory.method(obj, field)(obj);
466-
assert(previousValue is Function);
467-
} else {
468-
_mode = _MODE_GETTER_;
469-
_getter = _fieldGetterFactory.getter(obj, field);
470-
}
467+
_mode = _MODE_GETTER_;
468+
_checkForVaryingClosure = true;
469+
_getter = _fieldGetterFactory.getter(obj, field);
471470
}
472471
}
473472

@@ -479,12 +478,24 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
479478
return false;
480479
case _MODE_GETTER_:
481480
current = _getter(object);
481+
if (_checkForVaryingClosure) {
482+
_checkForVaryingClosure = false;
483+
// NOTE: Method as handled as _MODE_IDENTITY_. When Dart looks up a
484+
// method "foo" on object "x", it returns a new closure for each lookup.
485+
// They compare equal via "==" but are no identical(). There's no point
486+
// getting a new value each time and decide it's the same so we'll skip
487+
// further checking after the first time.
488+
if (current is Function && !identical(current, _getter(object))) {
489+
_mode = _MODE_IDENTITY_;
490+
_identityValue = current;
491+
}
492+
}
482493
break;
483494
case _MODE_MAP_FIELD_:
484495
current = object[field];
485496
break;
486497
case _MODE_IDENTITY_:
487-
current = object;
498+
current = _identityValue;
488499
break;
489500
case _MODE_MAP_:
490501
return (currentValue as _MapChangeRecord)._check(object);

lib/change_detection/dirty_checking_change_detector_dynamic.dart

+1-17
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,9 @@ export 'package:angular/change_detection/change_detection.dart' show
1111
import 'dart:mirrors';
1212

1313
class DynamicFieldGetterFactory implements FieldGetterFactory {
14-
final isMethodInvoke = true;
15-
16-
isMethod(Object object, String name) {
17-
var declaration = reflectClass(object.runtimeType).declarations[new Symbol(name)];
18-
return declaration != null &&
19-
declaration is MethodMirror &&
20-
(declaration as MethodMirror).isRegularMethod;
21-
}
22-
23-
Function method(Object object, String name) {
24-
Symbol symbol = new Symbol(name);
25-
InstanceMirror instanceMirror = reflect(object);
26-
return (List args, Map namedArgs) =>
27-
instanceMirror.invoke(symbol, args, namedArgs).reflectee;
28-
}
29-
3014
FieldGetter getter(Object object, String name) {
3115
Symbol symbol = new Symbol(name);
3216
InstanceMirror instanceMirror = reflect(object);
33-
return (Object object) => instanceMirror.getField(symbol).reflectee;
17+
return (Object object) => instanceMirror.getField(symbol).reflectee;
3418
}
3519
}

lib/change_detection/dirty_checking_change_detector_static.dart

-17
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,13 @@ library dirty_checking_change_detector_static;
33
import 'package:angular/change_detection/change_detection.dart';
44

55
class StaticFieldGetterFactory implements FieldGetterFactory {
6-
final isMethodInvoke = false;
76
Map<String, FieldGetter> getters;
87

98
StaticFieldGetterFactory(this.getters);
109

11-
bool isMethod(Object object, String name) {
12-
// We need to know if we are referring to method or field which is a
13-
// function. We can find out by calling it twice and seeing if we get
14-
// the same value. Methods create a new closure each time.
15-
FieldGetter getterFn = getter(object, name);
16-
dynamic property = getterFn(object);
17-
return (property is Function) &&
18-
(!identical(property, getterFn(object)));
19-
}
20-
2110
FieldGetter getter(Object object, String name) {
2211
var getter = getters[name];
2312
if (getter == null) throw "Missing getter: (o) => o.$name";
2413
return getter;
2514
}
26-
27-
Function method(Object object, String name) {
28-
var method = getters[name];
29-
if (method == null) throw "Missing method: $name";
30-
return method;
31-
}
3215
}

lib/change_detection/watch_group.dart

+29-25
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ typedef void ChangeLog(String expression, current, previous);
2626
* number of arguments with which the function will get called with.
2727
*/
2828
abstract class FunctionApply {
29-
// dartbug.com/16401
30-
// dynamic call() { throw new StateError('Use apply()'); }
29+
dynamic call() { throw new StateError('Use apply()'); }
3130
dynamic apply(List arguments);
3231
}
3332

@@ -198,7 +197,7 @@ class WatchGroup implements _EvalWatchList, _WatchGroupList {
198197
* - [isPure] A pure function is one which holds no internal state. This implies that the
199198
* function is idempotent.
200199
*/
201-
_EvalWatchRecord addFunctionWatch(/* dartbug.com/16401 Function */ fn, List<AST> argsAST,
200+
_EvalWatchRecord addFunctionWatch(Function fn, List<AST> argsAST,
202201
Map<Symbol, AST> namedArgsAST,
203202
String expression, bool isPure) =>
204203
_addEvalWatch(null, fn, null, argsAST, namedArgsAST, expression, isPure);
@@ -214,11 +213,11 @@ class WatchGroup implements _EvalWatchList, _WatchGroupList {
214213
_EvalWatchRecord addMethodWatch(AST lhs, String name, List<AST> argsAST,
215214
Map<Symbol, AST> namedArgsAST,
216215
String expression) =>
217-
_addEvalWatch(lhs, null, name, argsAST, namedArgsAST, expression, false);
216+
_addEvalWatch(lhs, null, name, argsAST, namedArgsAST, expression, false);
218217

219218

220219

221-
_EvalWatchRecord _addEvalWatch(AST lhsAST, /* dartbug.com/16401 Function */ fn, String name,
220+
_EvalWatchRecord _addEvalWatch(AST lhsAST, Function fn, String name,
222221
List<AST> argsAST,
223222
Map<Symbol, AST> namedArgsAST,
224223
String expression, bool isPure) {
@@ -722,19 +721,19 @@ class _EvalWatchRecord implements WatchRecord<_Handler> {
722721
static const int _MODE_FUNCTION_ = 2;
723722
static const int _MODE_PURE_FUNCTION_APPLY_ = 3;
724723
static const int _MODE_NULL_ = 4;
725-
static const int _MODE_FIELD_CLOSURE_ = 5;
726-
static const int _MODE_MAP_CLOSURE_ = 6;
727-
static const int _MODE_METHOD_ = 7;
728-
static const int _MODE_METHOD_INVOKE_ = 8;
724+
static const int _MODE_METHOD_ = 5;
725+
static const int _MODE_FIELD_CLOSURE_ = 6;
726+
static const int _MODE_MAP_CLOSURE_ = 7;
729727
WatchGroup watchGrp;
730728
final _Handler handler;
731729
final List args;
732730
final Map<Symbol, dynamic> namedArgs = new Map<Symbol, dynamic>();
733731
final String name;
734732
int mode;
735-
/* dartbug.com/16401 Function*/ var fn;
733+
Function fn;
736734
FieldGetterFactory _fieldGetterFactory;
737735
bool dirtyArgs = true;
736+
bool _checkForVaryingClosure = false;
738737

739738
dynamic currentValue, previousValue, _object;
740739
_EvalWatchRecord _prevEvalWatch, _nextEvalWatch;
@@ -789,13 +788,9 @@ class _EvalWatchRecord implements WatchRecord<_Handler> {
789788
if (value is Map) {
790789
mode = _MODE_MAP_CLOSURE_;
791790
} else {
792-
if (_fieldGetterFactory.isMethod(value, name)) {
793-
mode = _fieldGetterFactory.isMethodInvoke ? _MODE_METHOD_INVOKE_ : _MODE_METHOD_;
794-
fn = _fieldGetterFactory.method(value, name);
795-
} else {
796-
mode = _MODE_FIELD_CLOSURE_;
797-
fn = _fieldGetterFactory.getter(value, name);
798-
}
791+
mode = _MODE_FIELD_CLOSURE_;
792+
fn = _fieldGetterFactory.getter(value, name);
793+
_checkForVaryingClosure = true;
799794
}
800795
}
801796
}
@@ -820,19 +815,28 @@ class _EvalWatchRecord implements WatchRecord<_Handler> {
820815
value = (fn as FunctionApply).apply(args);
821816
dirtyArgs = false;
822817
break;
818+
case _MODE_METHOD_:
819+
value = Function.apply(fn, args, namedArgs);
820+
break;
823821
case _MODE_FIELD_CLOSURE_:
824822
var closure = fn(_object);
825-
value = closure == null ? null : Function.apply(closure, args, namedArgs);
823+
if (_checkForVaryingClosure) {
824+
_checkForVaryingClosure = false;
825+
// NOTE: Method as handled as _MODE_IDENTITY_. When Dart looks up a
826+
// method "foo" on object "x", it returns a new closure for each lookup.
827+
// They compare equal via "==" but are no identical(). There's no point
828+
// getting a new value each time and decide it's the same so we'll skip
829+
// further checking after the first time.
830+
if (closure is Function && !identical(closure, fn(_object))) {
831+
mode = _MODE_METHOD_;
832+
fn = closure;
833+
}
834+
}
835+
value = (closure == null) ? null : Function.apply(closure, args, namedArgs);
826836
break;
827837
case _MODE_MAP_CLOSURE_:
828838
var closure = object[name];
829-
value = closure == null ? null : Function.apply(closure, args, namedArgs);
830-
break;
831-
case _MODE_METHOD_:
832-
value = Function.apply(fn, args, namedArgs);
833-
break;
834-
case _MODE_METHOD_INVOKE_:
835-
value = fn(args, namedArgs);
839+
value = (closure == null) ? null : Function.apply(closure, args, namedArgs);
836840
break;
837841
default:
838842
assert(false);

test/change_detection/dirty_checking_change_detector_spec.dart

+17-78
Original file line numberDiff line numberDiff line change
@@ -8,89 +8,14 @@ import 'package:angular/change_detection/dirty_checking_change_detector_dynamic.
88
import 'dart:collection';
99
import 'dart:math';
1010

11-
void main() {
12-
describe('DirtyCheckingChangeDetector', () {
11+
void testWithGetterFactory(FieldGetterFactory getterFactory) {
12+
describe('DirtyCheckingChangeDetector with ${getterFactory.runtimeType}', () {
1313
DirtyCheckingChangeDetector<String> detector;
14-
FieldGetterFactory getterFactory = new StaticFieldGetterFactory({
15-
"first": (o) => o.first,
16-
"age": (o) => o.age,
17-
"last": (o) => o.last,
18-
"toString": (o) => o.toString,
19-
"isUnderAge": (o) => o.isUnderAge,
20-
"isUnderAgeAsVariable": (o) => o.isUnderAgeAsVariable
21-
});
2214

2315
beforeEach(() {
2416
detector = new DirtyCheckingChangeDetector<String>(getterFactory);
2517
});
2618

27-
describe('StaticFieldGetterFactory', () {
28-
DirtyCheckingChangeDetector<String> detector;
29-
var user = new _User('Marko', 'Vuksanovic', 30);
30-
FieldGetterFactory getterFactory = new StaticFieldGetterFactory({
31-
"first": (o) => o.first,
32-
"age": (o) => o.age,
33-
"last": (o) => o.last,
34-
"toString": (o) => o.toString,
35-
"isUnderAge": (o) => o.isUnderAge,
36-
"isUnderAgeAsVariable": (o) => o.isUnderAgeAsVariable,
37-
"list": (o) => o.list,
38-
"map": (o) => o.map
39-
});
40-
41-
beforeEach(() {
42-
detector = new DirtyCheckingChangeDetector<String>(getterFactory);
43-
});
44-
45-
it('should detect methods', () {
46-
var obj = new _User();
47-
expect(getterFactory.isMethod(obj, 'toString')).toEqual(true);
48-
expect(getterFactory.isMethod(obj, 'age')).toEqual(false);
49-
});
50-
51-
it('should return true is method is real method', () {
52-
expect(getterFactory.isMethod(user, 'isUnderAge')).toEqual(true);
53-
});
54-
55-
it('should return false is field is a function', () {
56-
expect(getterFactory.isMethod(user, 'isUnderAgeAsVariable')).toEqual(false);
57-
});
58-
59-
it('should return false is field is a list', () {
60-
expect(getterFactory.isMethod(user, 'list')).toEqual(false);
61-
});
62-
63-
it('should return false is field is a map', () {
64-
expect(getterFactory.isMethod(user, 'map')).toEqual(false);
65-
});
66-
});
67-
68-
describe('Dynamic GetterFactory', () {
69-
DirtyCheckingChangeDetector<String> detector;
70-
var user = new _User('Marko', 'Vuksanovic', 30);
71-
FieldGetterFactory getterFactory = new DynamicFieldGetterFactory();
72-
73-
beforeEach(() {
74-
detector = new DirtyCheckingChangeDetector<String>(getterFactory);
75-
});
76-
77-
it('should return true is method is real method', () {
78-
expect(getterFactory.isMethod(user, 'isUnderAge')).toEqual(true);
79-
});
80-
81-
it('should return false is field is a function', () {
82-
expect(getterFactory.isMethod(user, 'isUnderAgeAsVariable')).toEqual(false);
83-
});
84-
85-
it('should return false is field is a list', () {
86-
expect(getterFactory.isMethod(user, 'list')).toEqual(false);
87-
});
88-
89-
it('should return false is field is a map', () {
90-
expect(getterFactory.isMethod(user, 'map')).toEqual(false);
91-
});
92-
});
93-
9419
describe('object field', () {
9520
it('should detect nothing', () {
9621
var changes = detector.collectChanges();
@@ -787,6 +712,21 @@ void main() {
787712
});
788713
}
789714

715+
716+
void main() {
717+
testWithGetterFactory(new DynamicFieldGetterFactory());
718+
719+
testWithGetterFactory(new StaticFieldGetterFactory({
720+
"first": (o) => o.first,
721+
"age": (o) => o.age,
722+
"last": (o) => o.last,
723+
"toString": (o) => o.toString,
724+
"isUnderAge": (o) => o.isUnderAge,
725+
"isUnderAgeAsVariable": (o) => o.isUnderAgeAsVariable,
726+
}));
727+
}
728+
729+
790730
class _User {
791731
String first;
792732
String last;
@@ -1022,7 +962,6 @@ class MapRecordMatcher extends _CollectionMatcher<KeyValueRecord> {
1022962
}
1023963
}
1024964

1025-
1026965
class FooBar {
1027966
static int fooIds = 0;
1028967

0 commit comments

Comments
 (0)