Skip to content

Commit

Permalink
[firebase_auth_web] Add null check to _fromJsUser. (firebase#1798)
Browse files Browse the repository at this point in the history
* Added unit test for the onAuthStateChanged stream (from JS).
* Migrate tests from jsify to package:js
* Update version and CHANGELOG

Fixes firebase#1685
  • Loading branch information
ditman authored Jan 15, 2020
1 parent e82ad5d commit daf7b88
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 39 deletions.
5 changes: 5 additions & 0 deletions packages/firebase_auth/firebase_auth_web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.1.1+4

* Prevent `null` users (unauthenticated) from breaking the `onAuthStateChanged` Stream.
* Migrate tests from jsify to package:js.

## 0.1.1+3

* Fix the tests on dart2js.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform {
}

PlatformUser _fromJsUser(firebase.User user) {
if (user == null) {
return null;
}
return PlatformUser(
providerId: user.providerId,
uid: user.uid,
Expand Down Expand Up @@ -154,9 +157,6 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform {
Future<PlatformUser> getCurrentUser(String app) async {
final firebase.Auth auth = _getAuth(app);
final firebase.User currentUser = auth.currentUser;
if (currentUser == null) {
return null;
}
return _fromJsUser(currentUser);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/firebase_auth/firebase_auth_web/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: firebase_auth_web
description: The web implementation of firebase_auth
homepage: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_auth/firebase_auth_web
version: 0.1.1+3
version: 0.1.1+4

flutter:
plugin:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
// found in the LICENSE file.
@TestOn('chrome')

import 'dart:js' as js;
import 'dart:async';
import 'dart:js' show allowInterop;

import 'package:flutter_test/flutter_test.dart';
import 'package:firebase_auth/firebase_auth.dart';
Expand All @@ -12,57 +13,96 @@ import 'package:firebase_auth_web/firebase_auth_web.dart';
import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart';
import 'package:firebase_core_web/firebase_core_web.dart';

import 'mock/firebase_mock.dart';

void main() {
group('$FirebaseAuthWeb', () {
setUp(() {
final js.JsObject firebaseMock = js.JsObject.jsify(<String, dynamic>{});
js.context['firebase'] = firebaseMock;
js.context['firebase']['app'] = js.allowInterop((String name) {
return js.JsObject.jsify(<String, dynamic>{
'name': name,
'options': <String, String>{'appId': '123'},
});
});
js.context['firebase']['auth'] = js.allowInterop((dynamic app) {});
firebaseMock = FirebaseMock(
app: allowInterop(
(String name) => FirebaseAppMock(
name: name,
options: FirebaseAppOptionsMock(appId: '123'),
),
));

FirebaseCorePlatform.instance = FirebaseCoreWeb();
FirebaseAuthPlatform.instance = FirebaseAuthWeb();
});

test('signInAnonymously calls Firebase APIs', () async {
js.context['firebase']['auth'] = js.allowInterop((dynamic app) {
return js.JsObject.jsify(
<String, dynamic>{
'signInAnonymously': js.allowInterop(() {
firebaseMock.auth = allowInterop((_) => FirebaseAuthMock(
signInAnonymously: allowInterop(() {
return _jsPromise(_fakeUserCredential());
}),
},
);
});
));

FirebaseAuth auth = FirebaseAuth.instance;
AuthResult result = await auth.signInAnonymously();
expect(result, isNotNull);
});

group('onAuthStateChanged', () {
final List seenUsers = [];
final Completer<Function> nextUserCallback = Completer<Function>();

final List<dynamic> streamValues = [_fakeRawUser(), null, _fakeRawUser()];
final List<dynamic> expectedValueMatchers = [
isNotNull,
isNull,
isA<FirebaseUser>()
];

test('non authenticated user present in stream', () async {
firebaseMock.auth = allowInterop((_) => FirebaseAuthMock(
onAuthStateChanged:
allowInterop((Function nextUserCb, Function errorCb) {
if (!nextUserCallback.isCompleted) {
nextUserCallback.complete(nextUserCb);
}
return allowInterop(() {});
}),
));

FirebaseAuth auth = FirebaseAuth.instance;

// Subscribe our spy function
auth.onAuthStateChanged
.listen((FirebaseUser user) => seenUsers.add(user));

// Capture the JS function that lets us push users to the Stream from JS.
Function nextUser = await nextUserCallback.future;

streamValues.forEach((streamValue) => nextUser(streamValue));

for (int i = 0; i < expectedValueMatchers.length; i++) {
expect(seenUsers[i], expectedValueMatchers[i]);
}
});
});
});
}

js.JsObject _jsPromise(dynamic value) {
return js.JsObject.jsify(<String, dynamic>{
'then': js.allowInterop((js.JsFunction resolve, js.JsFunction reject) {
resolve.apply(<dynamic>[value]);
}),
});
Promise _jsPromise(dynamic value) {
return Promise(allowInterop((void resolve(dynamic result), Function reject) {
resolve(value);
}));
}

js.JsObject _fakeUserCredential() {
return js.JsObject.jsify(<String, dynamic>{
'user': <String, dynamic>{
'providerId': 'email',
'metadata': <String, dynamic>{
'creationTime': 'Wed, 04 Dec 2019 18:19:11 GMT',
'lastSignInTime': 'Wed, 04 Dec 2019 18:19:11 GMT',
},
'providerData': <dynamic>[],
},
'additionalUserInfo': <String, dynamic>{},
});
MockUserCredential _fakeUserCredential() {
return MockUserCredential(
user: _fakeRawUser(),
additionalUserInfo: MockAdditionalUserInfo(),
);
}

MockUser _fakeRawUser() {
return MockUser(
providerId: 'email',
metadata: MockUserMetadata(
creationTime: 'Wed, 04 Dec 2019 18:19:11 GMT',
lastSignInTime: 'Wed, 04 Dec 2019 18:19:11 GMT',
),
providerData: [],
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
@JS()
library firebase_mock;

import 'package:js/js.dart';

@JS()
@anonymous
class FirebaseAppOptionsMock {
external factory FirebaseAppOptionsMock({String appId});
external String get appId;
}

@JS()
@anonymous
class FirebaseAppMock {
external factory FirebaseAppMock({
String name,
FirebaseAppOptionsMock options,
});
external String get name;
external FirebaseAppOptionsMock get options;
}

@JS()
@anonymous
class FirebaseAuthMock {
external factory FirebaseAuthMock({
Function signInAnonymously,
Function onAuthStateChanged,
});
external Function get signInAnonymously;
external Function get onAuthStateChanged;
}

@JS()
@anonymous
class FirebaseMock {
external factory FirebaseMock({Function app});
external Function get app;

external set auth(Function auth);
external Function get auth;
}

@JS()
class Promise<T> {
external Promise(void executor(void resolve(T result), Function reject));
external Promise then(void onFulfilled(T result), [Function onRejected]);
}

@JS()
@anonymous
class MockUserMetadata {
external factory MockUserMetadata({
String creationTime,
String lastSignInTime,
});
external String get creationTime;
external String get lastSignInTime;
}

@JS()
@anonymous
class MockUser {
external factory MockUser({
String providerId,
MockUserMetadata metadata,
List providerData,
});
external String get providerId;
external MockUserMetadata get metadata;
external List get providerData;
}

@JS()
@anonymous
class MockAdditionalUserInfo {
external factory MockAdditionalUserInfo();
}

@JS()
@anonymous
class MockUserCredential {
external factory MockUserCredential({
MockUser user,
MockAdditionalUserInfo additionalUserInfo,
});
external MockUser get user;
external MockAdditionalUserInfo get additionalUserInfo;
}

// Wire to the global 'window.firebase' object.
@JS('firebase')
external set firebaseMock(FirebaseMock mock);
@JS('firebase')
external FirebaseMock get firebaseMock;

0 comments on commit daf7b88

Please sign in to comment.