Skip to content

Commit 615f44e

Browse files
committed
Support server timestamps and provide a mocking API
Closes dmurvihill#59, Closes dmurvihill#60
1 parent b36e090 commit 615f44e

File tree

5 files changed

+108
-2
lines changed

5 files changed

+108
-2
lines changed

API.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ Only `MockFirebase` methods are included here. For details on normal Firebase AP
1414
- [Auth](#auth)
1515
- [`changeAuthState(user)`](#changeauthstateauthdata---undefined)
1616
- [`getEmailUser(email)`](#getemailuseremail---objectnull)
17+
- [Server Timestamps](#server-timestamps)
18+
- [`setClock(fn)`](#firebasesetclockfn---undefined)
19+
- [`restoreClock()`](#firebasesetclockfn---undefined)
1720

1821
## Core
1922

@@ -192,3 +195,17 @@ console.assert(ref.getAuth().auth.myAuthProperty, 'authData has custom property'
192195
##### `getEmailUser(email)` -> `Object|null`
193196

194197
Finds a user previously created with [`createUser`](https://www.firebase.com/docs/web/api/firebase/createuser.html). If no user was created with the specified `email`, `null` is returned instead.
198+
199+
## Server Timestamps
200+
201+
MockFirebase allow you to simulate the behavior of [server timestamps](https://www.firebase.com/docs/web/api/servervalue/timestamp.html) when using a real Firebase instance. Unless you use `Firebase.setClock`, `Firebase.ServerValue.TIMESTAMP` will be transformed to the current date (`Date.now()`) when your data change is flushed.
202+
203+
##### `Firebase.setClock(fn)` -> `undefined`
204+
205+
Instead of using `Date.now()`, MockFirebase will call the `fn` you provide to generate a timestamp. `fn` should return a number.
206+
207+
<hr>
208+
209+
##### `Firebase.restoreClock()` -> `undefined`
210+
211+
After calling `Firebase.setClock`, calling `Firebase.restoreClock` will restore the default timestamp behavior.

src/firebase.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,25 @@ function MockFirebase (path, data, parent, name) {
3232
_.extend(this, Auth.prototype, new Auth());
3333
}
3434

35+
MockFirebase.ServerValue = {
36+
TIMESTAMP: {
37+
'.sv': 'timestamp'
38+
}
39+
};
40+
41+
var getServerTime, defaultClock;
42+
getServerTime = defaultClock = function () {
43+
return Date.now();
44+
};
45+
46+
MockFirebase.setClock = function (fn) {
47+
getServerTime = fn;
48+
};
49+
50+
MockFirebase.restoreClock = function () {
51+
getServerTime = defaultClock;
52+
};
53+
3554
MockFirebase.prototype.flush = function (delay) {
3655
this.queue.flush(delay);
3756
return this;
@@ -325,6 +344,11 @@ MockFirebase.prototype._childChanged = function (ref) {
325344
MockFirebase.prototype._dataChanged = function (unparsedData) {
326345
var pri = utils.getMeta(unparsedData, 'priority', this.priority);
327346
var data = utils.cleanData(unparsedData);
347+
348+
if (utils.isServerTimestamp(data)) {
349+
data = getServerTime();
350+
}
351+
328352
if( pri !== this.priority ) {
329353
this._priChanged(pri);
330354
}
@@ -345,7 +369,11 @@ MockFirebase.prototype._dataChanged = function (unparsedData) {
345369
}
346370
else {
347371
keysToChange.forEach(function(key) {
348-
this._updateOrAdd(key, unparsedData[key], events);
372+
var childData = unparsedData[key];
373+
if (utils.isServerTimestamp(childData)) {
374+
childData = getServerTime();
375+
}
376+
this._updateOrAdd(key, childData, events);
349377
}, this);
350378
}
351379

src/utils.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,7 @@ exports.priorityComparator = function priorityComparator (a, b) {
6969
}
7070
return 0;
7171
};
72+
73+
exports.isServerTimestamp = function isServerTimestamp (data) {
74+
return _.isObject(data) && data['.sv'] === 'timestamp';
75+
};

test/.jshintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"globals": {
55
"describe": false,
66
"beforeEach": false,
7+
"afterEach": false,
78
"it": false,
89
"xit": false
910
}
10-
}
11+
}

test/unit/firebase.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,62 @@ describe('MockFirebase', function () {
1515
spy = sinon.spy();
1616
});
1717

18+
describe('Server Timestamps', function () {
19+
20+
var clock;
21+
beforeEach(function () {
22+
clock = sinon.useFakeTimers();
23+
});
24+
25+
afterEach(function () {
26+
clock.restore();
27+
});
28+
29+
it('parses server timestamps', function () {
30+
ref.set(Firebase.ServerValue.TIMESTAMP);
31+
ref.flush();
32+
expect(ref.getData()).to.equal(Date.now());
33+
});
34+
35+
it('parses server timestamps in child data', function () {
36+
var child = ref.child('foo');
37+
ref.set({
38+
foo: Firebase.ServerValue.TIMESTAMP
39+
});
40+
ref.flush();
41+
expect(child.getData()).to.equal(Date.now());
42+
});
43+
44+
describe('Firebase#setClock', function () {
45+
46+
afterEach(Firebase.restoreClock);
47+
48+
it('sets a timestamp factory function', function () {
49+
var customClock = sinon.stub().returns(10);
50+
Firebase.setClock(customClock);
51+
ref.set(Firebase.ServerValue.TIMESTAMP);
52+
ref.flush();
53+
expect(customClock).to.have.been.calledOnce;
54+
expect(ref.getData()).to.equal(10);
55+
});
56+
57+
});
58+
59+
describe('#restoreClock', function () {
60+
61+
it('restores the normal clock', function () {
62+
Firebase.setClock(spy);
63+
Firebase.restoreClock();
64+
ref.set(Firebase.ServerValue.TIMESTAMP);
65+
ref.flush();
66+
expect(spy).to.not.have.been.called;
67+
expect(ref.getData()).to.equal(Date.now());
68+
});
69+
70+
});
71+
72+
});
73+
1874
describe('#flush', function () {
1975

2076
it('flushes the queue and returns itself', function () {

0 commit comments

Comments
 (0)