Skip to content

Commit

Permalink
fix(Dirty Checking): fix watching methods/closures
Browse files Browse the repository at this point in the history
-  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)
    dart-archive#1      DirtyCheckingRecord.object= (package:angular/change_detection/dirty_checking_change_detector.dart:465:78)
    dart-archive#2      _FieldHandler.acceptValue (package:angular/change_detection/watch_group.dart:630:17)
    dart-archive#3      WatchGroup.addFieldWatch (package:angular/change_detection/watch_group.dart:171:29)
    dart-archive#4      FieldReadAST.setupWatch (package:angular/change_detection/ast.dart:67:31)
    dart-archive#5      WatchGroup.watch.<anonymous closure> (package:angular/change_detection/watch_group.dart:144:40)
    dart-archive#6      _HashMap.putIfAbsent (dart:collection-patch/collection_patch.dart:124)
    dart-archive#7      WatchGroup.watch (package:angular/change_detection/watch_group.dart:143:27)
    dart-archive#8      Scope.watch (package:angular/core/scope.dart:240:31)
    dart-archive#9      main.<anonymous closure>.<anonymous closure>.<anonymous closure> (/Users/chirayu/work/angular.dart/test/core/scope_spec.dart:1308:24)
    dart-archive#10     _LocalInstanceMirror._invoke (dart:mirrors-patch/mirrors_impl.dart:440)
    dart-archive#11     _LocalInstanceMirror.invoke (dart:mirrors-patch/mirrors_impl.dart:436)
    dart-archive#12     _LocalClosureMirror.apply (dart:mirrors-patch/mirrors_impl.dart:466)
    dart-archive#13     DynamicInjector.invoke (package:di/dynamic_injector.dart:97:20)
    dart-archive#14     _SpecInjector.inject (package:angular/mock/test_injection.dart:58:22)
    dart-archive#15     inject.<anonymous closure> (package:angular/mock/test_injection.dart:100:44)
    dart-archive#16     _rootRun (dart:async/zone.dart:723)
    dart-archive#17     _ZoneDelegate.run (dart:async/zone.dart:453)
    dart-archive#18     _CustomizedZone.run (dart:async/zone.dart:663)
    dart-archive#19     runZoned (dart:async/zone.dart:954)
    dart-archive#20     _syncOuter.<anonymous closure> (package:angular/mock/zone.dart:227:22)
    dart-archive#21     _withSetup.<anonymous closure> (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:13:14)
    dart-archive#22     _run.<anonymous closure> (package:unittest/src/test_case.dart:102:27)
    dart-archive#23     _rootRunUnary (dart:async/zone.dart:730)
    dart-archive#24     _ZoneDelegate.runUnary (dart:async/zone.dart:462)
    dart-archive#25     _CustomizedZone.runUnary (dart:async/zone.dart:667)
    dart-archive#26     _Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:488)
    dart-archive#27     _Future._propagateToListeners (dart:async/future_impl.dart:571)
    dart-archive#28     _Future._completeWithValue (dart:async/future_impl.dart:331)
    dart-archive#29     _Future._asyncComplete.<anonymous closure> (dart:async/future_impl.dart:393)
    dart-archive#30     _rootRun (dart:async/zone.dart:723)
    dart-archive#31     _ZoneDelegate.run (dart:async/zone.dart:453)
    dart-archive#32     _CustomizedZone.run (dart:async/zone.dart:663)
    dart-archive#33     _BaseZone.runGuarded (dart:async/zone.dart:574)
    dart-archive#34     _BaseZone.bindCallback.<anonymous closure> (dart:async/zone.dart:599)
    dart-archive#35     _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:23)
    dart-archive#36     _asyncRunCallback (dart:async/schedule_microtask.dart:32)
    dart-archive#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)
    dart-archive#1      _injectify (/Users/chirayu/work/angular.dart/test/_specs.dart:236:25)
    dart-archive#2      iit (/Users/chirayu/work/angular.dart/test/_specs.dart:244:53)
    dart-archive#3      main.<anonymous closure>.<anonymous closure> (/Users/chirayu/work/angular.dart/test/core/scope_spec.dart:1305:10)
    dart-archive#4      describe.<anonymous closure> (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:62:9)
    dart-archive#5      group (package:unittest/unittest.dart:396:9)
    dart-archive#6      describe (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:60:15)
    dart-archive#7      describe (/Users/chirayu/work/angular.dart/test/_specs.dart:248:46)
    dart-archive#8      main.<anonymous closure> (/Users/chirayu/work/angular.dart/test/core/scope_spec.dart:1009:13)
    dart-archive#9      describe.<anonymous closure> (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:62:9)
    dart-archive#10     group (package:unittest/unittest.dart:396:9)
    dart-archive#11     describe (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:60:15)
    dart-archive#12     describe (/Users/chirayu/work/angular.dart/test/_specs.dart:248:46)
    dart-archive#13     main (/Users/chirayu/work/angular.dart/test/core/scope_spec.dart:14:11)
    dart-archive#14     main.<anonymous closure> (http://localhost:8765/__adapter_dart_unittest.dart:169:15)
    dart-archive#15     _rootRun (dart:async/zone.dart:723)
    dart-archive#16     _ZoneDelegate.run (dart:async/zone.dart:453)
    dart-archive#17     _CustomizedZone.run (dart:async/zone.dart:663)
    dart-archive#18     runZoned (dart:async/zone.dart:954)
    dart-archive#19     main (http://localhost:8765/__adapter_dart_unittest.dart:146:11)

    #0      _SpecInjector.inject (package:angular/mock/test_injection.dart:60:7)
    dart-archive#1      inject.<anonymous closure> (package:angular/mock/test_injection.dart:100:44)
    dart-archive#2      _rootRun (dart:async/zone.dart:723)
    dart-archive#3      _rootRun (dart:async/zone.dart:724)
    dart-archive#4      _rootRun (dart:async/zone.dart:724)
    dart-archive#5      _ZoneDelegate.run (dart:async/zone.dart:453)
    dart-archive#6      _CustomizedZone.run (dart:async/zone.dart:663)
    dart-archive#7      runZoned (dart:async/zone.dart:954)
    dart-archive#8      _syncOuter.<anonymous closure> (package:angular/mock/zone.dart:227:22)
    dart-archive#9      _withSetup.<anonymous closure> (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:13:14)
    dart-archive#10     _withSetup.<anonymous closure> (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:14:5)
    dart-archive#11     _withSetup.<anonymous closure> (/Users/chirayu/work/angular.dart/test/jasmine_syntax.dart:14:5)
    dart-archive#12     _run.<anonymous closure> (package:unittest/src/test_case.dart:102:27)
    dart-archive#13     _rootRunUnary (dart:async/zone.dart:730)
    dart-archive#14     _ZoneDelegate.runUnary (dart:async/zone.dart:462)
    dart-archive#15     _CustomizedZone.runUnary (dart:async/zone.dart:667)
    dart-archive#16     _Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:488)
    dart-archive#17     _Future._propagateToListeners (dart:async/future_impl.dart:571)
    dart-archive#18     _Future._completeWithValue (dart:async/future_impl.dart:331)
    dart-archive#19     _Future._asyncComplete.<anonymous closure> (dart:async/future_impl.dart:393)
    dart-archive#20     _rootRun (dart:async/zone.dart:723)
    dart-archive#21     _ZoneDelegate.run (dart:async/zone.dart:453)
    dart-archive#22     _CustomizedZone.run (dart:async/zone.dart:663)
    dart-archive#23     _BaseZone.runGuarded (dart:async/zone.dart:574)
    dart-archive#24     _BaseZone.bindCallback.<anonymous closure> (dart:async/zone.dart:599)
    dart-archive#25     _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:23)
    dart-archive#26     _asyncRunCallback (dart:async/schedule_microtask.dart:32)
    dart-archive#27     _handleMutation (file:///Volumes/data/b/build/slave/dartium-mac-full-dev/build/src/dart/tools/dom/src/native_DOMImplementation.dart:588)

