Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Timestamp support #143

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
2,966 changes: 1,488 additions & 1,478 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"lodash.clone": "^4.5.0",
"lodash.clonedeep": "^4.5.0",
"lodash.clonedeepwith": "^4.5.0",
"lodash.clonewith": "^4.5.0",
"lodash.compact": "^3.0.1",
"lodash.difference": "^4.5.0",
"lodash.every": "^4.6.0",
Expand All @@ -52,6 +53,7 @@
"lodash.isfunction": "^3.0.9",
"lodash.isnumber": "^3.0.3",
"lodash.isobject": "^3.0.2",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.isundefined": "^3.0.1",
"lodash.keys": "^4.2.0",
Expand Down
19 changes: 7 additions & 12 deletions src/firebase.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,12 @@ MockFirebase.ServerValue = {
}
};

var getServerTime, defaultClock;
getServerTime = defaultClock = function () {
return new Date().getTime();
};

MockFirebase.setClock = function (fn) {
getServerTime = fn;
utils.setServerClock(fn);
};

MockFirebase.restoreClock = function () {
getServerTime = defaultClock;
utils.restoreServerClock();
};

MockFirebase.autoId = function () {
Expand Down Expand Up @@ -449,7 +444,7 @@ MockFirebase.prototype._dataChanged = function (unparsedData) {
var data = utils.cleanData(unparsedData);

if (utils.isServerTimestamp(data)) {
data = getServerTime();
data = utils.getServerTime();
}

if (pri !== this.priority) {
Expand All @@ -475,7 +470,7 @@ MockFirebase.prototype._dataChanged = function (unparsedData) {
keysToChange.forEach(function (key) {
var childData = unparsedData[key];
if (utils.isServerTimestamp(childData)) {
childData = getServerTime();
childData = utils.getServerTime();
}
self._updateOrAdd(key, childData, events);
});
Expand All @@ -493,7 +488,7 @@ MockFirebase.prototype._dataChanged = function (unparsedData) {

MockFirebase.prototype._priChanged = function (newPriority) {
if (utils.isServerTimestamp(newPriority)) {
newPriority = getServerTime();
newPriority = utils.getServerTime();
}
this.priority = newPriority;
if (this.parent) {
Expand Down Expand Up @@ -719,10 +714,10 @@ function render(datum) {
return array;
}
} else {
return _.cloneDeep(datum);
return _.cloneDeepWith(datum, utils.cloneCustomizer);
}
} else {
return _.clone(datum);
return _.cloneWith(datum, utils.cloneCustomizer);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/firestore-document-snapshot.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
'use strict';

var _ = require('./lodash');
var utils = require('./utils');

function MockFirestoreDocumentSnapshot (id, ref, data) {
this.id = id;
this.ref = ref;
this._snapshotdata = _.cloneDeep(data) || null;
this.data = function() {
return _.cloneDeep(this._snapshotdata);
return _.cloneDeepWith(this._snapshotdata, utils.cloneCustomizer);
};
this.exists = this._snapshotdata !== null;
}
Expand Down
12 changes: 9 additions & 3 deletions src/firestore-document.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ var Promise = require('rsvp').Promise;
var autoId = require('firebase-auto-ids');
var DocumentSnapshot = require('./firestore-document-snapshot');
var Queue = require('./queue').Queue;
var Timestamp = require('./timestamp');
var utils = require('./utils');
var validate = require('./validators');
var WriteResult = require('./write-result');

function MockFirestoreDocument(path, data, parent, name, CollectionReference) {
this.ref = this;
Expand Down Expand Up @@ -108,9 +110,12 @@ MockFirestoreDocument.prototype.create = function (data, callback) {

var base = self._getData();
err = err || self._validateDoesNotExist(base);
if (err === null) {
if (err === null) {
var serverTime = utils.getServerTime();
var result = new WriteResult(Timestamp.fromMillis(serverTime));
data = utils.removeEmptyFirestoreProperties(data, serverTime);
self._dataChanged(data);
resolve();
resolve(result);
} else {
if (callback) {
callback(err);
Expand All @@ -132,6 +137,7 @@ MockFirestoreDocument.prototype.set = function (data, opts, callback) {
return new Promise(function (resolve, reject) {
self._defer('set', _.toArray(arguments), function () {
if (err === null) {
data = utils.removeEmptyFirestoreProperties(data, utils.getServerTime());
self._dataChanged(data);
resolve();
} else {
Expand Down Expand Up @@ -166,7 +172,7 @@ MockFirestoreDocument.prototype._update = function (changes, opts, callback) {
data = _.assign(_.isObject(base) ? base : {}, utils.updateToFirestoreObject(changes));
}
}
data = utils.removeEmptyFirestoreProperties(data);
data = utils.removeEmptyFirestoreProperties(data, utils.getServerTime());
self._dataChanged(data);
resolve(data);
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/firestore-query.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ MockFirestoreQuery.prototype.get = function () {
if (self.orderedProperties.length === 0) {
_.forEach(self.data, function(data, key) {
if (self.limited <= 0 || limit < self.limited) {
results[key] = _.cloneDeep(data);
results[key] = _.cloneDeepWith(data, utils.cloneCustomizer);
limit++;
}
});
Expand All @@ -92,7 +92,7 @@ MockFirestoreQuery.prototype.get = function () {

queryable.forEach(function(q) {
if (self.limited <= 0 || limit < self.limited) {
results[q.key] = _.cloneDeep(q.data);
results[q.key] = _.cloneDeepWith(q.data, utils.cloneCustomizer);
limit++;
}
});
Expand Down
2 changes: 2 additions & 0 deletions src/lodash.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module.exports = {
assign: require('lodash.assign'),
bind: require('lodash.bind'),
clone: require('lodash.clone'),
cloneWith: require('lodash.clonewith'),
cloneDeep: require('lodash.clonedeep'),
cloneDeepWith: require('lodash.clonedeepwith'),
compact: require('lodash.compact'),
Expand All @@ -22,6 +23,7 @@ module.exports = {
isEqual: require('lodash.isequal'),
isFunction: require('lodash.isfunction'),
isNumber: require('lodash.isnumber'),
isPlainObject: require('lodash.isplainobject'),
isObject: require('lodash.isobject'),
isString: require('lodash.isstring'),
isUndefined: require('lodash.isundefined'),
Expand Down
2 changes: 2 additions & 0 deletions src/sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var MockFirestore = require('./firestore');
var MockFieldValue = require('./firestore-field-value');
var MockMessaging = require('./messaging');
var MockStorage = require('./storage');
var MockTimestamp = require('./timestamp');

var EmailAuthProvider = function() {
this.providerId = EmailAuthProvider.PROVIDER_ID;
Expand Down Expand Up @@ -77,6 +78,7 @@ function MockFirebaseSdk(createDatabase, createAuth, createFirestore, createStor
return createFirestore ? createFirestore() : new MockFirestore();
}
MockFirebaseFirestore.FieldValue = MockFieldValue;
MockFirebaseFirestore.Timestamp = MockTimestamp;

function MockFirebaseStorage() {
return createStorage ? createStorage() : new MockStorage();
Expand Down
23 changes: 23 additions & 0 deletions src/timestamp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';

function Timestamp(seconds, nanoseconds) {
this.seconds = seconds;
this.nanoseconds = nanoseconds;
}

Timestamp.fromDate = function(date) {
return Timestamp.fromMillis(date.getTime());
};

Timestamp.fromMillis = function(ms) {
var sec = Math.floor(ms / 1000);
var ns = (ms % 1000) * 1000 * 1000;
return new Timestamp(sec, ns);
};

Timestamp.prototype.toDate = function () {
var millis = this.seconds * 1000 + this.nanoseconds / (1000 * 1000);
return new Date(millis);
};

module.exports = Timestamp;
40 changes: 32 additions & 8 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

var Snapshot = require('./snapshot');
var Timestamp = require('./timestamp');
var FieldValue = require('./firestore-field-value');
var _ = require('./lodash');

Expand Down Expand Up @@ -75,6 +76,24 @@ exports.priorityComparator = function priorityComparator(a, b) {
return 0;
};

var serverClock, defaultClock;

serverClock = defaultClock = function () {
return new Date().getTime();
};

exports.getServerTime = function getServerTime() {
return serverClock();
};

exports.setServerClock = function setServerTime(fn) {
serverClock = fn;
};

exports.restoreServerClock = function restoreServerTime() {
serverClock = defaultClock;
};

exports.isServerTimestamp = function isServerTimestamp(data) {
return _.isObject(data) && data['.sv'] === 'timestamp';
};
Expand Down Expand Up @@ -108,22 +127,21 @@ exports.removeEmptyRtdbProperties = function removeEmptyRtdbProperties(obj) {
}
};

exports.removeEmptyFirestoreProperties = function removeEmptyFirestoreProperties(obj) {
var t = typeof obj;
if (t === 'boolean' || t === 'string' || t === 'number' || t === 'undefined') {
exports.removeEmptyFirestoreProperties = function removeEmptyFirestoreProperties(obj, serverTime) {
if (!_.isPlainObject(obj)) {
return obj;
}
if (obj instanceof Date) return obj;

var keys = getKeys(obj);
if (keys.length > 0) {
for (var s in obj) {
var value = removeEmptyFirestoreProperties(obj[s]);
var value = removeEmptyFirestoreProperties(obj[s], serverTime);
if (FieldValue.delete().isEqual(value)) {
delete obj[s];
}
if (FieldValue.serverTimestamp().isEqual(value)) {
obj[s] = new Date();
} else if (FieldValue.serverTimestamp().isEqual(value)) {
obj[s] = new Date(serverTime);
} else if (value instanceof Timestamp) {
obj[s] = value.toDate();
}
}
}
Expand Down Expand Up @@ -206,3 +224,9 @@ exports.createThenableReference = function(reference, promise) {
};
return reference;
};

exports.cloneCustomizer = function(value) {
if (value instanceof Date) {
return Timestamp.fromMillis(value.getTime());
}
};
7 changes: 7 additions & 0 deletions src/write-result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

function WriteResult(writeTime) {
this.writeTime = writeTime;
}

module.exports = WriteResult;
61 changes: 61 additions & 0 deletions test/unit/firestore-collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ chai.use(require('sinon-chai'));
var expect = chai.expect;
var _ = require('../../src/lodash');
var Firestore = require('../../').MockFirestore;
var Timestamp = require('../../src/timestamp');

describe('MockFirestoreCollection', function () {

Expand Down Expand Up @@ -332,6 +333,66 @@ describe('MockFirestoreCollection', function () {
done();
}).catch(done);
});

it('returns documents ordered by timestamp', function(done) {
db.collection('group').doc().create({
name: 'a',
date: Timestamp.fromMillis(1000)
}).catch(done);
db.flush();
db.collection('group').add({
name: 'b',
date: Timestamp.fromMillis(2000)
}).catch(done);
db.flush();

db.collection('group').orderBy('date', 'asc').get().then(function (snap) {
expect(snap.size).to.equal(2);
expect(snap.docs[0].data().name).to.equal('a');
expect(snap.docs[0].data().date).to.have.property('seconds');
expect(snap.docs[1].data().name).to.equal('b');
expect(snap.docs[1].data().date).to.have.property('seconds');

db.collection('group').orderBy('date', 'desc').get().then(function (snap) {
expect(snap.size).to.equal(2);
expect(snap.docs[0].data().name).to.equal('b');
expect(snap.docs[1].data().name).to.equal('a');
done();
}).catch(done);
db.flush();
}).catch(done);
db.flush();
});

it('returns documents ordered by date', function(done) {
db.collection('group').doc().create({
name: 'a',
date: new Date(1000)
}).catch(done);
db.flush();
db.collection('group').add({
name: 'b',
date: new Date(2000)
}).catch(done);
db.flush();

db.collection('group').orderBy('date', 'asc').get().then(function (snap) {
expect(snap.size).to.equal(2);
expect(snap.docs[0].data().name).to.equal('a');
expect(snap.docs[0].data().date).to.have.property('seconds');
expect(snap.docs[1].data().name).to.equal('b');
expect(snap.docs[1].data().date).to.have.property('seconds');

db.collection('group').orderBy('date', 'desc').get().then(function (snap) {
expect(snap.size).to.equal(2);
expect(snap.docs[0].data().name).to.equal('b');
expect(snap.docs[1].data().name).to.equal('a');
done();
}).catch(done);
db.flush();
}).catch(done);
db.flush();
});
});

describe('#limit', function () {
Expand Down
Loading