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

Commit

Permalink
feat(dccd): Observable support
Browse files Browse the repository at this point in the history
  • Loading branch information
vicb authored and rkirov committed Jan 14, 2015
1 parent 7a10390 commit 3827f42
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 13 deletions.
37 changes: 24 additions & 13 deletions lib/change_detection/dirty_checking_change_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,11 @@ class DirtyCheckingRecord<H> implements WatchRecord<H> {
static const int _MODE_GETTER_OR_METHOD_CLOSURE_ = 5;
static const int _MODE_GETTER_OBS_OR_METHOD_CLOSURE_ = 6;
static const int _MODE_MAP_FIELD_ = 7;
static const int _MODE_ITERABLE_ = 8;
static const int _MODE_LIST_NOTIFIED_ = 9;
static const int _MODE_MAP_ = 10;
static const int _MODE_MAP_NOTIFIED_ = 11;
static const int _MODE_MAP_FIELD_NOTIFIED_ = 8;
static const int _MODE_ITERABLE_ = 9;
static const int _MODE_LIST_NOTIFIED_ = 10;
static const int _MODE_MAP_ = 11;
static const int _MODE_MAP_NOTIFIED_ = 12;

final DirtyCheckingChangeDetectorGroup _group;
final FieldGetterFactory _fieldGetterFactory;
Expand Down Expand Up @@ -476,7 +477,7 @@ class DirtyCheckingRecord<H> implements WatchRecord<H> {
while (_object is ContextLocals) {
var ctx = _object as ContextLocals;
if (ctx.hasProperty(field)) {
_mode = _MODE_MAP_FIELD_;
_mode = _MODE_MAP_FIELD_;
_getter = null;
return;
}
Expand All @@ -501,9 +502,9 @@ class DirtyCheckingRecord<H> implements WatchRecord<H> {
// mapping with the one from the new reference.
currentValue._revertToPreviousState();
}
if (_object is obs.ChangeNotifier) {
if (_object is obs.Observable) {
_mode = _MODE_MAP_NOTIFIED_; // Run the dccd after the map is added
var subscription = (_object as obs.ChangeNotifier).changes.listen((_) {
var subscription = (_object as obs.Observable).changes.listen((_) {
_mode = _MODE_MAP_NOTIFIED_; // Run the dccd after the map is updated
});
_group._registerObservable(this, subscription);
Expand Down Expand Up @@ -536,10 +537,19 @@ class DirtyCheckingRecord<H> implements WatchRecord<H> {
}

if (_object is Map) {
_mode = _MODE_MAP_FIELD_;
_getter = null;
if (_object is obs.Observable) {
_mode = _MODE_MAP_FIELD_NOTIFIED_;
var subscription = (_object as obs.Observable).changes.listen((_) {
_mode = _MODE_MAP_FIELD_NOTIFIED_; // Run the dccd after the map is updated
});
_group._registerObservable(this, subscription);
_getter = null;
} else {
_mode = _MODE_MAP_FIELD_;
_getter = null;
}
} else {
_mode = _object is obs.ChangeNotifier ?
_mode = _object is obs.Observable ?
_MODE_GETTER_OBS_OR_METHOD_CLOSURE_ :
_MODE_GETTER_OR_METHOD_CLOSURE_;
_getter = _fieldGetterFactory.getter(_object, field);
Expand Down Expand Up @@ -579,9 +589,6 @@ class DirtyCheckingRecord<H> implements WatchRecord<H> {
_mode = _MODE_NOOP_;
if (current is! Function || identical(current, _getter(object))) {
var subscription = (object as obs.Observable).changes.listen((records) {
// todo(vicb) we should only go to the _MODE_GETTER_NOTIFIED_ mode when a record
// is applicable to the current `field`. With the current implementation, any field
// on an observable object will trigger this listener.
_mode = _MODE_GETTER_NOTIFIED_;
});
_group._registerObservable(this, subscription);
Expand All @@ -590,6 +597,10 @@ class DirtyCheckingRecord<H> implements WatchRecord<H> {
case _MODE_MAP_FIELD_:
current = object[field];
break;
case _MODE_MAP_FIELD_NOTIFIED_:
_mode = _MODE_NOOP_; // no-op until next notification
current = object[field];
break;
case _MODE_IDENTITY_:
current = object;
_mode = _MODE_NOOP_;
Expand Down
43 changes: 43 additions & 0 deletions test/change_detection/dirty_checking_change_detector_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,29 @@ void testWithGetterFactory(FieldGetterFactory getterFactory) {
describe('map watching', () {
addMapSpec(useObservable: false);
addMapSpec(useObservable: true);

it('should only check ObservableMap items after notification', async(() {
var map = new ObservableMap.from({'foo': 'bar'});
detector.watch(map, 'foo', null);

// The value is always dirty-checked the first time
var iterator = detector.collectChanges();
expect(iterator.moveNext()).toEqual(true);
expect(iterator.current.currentValue).toEqual('bar');
expect(iterator.moveNext()).toEqual(false);

// The value should not be dirty-checked again before a change notification is received
map['foo'] = 'baz';
iterator = detector.collectChanges();
expect(iterator.moveNext()).toEqual(false);

// Trigger the ObservableMap notification, changes should be detected
microLeap();
iterator = detector.collectChanges();
expect(iterator.moveNext()).toEqual(true);
expect(iterator.current.currentValue).toEqual('baz');
expect(iterator.moveNext()).toEqual(false);
}));
});

describe('function watching', () {
Expand Down Expand Up @@ -1008,6 +1031,26 @@ void addMapSpec({bool useObservable}) {

describe('use observable: $useObservable', () {
describe('previous state', () {

it('should detect map value changes', wrap(() {
var map = mapFactory({'foo': 'bar'});
detector.watch(map, 'foo', null);

var iterator = getChangeIterator();
expect(iterator.moveNext()).toEqual(true);
expect(iterator.current.currentValue).toEqual('bar');
expect(iterator.moveNext()).toEqual(false);

iterator = getChangeIterator();
expect(iterator.moveNext()).toEqual(false);

map['foo'] = 'baz';
iterator = getChangeIterator();
expect(iterator.moveNext()).toEqual(true);
expect(iterator.current.currentValue).toEqual('baz');
expect(iterator.moveNext()).toEqual(false);
}));

it('should store on insertion', wrap(() {
var map = mapFactory({});
var record = detector.watch(map, null, null);
Expand Down

0 comments on commit 3827f42

Please sign in to comment.