Closes dart-archive#999
  • Loading branch information
chirayuk authored and Diana Salsbury committed Jul 16, 2014
1 parent 48e8688 commit 2894a1b
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 95 deletions.
3 changes: 0 additions & 3 deletions lib/change_detection/change_detection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,6 @@ typedef dynamic FieldGetter(object);
typedef void FieldSetter(object, value);

abstract class FieldGetterFactory {
get isMethodInvoke;
bool isMethod(Object object, String name);
Function method(Object object, String name);
FieldGetter getter(Object object, String name);
}

Expand Down
30 changes: 24 additions & 6 deletions lib/change_detection/dirty_checking_change_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -373,11 +373,13 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
static const List<String> _MODE_NAMES =
const ['MARKER', 'IDENT', 'GETTER', 'MAP[]', 'ITERABLE', 'MAP'];
static const int _MODE_MARKER_ = 0;
static const int _MODE_IDENTITY_ = 1;
static const int _MODE_GETTER_ = 2;
static const int _MODE_MAP_FIELD_ = 3;
static const int _MODE_ITERABLE_ = 4;
static const int _MODE_MAP_ = 5;
static const int _MODE_NOOP_ = 1;
static const int _MODE_IDENTITY_ = 2;
static const int _MODE_GETTER_ = 3;
static const int _MODE_GETTER_OR_METHOD_CLOSURE_ = 4;
static const int _MODE_MAP_FIELD_ = 5;
static const int _MODE_ITERABLE_ = 6;
static const int _MODE_MAP_ = 7;

