Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	ios/RNFirebase/RNFirebase.h
#	ios/RNFirebase/RNFirebaseAnalytics.h
#	ios/RNFirebase/RNFirebaseAuth.h
#	ios/RNFirebase/RNFirebaseCrash.h
#	ios/RNFirebase/RNFirebaseDatabase.h
#	ios/RNFirebase/RNFirebaseErrors.h
#	ios/RNFirebase/RNFirebaseMessaging.h
#	ios/RNFirebase/RNFirebaseStorage.h
This commit is contained in:
Alexander Kuttig 2017-05-14 14:30:10 +02:00
commit db792b8857
50 changed files with 1905 additions and 512 deletions

View File

@ -4,6 +4,7 @@
[![Gitter](https://badges.gitter.im/invertase/react-native-firebase.svg)](https://gitter.im/invertase/react-native-firebase?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitter](https://badges.gitter.im/invertase/react-native-firebase.svg)](https://gitter.im/invertase/react-native-firebase?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![npm version](https://img.shields.io/npm/v/react-native-firebase.svg)](https://www.npmjs.com/package/react-native-firebase) [![npm version](https://img.shields.io/npm/v/react-native-firebase.svg)](https://www.npmjs.com/package/react-native-firebase)
[![License](https://img.shields.io/npm/l/react-native-firebase.svg)](/LICENSE) [![License](https://img.shields.io/npm/l/react-native-firebase.svg)](/LICENSE)
[![Donate](https://img.shields.io/badge/Donate-Patreon-green.svg)](https://www.patreon.com/invertase)
**RNFirebase** makes using [Firebase](http://firebase.com) with React Native simple. **RNFirebase** makes using [Firebase](http://firebase.com) with React Native simple.

View File

@ -1,5 +1,7 @@
package io.invertase.firebase; package io.invertase.firebase;
import android.app.Activity;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
@ -34,7 +36,10 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule implements Life
int status = gapi.isGooglePlayServicesAvailable(getReactApplicationContext()); int status = gapi.isGooglePlayServicesAvailable(getReactApplicationContext());
if (status != ConnectionResult.SUCCESS && gapi.isUserResolvableError(status)) { if (status != ConnectionResult.SUCCESS && gapi.isUserResolvableError(status)) {
gapi.getErrorDialog(getCurrentActivity(), status, 2404).show(); Activity activity = getCurrentActivity();
if (activity != null) {
gapi.getErrorDialog(activity, status, 2404).show();
}
} }
} }

View File

@ -216,12 +216,12 @@ public class Utils {
* @return * @return
*/ */
private static boolean isArray(DataSnapshot snapshot) { private static boolean isArray(DataSnapshot snapshot) {
long expectedKey = 0; long expectedKey = -1;
for (DataSnapshot child : snapshot.getChildren()) { for (DataSnapshot child : snapshot.getChildren()) {
try { try {
long key = Long.parseLong(child.getKey()); long key = Long.parseLong(child.getKey());
if (key == expectedKey) { if (key > expectedKey) {
expectedKey++; expectedKey = key;
} else { } else {
return false; return false;
} }
@ -238,11 +238,11 @@ public class Utils {
* @return * @return
*/ */
private static boolean isArray(MutableData mutableData) { private static boolean isArray(MutableData mutableData) {
long expectedKey = 0; long expectedKey = -1;
for (MutableData child : mutableData.getChildren()) { for (MutableData child : mutableData.getChildren()) {
try { try {
long key = Long.parseLong(child.getKey()); long key = Long.parseLong(child.getKey());
if (key == expectedKey) { if (key > expectedKey) {
expectedKey++; expectedKey++;
} else { } else {
return false; return false;
@ -261,8 +261,16 @@ public class Utils {
* @return * @return
*/ */
private static <Any> WritableArray buildArray(DataSnapshot snapshot) { private static <Any> WritableArray buildArray(DataSnapshot snapshot) {
long expectedKey = 0;
WritableArray array = Arguments.createArray(); WritableArray array = Arguments.createArray();
for (DataSnapshot child : snapshot.getChildren()) { for (DataSnapshot child : snapshot.getChildren()) {
long key = Long.parseLong(child.getKey());
if (key > expectedKey) {
for (long i = expectedKey; i < key; i++) {
array.pushNull();
}
expectedKey = key;
}
Any castedChild = castValue(child); Any castedChild = castValue(child);
switch (castedChild.getClass().getName()) { switch (castedChild.getClass().getName()) {
case "java.lang.Boolean": case "java.lang.Boolean":
@ -288,6 +296,7 @@ public class Utils {
Log.w(TAG, "Invalid type: " + castedChild.getClass().getName()); Log.w(TAG, "Invalid type: " + castedChild.getClass().getName());
break; break;
} }
expectedKey++;
} }
return array; return array;
} }
@ -299,8 +308,16 @@ public class Utils {
* @return * @return
*/ */
private static <Any> WritableArray buildArray(MutableData mutableData) { private static <Any> WritableArray buildArray(MutableData mutableData) {
long expectedKey = 0;
WritableArray array = Arguments.createArray(); WritableArray array = Arguments.createArray();
for (MutableData child : mutableData.getChildren()) { for (MutableData child : mutableData.getChildren()) {
long key = Long.parseLong(child.getKey());
if (key > expectedKey) {
for (long i = expectedKey; i < key; i++) {
array.pushNull();
}
expectedKey = key;
}
Any castedChild = castValue(child); Any castedChild = castValue(child);
switch (castedChild.getClass().getName()) { switch (castedChild.getClass().getName()) {
case "java.lang.Boolean": case "java.lang.Boolean":
@ -326,6 +343,7 @@ public class Utils {
Log.w(TAG, "Invalid type: " + castedChild.getClass().getName()); Log.w(TAG, "Invalid type: " + castedChild.getClass().getName());
break; break;
} }
expectedKey++;
} }
return array; return array;
} }

View File

@ -4,6 +4,7 @@ import android.util.Log;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -12,6 +13,7 @@ import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContext;
@ -25,6 +27,7 @@ import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult; import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.GithubAuthProvider; import com.google.firebase.auth.GithubAuthProvider;
import com.google.firebase.auth.TwitterAuthProvider; import com.google.firebase.auth.TwitterAuthProvider;
import com.google.firebase.auth.UserInfo;
import com.google.firebase.auth.UserProfileChangeRequest; import com.google.firebase.auth.UserProfileChangeRequest;
import com.google.firebase.auth.FacebookAuthProvider; import com.google.firebase.auth.FacebookAuthProvider;
import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuth;
@ -739,6 +742,35 @@ public class RNFirebaseAuth extends ReactContextBaseJavaModule {
} }
/**
* Converts a List of UserInfo instances into the correct format to match the web sdk
* @param providerData List<UserInfo> user.getProviderData()
* @return WritableArray array
*/
private WritableArray convertProviderData(List<? extends UserInfo> providerData) {
WritableArray output = Arguments.createArray();
for (UserInfo userInfo : providerData) {
WritableMap userInfoMap = Arguments.createMap();
userInfoMap.putString("providerId", userInfo.getProviderId());
userInfoMap.putString("uid", userInfo.getUid());
userInfoMap.putString("displayName", userInfo.getDisplayName());
final Uri photoUrl = userInfo.getPhotoUrl();
if (photoUrl != null) {
userInfoMap.putString("photoURL", photoUrl.toString());
} else {
userInfoMap.putNull("photoURL");
}
userInfoMap.putString("email", userInfo.getEmail());
output.pushMap(userInfoMap);
}
return output;
}
/** /**
* firebaseUserToMap * firebaseUserToMap
* *
@ -778,7 +810,7 @@ public class RNFirebaseAuth extends ReactContextBaseJavaModule {
userMap.putNull("photoURL"); userMap.putNull("photoURL");
} }
// todo providerData userMap.putArray("providerData", convertProviderData(user.getProviderData()));
return userMap; return userMap;
} }

View File

@ -28,6 +28,7 @@ import com.google.firebase.database.OnDisconnect;
import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.DatabaseException;
import com.google.firebase.database.Transaction; import com.google.firebase.database.Transaction;
import io.invertase.firebase.Utils; import io.invertase.firebase.Utils;
@ -55,8 +56,8 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
final Callback callback) { final Callback callback) {
try { try {
mFirebaseDatabase.setPersistenceEnabled(enable); mFirebaseDatabase.setPersistenceEnabled(enable);
} catch (Throwable t) { } catch (DatabaseException t) {
Log.e(TAG, "FirebaseDatabase setPersistenceEnabled exception", t);
} }
WritableMap res = Arguments.createMap(); WritableMap res = Arguments.createMap();

View File

@ -10,6 +10,7 @@
[![Gitter](https://badges.gitter.im/invertase/react-native-firebase.svg)](https://gitter.im/invertase/react-native-firebase?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitter](https://badges.gitter.im/invertase/react-native-firebase.svg)](https://gitter.im/invertase/react-native-firebase?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![npm version](https://img.shields.io/npm/v/react-native-firebase.svg)](https://www.npmjs.com/package/react-native-firebase) [![npm version](https://img.shields.io/npm/v/react-native-firebase.svg)](https://www.npmjs.com/package/react-native-firebase)
[![License](https://img.shields.io/npm/l/react-native-firebase.svg)](/LICENSE) [![License](https://img.shields.io/npm/l/react-native-firebase.svg)](/LICENSE)
[![Donate](https://img.shields.io/badge/Donate-Patreon-green.svg)](https://www.patreon.com/invertase)
<br /> <br />
A well tested Firebase implementation for React Native, supporting both iOS & Android apps. A well tested Firebase implementation for React Native, supporting both iOS & Android apps.
</div> </div>

View File

@ -73,9 +73,9 @@ Add permissions:
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
``` ```
Set app [launch mode](https://inthecheesefactory.com/blog/understand-android-activity-launchmode/en) inside application props: Set app [launch mode](https://inthecheesefactory.com/blog/understand-android-activity-launchmode/en) inside activity props:
```xml ```xml
<application <activity
... ...
android:launchMode="singleTop" android:launchMode="singleTop"
> >

View File

@ -112,7 +112,7 @@ Follow the instructions at https://firebase.google.com/docs/cloud-messaging/ios/
In Xcode, enable the following capabilities: In Xcode, enable the following capabilities:
1) Push Notifications 1) Push Notifications
2) Background modes > Remove notifications 2) Background modes > Remote notifications
### 3.3) Update `AppDelegate.h` ### 3.3) Update `AppDelegate.h`

667
index.d.ts vendored Normal file
View File

@ -0,0 +1,667 @@
// Type definitions for React Native Firebase v1.0.0-alpha7
// Project: https://github.com/invertase/react-native-firebase
// Definitions by: Tal <https://github.com/taljacobson>
// TypeScript Version: 2.1
declare module "react-native-firebase" {
export default class FireBase {
constructor(config?: RNFirebase.configurationOptions)
log: any
analytics(): RNFirebase.Analytics;
auth(): RNFirebase.auth.Auth;
on(type: string, handler: (msg: any) => void): any;
/** mimics firebase Web SDK */
database(): RNFirebase.database.Database;
/**RNFirebase mimics the Web Firebase SDK Storage,
* whilst providing some iOS and Android specific functionality.
*/
storage(): RNFirebase.storage.Storage;
/**
* Firebase Cloud Messaging (FCM) allows you to send push messages at no cost to both Android & iOS platforms.
* Assuming the installation instructions have been followed, FCM is ready to go.
* As the Firebase Web SDK has limited messaging functionality,
* the following methods within react-native-firebase have been created to handle FCM in the React Native environment.
*/
messaging(): RNFirebase.messaging.Messaging;
/**
* RNFirebase provides crash reporting for your app out of the box.
* Please note crashes do not appear in real-time on the console,
* they tend to take a number of hours to appear
* If you want to manually report a crash,
* such as a pre-caught exception this is possible by using the report method.
*/
crash(): RNFirebase.crash.Crash;
apps: Array<string>;
googleApiAvailability: RNFirebase.GoogleApiAvailabilityType;
static initializeApp(options?: any | RNFirebase.configurationOptions, name?: string): FireBase;
[key: string]: any;
}
namespace RNFirebase {
interface RnError extends Error {
code?: string;
}
type GoogleApiAvailabilityType = {
status: number,
isAvailable: boolean,
isUserResolvableError?: boolean,
error?: string
};
/**
* pass custom options by passing an object with configuration options.
* The configuration object will be generated first by the native configuration object, if set and then will be overridden if passed in JS.
* That is, all of the following key/value pairs are optional if the native configuration is set.
*/
interface configurationOptions {
/**
* default false
* When set to true, RNFirebase will log messages to the console and fire debug events we can listen to in js
* @usage
* firebase.on('debug', msg => console.log('Received debug message', msg))
*/
debug?: boolean;
/**
* default false
* When set to true, database persistence will be enabled.
*/
persistence?: boolean;
/**
* Default from app [NSBundle mainBundle] The bundle ID for the app to be bundled with
*/
bundleID?: string;
/**
* defualt ""
* The Google App ID that is used to uniquely identify an instance of an app.
*/
googleAppID?: string;
/**
* deufalt ""
* The database root (i.e. https://my-app.firebaseio.com)
*/
databaseURL?: string;
/**
* defualt ""
* URL scheme to set up durable deep link service
*/
deepLinkURLScheme?: string;
/**
* defualt ""
* The Google Cloud storage bucket name
*/
storageBucket?: string;
/**
* default ""
* The Android client ID used in Google AppInvite when an iOS app has it's android version
*/
androidClientID?: string;
/**
* default ""
* The Project number from the Google Developer's console used to configure Google Cloud Messaging
*/
GCMSenderID?: string;
/**
* default ""
* The tracking ID for Google Analytics
*/
trackingID?: string;
/**
* default ""
* The OAuth2 client ID for iOS application used to authenticate Google Users for signing in with Google
*/
clientID?: string;
/**
* defualt ""
* The secret iOS API key used for authenticating requests from our app
*/
APIKey?: string
}
namespace storage {
interface StorageTask<T> extends Promise<T> {
on(
event: TaskEvent,
nextOrObserver: (snapshot: any) => any,
error: (error: RnError) => any,
complete: (complete: any) => any
): any
/**
* is not currently supported by react-native-firebase
*/
pause(): void
/**
* is not currently supported by react-native-firebase
*/
resume(): void
/**
* is not currently supported by react-native-firebase
*/
cancel(): void
}
interface RNStorage extends Reference {
/**
* Downloads a reference to the device
* @param {String} filePath Where to store the file
* @return {Promise}
* */
downloadFile(filePath: string): StorageTask<any>;
/**
* Upload a file path
* @returns {Promise}
*/
putFile(filePath: string, metadata?: any): StorageTask<any>;
setMaxDownloadRetryTime(time: number): void
[key: string]: any;
}
interface Storage {
maxOperationRetryTime: number;
maxUploadRetryTime: number;
ref(path?: string): storage.RNStorage;
refFromURL(url: string): storage.RNStorage;
setMaxOperationRetryTime(time: number): any;
setMaxUploadRetryTime(time: number): any;
}
interface Reference {
bucket: string;
child(path: string): storage.Reference;
delete(): Promise<any>;
fullPath: string;
getDownloadURL(): Promise<any>;
getMetadata(): Promise<any>;
name: string;
parent: storage.Reference | null;
put(data: any | Uint8Array | ArrayBuffer,
metadata?: storage.UploadMetadata):
storage.UploadTask;
putString(
data: string, format?: storage.StringFormat,
metadata?: storage.UploadMetadata):
storage.UploadTask;
root: storage.Reference;
storage: storage.Storage;
toString(): string;
updateMetadata(metadata: storage.SettableMetadata):
Promise<any>;
}
interface UploadMetadata extends storage.SettableMetadata {
md5Hash?: string | null;
}
interface SettableMetadata {
cacheControl?: string | null;
contentDisposition?: string | null;
contentEncoding?: string | null;
contentLanguage?: string | null;
contentType?: string | null;
customMetadata?: { [/* warning: coerced from ? */ key: string]: string } | null;
}
type StringFormat = string;
var StringFormat: {
BASE64: StringFormat,
BASE64URL: StringFormat,
DATA_URL: StringFormat,
RAW: StringFormat,
}
interface UploadTask {
cancel(): boolean;
catch(onRejected: (a: RnError) => any): Promise<any>;
on(event: storage.TaskEvent, nextOrObserver?: null | Object,
error?: ((a: RnError) => any) | null, complete?: (() => any) | null): Function;
pause(): boolean;
resume(): boolean;
snapshot: storage.UploadTaskSnapshot;
then(
onFulfilled?: ((a: storage.UploadTaskSnapshot) => any) | null,
onRejected?: ((a: RnError) => any) | null): Promise<any>;
}
interface UploadTaskSnapshot {
bytesTransferred: number;
downloadURL: string | null;
metadata: storage.FullMetadata;
ref: storage.Reference;
state: storage.TaskState;
task: storage.UploadTask;
totalBytes: number;
}
interface FullMetadata extends storage.UploadMetadata {
bucket: string;
downloadURLs: string[];
fullPath: string;
generation: string;
metageneration: string;
name: string;
size: number;
timeCreated: string;
updated: string;
}
type TaskEvent = string;
var TaskEvent: {
STATE_CHANGED: TaskEvent,
};
type TaskState = string;
var TaskState: {
CANCELED: TaskState,
ERROR: TaskState,
PAUSED: TaskState,
RUNNING: TaskState,
SUCCESS: TaskState,
};
}
namespace database {
interface Database {
/**
* Returns a new firebase reference instance
* */
ref(path: string): RnReference
/**
* register listener
*/
on(path: string, modifiersString: string, modifiers: Array<string>, eventName: string, cb: () => void, errorCb: () => void): any
/**
* unregister listener
*/
off(path: string, modifiersString: string, eventName?: string, origCB?: () => void): any
/**
* Removes all event handlers and their native subscriptions
*/
cleanup(): Promise<any>
/**
* connect to firebase backend
*/
goOnline(): void
/**
* disconnect to firebase backend
*/
goOffline(): void
[key: string]: any;
}
interface RnReference extends Reference {
keepSynced(bool: boolean): any
filter(name: string, value: any, key?: string): any;
[key: string]: any;
}
interface Query {
endAt(value: number | string | boolean | null, key?: string): database.Query;
equalTo(value: number | string | boolean | null, key?: string): database.Query;
isEqual(other: database.Query | null): boolean;
limitToFirst(limit: number): database.Query;
limitToLast(limit: number): database.Query;
off(eventType?: string,
callback?: (a: database.DataSnapshot, b?: string | null) => any,
context?: Object | null): any;
on(eventType: string,
callback: (a: database.DataSnapshot | null, b?: string) => any,
cancelCallbackOrContext?: Object | null, context?: Object | null):
(a: database.DataSnapshot | null, b?: string) => any;
once(
eventType: string,
successCallback?:
(a: database.DataSnapshot, b?: string) => any,
failureCallbackOrContext?: Object | null,
context?: Object | null): Promise<any>;
orderByChild(path: string): database.Query;
orderByKey(): database.Query;
orderByPriority(): database.Query;
orderByValue(): database.Query;
ref: database.Reference;
startAt(value: number | string | boolean | null, key?: string): database.Query;
toJSON(): Object;
toString(): string;
}
interface DataSnapshot {
child(path: string): database.DataSnapshot;
exists(): boolean;
exportVal(): any;
forEach(action: (a: database.DataSnapshot) => boolean): boolean;
getPriority(): string | number | null;
hasChild(path: string): boolean;
hasChildren(): boolean;
key: string | null;
numChildren(): number;
ref: database.Reference;
toJSON(): Object | null;
val(): any;
}
interface Reference extends database.Query {
child(path: string): database.Reference;
key: string | null;
onDisconnect(): any;
parent: database.Reference | null;
push(value?: any, onComplete?: (a: RnError | null) => any): any
remove(onComplete?: (a: RnError | null) => any): Promise<any>;
root: database.Reference;
set(value: any, onComplete?: (a: RnError | null) => any): Promise<any>;
setPriority(
priority: string | number | null,
onComplete: (a: RnError | null) => any): Promise<any>;
setWithPriority(
newVal: any, newPriority: string | number | null,
onComplete?: (a: RnError | null) => any): Promise<any>;
transaction(
transactionUpdate: (a: any) => any,
onComplete?:
(a: RnError | null, b: boolean,
c: database.DataSnapshot | null) => any,
applyLocally?: boolean): Promise<any>;
update(values: Object, onComplete?: (a: RnError | null) => any): Promise<any>;
}
}
/**
* firebase Analytics
*/
interface Analytics {
/**Log a custom event with optional params. */
logEvent(event: string, params?: Object): void
/** Sets whether analytics collection is enabled for this app on this device. */
setAnalyticsCollectionEnabled(enabled: boolean): void
/**
* Sets the current screen name, which specifies the current visual context in your app.
* Whilst screenClassOverride is optional,
* it is recommended it is always sent as your current class name,
* for example on Android it will always show as 'MainActivity' if not specified.
*/
setCurrentScreen(screenName: string, screenClassOverride?: string): void
/**
* Sets the minimum engagement time required before starting a session.
* The default value is 10000 (10 seconds)
*/
setMinimumSessionDuration(miliseconds: number): void
/**
* Sets the duration of inactivity that terminates the current session.
* The default value is 1800000 (30 minutes).
*/
setSessionTimeoutDuration(miliseconds: number): void
/**
* Gives a user a uniqiue identificaition.
* @example
* const id = firebase.auth().currentUser.uid;
*
* firebase.analytics().setUserId(id);
*/
setUserId(id: string): void
/**
* Sets a key/value pair of data on the current user.
*/
setUserProperty(name: string, value: string): void;
[key: string]: any;
}
interface User {
/**
* The user's display name (if available).
*/
displayName: string | null
/**
* - The user's email address (if available).
*/
email: string | null
/**
* - True if the user's email address has been verified.
*/
emailVerified: boolean
/**
*
*/
isAnonymous: boolean
/**
* - The URL of the user's profile picture (if available).
*/
photoURL: string | null
/**
* - Additional provider-specific information about the user.
*/
providerData: any | null
/**
* - The authentication provider ID for the current user.
* For example, 'facebook.com', or 'google.com'.
*/
providerId: string | null
/**
* - The user's unique ID.
*/
uid: string
/**
* Delete the current user.
*/
delete(): Promise<void>
/**
* Returns the users authentication token.
*/
getToken(): Promise<string>
/**
* Reauthenticate the current user with credentials:
*/
reauthenticate(credential: Credential): Promise<void>
/**
* Refreshes the current user.
*/
reload(): Promise<void>
/**
* Sends a verification email to a user.
* This will Promise reject is the user is anonymous.
*/
sendEmailVerification(): Promise<void>
/**
* Updates the user's email address.
* See Firebase docs for more information on security & email validation.
* This will Promise reject is the user is anonymous.
*/
updateEmail(email: string): Promise<void>
/**
* Important: this is a security sensitive operation that requires the user to have recently signed in.
* If this requirement isn't met, ask the user to authenticate again and then call firebase.User#reauthenticate.
* This will Promise reject is the user is anonymous.
*/
updatePassword(password: string): Promise<void>
/**
* Updates a user's profile data.
* Profile data should be an object of fields to update:
*/
updateProfile(profile: Object): Promise<void>
}
/** 3rd party provider Credentials */
interface Credential {
provider: string,
token: string,
secret: string
}
namespace auth {
interface Auth {
/**
* Returns the current Firebase authentication state.
*/
authenticated: boolean;
/**
* Returns the currently signed-in user (or null). See the User class documentation for further usage.
*/
currentUser: User | null
/**
* Listen for changes in the users auth state (logging in and out).
* This method returns a unsubscribe function to stop listening to events.
* Always ensure you unsubscribe from the listener when no longer needed to prevent updates to components no longer in use.
*/
onAuthStateChanged(
nextOrObserver: Object, error?: (a: RnError) => any,
completed?: () => any): () => any;
/**
* We can create a user by calling the createUserWithEmailAndPassword() function.
* The method accepts two parameters, an email and a password.
*/
createUserWithEmailAndPassword(email: string, password: string): Promise<User>
/**
* To sign a user in with their email and password, use the signInWithEmailAndPassword() function.
* It accepts two parameters, the user's email and password:
*/
signInWithEmailAndPassword(email: string, password: string): Promise<User>
/**
* Sign an anonymous user.
* If the user has already signed in, that user will be returned
*/
signInAnonymously(): Promise<User>
/**
* Sign in the user with a 3rd party credential provider.
* credential requires the following properties:
*/
signInWithCredential(credential: Credential): Promise<User>
/**
* Sign a user in with a self-signed JWT token.
* To sign a user using a self-signed custom token,
* use the signInWithCustomToken() function.
* It accepts one parameter, the custom token:
*/
signInWithCustomToken(token: string): Promise<User>
/**
* Sends a password reset email to the given email address.
* Unlike the web SDK,
* the email will contain a password reset link rather than a code.
*/
sendPasswordResetEmail(email: string): Promise<void>
/**
* Completes the password reset process,
* given a confirmation code and new password.
*/
signOut(): Promise<void>
[key: string]: any;
}
}
namespace messaging {
interface Messaging {
/**
* Subscribes the device to a topic.
*/
subscribeToTopic(topic: string): void
/**
* Unsubscribes the device from a topic.
*/
unsubscribeFromTopic(topic: string): void
/**
* When the application has been opened from a notification
* getInitialNotification is called and the notification payload is returned.
* Use onMessage for notifications when the app is running.
*/
getInitialNotification(): Promise<any>
/**
* Returns the devices FCM token.
* This token can be used in the Firebase console to send messages to directly.
*/
getToken(forceRefresh?: Boolean): Promise<string>
/**
* On the event a devices FCM token is refreshed by Google,
* the new token is returned in a callback listener.
*/
onTokenRefresh(listener: (token: string) => any): void
/**
* On a new message,
* the payload object is passed to the listener callback.
* This method is only triggered when the app is running.
* Use getInitialNotification for notifications which cause the app to open.
*/
onMessage(listener: (message: any) => any): void
/**
* Create a local notification from the device itself.
*/
createLocalNotification(notification: any): any
/**
* Schedule a local notification to be shown on the device.
*/
scheduleLocalNotification(notification: any): any
/**
* Returns an array of all currently scheduled notifications.
* ```
* firebase.messaging().getScheduledLocalNotifications()
* .then((notifications) => {
* console.log('Current scheduled notifications: ', notifications);
* });
* ```
*/
getScheduledLocalNotifications(): Promise<any[]>
/**
* Cancels a location notification by ID,
* or all notifications by *.
*/
cancelLocalNotification(id: string): void
/**
* Removes all delivered notifications from device by ID,
* or all notifications by *.
*/
removeDeliveredNotification(id: string): void
/**
* IOS
* Requests app notification permissions in an Alert dialog.
*/
requestPermissions(): void
/**
* IOS
* Sets the badge number on the iOS app icon.
*/
setBadgeNumber(value: number): void
/**
* IOS
* Returns the current badge number on the app icon.
*/
getBadgeNumber(): number
/**
* Send an upstream message
* @param senderId
* @param payload
*/
send(senderId: string, payload: RemoteMessage): any
NOTIFICATION_TYPE: Object
REMOTE_NOTIFICATION_RESULT: Object
WILL_PRESENT_RESULT: Object
EVENT_TYPE: Object
}
interface RemoteMessage {
id: string,
type: string,
ttl?: number,
sender: string,
collapseKey?: string,
data: Object,
}
}
namespace crash {
interface Crash {
/** Logs a message that will appear in a subsequent crash report. */
log(message: string): void
/**
* Android: Logs a message that will appear in a subsequent crash report as well as in logcat.
* iOS: Logs the message in the subsequest crash report only (same as log).
*/
logcat(level: number, tag: string, message: string): void
/**
* Files a crash report, along with any previous logs to Firebase.
* An Error object must be passed into the report method.
*/
report(error: RnError, maxStackSize: Number): void
[key: string]: any;
}
}
}
}

View File

@ -2,9 +2,21 @@
#define RNFirebase_h #define RNFirebase_h
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <React/RCTBridgeModule.h> #if __has_include(<React/RCTEventDispatcher.h>)
#import <React/RCTEventDispatcher.h> #import <React/RCTEventDispatcher.h>
#else // Compatibility for RN version < 0.40
#import "RCTEventDispatcher.h"
#endif
#if __has_include(<React/RCTEventEmitter.h>)
#import <React/RCTEventEmitter.h> #import <React/RCTEventEmitter.h>
#else // Compatibility for RN version < 0.40
#import "RCTEventEmitter.h"
#endif
#if __has_include(<React/RCTBridgeModule.h>)
#import <React/RCTBridgeModule.h>
#else // Compatibility for RN version < 0.40
#import "RCTBridgeModule.h"
#endif
@interface RNFirebase : RCTEventEmitter <RCTBridgeModule> { @interface RNFirebase : RCTEventEmitter <RCTBridgeModule> {
} }

View File

@ -1,7 +1,11 @@
#ifndef RNFirebaseAnalytics_h #ifndef RNFirebaseAnalytics_h
#define RNFirebaseAnalytics_h #define RNFirebaseAnalytics_h
#if __has_include(<React/RCTBridgeModule.h>)
#import <React/RCTBridgeModule.h> #import <React/RCTBridgeModule.h>
#else // Compatibility for RN version < 0.40
#import "RCTBridgeModule.h"
#endif
@interface RNFirebaseAnalytics : NSObject <RCTBridgeModule> { @interface RNFirebaseAnalytics : NSObject <RCTBridgeModule> {

View File

@ -2,8 +2,16 @@
#define RNFirebaseAuth_h #define RNFirebaseAuth_h
#import "Firebase.h" #import "Firebase.h"
#if __has_include(<React/RCTEventEmitter.h>)
#import <React/RCTEventEmitter.h> #import <React/RCTEventEmitter.h>
#else // Compatibility for RN version < 0.40
#import "RCTEventEmitter.h"
#endif
#if __has_include(<React/RCTBridgeModule.h>)
#import <React/RCTBridgeModule.h> #import <React/RCTBridgeModule.h>
#else // Compatibility for RN version < 0.40
#import "RCTBridgeModule.h"
#endif
@interface RNFirebaseAuth : RCTEventEmitter <RCTBridgeModule> { @interface RNFirebaseAuth : RCTEventEmitter <RCTBridgeModule> {
FIRAuthStateDidChangeListenerHandle authListenerHandle; FIRAuthStateDidChangeListenerHandle authListenerHandle;

View File

@ -636,6 +636,44 @@ RCT_EXPORT_METHOD(reauthenticate:(NSString *)provider authToken:(NSString *)auth
} }
/**
Converts an array of FIRUserInfo instances into the correct format to match the web sdk
@param providerData FIRUser.providerData
@return NSArray
*/
- (NSArray <NSObject *> *) convertProviderData:(NSArray <id<FIRUserInfo>> *) providerData {
NSMutableArray *output = [NSMutableArray array];
for (id<FIRUserInfo> userInfo in providerData) {
NSMutableDictionary *pData = [NSMutableDictionary dictionary];
if (userInfo.providerID != nil) {
[pData setValue: userInfo.providerID forKey:@"providerId"];
}
if (userInfo.uid != nil) {
[pData setValue: userInfo.uid forKey:@"uid"];
}
if (userInfo.displayName != nil) {
[pData setValue: userInfo.displayName forKey:@"displayName"];
}
if (userInfo.photoURL != nil) {
[pData setValue: [userInfo.photoURL absoluteString] forKey:@"photoURL"];
}
if (userInfo.email != nil) {
[pData setValue: userInfo.email forKey:@"email"];
}
[output addObject:pData];
}
return output;
}
/** /**
Converts a FIRUser instance into a dictionary to send via RNBridge Converts a FIRUser instance into a dictionary to send via RNBridge
@ -650,15 +688,13 @@ RCT_EXPORT_METHOD(reauthenticate:(NSString *)provider authToken:(NSString *)auth
@"isAnonymous": @(user.anonymous), @"isAnonymous": @(user.anonymous),
@"displayName": user.displayName ? user.displayName : [NSNull null], @"displayName": user.displayName ? user.displayName : [NSNull null],
@"refreshToken": user.refreshToken, @"refreshToken": user.refreshToken,
@"providerId": [user.providerID lowercaseString] @"providerId": [user.providerID lowercaseString],
} @"providerData": [self convertProviderData: user.providerData]
mutableCopy } mutableCopy
]; ];
// todo providerData
if ([user valueForKey:@"photoURL"] != nil) { if ([user valueForKey:@"photoURL"] != nil) {
[userDict setValue: [NSString stringWithFormat:@"%@", user.photoURL] forKey:@"photoURL"]; [userDict setValue: [user.photoURL absoluteString] forKey:@"photoURL"];
} }
return userDict; return userDict;

View File

@ -1,7 +1,11 @@
#ifndef RNFirebaseCrash_h #ifndef RNFirebaseCrash_h
#define RNFirebaseCrash_h #define RNFirebaseCrash_h
#if __has_include(<React/RCTBridgeModule.h>)
#import <React/RCTBridgeModule.h> #import <React/RCTBridgeModule.h>
#else // Compatibility for RN version < 0.40
#import "RCTBridgeModule.h"
#endif
@interface RNFirebaseCrash : NSObject <RCTBridgeModule> { @interface RNFirebaseCrash : NSObject <RCTBridgeModule> {

View File

@ -2,8 +2,16 @@
#define RNFirebaseDatabase_h #define RNFirebaseDatabase_h
#import "Firebase.h" #import "Firebase.h"
#if __has_include(<React/RCTEventEmitter.h>)
#import <React/RCTEventEmitter.h> #import <React/RCTEventEmitter.h>
#else // Compatibility for RN version < 0.40
#import "RCTEventEmitter.h"
#endif
#if __has_include(<React/RCTBridgeModule.h>)
#import <React/RCTBridgeModule.h> #import <React/RCTBridgeModule.h>
#else // Compatibility for RN version < 0.40
#import "RCTBridgeModule.h"
#endif
@interface RNFirebaseDatabase : RCTEventEmitter <RCTBridgeModule> { @interface RNFirebaseDatabase : RCTEventEmitter <RCTBridgeModule> {

View File

@ -219,7 +219,7 @@
type:(NSString *) type type:(NSString *) type
{ {
if ([type isEqualToString:@"number"]) { if ([type isEqualToString:@"number"]) {
return [NSNumber numberWithInteger:value.integerValue]; return [NSNumber numberWithDouble:value.doubleValue];
} else if ([type isEqualToString:@"boolean"]) { } else if ([type isEqualToString:@"boolean"]) {
return [NSNumber numberWithBool:value.boolValue]; return [NSNumber numberWithBool:value.boolValue];
} else { } else {
@ -361,7 +361,11 @@ RCT_EXPORT_METHOD(enablePersistence:(BOOL) enable
BOOL isEnabled = [FIRDatabase database].persistenceEnabled; BOOL isEnabled = [FIRDatabase database].persistenceEnabled;
if ( isEnabled != enable) { if ( isEnabled != enable) {
@try {
[FIRDatabase database].persistenceEnabled = enable; [FIRDatabase database].persistenceEnabled = enable;
} @catch (NSException *exception) {
// do nothing - for RN packager reloads
}
} }
callback(@[[NSNull null], @{ callback(@[[NSNull null], @{
@"result": @"success" @"result": @"success"

View File

@ -1,7 +1,11 @@
#ifndef RNFirebaseErrors_h #ifndef RNFirebaseErrors_h
#define RNFirebaseErrors_h #define RNFirebaseErrors_h
#if __has_include(<React/RCTBridgeModule.h>)
#import <React/RCTBridgeModule.h> #import <React/RCTBridgeModule.h>
#else // Compatibility for RN version < 0.40
#import "RCTBridgeModule.h"
#endif
#import "Firebase.h" #import "Firebase.h"
@interface RNFirebaseErrors : NSObject <RCTBridgeModule> { @interface RNFirebaseErrors : NSObject <RCTBridgeModule> {

View File

@ -5,9 +5,22 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "Firebase.h" #import "Firebase.h"
#if __has_include(<React/RCTEventEmitter.h>)
#import <React/RCTEventEmitter.h> #import <React/RCTEventEmitter.h>
#else // Compatibility for RN version < 0.40
#import "RCTEventEmitter.h"
#endif
#if __has_include(<React/RCTBridgeModule.h>)
#import <React/RCTBridgeModule.h> #import <React/RCTBridgeModule.h>
#else // Compatibility for RN version < 0.40
#import "RCTBridgeModule.h"
#endif
#if __has_include(<React/RCTUtils.h>)
#import <React/RCTUtils.h> #import <React/RCTUtils.h>
#else // Compatibility for RN version < 0.40
#import "RCTUtils.h"
#endif
@import UserNotifications; @import UserNotifications;

View File

@ -1,8 +1,20 @@
#import "RNFirebaseMessaging.h" #import "RNFirebaseMessaging.h"
#import <React/RCTConvert.h> #if __has_include(<React/RCTEventDispatcher.h>)
#import <React/RCTEventDispatcher.h> #import <React/RCTEventDispatcher.h>
#else // Compatibility for RN version < 0.40
#import "RCTEventDispatcher.h"
#endif
#if __has_include(<React/RCTConvert.h>)
#import <React/RCTConvert.h>
#else // Compatibility for RN version < 0.40
#import "RCTConvert.h"
#endif
#if __has_include(<React/RCTUtils.h>)
#import <React/RCTUtils.h> #import <React/RCTUtils.h>
#else // Compatibility for RN version < 0.40
#import "RCTUtils.h"
#endif
@import UserNotifications; @import UserNotifications;
#import <FirebaseMessaging/FirebaseMessaging.h> #import <FirebaseMessaging/FirebaseMessaging.h>

View File

@ -2,8 +2,16 @@
#define RNFirebaseStorage_h #define RNFirebaseStorage_h
#import "Firebase.h" #import "Firebase.h"
#import <React/RCTBridgeModule.h> #if __has_include(<React/RCTEventEmitter.h>)
#import <React/RCTEventEmitter.h> #import <React/RCTEventEmitter.h>
#else // Compatibility for RN version < 0.40
#import "RCTEventEmitter.h"
#endif
#if __has_include(<React/RCTBridgeModule.h>)
#import <React/RCTBridgeModule.h>
#else // Compatibility for RN version < 0.40
#import "RCTBridgeModule.h"
#endif
@interface RNFirebaseStorage : RCTEventEmitter <RCTBridgeModule> { @interface RNFirebaseStorage : RCTEventEmitter <RCTBridgeModule> {

View File

@ -1,9 +1,7 @@
import { NativeModules } from 'react-native'; import { NativeModules } from 'react-native';
import { promisify } from './../../utils';
const FirebaseAuth = NativeModules.RNFirebaseAuth; const FirebaseAuth = NativeModules.RNFirebaseAuth;
// TODO refreshToken property
/** /**
* @url https://firebase.google.com/docs/reference/js/firebase.User * @url https://firebase.google.com/docs/reference/js/firebase.User
@ -144,10 +142,13 @@ export default class User {
return FirebaseAuth.getToken(forceRefresh); return FirebaseAuth.getToken(forceRefresh);
} }
// TODO return from native /**
// get providerData() { *
// return this._valueOrNull('providerData'); * @returns {Array}
// } */
get providerData(): Array {
return this._valueOrNull('providerData') || [];
}
/** /**
* Update the current user's email * Update the current user's email

View File

@ -76,6 +76,12 @@ export class ReferenceBase extends Base {
this.path = path || '/'; this.path = path || '/';
} }
/**
* The last part of a Reference's path (after the last '/')
* The key of a root Reference is null.
* @type {String}
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#key}
*/
get key(): string|null { get key(): string|null {
return this.path === '/' ? null : this.path.substring(this.path.lastIndexOf('/') + 1); return this.path === '/' ? null : this.path.substring(this.path.lastIndexOf('/') + 1);
} }

View File

@ -14,8 +14,44 @@ const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
let refId = 1; let refId = 1;
/** /**
* Enum for event types
* @readonly
* @enum {String}
*/
const ReferenceEventTypes = {
value: 'value',
child_added: 'child_added',
child_removed: 'child_removed',
child_changed: 'child_changed',
child_moved: 'child_moved',
};
/**
* @typedef {String} ReferenceLocation - Path to location in the database, relative
* to the root reference. Consists of a path where segments are separated by a
* forward slash (/) and ends in a ReferenceKey - except the root location, which
* has no ReferenceKey.
*
* @example
* // root reference location: '/'
* // non-root reference: '/path/to/referenceKey'
*/
/**
* @typedef {String} ReferenceKey - Identifier for each location that is unique to that
* location, within the scope of its parent. The last part of a ReferenceLocation.
*/
/**
* Represents a specific location in your Database that can be used for
* reading or writing data.
*
* You can reference the root using firebase.database().ref() or a child location
* by calling firebase.database().ref("child/path").
*
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference * @link https://firebase.google.com/docs/reference/js/firebase.database.Reference
* @class Reference * @class Reference
* @extends ReferenceBase
*/ */
export default class Reference extends ReferenceBase { export default class Reference extends ReferenceBase {
@ -99,23 +135,157 @@ export default class Reference extends ReferenceBase {
} }
/** /**
* iOS: Called once with the initial data at the specified location and then once each
* time the data changes. It won't trigger until the entire contents have been
* synchronized.
* *
* @param eventName * Android: (@link https://github.com/invertase/react-native-firebase/issues/92)
* @param successCallback * - Array & number values: Called once with the initial data at the specified
* @param failureCallback * location and then twice each time the value changes.
* @param context TODO * - Other data types: Called once with the initial data at the specified location
* @returns {*} * and once each time the data type changes.
*
* @callback onValueCallback
* @param {!DataSnapshot} dataSnapshot - Snapshot representing data at the location
* specified by the current ref. If location has no data, .val() will return null.
*/ */
on(eventName: string, successCallback: () => any, failureCallback: () => any) {
if (!isFunction(successCallback)) throw new Error('The specified callback must be a function'); /**
if (failureCallback && !isFunction(failureCallback)) throw new Error('The specified error callback must be a function'); * Called once for each initial child at the specified location and then again
this.log.debug('adding reference.on', this.refId, eventName); * every time a new child is added.
*
* @callback onChildAddedCallback
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting the data for the
* relevant child.
* @param {?ReferenceKey} previousChildKey - For ordering purposes, the key
* of the previous sibling child by sort order, or null if it is the first child.
*/
/**
* Called once every time a child is removed.
*
* A child will get removed when either:
* - remove() is explicitly called on a child or one of its ancestors
* - set(null) is called on that child or one of its ancestors
* - a child has all of its children removed
* - there is a query in effect which now filters out the child (because it's sort
* order changed or the max limit was hit)
*
* @callback onChildRemovedCallback
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting the old data for
* the child that was removed.
*/
/**
* Called when a child (or any of its descendants) changes.
*
* A single child_changed event may represent multiple changes to the child.
*
* @callback onChildChangedCallback
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting new child contents.
* @param {?ReferenceKey} previousChildKey - For ordering purposes, the key
* of the previous sibling child by sort order, or null if it is the first child.
*/
/**
* Called when a child's sort order changes, i.e. its position relative to its
* siblings changes.
*
* @callback onChildMovedCallback
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting the data of the moved
* child.
* @param {?ReferenceKey} previousChildKey - For ordering purposes, the key
* of the previous sibling child by sort order, or null if it is the first child.
*/
/**
* @typedef (onValueCallback|onChildAddedCallback|onChildRemovedCallback|onChildChangedCallback|onChildMovedCallback) ReferenceEventCallback
*/
/**
* Called if the event subscription is cancelled because the client does
* not have permission to read this data (or has lost the permission to do so).
*
* @callback onFailureCallback
* @param {Error} error - Object indicating why the failure occurred
*/
/**
* Binds callback handlers to when data changes at the current ref's location.
* The primary method of reading data from a Database.
*
* Callbacks can be unbound using {@link off}.
*
* Event Types:
*
* - value: {@link onValueCallback}.
* - child_added: {@link onChildAddedCallback}
* - child_removed: {@link onChildRemovedCallback}
* - child_changed: {@link onChildChangedCallback}
* - child_moved: {@link onChildMovedCallback}
*
* @param {ReferenceEventType} eventType - Type of event to attach a callback for.
* @param {ReferenceEventCallback} successCallback - Function that will be called
* when the event occurs with the new data.
* @param {onFailureCallback=} failureCallbackOrContext - Optional callback that is called
* if the event subscription fails. {@link onFailureCallback}
* @param {*=} context - Optional object to bind the callbacks to when calling them.
* @returns {ReferenceEventCallback} callback function, unmodified (unbound), for
* convenience if you want to pass an inline function to on() and store it later for
* removing using off().
*
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#on}
*/
on(eventType: string, successCallback: () => any, failureCallbackOrContext: () => any, context: any) {
if (!eventType) throw new Error('Error: Query on failed: Was called with 0 arguments. Expects at least 2');
if (!ReferenceEventTypes[eventType]) throw new Error('Query.on failed: First argument must be a valid event type: "value", "child_added", "child_removed", "child_changed", or "child_moved".');
if (!successCallback) throw new Error('Query.on failed: Was called with 1 argument. Expects at least 2.');
if (!isFunction(successCallback)) throw new Error('Query.on failed: Second argument must be a valid function.');
if (arguments.length > 2 && !failureCallbackOrContext) throw new Error('Query.on failed: third argument must either be a cancel callback or a context object.');
this.log.debug('adding reference.on', this.refId, eventType);
let _failureCallback;
let _context;
if (context) {
_context = context;
_failureCallback = failureCallbackOrContext;
} else {
if (isFunction(failureCallbackOrContext)) {
_failureCallback = failureCallbackOrContext;
} else {
_context = failureCallbackOrContext;
}
}
if (_failureCallback) {
_failureCallback = (error) => {
if (error.message.startsWith('FirebaseError: permission_denied')) {
error.message = `permission_denied at /${this.path}: Client doesn\'t have permission to access the desired data.`
}
failureCallbackOrContext(error);
}
}
let _successCallback;
if (_context) {
_successCallback = successCallback.bind(_context);
} else {
_successCallback = successCallback;
}
const listener = { const listener = {
listenerId: Object.keys(this.listeners).length + 1, listenerId: Object.keys(this.listeners).length + 1,
eventName, eventName: eventType,
successCallback, successCallback: _successCallback,
failureCallback, failureCallback: _failureCallback,
}; };
this.listeners[listener.listenerId] = listener; this.listeners[listener.listenerId] = listener;
this.database.on(this, listener); this.database.on(this, listener);
return successCallback; return successCallback;
@ -144,29 +314,49 @@ export default class Reference extends ReferenceBase {
} }
/** /**
* Detaches a callback attached with on().
* *
* @param eventName * Calling off() on a parent listener will not automatically remove listeners
* @param origCB * registered on child nodes.
* @returns {*} *
* If on() was called multiple times with the same eventType off() must be
* called multiple times to completely remove it.
*
* If a callback is not specified, all callbacks for the specified eventType
* will be removed. If no eventType or callback is specified, all callbacks
* for the Reference will be removed.
*
* If a context is specified, it too is used as a filter parameter: a callback
* will only be detached if, when it was attached with on(), the same event type,
* callback function and context were provided.
*
* If no callbacks matching the parameters provided are found, no callbacks are
* detached.
*
* @param {('value'|'child_added'|'child_changed'|'child_removed'|'child_moved')=} eventType - Type of event to detach callback for.
* @param {Function=} originalCallback - Original callback passed to on()
* @param {*=} context - The context passed to on() when the callback was bound
*
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#off}
*/ */
off(eventName?: string = '', origCB?: () => any) { off(eventType?: string = '', originalCallback?: () => any) {
this.log.debug('ref.off(): ', this.refId, eventName); this.log.debug('ref.off(): ', this.refId, eventType);
// $FlowFixMe // $FlowFixMe
const listeners: Array<DatabaseListener> = Object.values(this.listeners); const listeners: Array<DatabaseListener> = Object.values(this.listeners);
let listenersToRemove; let listenersToRemove;
if (eventName && origCB) { if (eventType && originalCallback) {
listenersToRemove = listeners.filter((listener) => { listenersToRemove = listeners.filter((listener) => {
return listener.eventName === eventName && listener.successCallback === origCB; return listener.eventName === eventType && listener.successCallback === originalCallback;
}); });
// Only remove a single listener as per the web spec // Only remove a single listener as per the web spec
if (listenersToRemove.length > 1) listenersToRemove = [listenersToRemove[0]]; if (listenersToRemove.length > 1) listenersToRemove = [listenersToRemove[0]];
} else if (eventName) { } else if (eventType) {
listenersToRemove = listeners.filter((listener) => { listenersToRemove = listeners.filter((listener) => {
return listener.eventName === eventName; return listener.eventName === eventType;
}); });
} else if (origCB) { } else if (originalCallback) {
listenersToRemove = listeners.filter((listener) => { listenersToRemove = listeners.filter((listener) => {
return listener.successCallback === origCB; return listener.successCallback === originalCallback;
}); });
} else { } else {
listenersToRemove = listeners; listenersToRemove = listeners;
@ -185,8 +375,16 @@ export default class Reference extends ReferenceBase {
* @param onComplete * @param onComplete
* @param applyLocally * @param applyLocally
*/ */
transaction(transactionUpdate: Function, onComplete: (?Error, boolean, ?Snapshot) => *, applyLocally: boolean = false) { transaction(
if (!isFunction(transactionUpdate)) return Promise.reject(new Error('Missing transactionUpdate function argument.')); transactionUpdate: Function,
onComplete: (error: ?Error, committed: boolean, snapshot: ?Snapshot) => *,
applyLocally: boolean = false
) {
if (!isFunction(transactionUpdate)) {
return Promise.reject(
new Error('Missing transactionUpdate function argument.')
);
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const onCompleteWrapper = (error, committed, snapshotData) => { const onCompleteWrapper = (error, committed, snapshotData) => {
@ -349,9 +547,12 @@ export default class Reference extends ReferenceBase {
} }
/** /**
* Get a specified child * Creates a Reference to a child of the current Reference, using a relative path.
* @param path * No validation is performed on the path to ensure it has a valid format.
* @returns {Reference} * @param {String} path relative to current ref's location
* @returns {!Reference} A new Reference to the path provided, relative to the current
* Reference
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#child}
*/ */
child(path: string) { child(path: string) {
return new Reference(this.database, `${this.path}/${path}`); return new Reference(this.database, `${this.path}/${path}`);
@ -370,6 +571,7 @@ export default class Reference extends ReferenceBase {
* same instance of firebase.app.App - multiple firebase apps not currently supported. * same instance of firebase.app.App - multiple firebase apps not currently supported.
* @param {Reference} otherRef - Other reference to compare to this one * @param {Reference} otherRef - Other reference to compare to this one
* @return {Boolean} Whether otherReference is equal to this one * @return {Boolean} Whether otherReference is equal to this one
*
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#isEqual} * {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#isEqual}
*/ */
isEqual(otherRef: Reference): boolean { isEqual(otherRef: Reference): boolean {
@ -381,8 +583,10 @@ export default class Reference extends ReferenceBase {
*/ */
/** /**
* Returns the parent ref of the current ref i.e. a ref of /foo/bar would return a new ref to '/foo' * The parent location of a Reference, or null for the root Reference.
* @returns {*} * @type {Reference}
*
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#parent}
*/ */
get parent(): Reference|null { get parent(): Reference|null {
if (this.path === '/') return null; if (this.path === '/') return null;
@ -392,6 +596,7 @@ export default class Reference extends ReferenceBase {
/** /**
* A reference to itself * A reference to itself
* @type {!Reference} * @type {!Reference}
*
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#ref} * {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#ref}
*/ */
get ref(): Reference { get ref(): Reference {
@ -399,8 +604,10 @@ export default class Reference extends ReferenceBase {
} }
/** /**
* Returns a ref to the root of db - '/' * Reference to the root of the database: '/'
* @returns {Reference} * @type {!Reference}
*
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#root}
*/ */
get root(): Reference { get root(): Reference {
return new Reference(this.database, '/'); return new Reference(this.database, '/');

View File

@ -5,6 +5,7 @@ import Reference from './reference.js';
import { isObject, deepGet, deepExists } from './../../utils'; import { isObject, deepGet, deepExists } from './../../utils';
/** /**
* @class DataSnapshot
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot * @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot
*/ */
export default class Snapshot { export default class Snapshot {

View File

@ -165,7 +165,7 @@ export function promisify(
fn: Function | string, fn: Function | string,
NativeModule: Object, NativeModule: Object,
errorPrefix?: string errorPrefix?: string
): (any) => Promise<> { ): (args: any) => Promise<> {
return (...args) => { return (...args) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const _fn = typeof fn === 'function' ? fn : NativeModule[fn]; const _fn = typeof fn === 'function' ? fn : NativeModule[fn];

View File

@ -1,6 +1,6 @@
{ {
"name": "react-native-firebase", "name": "react-native-firebase",
"version": "1.0.1", "version": "1.0.10",
"author": "Invertase <contact@invertase.io> (http://invertase.io)", "author": "Invertase <contact@invertase.io> (http://invertase.io)",
"description": "A react native firebase library supporting both android and ios native firebase SDK's", "description": "A react native firebase library supporting both android and ios native firebase SDK's",
"main": "index", "main": "index",
@ -48,7 +48,7 @@
], ],
"peerDependencies": { "peerDependencies": {
"react": "*", "react": "*",
"react-native": "*" "react-native": ">= 0.38.0"
}, },
"rnpm": { "rnpm": {
"commands": { "commands": {
@ -77,7 +77,7 @@
"flow-bin": "^0.40.0", "flow-bin": "^0.40.0",
"react": "^15.3.0", "react": "^15.3.0",
"react-dom": "^15.3.0", "react-dom": "^15.3.0",
"react-native": "^0.42.0", "react-native": "^0.44.0",
"wml": "0.0.82" "wml": "0.0.82"
}, },
"dependencies": { "dependencies": {

View File

@ -1,9 +1,5 @@
{ {
"presets": [ "presets": [
"react-native" "react-native"
],
"ignore": [
"node_modules/diff/lib/**/*.js",
"node_modules/diff/node_modules/**/*.js"
] ]
} }

2
tests/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__tests__/build/
ios/build/

View File

@ -68,7 +68,7 @@ Tests are bootstrapped and ran when the app is booted. The status of each test s
Tests can be run by pressing the play button in the toolbar of the app. Test can be run individually, by suite, or all at once. Tests can be run by pressing the play button in the toolbar of the app. Test can be run individually, by suite, or all at once.
![Test suite Android](/docs/assets/test-suite-screenshot-android.png?raw=true) ![Test suite Android](/tests/docs/assets/test-suite-screenshot-android.png?raw=true)
### Adding test ### Adding test

View File

@ -103,6 +103,84 @@ function pendingTestTests({ it: _it, describe: _describe }) {
otherTest.should.be.called(); otherTest.should.be.called();
}); });
}); });
_describe('when an outer context is focused', () => {
_it('a pending test will still not run', async () => {
const pendingTest = sinon.spy();
const otherTest = sinon.spy();
const unfocusedTest = sinon.spy();
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ fdescribe, it, xit }) => {
fdescribe('', () => {
xit('', pendingTest);
it('', otherTest);
});
it('', unfocusedTest);
});
testSuite.setStore({
getState: () => { return {}; },
});
const testIdsToRun = Object.keys(testSuite.testDefinitions.focusedTestIds).reduce((memo, testId) => {
if (!testSuite.testDefinitions.pendingTestIds[testId]) {
memo.push(testId);
}
return memo;
}, []);
await testSuite.run(testIdsToRun);
pendingTest.should.not.be.called();
otherTest.should.be.called();
unfocusedTest.should.not.be.called();
});
});
_describe('when an outer context is focused', () => {
_it('a pending context will still not run', async () => {
const pendingTest = sinon.spy();
const otherTest = sinon.spy();
const unfocusedTest = sinon.spy();
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ fdescribe, it, xdescribe }) => {
fdescribe('', () => {
xdescribe('', () => {
it('', pendingTest);
});
it('', otherTest);
});
it('', unfocusedTest);
});
testSuite.setStore({
getState: () => { return {}; },
});
const testIdsToRun = Object.keys(testSuite.testDefinitions.focusedTestIds).reduce((memo, testId) => {
if (!testSuite.testDefinitions.pendingTestIds[testId]) {
memo.push(testId);
}
return memo;
}, []);
await testSuite.run(testIdsToRun);
pendingTest.should.not.be.called();
otherTest.should.be.called();
unfocusedTest.should.not.be.called();
});
});
} }
export default pendingTestTests; export default pendingTestTests;

View File

@ -17,7 +17,7 @@ public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override @Override
protected boolean getUseDeveloperSupport() { public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG; return BuildConfig.DEBUG;
} }

View File

@ -5,7 +5,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.3.0' classpath 'com.android.tools.build:gradle:2.3.1'
classpath 'com.google.gms:google-services:3.0.0' classpath 'com.google.gms:google-services:3.0.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong

View File

@ -18,3 +18,4 @@
# org.gradle.parallel=true # org.gradle.parallel=true
android.useDeprecatedNdk=true android.useDeprecatedNdk=true
org.gradle.jvmargs=-Xmx1536M

View File

@ -2,3 +2,4 @@ import { AppRegistry } from 'react-native';
import bootstrap from './src/main'; import bootstrap from './src/main';
AppRegistry.registerComponent('ReactNativeFirebaseDemo', () => bootstrap); AppRegistry.registerComponent('ReactNativeFirebaseDemo', () => bootstrap);

View File

@ -7,23 +7,11 @@ target 'ReactNativeFirebaseDemo' do
# Uncomment this line if you're using Swift or would like to use dynamic frameworks # Uncomment this line if you're using Swift or would like to use dynamic frameworks
# use_frameworks! # use_frameworks!
react_native_path = "../node_modules/react-native"
pod "Yoga", :path => "#{react_native_path}/ReactCommon/yoga"
# Pods for ReactNativeFirebaseDemo # Pods for ReactNativeFirebaseDemo
pod 'React', :path => '../node_modules/react-native', :subspecs => [ pod 'React', :path => '../node_modules/react-native'
'Core',
'RCTActionSheet',
'RCTAnimation',
'RCTCameraRoll',
'RCTGeolocation',
'RCTImage',
'RCTLinkingIOS',
'RCTNetwork',
'RCTPushNotification',
'RCTSettings',
'RCTText',
'RCTVibration',
'RCTWebSocket'
# Add any other subspecs you want to use in your project
]
pod 'Firebase/Auth' pod 'Firebase/Auth'
pod 'Firebase/Analytics' pod 'Firebase/Analytics'

View File

@ -1,168 +0,0 @@
PODS:
- Firebase/Analytics (3.14.0):
- Firebase/Core
- Firebase/AppIndexing (3.14.0):
- Firebase/Core
- FirebaseAppIndexing (= 1.2.0)
- Firebase/Auth (3.14.0):
- Firebase/Core
- FirebaseAuth (= 3.1.1)
- Firebase/Core (3.14.0):
- FirebaseAnalytics (= 3.7.0)
- FirebaseCore (= 3.5.1)
- Firebase/Crash (3.14.0):
- Firebase/Core
- FirebaseCrash (= 1.1.6)
- Firebase/Database (3.14.0):
- Firebase/Core
- FirebaseDatabase (= 3.1.2)
- Firebase/DynamicLinks (3.14.0):
- Firebase/Core
- FirebaseDynamicLinks (= 1.3.3)
- Firebase/Messaging (3.14.0):
- Firebase/Core
- FirebaseMessaging (= 1.2.2)
- Firebase/RemoteConfig (3.14.0):
- Firebase/Core
- FirebaseRemoteConfig (= 1.3.4)
- Firebase/Storage (3.14.0):
- Firebase/Core
- FirebaseStorage (= 1.1.0)
- FirebaseAnalytics (3.7.0):
- FirebaseCore (~> 3.5)
- FirebaseInstanceID (~> 1.0)
- GoogleToolboxForMac/NSData+zlib (~> 2.1)
- FirebaseAppIndexing (1.2.0)
- FirebaseAuth (3.1.1):
- FirebaseAnalytics (~> 3.7)
- GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)
- GTMSessionFetcher/Core (~> 1.1)
- FirebaseCore (3.5.1):
- GoogleToolboxForMac/NSData+zlib (~> 2.1)
- FirebaseCrash (1.1.6):
- FirebaseAnalytics (~> 3.7)
- FirebaseInstanceID (~> 1.0)
- GoogleToolboxForMac/Logger (~> 2.1)
- GoogleToolboxForMac/NSData+zlib (~> 2.1)
- Protobuf (~> 3.1)
- FirebaseDatabase (3.1.2):
- FirebaseAnalytics (~> 3.7)
- FirebaseDynamicLinks (1.3.3):
- FirebaseAnalytics (~> 3.7)
- FirebaseInstanceID (1.0.9)
- FirebaseMessaging (1.2.2):
- FirebaseAnalytics (~> 3.7)
- FirebaseInstanceID (~> 1.0)
- GoogleToolboxForMac/Logger (~> 2.1)
- Protobuf (~> 3.1)
- FirebaseRemoteConfig (1.3.4):
- FirebaseAnalytics (~> 3.7)
- FirebaseInstanceID (~> 1.0)
- GoogleToolboxForMac/NSData+zlib (~> 2.1)
- Protobuf (~> 3.1)
- FirebaseStorage (1.1.0):
- FirebaseAnalytics (~> 3.7)
- GTMSessionFetcher/Core (~> 1.1)
- GoogleToolboxForMac/DebugUtils (2.1.1):
- GoogleToolboxForMac/Defines (= 2.1.1)
- GoogleToolboxForMac/Defines (2.1.1)
- GoogleToolboxForMac/Logger (2.1.1):
- GoogleToolboxForMac/Defines (= 2.1.1)
- GoogleToolboxForMac/NSData+zlib (2.1.1):
- GoogleToolboxForMac/Defines (= 2.1.1)
- GoogleToolboxForMac/NSDictionary+URLArguments (2.1.1):
- GoogleToolboxForMac/DebugUtils (= 2.1.1)
- GoogleToolboxForMac/Defines (= 2.1.1)
- GoogleToolboxForMac/NSString+URLArguments (= 2.1.1)
- GoogleToolboxForMac/NSString+URLArguments (2.1.1)
- GTMSessionFetcher/Core (1.1.8)
- Protobuf (3.2.0)
- React/Core (0.40.0):
- React/cxxreact
- React/yoga
- React/cxxreact (0.40.0):
- React/jschelpers
- React/jschelpers (0.40.0)
- React/RCTActionSheet (0.40.0):
- React/Core
- React/RCTAnimation (0.40.0):
- React/Core
- React/RCTCameraRoll (0.40.0):
- React/Core
- React/RCTImage
- React/RCTGeolocation (0.40.0):
- React/Core
- React/RCTImage (0.40.0):
- React/Core
- React/RCTNetwork
- React/RCTLinkingIOS (0.40.0):
- React/Core
- React/RCTNetwork (0.40.0):
- React/Core
- React/RCTPushNotification (0.40.0):
- React/Core
- React/RCTSettings (0.40.0):
- React/Core
- React/RCTText (0.40.0):
- React/Core
- React/RCTVibration (0.40.0):
- React/Core
- React/RCTWebSocket (0.40.0):
- React/Core
- React/yoga (0.40.0)
- RNFirebase (1.0.0-alpha13)
DEPENDENCIES:
- Firebase/Analytics
- Firebase/AppIndexing
- Firebase/Auth
- Firebase/Core
- Firebase/Crash
- Firebase/Database
- Firebase/DynamicLinks
- Firebase/Messaging
- Firebase/RemoteConfig
- Firebase/Storage
- React/Core (from `../node_modules/react-native`)
- React/RCTActionSheet (from `../node_modules/react-native`)
- React/RCTAnimation (from `../node_modules/react-native`)
- React/RCTCameraRoll (from `../node_modules/react-native`)
- React/RCTGeolocation (from `../node_modules/react-native`)
- React/RCTImage (from `../node_modules/react-native`)
- React/RCTLinkingIOS (from `../node_modules/react-native`)
- React/RCTNetwork (from `../node_modules/react-native`)
- React/RCTPushNotification (from `../node_modules/react-native`)
- React/RCTSettings (from `../node_modules/react-native`)
- React/RCTText (from `../node_modules/react-native`)
- React/RCTVibration (from `../node_modules/react-native`)
- React/RCTWebSocket (from `../node_modules/react-native`)
- RNFirebase (from `./../../`)
EXTERNAL SOURCES:
React:
:path: ../node_modules/react-native
RNFirebase:
:path: ./../../
SPEC CHECKSUMS:
Firebase: 85a581fb04e44f63ae9f4fbc8d6dabf4a4c18653
FirebaseAnalytics: 0d1b7d81d5021155be37702a94ba1ec16d45365d
FirebaseAppIndexing: d0fa52ce0ad13f4b5b2f09e4b47fb0dc2213f4e9
FirebaseAuth: cc8a1824170adbd351edb7f994490a3fb5c18be6
FirebaseCore: 225d40532489835a034b8f4e2c9c87fbf4f615a2
FirebaseCrash: db4c05d9c75baa050744d31b36357c8f1efba481
FirebaseDatabase: 05c96d7b43a7368dc91c07791adb49683e1738d1
FirebaseDynamicLinks: f0d025dd29a1d70418c003344813b67ab748ffb9
FirebaseInstanceID: 2d0518b1378fe9d685ef40cbdd63d2fdc1125339
FirebaseMessaging: df8267f378580a24174ce7861233aa11d5c90109
FirebaseRemoteConfig: af3003f4e8daa2bd1d5cf90d3cccc1fe224f8ed9
FirebaseStorage: a5c55b23741a49a72af8f30f95b3bb5ddbeda12d
GoogleToolboxForMac: 8e329f1b599f2512c6b10676d45736bcc2cbbeb0
GTMSessionFetcher: 6f8d8b28b7e345549ac471071608170b31cb4977
Protobuf: 745f59e122e5de98d4d7ef291e264a0eef80f58e
React: 6dfb2f72edb1d74a800127ae157af038646673ce
RNFirebase: 46bfe1099349ac6fac8c5e57cf4f0b0f4b7938ac
PODFILE CHECKSUM: f8bc5de55afd159ec2faf523f1b8e0d861d0832b
COCOAPODS: 1.2.0

View File

@ -286,8 +286,7 @@ class TestRun {
suiteId: this.testSuite.id, suiteId: this.testSuite.id,
status: RunStatus.ERR, status: RunStatus.ERR,
time: Date.now() - this.runStartTime, time: Date.now() - this.runStartTime,
message: `Test suite failed: ${error.message}`, message: `Test suite failed: ${error.message}`
stackTrace: error.stack,
}); });
}); });
} }
@ -306,7 +305,7 @@ class TestRun {
} }
async _safelyRunFunction(func, timeOutDuration, description) { async _safelyRunFunction(func, timeOutDuration, description) {
const syncResultOrPromise = tryCatcher(func); const syncResultOrPromise = captureThrownErrors(func);
if (syncResultOrPromise.error) { if (syncResultOrPromise.error) {
// Synchronous Error // Synchronous Error
@ -314,49 +313,59 @@ class TestRun {
} }
// Asynchronous Error // Asynchronous Error
return promiseToCallBack(syncResultOrPromise.value, timeOutDuration, description); return capturePromiseErrors(syncResultOrPromise.result, timeOutDuration, description);
} }
} }
/** /**
* Try catch to object * Call a function and capture any errors that are immediately thrown.
* @returns {{}} * @returns {Object} Object containing result of executing the function, or the error
* message that was captured
* @private * @private
*/ */
function tryCatcher(func) { function captureThrownErrors(func) {
const result = {}; const result = {};
try { try {
result.value = func(); result.result = func();
} catch (e) { } catch (error) {
result.error = e; result.error = error;
} }
return result; return result;
} }
/** /**
* Make a promise callback-able to trap errors * Wraps a promise so that if it's rejected or an error is thrown while it's being
* @param promise * evaluated, it's captured and thrown no further
* @param {*} target - Target to wrap. If a thenable object, it's wrapped so if it's
* rejected or an error is thrown, it will be captured. If a non-thenable object,
* wrapped in resolved promise and returned.
* @param {Number} timeoutDuration - Number of milliseconds the promise is allowed
* to pend before it's considered timed out
* @param {String} description - Description of the context the promises is defined
* in, used for reporting where a timeout occurred in the resulting error message.
* @private * @private
*/ */
function promiseToCallBack(promise, timeoutDuration, description) { function capturePromiseErrors(target, timeoutDuration, description) {
let returnValue = null; let returnValue = null;
try { try {
returnValue = Promise.resolve(promise) returnValue = Promise.resolve(target)
.then(() => { .then(() => {
return null; return null;
}, (error) => { }, (error) => {
return Promise.resolve(error); return Promise.resolve(error);
}) })
.timeout(timeoutDuration, `${description} took longer than ${timeoutDuration}ms. This can be extended with the timeout option.`)
.catch((error) => { .catch((error) => {
return Promise.resolve(error); return Promise.resolve(error);
}); })
.timeout(timeoutDuration,
`${description} took longer than ${timeoutDuration}ms. This can be extended with the timeout option.`
);
} catch (error) { } catch (error) {
returnValue = Promise.resolve(error); returnValue = Promise.resolve(error);
} }

View File

@ -111,19 +111,19 @@ class TestSuite {
*/ */
async run(testIds = undefined) { async run(testIds = undefined) {
const testsToRun = (() => { const testsToRun = (() => {
if (testIds) { return (testIds || Object.keys(this.testDefinitions.tests)).reduce((memo, id) => {
return testIds.map((id) => {
const test = this.testDefinitions.tests[id]; const test = this.testDefinitions.tests[id];
if (!test) { if (!test) {
throw new RangeError(`ReactNativeFirebaseTests.TestRunError: Test with id ${id} not found in test suite ${this.name}`); throw new RangeError(`ReactNativeFirebaseTests.TestRunError: Test with id ${id} not found in test suite ${this.name}`);
} }
return test; if (!this.testDefinitions.pendingTestIds[id]) {
}); memo.push(test);
} }
return Object.values(this.testDefinitions.tests); return memo;
}, []);
})(); })();
const testRun = new TestRun(this, testsToRun.reverse(), this.testDefinitions); const testRun = new TestRun(this, testsToRun.reverse(), this.testDefinitions);

View File

@ -32,12 +32,12 @@
"js-beautify": "^1.6.11", "js-beautify": "^1.6.11",
"lodash.groupby": "^4.6.0", "lodash.groupby": "^4.6.0",
"lodash.some": "^4.6.0", "lodash.some": "^4.6.0",
"react": "~15.4.1", "react": "16.0.0-alpha.6",
"react-native": "0.40.0", "react-native": "^0.44.0",
"react-native-firebase": "file:..", "react-native-firebase": "file:..",
"react-native-simple-toast": "0.0.5", "react-native-simple-toast": "0.0.5",
"react-native-vector-icons": "^4.0.0", "react-native-vector-icons": "^4.0.0",
"react-navigation": "^1.0.0-beta.7", "react-navigation": "^1.0.0-beta.9",
"react-redux": "^5.0.3", "react-redux": "^5.0.3",
"redux": "^3.6.0", "redux": "^3.6.0",
"redux-logger": "^2.8.2", "redux-logger": "^2.8.2",

View File

@ -14,13 +14,14 @@ export function setSuiteStatus({ suiteId, status, time, message, progress }) {
}; };
} }
export function setTestStatus({ testId, status, time = 0, message = null }) { export function setTestStatus({ testId, status, stackTrace, time = 0, message = null }) {
return { return {
type: TEST_SET_STATUS, type: TEST_SET_STATUS,
testId, testId,
status, status,
message, message,
stackTrace,
time, time,
}; };
} }

View File

@ -12,6 +12,7 @@ function testsReducers(state = initState.tests, action: Object): State {
flattened[`${action.testId}.status`] = action.status; flattened[`${action.testId}.status`] = action.status;
flattened[`${action.testId}.message`] = action.message; flattened[`${action.testId}.message`] = action.message;
flattened[`${action.testId}.time`] = action.time; flattened[`${action.testId}.time`] = action.time;
flattened[`${action.testId}.stackTrace`] = action.stackTrace;
return unflatten(flattened); return unflatten(flattened);
} }

View File

@ -13,17 +13,11 @@ class Overview extends React.Component {
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
static navigationOptions = { static navigationOptions = {
title: 'Test Suites', title: 'Test Suites',
header: () => { headerTintColor: '#ffffff',
return { headerStyle: { backgroundColor: '#0288d1' },
style: { backgroundColor: '#0288d1' }, headerRight: <View style={{ marginRight: 8 }}>
tintColor: '#ffffff',
right: (
<View style={{ marginRight: 8 }}>
<OverviewControlButton /> <OverviewControlButton />
</View> </View>
),
};
},
}; };
/** /**

View File

@ -11,25 +11,19 @@ import TestSuiteControlButton from '../components/TestSuiteControlButton';
class Suite extends React.Component { class Suite extends React.Component {
static navigationOptions = { static navigationOptions = ({ navigation: { state: { params: { title, testSuiteId, onlyShowFailingTests } }, setParams } }) => {
title: ({ state: { params: { title } } }) => {
return title;
},
header: ({ state: { params: { testSuiteId, onlyShowFailingTests } }, setParams }) => {
return { return {
style: { backgroundColor: '#0288d1' }, title,
tintColor: '#ffffff', headerTintColor: '#ffffff',
right: ( headerStyle: { backgroundColor: '#0288d1' },
<View style={{ flexDirection: 'row', marginRight: 8 }}> headerRight: <View style={{ flexDirection: 'row', marginRight: 8 }}>
<TestSuiteControlButton <TestSuiteControlButton
testSuiteId={testSuiteId} testSuiteId={testSuiteId}
onlyShowFailingTests={onlyShowFailingTests} onlyShowFailingTests={onlyShowFailingTests}
onFilterChange={setParams} onFilterChange={setParams}
/> />
</View> </View>,
),
}; };
},
}; };
/** /**

View File

@ -9,21 +9,15 @@ import TestControlButton from '../components/TestControlButton';
class Test extends React.Component { class Test extends React.Component {
static navigationOptions = { static navigationOptions = ({ navigation: { state: { params: { title, testId } } } }) => {
title: ({ state: { params: { title } } }) => {
return title;
},
header: ({ state: { params: { testId } } }) => {
return { return {
style: { backgroundColor: '#0288d1' }, title,
tintColor: '#ffffff', headerTintColor: '#ffffff',
right: ( headerStyle: { backgroundColor: '#0288d1' },
<View style={{ marginRight: 8 }}> headerRight: <View style={{ marginRight: 8 }}>
<TestControlButton testId={testId} /> <TestControlButton testId={testId} />
</View> </View>,
),
}; };
},
}; };
static renderBanner({ status, time }) { static renderBanner({ status, time }) {
@ -57,35 +51,28 @@ class Test extends React.Component {
setParams({ test }); setParams({ test });
} }
renderError() {
const { test: { message } } = this.props;
if (message) {
return (
<ScrollView>
<Text style={styles.codeHeader}>Test Error</Text>
<Text style={styles.code}>
<Text>{message}</Text>
</Text>
</ScrollView>
);
}
return null;
}
render() { render() {
const { test: { func, status, time } } = this.props; const { test: { stackTrace, description, func, status, time }, testContextName } = this.props;
return ( return (
<View style={styles.container}> <View style={styles.container}>
{Test.renderBanner({ status, time })} {Test.renderBanner({ status, time })}
<View style={styles.content}> <View >
{this.renderError()} <ScrollView style={styles.sectionContainer}>
<Text style={styles.codeHeader}>Test Code Preview</Text> <Text style={styles.heading}>{testContextName}</Text>
<ScrollView> <Text style={styles.description}>{description}</Text>
<Text style={styles.code}> </ScrollView>
<ScrollView style={styles.sectionContainer}>
<Text style={styles.heading}>Test Error</Text>
<Text style={styles.description}>
<Text>{stackTrace || 'None.'}</Text>
</Text>
</ScrollView>
<Text style={styles.heading}>
Test Code Preview
</Text>
<ScrollView style={styles.sectionContainer}>
<Text style={styles.description}>
{beautify(removeLastLine(removeFirstLine(func.toString())), { indent_size: 4, break_chained_methods: true })} {beautify(removeLastLine(removeFirstLine(func.toString())), { indent_size: 4, break_chained_methods: true })}
</Text> </Text>
</ScrollView> </ScrollView>
@ -99,10 +86,13 @@ Test.propTypes = {
test: PropTypes.shape({ test: PropTypes.shape({
status: PropTypes.string, status: PropTypes.string,
time: PropTypes.number, time: PropTypes.number,
message: PropTypes.string,
func: PropTypes.function, func: PropTypes.function,
stackTrace: PropTypes.function,
description: PropTypes.string,
}).isRequired, }).isRequired,
testContextName: PropTypes.string,
navigation: PropTypes.shape({ navigation: PropTypes.shape({
setParams: PropTypes.func.isRequired, setParams: PropTypes.func.isRequired,
}).isRequired, }).isRequired,
@ -113,27 +103,32 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
}, },
content: {}, sectionContainer: {
code: { minHeight: 100,
backgroundColor: '#3F373A',
color: '#c3c3c3',
padding: 5,
fontSize: 12,
}, },
codeHeader: { heading: {
fontWeight: '600',
fontSize: 18,
backgroundColor: '#000',
color: '#fff',
padding: 5, padding: 5,
backgroundColor: '#0288d1',
fontWeight: '600',
color: '#ffffff',
fontSize: 16,
},
description: {
padding: 5,
fontSize: 14,
}, },
}); });
function select({ tests }, { navigation: { state: { params: { testId } } } }) { function select({ tests, testContexts }, { navigation: { state: { params: { testId } } } }) {
const test = tests[testId]; const test = tests[testId];
let testContext = testContexts[test.testContextId];
while(testContext.parentContextId && testContexts[testContext.parentContextId].parentContextId) {
testContext = testContexts[testContext.parentContextId];
}
return { return {
test, test,
testContextName: testContext.name,
}; };
} }

View File

@ -1,4 +1,5 @@
import onTests from './onTests'; import onTests from './on/onTests';
import onValueTests from './on/onValueTests';
import offTests from './offTests'; import offTests from './offTests';
import onceTests from './onceTests'; import onceTests from './onceTests';
import setTests from './setTests'; import setTests from './setTests';
@ -14,13 +15,14 @@ import refTests from './refTests';
import rootTests from './rootTests'; import rootTests from './rootTests';
import transactionTests from './transactionTests'; import transactionTests from './transactionTests';
import queryTests from './queryTests'; import queryTests from './queryTests';
import issueSpecificTests from './issueSpecificTests';
import DatabaseContents from '../../support/DatabaseContents'; import DatabaseContents from '../../support/DatabaseContents';
const testGroups = [ const testGroups = [
factoryTests, keyTests, parentTests, childTests, rootTests, issueSpecificTests, factoryTests, keyTests, parentTests, childTests, rootTests,
pushTests, onTests, offTests, onceTests, updateTests, removeTests, setTests, pushTests, onTests, onValueTests, offTests, onceTests, updateTests,
transactionTests, queryTests, refTests, isEqualTests, removeTests, setTests, transactionTests, queryTests, refTests, isEqualTests,
]; ];
function registerTestSuite(testSuite) { function registerTestSuite(testSuite) {
@ -28,10 +30,12 @@ function registerTestSuite(testSuite) {
this._databaseRef = testSuite.firebase.native.database().ref('tests/types'); this._databaseRef = testSuite.firebase.native.database().ref('tests/types');
await this._databaseRef.set(DatabaseContents.DEFAULT); await this._databaseRef.set(DatabaseContents.DEFAULT);
await this._databaseRef.parent.child('issues').set(DatabaseContents.ISSUES);
}); });
testSuite.afterEach(async function () { testSuite.afterEach(async function () {
await this._databaseRef.set(DatabaseContents.DEFAULT); await this._databaseRef.set(DatabaseContents.DEFAULT);
await this._databaseRef.parent.child('issues').set(DatabaseContents.ISSUES);
}); });
testGroups.forEach((testGroup) => { testGroups.forEach((testGroup) => {

View File

@ -0,0 +1,74 @@
import should from 'should';
import DatabaseContents from '../../support/DatabaseContents';
function issueTests({ fdescribe, describe, it, context, firebase }) {
describe('issue_100', () => {
context('array-like values should', () => {
it('return null in returned array at positions where a key is missing', async() => {
// Setup
const ref = firebase.native.database().ref('tests/issues/100');
// Test
return ref.once('value').then((snapshot) => {
// Assertion
// console.warn(JSON.stringify(snapshot.val()));
snapshot.val().should.eql([null, DatabaseContents.ISSUES[100][1], DatabaseContents.ISSUES[100][2], DatabaseContents.ISSUES[100][3]]);
});
});
});
});
describe('issue_108', () => {
context('filters using floats', () => {
it('return correct results', async() => {
// Setup
const ref = firebase.native.database().ref('tests/issues/108');
// Test
return ref
.orderByChild('latitude')
.startAt(34.00867000999119)
.endAt(34.17462960866099)
.once('value')
.then((snapshot) => {
const val = snapshot.val();
// Assertion
val.foobar.should.eql(DatabaseContents.ISSUES[108].foobar);
should.equal(Object.keys(val).length, 1);
return Promise.resolve();
});
});
it('return correct results when not using float values', async() => {
// Setup
const ref = firebase.native.database().ref('tests/issues/108');
// Test
return ref
.orderByChild('latitude')
.equalTo(37)
.once('value')
.then((snapshot) => {
const val = snapshot.val();
// Assertion
val.notAFloat.should.eql(DatabaseContents.ISSUES[108].notAFloat);
should.equal(Object.keys(val).length, 1);
return Promise.resolve();
});
});
});
});
}
export default issueTests;

View File

@ -0,0 +1,52 @@
import 'should-sinon';
function onTests({ describe, it, firebase, context }) {
describe('ref().on()', () => {
// Observed Web API Behaviour
context('when no eventName is provided', () => {
it('then raises an error', () => {
const ref = firebase.native.database().ref('tests/types/number');
(() => { ref.on(); }).should.throw('Error: Query on failed: Was called with 0 arguments. Expects at least 2');
});
});
// Observed Web API Behaviour
context('when no callback function is provided', () => {
it('then raises an error', () => {
const ref = firebase.native.database().ref('tests/types/number');
(() => { ref.on('value'); }).should.throw('Query.on failed: Was called with 1 argument. Expects at least 2.');
});
});
// Observed Web API Behaviour
context('when an invalid eventName is provided', () => {
it('then raises an error', () => {
const ref = firebase.native.database().ref('tests/types/number');
(() => { ref.on('invalid', () => {}); }).should.throw('Query.on failed: First argument must be a valid event type: "value", "child_added", "child_removed", "child_changed", or "child_moved".');
});
});
// Observed Web API Behaviour
context('when an invalid success callback function is provided', () => {
it('then raises an error', () => {
const ref = firebase.native.database().ref('tests/types/number');
(() => { ref.on('value', 1); }).should.throw('Query.on failed: Second argument must be a valid function.');
});
});
// Observed Web API Behaviour
context('when an invalid failure callback function is provided', () => {
it('then raises an error', () => {
const ref = firebase.native.database().ref('tests/types/number');
(() => { ref.on('value', () => {}, null); }).should.throw('Query.on failed: third argument must either be a cancel callback or a context object.');
});
});
});
}
export default onTests;

View File

@ -0,0 +1,409 @@
import { Platform } from 'react-native';
import sinon from 'sinon';
import 'should-sinon';
import Promise from 'bluebird';
import DatabaseContents from '../../../support/DatabaseContents';
/**
* On Android, some data types result in callbacks that get called twice every time
* they are updated. This appears to be behaviour coming from the Android Firebase
* library itself.
*
* See https://github.com/invertase/react-native-firebase/issues/92 for details
*/
const DATATYPES_WITH_DUPLICATE_CALLBACK_CALLS = [
'array',
'number',
];
function onTests({ describe, context, it, firebase, tryCatch }) {
describe('ref().on(\'value\')', () => {
// Documented Web API Behaviour
it('returns the success callback', () => {
// Setup
const successCallback = sinon.spy();
const ref = firebase.native.database().ref('tests/types/array');
// Assertions
ref.on('value', successCallback).should.eql(successCallback);
// Tear down
ref.off();
});
// Documented Web API Behaviour
it('calls callback with null if there is no data at ref', async () => {
// Setup
const ref = firebase.native.database().ref('tests/types/invalid');
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
// Assertions
callback.should.be.calledWith(null);
await ref.set(1);
callback.should.be.calledWith(1);
// Teardown
ref.off();
await ref.set(null);
});
// Documented Web API Behaviour
it('calls callback with the initial data and then when value changes', () => {
return Promise.each(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
// Setup
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
const currentDataValue = DatabaseContents.DEFAULT[dataRef];
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
callback.should.be.calledWith(currentDataValue);
const newDataValue = DatabaseContents.NEW[dataRef];
await ref.set(newDataValue);
// Assertions
callback.should.be.calledWith(newDataValue);
if (Platform.OS === 'android' && DATATYPES_WITH_DUPLICATE_CALLBACK_CALLS.includes(dataRef)) {
callback.should.be.calledThrice();
} else {
callback.should.be.calledTwice();
}
// Tear down
ref.off();
await ref.set(currentDataValue);
});
});
it('calls callback when children of the ref change', async () => {
const ref = firebase.native.database().ref('tests/types/object');
const currentDataValue = DatabaseContents.DEFAULT.object;
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
callback.should.be.calledWith(currentDataValue);
const newDataValue = DatabaseContents.NEW.string;
const childRef = firebase.native.database().ref('tests/types/object/foo2');
await childRef.set(newDataValue);
// Assertions
callback.should.be.calledWith({
...currentDataValue,
foo2: newDataValue,
});
callback.should.be.calledTwice();
// Tear down
ref.off();
await ref.set(currentDataValue);
});
it('calls callback when child of the ref is added', async () => {
const ref = firebase.native.database().ref('tests/types/array');
const currentDataValue = DatabaseContents.DEFAULT.array;
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
callback.should.be.calledWith(currentDataValue);
const newElementRef = await ref.push(37);
const arrayAsObject = currentDataValue.reduce((memo, element, index) => {
memo[index] = element;
return memo;
}, {});
// Assertions
callback.should.be.calledWith({
...arrayAsObject,
[newElementRef.key]: 37,
});
if (Platform.OS === 'android') {
callback.should.be.calledThrice();
} else {
callback.should.be.calledTwice();
}
// Tear down
ref.off();
await ref.set(currentDataValue);
});
it('doesn\'t call callback when the ref is updated with the same value', async () => {
const ref = firebase.native.database().ref('tests/types/object');
const currentDataValue = DatabaseContents.DEFAULT.object;
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
callback.should.be.calledWith(currentDataValue);
await ref.set(currentDataValue);
// Assertions
callback.should.be.calledOnce(); // Callback is not called again
// Tear down
ref.off();
});
// Documented Web API Behaviour
it('allows binding multiple callbacks to the same ref', () => {
return Promise.each(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
// Setup
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
const currentDataValue = DatabaseContents.DEFAULT[dataRef];
const callbackA = sinon.spy();
const callbackB = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callbackA(snapshot.val());
resolve();
});
});
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callbackB(snapshot.val());
resolve();
});
});
callbackA.should.be.calledWith(currentDataValue);
callbackA.should.be.calledOnce();
callbackB.should.be.calledWith(currentDataValue);
callbackB.should.be.calledOnce();
const newDataValue = DatabaseContents.NEW[dataRef];
await ref.set(newDataValue);
callbackA.should.be.calledWith(newDataValue);
callbackB.should.be.calledWith(newDataValue);
if (Platform.OS === 'android' && DATATYPES_WITH_DUPLICATE_CALLBACK_CALLS.includes(dataRef)) {
callbackA.should.be.calledThrice();
callbackB.should.be.calledThrice();
} else {
callbackA.should.be.calledTwice();
callbackB.should.be.calledTwice();
}
// Tear down
ref.off();
});
});
context('when no failure callback is provided', () => {
it('then does not call the callback for a ref to un-permitted location', () => {
const invalidRef = firebase.native.database().ref('nope');
const callback = sinon.spy();
invalidRef.on('value', callback);
/**
* As we are testing that a callback is "never" called, we just wait for
* a reasonable time before giving up.
*/
return new Promise((resolve) => {
setTimeout(() => {
callback.should.not.be.called();
invalidRef.off();
resolve();
}, 1000);
});
});
// Documented Web API Behaviour
it('then calls callback bound to the specified context with the initial data and then when value changes', () => {
return Promise.each(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
// Setup
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
const currentDataValue = DatabaseContents.DEFAULT[dataRef];
const context = {
callCount: 0,
};
// Test
await new Promise((resolve) => {
ref.on('value', function(snapshot) {
this.value = snapshot.val();
this.callCount += 1;
resolve();
}, context);
});
context.value.should.eql(currentDataValue);
context.callCount.should.eql(1);
const newDataValue = DatabaseContents.NEW[dataRef];
await ref.set(newDataValue);
// Assertions
context.value.should.eql(newDataValue);
if (Platform.OS === 'android' && DATATYPES_WITH_DUPLICATE_CALLBACK_CALLS.includes(dataRef)) {
context.callCount.should.eql(3);
} else {
context.callCount.should.eql(2);
}
// Tear down
ref.off();
await ref.set(currentDataValue);
});
});
});
// Observed Web API Behaviour
context('when a failure callback is provided', () => {
it('then calls only the failure callback for a ref to un-permitted location', () => {
const invalidRef = firebase.native.database().ref('nope');
const callback = sinon.spy();
return new Promise((resolve, reject) => {
invalidRef.on('value', callback, tryCatch((error) => {
error.message.should.eql(
'permission_denied at /nope: Client doesn\'t have permission to access the desired data.'
);
error.name.should.eql('Error');
callback.should.not.be.called();
invalidRef.off();
resolve();
}, reject));
});
});
// Documented Web API Behaviour
it('then calls callback bound to the specified context with the initial data and then when value changes', () => {
return Promise.each(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
// Setup
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
const currentDataValue = DatabaseContents.DEFAULT[dataRef];
const context = {
callCount: 0,
};
const failureCallback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', function(snapshot) {
this.value = snapshot.val();
this.callCount += 1;
resolve();
}, failureCallback, context);
});
failureCallback.should.not.be.called();
context.value.should.eql(currentDataValue);
context.callCount.should.eql(1);
const newDataValue = DatabaseContents.NEW[dataRef];
await ref.set(newDataValue);
// Assertions
context.value.should.eql(newDataValue);
if (Platform.OS === 'android' && DATATYPES_WITH_DUPLICATE_CALLBACK_CALLS.includes(dataRef)) {
context.callCount.should.eql(3);
} else {
context.callCount.should.eql(2);
}
// Tear down
ref.off();
await ref.set(currentDataValue);
});
})
});
});
}
export default onTests;

View File

@ -1,125 +0,0 @@
import sinon from 'sinon';
import 'should-sinon';
import Promise from 'bluebird';
import DatabaseContents from '../../support/DatabaseContents';
function onTests({ describe, it, firebase, tryCatch }) {
describe('ref().on()', () => {
it('calls callback when value changes', () => {
return Promise.each(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
// Setup
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
const currentDataValue = DatabaseContents.DEFAULT[dataRef];
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
callback.should.be.calledWith(currentDataValue);
const newDataValue = DatabaseContents.NEW[dataRef];
await ref.set(newDataValue);
// Assertions
callback.should.be.calledWith(newDataValue);
// Tear down
ref.off();
});
});
it('allows binding multiple callbacks to the same ref', () => {
return Promise.each(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
// Setup
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
const currentDataValue = DatabaseContents.DEFAULT[dataRef];
const callbackA = sinon.spy();
const callbackB = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callbackA(snapshot.val());
resolve();
});
});
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callbackB(snapshot.val());
resolve();
});
});
callbackA.should.be.calledWith(currentDataValue);
callbackB.should.be.calledWith(currentDataValue);
// Tear down
ref.off();
});
});
it('calls callback with current values', () => {
return Promise.each(Object.keys(DatabaseContents.DEFAULT), (dataRef) => {
// Setup
const dataTypeValue = DatabaseContents.DEFAULT[dataRef];
const ref = firebase.native.database().ref(`tests/types/${dataRef}`);
// Test
return ref.on('value', (snapshot) => {
// Assertion
snapshot.val().should.eql(dataTypeValue);
// Tear down
ref.off();
});
});
});
it('errors if permission denied', () => {
return new Promise((resolve, reject) => {
const successCb = tryCatch(() => {
// Assertion
reject(new Error('No permission denied error'));
}, reject);
const failureCb = tryCatch((error) => {
// Assertion
error.message.includes('permission_denied').should.be.true();
resolve();
}, reject);
// Setup
const invalidRef = firebase.native.database().ref('nope');
// Test
invalidRef.on('value', successCb, failureCb);
});
});
});
}
export default onTests;

View File

@ -22,4 +22,38 @@ export default {
foo: 'baz', foo: 'baz',
}, },
}, },
ISSUES: {
// https://github.com/invertase/react-native-firebase/issues/100
100: {
1: {
someKey: 'someValue',
someOtherKey: 'someOtherValue',
},
2: {
someKey: 'someValue',
someOtherKey: 'someOtherValue',
},
3: {
someKey: 'someValue',
someOtherKey: 'someOtherValue',
},
},
// https://github.com/invertase/react-native-firebase/issues/108
108: {
foobar: {
name: 'Foobar Pizzas',
latitude: 34.1013717,
},
notTheFoobar: {
name: 'Not the pizza you\'re looking for',
latitude: 34.456787,
},
notAFloat: {
name: 'Not a float',
latitude: 37,
},
},
},
}; };