From f8ba688cb72f4dd5f8d0e5fab67e4a309b3213bc Mon Sep 17 00:00:00 2001 From: Boyko Amarov Date: Tue, 28 May 2019 02:50:23 +0300 Subject: [PATCH 1/2] Add docChanges to MockFirestoreQuerySnapshot. --- src/firestore-query-snapshot.js | 46 +++++++++++++++- src/firestore-query.js | 31 ++++++++++- test/.jshintrc | 2 +- test/unit/firestore-collection.js | 91 +++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+), 5 deletions(-) diff --git a/src/firestore-query-snapshot.js b/src/firestore-query-snapshot.js index 597d8226..5afbf572 100644 --- a/src/firestore-query-snapshot.js +++ b/src/firestore-query-snapshot.js @@ -3,7 +3,8 @@ var _ = require('./lodash'); var DocumentSnapshot = require('./firestore-document-snapshot'); -function MockFirestoreQuerySnapshot (ref, data, keyOrder) { +function MockFirestoreQuerySnapshot (ref, data, keyOrder, changes = {}) { + this.changes = changes; this._ref = ref; this.data = _.cloneDeep(data) || {}; this.keyOrder = keyOrder; @@ -25,4 +26,47 @@ MockFirestoreQuerySnapshot.prototype.forEach = function (callback, context) { }); }; +MockFirestoreQuerySnapshot.prototype.docChanges = function () { + var {added = {}, removed = {}, modified = {}} = this.changes; + const result = []; + + _.forEach(added, function(value, key) { + result.push({ + type: 'added', + doc: { + id: key, + data: function() { + return value; + } + } + }); + }); + + _.forEach(modified, function(value, key) { + result.push({ + type: 'modified', + doc: { + id: key, + data: function() { + return value; + } + } + }); + }); + + _.forEach(removed, function(value, key) { + result.push({ + type: 'removed', + doc: { + id: key, + data: function() { + return value; + } + } + }); + }); + + return result; +}; + module.exports = MockFirestoreQuerySnapshot; diff --git a/src/firestore-query.js b/src/firestore-query.js index 527cbeee..916b6910 100644 --- a/src/firestore-query.js +++ b/src/firestore-query.js @@ -160,7 +160,9 @@ MockFirestoreQuery.prototype.onSnapshot = function (optionsOrObserverOrOnNext, o onError = onErrorArg; } var context = { - data: self._results(), + data: { + results: {} + }, }; var onSnapshot = function () { // compare the current state to the one from when this function was created @@ -168,8 +170,31 @@ MockFirestoreQuery.prototype.onSnapshot = function (optionsOrObserverOrOnNext, o if (err === null) { self.get().then(function (querySnapshot) { var results = self._results(); - if (JSON.stringify(results) !== JSON.stringify(context.data) || includeMetadataChanges) { - onNext(new QuerySnapshot(self.parent === null ? self : self.parent.collection(self.id), results.results, results.keyOrder)); + + var added = {}; + var removed = {}; + var modified = {}; + + _.forEach(results.results, function(nextValue, nextKey) { + if(Object.keys(context.data.results || {}).indexOf(nextKey) === -1) { + added[nextKey] = nextValue; + } else if (!_.isEqual(context.data.results[nextKey], nextValue)) { + modified[nextKey] = nextValue; + } + }); + + _.forEach(context.data.results, function(value, key) { + if (Object.keys(results.results).indexOf(key) === -1) { + removed[key] = value; + } + }); + + var hasAdditions = Object.keys(added).length > 0; + var hasRemovals = Object.keys(removed).length > 0; + var hasModififations = Object.keys(modified).length > 0; + + if (hasAdditions || hasRemovals || hasModififations || includeMetadataChanges) { + onNext(new QuerySnapshot(self.parent === null ? self : self.parent.collection(self.id), results.results, results.keyOrder, {added, removed, modified})); // onNext(new QuerySnapshot(self.id, self.ref, results)); context.data = results; } diff --git a/test/.jshintrc b/test/.jshintrc index 6fadeb8f..c687f97c 100644 --- a/test/.jshintrc +++ b/test/.jshintrc @@ -1,5 +1,5 @@ { "node": true, "mocha": true, - "esversion" : "6" + "esversion" : "8" } diff --git a/test/unit/firestore-collection.js b/test/unit/firestore-collection.js index 72ded957..6c8399b9 100644 --- a/test/unit/firestore-collection.js +++ b/test/unit/firestore-collection.js @@ -390,6 +390,97 @@ describe('MockFirestoreCollection', function () { collection.flush(); }); + it('returns value on first call', function(done) { + collection.doc('a').update({name: 'A'}, {setMerge: true}); + db.autoFlush(); + + collection.onSnapshot(function(snap) { + var names = []; + snap.docs.forEach(function(doc) { + names.push(doc.data().name); + }); + expect(names).to.contain('A'); + expect(names).not.to.contain('a'); + done(); + }); + }); + + it('(TODO) returns doc changes as "added" on first call', function (done) { + collection.onSnapshot(function(snap) { + var names = []; + var changes = snap.docChanges(); + // TODO! + expect(changes).to.have.length(6); + const allAdded = _.every(changes, function(change) { + return change.type === 'added'; + }); + expect(allAdded).to.equal(true); + done(); + }); + collection.doc('a').update({name: 'A'}, {setMerge: true}); + collection.flush(); + }); + + it('(TODO) returns doc changes as "added" on document addition', function (done) { + db.autoFlush(); + var call = 0; + collection.onSnapshot(function(snap) { + call += 1; + if (call === 1) { + collection.add({name: 'New'}); + } + if (call === 2) { + var changes = snap.docChanges(); + expect(changes).to.have.length(1); + expect(changes[0]).to.have.property('type', 'added'); + done(); + } + }); + + // collection.add({name: 'New'}); + // collection.flush(); + }); + + it('(TODO) returns doc changes as "modified" on document modification', function (done) { + db.autoFlush(); + var call = 0; + collection.onSnapshot(async function(snap) { + call += 1; + if (call === 1) { + await collection.doc('1').update({name: 'Modified'}); + } + if (call === 2) { + var changes = snap.docChanges(); + expect(changes).to.have.length(1); + expect(changes[0]).to.have.property('type', 'modified'); + done(); + } + }); + + // collection.add({name: 'New'}); + // collection.flush(); + }); + + it('(TODO) returns doc changes as "removed" on document deletion', function (done) { + db.autoFlush(); + var call = 0; + collection.onSnapshot(async function(snap) { + call += 1; + if (call === 1) { + await collection.doc('1').delete(); + } + if (call === 2) { + var changes = snap.docChanges(); + expect(changes).to.have.length(1); + expect(changes[0]).to.have.property('type', 'removed'); + done(); + } + }); + + // collection.add({name: 'New'}); + // collection.flush(); + }); + it('calls callback after multiple updates', function (done) { var callCount = 0; collection.onSnapshot(function(snap) { From 3dac23023c8b2292f4f3b9af187b40bbb78efc6b Mon Sep 17 00:00:00 2001 From: Boyko Amarov Date: Tue, 28 May 2019 03:16:55 +0300 Subject: [PATCH 2/2] Add docChanges to MockFirestoreQuerySnapshot. --- src/firestore-query-snapshot.js | 41 ++++++++++----------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/src/firestore-query-snapshot.js b/src/firestore-query-snapshot.js index 5afbf572..78bf3426 100644 --- a/src/firestore-query-snapshot.js +++ b/src/firestore-query-snapshot.js @@ -26,37 +26,22 @@ MockFirestoreQuerySnapshot.prototype.forEach = function (callback, context) { }); }; + MockFirestoreQuerySnapshot.prototype.docChanges = function () { - var {added = {}, removed = {}, modified = {}} = this.changes; + var {added = {}, removed = {}, modified = {}} = this.changes || {}; const result = []; - _.forEach(added, function(value, key) { - result.push({ - type: 'added', - doc: { - id: key, - data: function() { - return value; - } - } - }); - }); + _.forEach(added, addChange(result, 'added')); + _.forEach(modified, addChange(result, 'modified')); + _.forEach(removed, addChange(result, 'removed')); - _.forEach(modified, function(value, key) { - result.push({ - type: 'modified', - doc: { - id: key, - data: function() { - return value; - } - } - }); - }); + return result; +}; - _.forEach(removed, function(value, key) { +function addChange (result, changeType) { + return function(value, key) { result.push({ - type: 'removed', + type: changeType, doc: { id: key, data: function() { @@ -64,9 +49,7 @@ MockFirestoreQuerySnapshot.prototype.docChanges = function () { } } }); - }); - - return result; -}; + }; +} module.exports = MockFirestoreQuerySnapshot;