final DirtyCheckingChangeDetectorGroup _group;
final FieldGetterFactory _fieldGetterFactory;
Expand Down Expand Up @@ -460,7 +462,7 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
_mode = _MODE_MAP_FIELD_;
_getter = null;
} else {
_mode = _MODE_GETTER_;
_mode = _MODE_GETTER_OR_METHOD_CLOSURE_;
_getter = _fieldGetterFactory.getter(obj, field);
}
}
Expand All @@ -471,14 +473,30 @@ class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
switch (_mode) {
case _MODE_MARKER_:
return false;
case _MODE_NOOP_:
return false;
case _MODE_GETTER_:
current = _getter(object);
break;
case _MODE_GETTER_OR_METHOD_CLOSURE_:
// NOTE: When Dart looks up a method "foo" on object "x", it returns a
// new closure for each lookup. They compare equal via "==" but are no
// identical(). There's no point getting a new value each time and
// decide it's the same so we'll skip further checking after the first
// time.
current = _getter(object);
if (current is Function && !identical(current, _getter(object))) {
_mode = _MODE_NOOP_;
} else {
_mode = _MODE_GETTER_;
}
break;
case _MODE_MAP_FIELD_:
current = object[field];
break;
case _MODE_IDENTITY_:
current = object;
_mode = _MODE_NOOP_;
break;
case _MODE_MAP_:
return (currentValue as _MapChangeRecord)._check(object);
Expand Down
19 changes: 1 addition & 18 deletions lib/change_detection/dirty_checking_change_detector_dynamic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,9 @@ export 'package:angular/change_detection/change_detection.dart' show
import 'dart:mirrors';

class DynamicFieldGetterFactory implements FieldGetterFactory {
final isMethodInvoke = true;

bool isMethod(Object object, String name) {
try {
return method(object, name) != null;
} catch (e, s) {
return false;
}
}

Function method(Object object, String name) {
Symbol symbol = new Symbol(name);
InstanceMirror instanceMirror = reflect(object);
return (List args, Map namedArgs) =>
instanceMirror.invoke(symbol, args, namedArgs).reflectee;
}

FieldGetter getter(Object object, String name) {
Symbol symbol = new Symbol(name);
InstanceMirror instanceMirror = reflect(object);
return (Object object) => instanceMirror.getField(symbol).reflectee;
return (Object object) => instanceMirror.getField(symbol).reflectee;
}
}
17 changes: 0 additions & 17 deletions lib/change_detection/dirty_checking_change_detector_static.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,13 @@ library dirty_checking_change_detector_static;
import 'package:angular/change_detection/change_detection.dart';

class StaticFieldGetterFactory implements FieldGetterFactory {
final isMethodInvoke = false;
Map<String, FieldGetter> getters;

StaticFieldGetterFactory(this.getters);

bool isMethod(Object object, String name) {
// We need to know if we are referring to method or field which is a
// function. We can find out by calling it twice and seeing if we get
// the same value. Methods create a new closure each time.
FieldGetter getterFn = getter(object, name);
dynamic property = getterFn(object);
return (property is Function) &&
(!identical(property, getterFn(object)));
}

FieldGetter getter(Object object, String name) {
var getter = getters[name];
if (getter == null) throw "Missing getter: (o) => o.$name";
return getter;
}

Function method(Object object, String name) {
var method = getters[name];
if (method == null) throw "Missing method: $name";
return method;
}
}
70 changes: 38 additions & 32 deletions lib/change_detection/watch_group.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ typedef void ChangeLog(String expression, current, previous);
* number of arguments with which the function will get called with.
*/
abstract class FunctionApply {
// dartbug.com/16401
// dynamic call() { throw new StateError('Use apply()'); }
dynamic call() { throw new StateError('Use apply()'); }
dynamic apply(List arguments);
}

Expand Down Expand Up @@ -198,7 +197,7 @@ class WatchGroup implements _EvalWatchList, _WatchGroupList {
* - [isPure] A pure function is one which holds no internal state. This implies that the
* function is idempotent.
*/
_EvalWatchRecord addFunctionWatch(/* dartbug.com/16401 Function */ fn, List<AST> argsAST,
_EvalWatchRecord addFunctionWatch(Function fn, List<AST> argsAST,
Map<Symbol, AST> namedArgsAST,
String expression, bool isPure) =>
_addEvalWatch(null, fn, null, argsAST, namedArgsAST, expression, isPure);
Expand All @@ -214,11 +213,11 @@ class WatchGroup implements _EvalWatchList, _WatchGroupList {
_EvalWatchRecord addMethodWatch(AST lhs, String name, List<AST> argsAST,
Map<Symbol, AST> namedArgsAST,
String expression) =>
_addEvalWatch(lhs, null, name, argsAST, namedArgsAST, expression, false);
_addEvalWatch(lhs, null, name, argsAST, namedArgsAST, expression, false);



_EvalWatchRecord _addEvalWatch(AST lhsAST, /* dartbug.com/16401 Function */ fn, String name,
_EvalWatchRecord _addEvalWatch(AST lhsAST, Function fn, String name,
List<AST> argsAST,
Map<Symbol, AST> namedArgsAST,
String expression, bool isPure) {
Expand Down Expand Up @@ -715,24 +714,24 @@ class _InvokeHandler extends _Handler implements _ArgHandlerList {


class _EvalWatchRecord implements WatchRecord<_Handler> {
static const int _MODE_INVALID_ = -2;
static const int _MODE_DELETED_ = -1;
static const int _MODE_MARKER_ = 0;
static const int _MODE_PURE_FUNCTION_ = 1;
static const int _MODE_FUNCTION_ = 2;
static const int _MODE_PURE_FUNCTION_APPLY_ = 3;
static const int _MODE_NULL_ = 4;
static const int _MODE_FIELD_CLOSURE_ = 5;
static const int _MODE_MAP_CLOSURE_ = 6;
static const int _MODE_METHOD_ = 7;
static const int _MODE_METHOD_INVOKE_ = 8;
static const int _MODE_INVALID_ = -2;
static const int _MODE_DELETED_ = -1;
static const int _MODE_MARKER_ = 0;
static const int _MODE_PURE_FUNCTION_ = 1;
static const int _MODE_FUNCTION_ = 2;
static const int _MODE_PURE_FUNCTION_APPLY_ = 3;
static const int _MODE_NULL_ = 4;
static const int _MODE_FIELD_OR_METHOD_CLOSURE_ = 5;
static const int _MODE_METHOD_ = 6;
static const int _MODE_FIELD_CLOSURE_ = 7;
static const int _MODE_MAP_CLOSURE_ = 8;
WatchGroup watchGrp;
final _Handler handler;
final List args;
final Map<Symbol, dynamic> namedArgs = new Map<Symbol, dynamic>();
final String name;
int mode;
/* dartbug.com/16401 Function*/ var fn;
Function fn;
FieldGetterFactory _fieldGetterFactory;
bool dirtyArgs = true;

Expand Down Expand Up @@ -789,13 +788,8 @@ class _EvalWatchRecord implements WatchRecord<_Handler> {
if (value is Map) {
mode = _MODE_MAP_CLOSURE_;
} else {
if (_fieldGetterFactory.isMethod(value, name)) {
mode = _fieldGetterFactory.isMethodInvoke ? _MODE_METHOD_INVOKE_ : _MODE_METHOD_;
fn = _fieldGetterFactory.method(value, name);
} else {
mode = _MODE_FIELD_CLOSURE_;
fn = _fieldGetterFactory.getter(value, name);
}
mode = _MODE_FIELD_OR_METHOD_CLOSURE_;
fn = _fieldGetterFactory.getter(value, name);
}
}
}
Expand All @@ -820,19 +814,31 @@ class _EvalWatchRecord implements WatchRecord<_Handler> {
value = (fn as FunctionApply).apply(args);
dirtyArgs = false;
break;
case _MODE_FIELD_CLOSURE_:
case _MODE_FIELD_OR_METHOD_CLOSURE_:
var closure = fn(_object);
value = closure == null ? null : Function.apply(closure, args, namedArgs);
break;
case _MODE_MAP_CLOSURE_:
var closure = object[name];
value = closure == null ? null : Function.apply(closure, args, namedArgs);
// NOTE: When Dart looks up a method "foo" on object "x", it returns a
// new closure for each lookup. They compare equal via "==" but are no
// identical(). There's no point getting a new value each time and
// decide it's the same so we'll skip further checking after the first
// time.
if (closure is Function && !identical(closure, fn(_object))) {
fn = closure;
mode = _MODE_METHOD_;
} else {
mode = _MODE_FIELD_CLOSURE_;
}
value = (closure == null) ? null : Function.apply(closure, args, namedArgs);
break;
case _MODE_METHOD_:
value = Function.apply(fn, args, namedArgs);
break;
case _MODE_METHOD_INVOKE_:
value = fn(args, namedArgs);
case _MODE_FIELD_CLOSURE_:
var closure = fn(_object);
value = (closure == null) ? null : Function.apply(closure, args, namedArgs);
break;
case _MODE_MAP_CLOSURE_:
var closure = object[name];
value = (closure == null) ? null : Function.apply(closure, args, namedArgs);
break;
default:
assert(false);
Expand Down
82 changes: 64 additions & 18 deletions test/change_detection/dirty_checking_change_detector_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,18 @@ 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';

void main() {
describe('DirtyCheckingChangeDetector', () {
void testWithGetterFactory(FieldGetterFactory getterFactory) {
describe('DirtyCheckingChangeDetector with ${getterFactory.runtimeType}', () {
DirtyCheckingChangeDetector<String> detector;
FieldGetterFactory getterFactory = new StaticFieldGetterFactory({
"first": (o) => o.first,
"age": (o) => o.age,
"last": (o) => o.last,
"toString": (o) => o.toString
});

beforeEach(() {
detector = new DirtyCheckingChangeDetector<String>(getterFactory);
});

describe('StaticFieldGetterFactory', () {
it('should detect methods', () {
var obj = new _User();
expect(getterFactory.isMethod(obj, 'toString')).toEqual(true);
expect(getterFactory.isMethod(obj, 'age')).toEqual(false);
});
});

describe('object field', () {
it('should detect nothing', () {
var changes = detector.collectChanges();
Expand Down Expand Up @@ -657,6 +644,42 @@ 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(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(true);

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());
Expand Down Expand Up @@ -689,12 +712,36 @@ void main() {
});
}


void main() {
testWithGetterFactory(new DynamicFieldGetterFactory());

testWithGetterFactory(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,
}));
}


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, previous, additions, moves, removals}) =>
Expand Down Expand Up @@ -915,7 +962,6 @@ class MapRecordMatcher extends _CollectionMatcher<KeyValueRecord> {
}
}


class FooBar {
static int fooIds = 0;

Expand Down

0 comments on commit 2894a1b

Please sign in to comment.