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

Commit

Permalink
fix(change-detection): correctly process watch registration inside re…
Browse files Browse the repository at this point in the history
…action FN.

Fix #678
  • Loading branch information
mhevery committed Mar 7, 2014
1 parent d7e77de commit d6bc9ab
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 16 deletions.
37 changes: 25 additions & 12 deletions lib/change_detection/watch_group.dart
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,13 @@ class WatchGroup implements _EvalWatchList, _WatchGroupList {
// Must be done last
_EvalWatchList._add(this, evalWatchRecord);
_evalCost++;

if (_rootGroup.isInsideInvokeDirty) {
// This check means that we are inside invoke reaction function.
// Registering a new EvalWatch at this point will not run the
// .check() on it which means it will not be processed, but its
// reaction function will be run with null. So we process it manually.
evalWatchRecord.check();
}
return evalWatchRecord;
}

Expand Down Expand Up @@ -406,26 +412,33 @@ class RootWatchGroup extends WatchGroup {
int count = 0;
if (processStopwatch != null) processStopwatch.stop();
Watch dirtyWatch = _dirtyWatchHead;
_dirtyWatchHead = _dirtyWatchTail = null;
_dirtyWatchHead = null;
RootWatchGroup root = _rootGroup;
root._removeCount = 0;
while(dirtyWatch != null) {
count++;
try {
if (root._removeCount == 0 || dirtyWatch._watchGroup.isAttached) {
dirtyWatch.invoke();
try {
while(dirtyWatch != null) {
count++;
try {
if (root._removeCount == 0 || dirtyWatch._watchGroup.isAttached) {
dirtyWatch.invoke();
}
} catch (e, s) {
if (exceptionHandler == null) rethrow; else exceptionHandler(e, s);
}
} catch (e, s) {
if (exceptionHandler == null) rethrow; else exceptionHandler(e, s);
var nextDirtyWatch = dirtyWatch._nextDirtyWatch;
dirtyWatch._nextDirtyWatch = null;
dirtyWatch = nextDirtyWatch;
}
var nextDirtyWatch = dirtyWatch._nextDirtyWatch;
dirtyWatch._nextDirtyWatch = null;
dirtyWatch = nextDirtyWatch;
} finally {
_dirtyWatchTail = null;
}
if (processStopwatch != null) processStopwatch..stop()..increment(count);
return count;
}

bool get isInsideInvokeDirty =>
_dirtyWatchHead == null && _dirtyWatchTail != null;

/**
* Add Watch into the asynchronous queue for later processing.
*/
Expand Down
23 changes: 23 additions & 0 deletions test/change_detection/watch_group_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,29 @@ void main() {
expect(logger).toEqual(['ABC']);
});


it('should eval function eagerly when registered during reaction', () {
var fn = (arg) { logger('fn($arg)'); return arg; };
context['obj'] = {'fn': fn};
context['arg1'] = 'OUT';
context['arg2'] = 'IN';
var ast = new MethodAST(parse('obj'), 'fn', [parse('arg1')]);
var watch = watchGrp.watch(ast, (v, p) {
var ast = new MethodAST(parse('obj'), 'fn', [parse('arg2')]);
watchGrp.watch(ast, (v, p) {
logger('reaction: $v');
});
});

expect(logger).toEqual([]);
watchGrp.detectChanges();
expect(logger).toEqual(['fn(OUT)', 'fn(IN)', 'reaction: IN']);
logger.clear();
watchGrp.detectChanges();
expect(logger).toEqual(['fn(OUT)', 'fn(IN)']);
});


it('should read connstant', () {
// should fire on initial adding
expect(watchGrp.fieldCost).toEqual(0);
Expand Down
51 changes: 47 additions & 4 deletions test/core/scope_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1226,14 +1226,57 @@ void main() {
it('should not trigger new watcher in the flush where it was added', inject((Scope scope) {
var log = [] ;
scope.context['foo'] = () => 'foo';
scope.context['name'] = 'misko';
scope.context['list'] = [2, 3];
scope.context['map'] = {'bar': 'chocolate'};
scope.watch('1', (value, __) {
expect(value).toEqual(1);
scope.watch('foo()', (value, __) {
log.add(value);
});
scope.watch('foo()', (value, __) => log.add(value));
scope.watch('name', (value, __) => log.add(value));
scope.watch('(foo() + "-" + name).toUpperCase()', (value, __) => log.add(value));
scope.watch('list', (value, __) => log.add(value));
scope.watch('map', (value, __) => log.add(value));
});
scope.apply();
expect(log).toEqual(['foo']);
expect(log).toEqual(['foo', 'misko', 'FOO-MISKO', [2, 3], {'bar': 'chocolate'}]);
}));


it('should allow multiple nested watches', inject((RootScope scope) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
scope.watch('1', (_, __) {
// make this deeper then ScopeTTL;
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
expect(scope.apply).not.toThrow();
}));


Expand Down

0 comments on commit d6bc9ab

Please sign in to comment.