Finished reimplementation of merge box. Needs more tests

This commit is contained in:
Naomi Seyfer
2012-12-11 12:20:21 -08:00
committed by David Glasser
parent bbb127c3ca
commit fbc787cd1d
2 changed files with 159 additions and 7 deletions

View File

@@ -10,9 +10,55 @@ Meteor._SessionDocumentView = function (id) {
var self = this;
self.id = id;
self.existsIn = {}; // set of subId
self.dataByKey = {}; // key-> [ {subId -> value} by precedence]
self.dataByKey = {}; // key-> [ {subscriptionId -> value} by precedence]
};
_.extend(Meteor._SessionDocumentView.prototype, {
clearField: function (subscriptionId, key, changeCollector, clearCollector) {
var self = this;
var precedenceList = self.dataByKey[key];
var old = precedenceList[0];
var i = 0;
_.each(precedenceList, function (precedence) {
if (precedence.subscriptionId === subscriptionId) {
precedenceList.splice(i, 1);
}
i++;
});
if (_.isEmpty(precedenceList)) {
delete self.dataByKey[key];
clearCollector.push(key);
} else if (old.value !== precedenceList[0].value) {
changeCollector[key] = precedenceList[0].value;
}
},
changeField: function (subscriptionId, key, value, changeCollector) {
var self = this;
var precedenceList = self.dataByKey[key];
if (!precedenceList) {
self.dataByKey[key] = [{subscriptionId: subscriptionId, value: value}];
changeCollector[key] = value;
return;
}
var elt = _.find(precedenceList, function (precedence) {
return precedence.subscriptionId === subscriptionId;
});
if (elt) {
if (elt === precedenceList[0] && !_.isEqual(value, elt.value)) {
// this subscription is changing the value of this field.
changeCollector[key] = value;
}
elt.value = value;
} else {
// this subscription is newly caring about this field
precedenceList.push({subscriptionId: subscriptionId, value: value});
}
}
});
Meteor._SessionCollectionView = function (collectionName, sessionCallbacks) {
var self = this;
self.collectionName = collectionName;
@@ -21,28 +67,68 @@ Meteor._SessionCollectionView = function (collectionName, sessionCallbacks) {
};
_.extend(Meteor._SessionCollectionView.prototype, {
added: function (subscriptionId, doc) {
var self = this;
var docView = self.documents[doc._id];
if (docView) {
// somebody else knew about this doc; reconicle. The effective order of
// precedence here is that the first subscription to say anything about a
// key determines its value.
var newPairs = {};
if (_.has(docView.existsIn, subscriptionId)) {
throw new Error("Duplicate add for " + doc._id);
}
docView.existsIn[subscriptionId] = true;
//XXX: Deal with fields here.
_.each(doc, function (value, key) {
var dataAssertionList = docView.dataByKey[key];
if (dataAssertionList) {
// just assume that we're not already in the assertion list yet,
// but we could check this.
} else {
// nobody has taken responsibility for this key yet
dataAssertionList = [];
self.dataByKey[key] = dataAssertionList;
newPairs[key] = value;
}
// add our assertion
var assertion = {subscriptionId: subscriptionId, value: value};
dataAssertionList.push(assertion);
});
if (!_.isEmpty(newPairs))
self.callbacks.changed(self.collectionName, doc._id, newPairs, []);
} else {
docView = new Meteor._SessionDocumentView(doc._id);
self.documents[doc._id] = docView;
docView.existsIn[subscriptionId] = true;
//XXX: Deal with fields here
_.each(doc, function (value, key) {
docView.dataByKey[key] = [{subscriptionId: subscriptionId, value: value}];
});
// since nobody else knew about this doc, we can just call added.
self.callbacks.added(self.collectionName, doc);
}
},
changed: function (subscriptionId, id, changed, cleared) {
var self = this;
var changedResult = {};
var clearedResult = [];
var docView = self.documents[id];
if (!docView)
throw new Error("Could not find element with id " + id + "to change");
_.each(changed, function (value, key) {
docView.changeField(subscriptionId, key, value, changedResult);
});
_.each(cleared, function (clearKey) {
docView.clearField(subscriptionId, clearKey, changedResult, clearedResult);
});
if (!_.isEmpty(changed) || !_.isEmpty(cleared))
self.callbacks.changed(self.collectionName, id, changedResult, clearedResult);
},
removed: function (subscriptionId, ids) {
var self = this;
var removedIds = [];
_.each(ids, function (id) {
var docView = self.documents[id];
if (!docView) {
@@ -50,12 +136,23 @@ _.extend(Meteor._SessionCollectionView.prototype, {
}
delete docView.existsIn[subscriptionId];
if (_.isEmpty(docView.existsIn)) {
//XXX: Stop splitting it up
self.callbacks.removed(self.collectionName, [id]);
// it is gone from everyone
removedIds.push(id);
delete self.documents[id];
} else {
//XXX: DO STUFF
var changed = {};
var cleared = [];
// remove this subscription from every precedence list
// and record the changes
_.each(docView.dataByKey, function (precedenceList, key) {
docView.clearField(subscriptionId, key, changed, cleared);
});
if (!_.isEmpty(changed) || !_.isEmpty(cleared))
self.callbacks.changed(self.collectionName, id, changed, cleared);
}
});
if (!_.isEmpty(removedIds))
self.callbacks.removed(self.collectionName, removedIds);
}
});
/******************************************************************************/

View File

@@ -27,7 +27,7 @@ var newView = function(test) {
return ret;
};
Tinytest.add('livedata - sessionview - basic', function (test) {
Tinytest.add('livedata - sessionview - exists reveal', function (test) {
var v = newView(test);
v.added("A", {_id: "A1"});
@@ -44,3 +44,58 @@ Tinytest.add('livedata - sessionview - basic', function (test) {
v.expectResult({fun: 'removed', ids: ["A1"]});
v.expectNoResult();
});
Tinytest.add('livedata - sessionview - field reveal', function (test) {
var v = newView(test);
v.added("A", {_id: "A1", foo: "bar"});
v.expectResult({fun: 'added', doc: {_id: "A1", foo: "bar"}});
v.expectNoResult();
v.added("B", {_id: "A1", foo: "baz"});
v.removed("A", ["A1"]);
v.expectResult({fun: 'changed', id: "A1", changed: {foo: "baz"}, cleared: []});
v.expectNoResult();
// Somewhere in here we must have changed foo to baz. Legal either on the
// added or on the removed, but only once.
v.removed("B", ["A1"]);
v.expectResult({fun: 'removed', ids: ["A1"]});
v.expectNoResult();
});
Tinytest.add('livedata - sessionview - field change', function (test) {
var v = newView(test);
v.added("A", {_id: "A1", foo: "bar"});
v.expectResult({fun: 'added', doc: {_id: "A1", foo: "bar"}});
v.expectNoResult();
v.changed("A", "A1", {foo: "baz"}, []);
v.expectResult({fun: 'changed', id: "A1", changed: {foo: "baz"}, cleared: []});
v.expectNoResult();
v.removed("A", ["A1"]);
v.expectResult({fun: 'removed', ids: ["A1"]});
v.expectNoResult();
});
Tinytest.add('livedata - sessionview - field change reveal', function (test) {
var v = newView(test);
v.added("A", {_id: "A1", foo: "bar"});
v.expectResult({fun: 'added', doc: {_id: "A1", foo: "bar"}});
v.expectNoResult();
v.added("B", {_id: "A1", foo: "baz"});
v.changed("A", "A1", {}, ["foo"]);
v.expectResult({fun: 'changed', id: "A1", changed: {foo: "baz"}, cleared: []});
v.expectNoResult();
v.removed("A", ["A1"]);
v.expectNoResult();
v.removed("B", ["A1"]);
v.expectResult({fun: 'removed', ids: ["A1"]});
v.expectNoResult();
});