Skip to content

Commit

Permalink
Support server timestamps and provide a mocking API
Browse files Browse the repository at this point in the history
  • Loading branch information
bendrucker committed Jan 21, 2015
1 parent b36e090 commit 615f44e
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 2 deletions.
17 changes: 17 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ Only `MockFirebase` methods are included here. For details on normal Firebase AP
- [Auth](#auth)
- [`changeAuthState(user)`](#changeauthstateauthdata---undefined)
- [`getEmailUser(email)`](#getemailuseremail---objectnull)
- [Server Timestamps](#server-timestamps)
- [`setClock(fn)`](#firebasesetclockfn---undefined)
- [`restoreClock()`](#firebasesetclockfn---undefined)

## Core

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

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.

## Server Timestamps

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.

##### `Firebase.setClock(fn)` -> `undefined`

Instead of using `Date.now()`, MockFirebase will call the `fn` you provide to generate a timestamp. `fn` should return a number.

<hr>

##### `Firebase.restoreClock()` -> `undefined`

After calling `Firebase.setClock`, calling `Firebase.restoreClock` will restore the default timestamp behavior.
30 changes: 29 additions & 1 deletion src/firebase.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ function MockFirebase (path, data, parent, name) {
_.extend(this, Auth.prototype, new Auth());
}

MockFirebase.ServerValue = {
TIMESTAMP: {
'.sv': 'timestamp'
}
};

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

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

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

MockFirebase.prototype.flush = function (delay) {
this.queue.flush(delay);
return this;
Expand Down Expand Up @@ -325,6 +344,11 @@ MockFirebase.prototype._childChanged = function (ref) {
MockFirebase.prototype._dataChanged = function (unparsedData) {
var pri = utils.getMeta(unparsedData, 'priority', this.priority);
var data = utils.cleanData(unparsedData);

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

if( pri !== this.priority ) {
this._priChanged(pri);
}
Expand All @@ -345,7 +369,11 @@ MockFirebase.prototype._dataChanged = function (unparsedData) {
}
else {
keysToChange.forEach(function(key) {
this._updateOrAdd(key, unparsedData[key], events);
var childData = unparsedData[key];
if (utils.isServerTimestamp(childData)) {
childData = getServerTime();
}
this._updateOrAdd(key, childData, events);
}, this);
}

Expand Down
4 changes: 4 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@ exports.priorityComparator = function priorityComparator (a, b) {
}
return 0;
};

exports.isServerTimestamp = function isServerTimestamp (data) {
return _.isObject(data) && data['.sv'] === 'timestamp';
};
3 changes: 2 additions & 1 deletion test/.jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"globals": {
"describe": false,
"beforeEach": false,
"afterEach": false,
"it": false,
"xit": false
}
}
}
56 changes: 56 additions & 0 deletions test/unit/firebase.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,62 @@ describe('MockFirebase', function () {
spy = sinon.spy();
});

describe('Server Timestamps', function () {

var clock;
beforeEach(function () {
clock = sinon.useFakeTimers();
});

afterEach(function () {
clock.restore();
});

it('parses server timestamps', function () {
ref.set(Firebase.ServerValue.TIMESTAMP);
ref.flush();
expect(ref.getData()).to.equal(Date.now());
});

it('parses server timestamps in child data', function () {
var child = ref.child('foo');
ref.set({
foo: Firebase.ServerValue.TIMESTAMP
});
ref.flush();
expect(child.getData()).to.equal(Date.now());
});

describe('Firebase#setClock', function () {

afterEach(Firebase.restoreClock);

it('sets a timestamp factory function', function () {
var customClock = sinon.stub().returns(10);
Firebase.setClock(customClock);
ref.set(Firebase.ServerValue.TIMESTAMP);
ref.flush();
expect(customClock).to.have.been.calledOnce;
expect(ref.getData()).to.equal(10);
});

});

describe('#restoreClock', function () {

it('restores the normal clock', function () {
Firebase.setClock(spy);
Firebase.restoreClock();
ref.set(Firebase.ServerValue.TIMESTAMP);
ref.flush();
expect(spy).to.not.have.been.called;
expect(ref.getData()).to.equal(Date.now());
});

});

});

describe('#flush', function () {

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

0 comments on commit 615f44e

Please sign in to comment.