Skip to content

Commit 9961012

Browse files
committed
Merge branch 'master' into timestamp
# Conflicts: # test/unit/utils.js
2 parents 226a6db + 3175bb8 commit 9961012

File tree

18 files changed

+906
-51
lines changed

18 files changed

+906
-51
lines changed

API.md

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ Only `MockFirebase` methods are included here. For details on normal Firebase AP
1919
- [Server Timestamps](#server-timestamps)
2020
- [`setClock(fn)`](#firebasesetclockfn---undefined)
2121
- [`restoreClock()`](#firebasesetclockfn---undefined)
22+
- [Messaging](#messaging)
23+
- [`respondNext(methodName, result)`](#respondnextmethodname-result---undefined)
24+
- [`failNext(methodName, err)`](#failnextmethodname-err---undefined)
25+
- [`on(methodName, callback)`](#onmethodname-callback---undefined)
2226

2327
## Core
2428

@@ -175,8 +179,9 @@ Authentication methods for simulating changes to the auth state of a Firebase re
175179
Changes the active authentication credentials to the `authData` object.
176180
Before changing the authentication state, `changeAuthState` checks the
177181
`user` object against the current authentication data.
178-
`onAuthStateChanged` listeners will only be triggered if the data is not
179-
deeply equal.
182+
`onIdTokenChanged` listeners will be triggered if the data is not
183+
deeply equal. `onAuthStateChanged` listeners will be triggered if the
184+
data is deeply equal but with different ID token validity.
180185

181186
`user` should be a `MockUser` object or an object with the same fields
182187
as `MockUser`. To simulate no user being authenticated, pass `null` for
@@ -225,9 +230,13 @@ Finds a user previously created with [`createUser`](https://www.firebase.com/doc
225230
##### `updateUser(user)` -> `Promise<MockUser>`
226231

227232
Replace the existing user with a new one, by matching uid. Throws an
228-
error if no user exists whose uid matches the given user's uid. Resolves
229-
with the updated user when complete. This operation is queued until the
230-
next flush.
233+
error if no user exists whose uid matches the given user's uid.
234+
Appropriate `onAuthStateChanged` and `onIdTokenChanged` listeners will
235+
be triggered if the new user has the same `uid` as the current
236+
authenticated user.
237+
238+
Resolves with the updated user when complete. This operation is queued
239+
until the next flush.
231240

232241
## Server Timestamps
233242

@@ -242,3 +251,31 @@ Instead of using `Date.now()`, MockFirebase will call the `fn` you provide to ge
242251
##### `Firebase.restoreClock()` -> `undefined`
243252

244253
After calling `Firebase.setClock`, calling `Firebase.restoreClock` will restore the default timestamp behavior.
254+
255+
## Messaging
256+
257+
API reference of `MockMessaging`.
258+
259+
##### `respondNext(methodName, result)` -> `undefined`
260+
261+
When `methodName` is next invoked, the `Promise` (that is returned from the `methodName`) will be resolved with the specified `result`. This is useful for testing specific results of firebase messaging (e. g. partial success of sending messaging). The result will be triggered with the next `flush`.
262+
263+
If no result is specified, `methodName` will resolve a default response.
264+
265+
`result` must not be undefined.
266+
267+
<hr>
268+
269+
##### `failNext(methodName, err)` -> `undefined`
270+
271+
When `methodName` is next invoked, the `Promise` will be rejected with the specified `err`. This is useful for simulating validation or any other errors. The error will be triggered with the next `flush`.
272+
273+
`err` must be a proper `Error` object and not a string or any other primitive.
274+
275+
<hr>
276+
277+
##### `on(methodName, callback)` -> `undefined`
278+
279+
When `methodName` is next invoked, the `callback` will be triggered. The callback gets an array as argument. The array contains all arguments, that were passed on invoking `methodName`. This is useful to assert the input arguments of `methodName`.
280+
281+
See [docs.js](/test/unit/docs.js) for an example.

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88
### Added
99
- Changelog
10+
- Mock `auth.Auth.onIdTokenChanged()` method, matching the previous
11+
behavior of `onAuthStateChanged()` (see below)
12+
- Support for Firebase Messaging (Admin API)
1013

1114
### Changed
15+
- (Breaking) Consistent with Firebase SDK [version 4.0.0](https://firebase.google.com/support/release-notes/js#version_500_-_may_8_2018) and later,
16+
and later, `onAuthStateChanged` no longer issues an event when a new
17+
ID token is issued for the same user. The `onIdTokenChanged` method is
18+
now mocked, keeping the previous behavior.
1219
- `MockStorageFile.download()` now allows omitting the destination arg;
1320
in that case, it simply resolves the `Promise` with the file contents
1421
and does not write it anywhere else.
@@ -21,6 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2128
one.
2229
- `DataSnapshot.child` now correctly splits child paths by '/'
2330
characters
31+
- Boolean values are now allowed in RTDB priority fields and as
32+
arguments to `Query.startAt`, `Query.endAt`, and `Query.equalTo`.
2433

2534

2635
[Unreleased]: https://github.com/dmurvihill/firebase-mock/compare/v2.2.10...HEAD

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Firebase Mock supports the client-side [JavaScript API](https://firebase.google.
3535
* Authentication
3636
* [Basic](tutorials/admin/authentication.md)
3737
* [JWT Tokens](tutorials/admin/tokens.md)
38+
* [Messaging](tutorials/admin/messaging.md)
3839
* [Realtime Database](tutorials/admin/rtdb.md)
3940
* [Firestore](tutorials/admin/firestore.md)
4041
* [Storage](tutorials/admin/storage.md)

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
"repository": {
1414
"type": "git",
15-
"url": "git://github.com/soumak77/firebase-mock.git"
15+
"url": "git://github.com/dmurvihill/firebase-mock.git"
1616
},
1717
"keywords": [
1818
"firebase",
@@ -21,13 +21,13 @@
2121
"author": "Brian Soumakian",
2222
"license": "MIT",
2323
"bugs": {
24-
"url": "https://github.com/soumak77/firebase-mock/issues"
24+
"url": "https://github.com/dmurvihill/firebase-mock/issues"
2525
},
2626
"files": [
2727
"src/",
2828
"browser/"
2929
],
30-
"homepage": "https://github.com/soumak77/firebase-mock",
30+
"homepage": "https://github.com/dmurvihill/firebase-mock",
3131
"dependencies": {
3232
"firebase-auto-ids": "~1.1.0",
3333
"lodash.assign": "^4.2.0",

src/firebase-auth.js

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ function FirebaseAuth () {
99
this.currentUser = null;
1010
this._auth = {
1111
listeners: [],
12+
idTokenListeners: [],
1213
completionListeners: [],
1314
users: [],
1415
uidCounter: 1
@@ -17,10 +18,10 @@ function FirebaseAuth () {
1718

1819
FirebaseAuth.prototype.changeAuthState = function (userData) {
1920
this._defer('changeAuthState', _.toArray(arguments), function() {
20-
if (!_.isEqual(this.currentUser, userData)) {
21-
this.currentUser = _.isObject(userData) ? userData : null;
22-
this._triggerAuthEvent();
23-
}
21+
userData = _.isObject(userData) ? userData : null;
22+
const oldUser = _.cloneDeep(this.currentUser);
23+
this.currentUser = userData;
24+
this._notify_state_listeners(oldUser);
2425
});
2526
};
2627

@@ -47,6 +48,13 @@ FirebaseAuth.prototype.onAuthStateChanged = function (callback) {
4748
}
4849
};
4950

51+
FirebaseAuth.prototype.onIdTokenChanged = function (callback) {
52+
const currentUser = this.currentUser;
53+
this._auth.idTokenListeners.push({fn: callback});
54+
callback.call(null, _.cloneDeep(currentUser));
55+
return () => this.offAuth(callback);
56+
};
57+
5058
FirebaseAuth.prototype.getUserByEmail = function (email, onComplete) {
5159
var err = this._nextErr('getUserByEmail');
5260
var self = this;
@@ -100,6 +108,11 @@ FirebaseAuth.prototype.updateUser = function (newUser) {
100108
reject(new Error('Tried to update a nonexistent user'));
101109
} else {
102110
self._auth.users[i] = newUser.clone();
111+
if (this.currentUser && this.currentUser.uid === newUser.uid) {
112+
const oldUser = this.currentUser.clone();
113+
this.currentUser = self._auth.users[i];
114+
this._notify_state_listeners(oldUser);
115+
}
103116
resolve(newUser);
104117
}
105118
});
@@ -167,15 +180,14 @@ Object.keys(signinMethods)
167180
FirebaseAuth.prototype[method] = function () {
168181
var self = this;
169182
var user = getUser.apply(this, arguments);
170-
var promise = new Promise(function(resolve, reject) {
183+
return new Promise(function(resolve, reject) {
171184
self._authEvent(method, function(err) {
172185
if (err) reject(err);
173186
self.currentUser = user;
174187
resolve(user);
175188
self._triggerAuthEvent();
176189
}, true);
177190
});
178-
return promise;
179191
};
180192
});
181193

@@ -218,6 +230,15 @@ FirebaseAuth.prototype._triggerAuthEvent = function () {
218230
listeners.forEach(function (parts) {
219231
parts.fn.call(parts.context, _.cloneDeep(user));
220232
});
233+
this._triggerIdTokenEvent();
234+
};
235+
236+
FirebaseAuth.prototype._triggerIdTokenEvent = function () {
237+
var user = this.currentUser;
238+
var listeners = _.cloneDeep(this._auth.idTokenListeners);
239+
listeners.forEach(function (parts) {
240+
parts.fn.call(parts.context, _.cloneDeep(user));
241+
});
221242
};
222243

223244
FirebaseAuth.prototype._getUser = function (uid) {
@@ -239,12 +260,11 @@ FirebaseAuth.prototype.onAuth = function (onComplete, context) {
239260
};
240261

241262
FirebaseAuth.prototype.offAuth = function (onComplete, context) {
242-
var index = _.findIndex(this._auth.listeners, function (listener) {
263+
function shouldRemove(listener) {
243264
return listener.fn === onComplete && listener.context === context;
244-
});
245-
if (index > -1) {
246-
this._auth.listeners.splice(index, 1);
247265
}
266+
[this._auth.listeners, this._auth.idTokenListeners]
267+
.forEach(event => _.remove(event, shouldRemove));
248268
};
249269

250270
FirebaseAuth.prototype.unauth = function () {
@@ -256,7 +276,7 @@ FirebaseAuth.prototype.unauth = function () {
256276

257277
FirebaseAuth.prototype.signOut = function () {
258278
var self = this, updateuser = this.currentUser !== null;
259-
var promise = new Promise(function(resolve, reject) {
279+
return new Promise(function(resolve, reject) {
260280
self._authEvent('signOut', function(err) {
261281
if (err) reject(err);
262282
self.currentUser = null;
@@ -267,7 +287,6 @@ FirebaseAuth.prototype.signOut = function () {
267287
}
268288
}, true);
269289
});
270-
return promise;
271290
};
272291

273292
FirebaseAuth.prototype.createUserWithEmailAndPassword = function (email, password) {
@@ -502,14 +521,54 @@ FirebaseAuth.prototype.setCustomUserClaims = function (uid, claims) {
502521
});
503522
};
504523

524+
FirebaseAuth.prototype._notify_state_listeners = function (previousUser) {
525+
const difference = scanDifference(previousUser, this.currentUser);
526+
if (difference === 'different_user') {
527+
this._triggerAuthEvent();
528+
} else if(difference === 'different_token') {
529+
this._triggerIdTokenEvent();
530+
} else if (difference === 'same') {
531+
// do nothing
532+
} else {
533+
throw new Error('Unexpected result from scanDifference');
534+
}
535+
536+
function scanDifference(oldUser, newUser) {
537+
if (_.isObject(oldUser)) {
538+
if (_.isObject(newUser)) {
539+
if (_.isEqual(oldUser, newUser)) {
540+
return 'same';
541+
} else {
542+
return equalExceptToken(oldUser, newUser) ?
543+
'different_token' : 'different_user';
544+
}
545+
} else {
546+
return 'different_user';
547+
}
548+
} else {
549+
return _.isObject(newUser) ? 'different_user' : 'same';
550+
}
551+
}
552+
553+
function equalExceptToken(user1, user2) {
554+
const u1 = user1.clone();
555+
const u2 = user2.clone();
556+
delete u1._idtoken;
557+
delete u1._tokenValidity;
558+
delete u2._idtoken;
559+
delete u2._tokenValidity;
560+
return _.isEqual(u1, u2);
561+
}
562+
};
563+
505564
FirebaseAuth.prototype._nextUid = function () {
506565
return 'simplelogin:' + (this._auth.uidCounter++);
507566
};
508567

509568
FirebaseAuth.prototype._validateNewUid = function (uid) {
510569
if (uid) {
511570
var user = _.find(this._auth.users, function(user) {
512-
return user.uid == uid;
571+
return user.uid === uid;
513572
});
514573
if (user) {
515574
var err = new Error('The provided uid is already in use by an existing user. Each user must have a unique uid.');
@@ -523,7 +582,7 @@ FirebaseAuth.prototype._validateNewUid = function (uid) {
523582
FirebaseAuth.prototype._validateExistingUid = function (uid) {
524583
if (uid) {
525584
var user = _.find(this._auth.users, function(user) {
526-
return user.uid == uid;
585+
return user.uid === uid;
527586
});
528587
if (!user) {
529588
var err = new Error('There is no existing user record corresponding to the provided identifier.');

0 commit comments

Comments
 (0)