2
0
mirror of synced 2025-02-25 12:35:25 +00:00

merge remote-tracking branch 'upstream/master'

This commit is contained in:
taljacobson 2017-04-26 21:54:52 +03:00
commit 8f2f078ab3
191 changed files with 12534 additions and 805 deletions

View File

@ -103,3 +103,6 @@ suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
[version]
^0.40.0

11
.gitignore vendored
View File

@ -14,16 +14,16 @@ xcuserdata/
# Android
# Built application files
android/*/build/
**/android/**/build/
# Crashlytics configuations
android/com_crashlytics_export_strings.xml
# Local configuration file (sdk path, etc)
android/local.properties
**/android/local.properties
# Gradle generated files
android/.gradle/
**/android/.gradle/
# Signing files
android/.signing/
@ -41,7 +41,7 @@ android/.idea/misc.xml
android/.idea/modules.xml
android/.idea/scopes/scope_settings.xml
android/.idea/vcs.xml
android/*.iml
**/android/**/*.iml
ios/RnFirebase.xcodeproj/xcuserdata
# OS-specific files
@ -60,3 +60,6 @@ android/gradle/
.idea
coverage
yarn.lock
**/ios/Pods/**
**/ios/ReactNativeFirebaseDemo.xcworkspace/

View File

@ -60,3 +60,4 @@ docs
.idea
coverage
yarn.lock
tests

View File

@ -32,7 +32,7 @@ The native SDKs also allow us to hook into device sdk's which are not possible w
### Test app
To help ensure changes and features work across both iOS & Android, we've developed an app specifically to test `react-native-firebase` against the [`firebase` web SDK](https://www.npmjs.com/package/firebase). Please see the [`react-native-firebase-tests`](https://github.com/invertase/react-native-firebase-tests) repository for more information.
To help ensure changes and features work across both iOS & Android, we've developed an app specifically to test `react-native-firebase` against the [`firebase` web SDK](https://www.npmjs.com/package/firebase). Please see the [`tests`](tests/README.md) directory for more information.
<hr>
@ -57,6 +57,7 @@ RNFirebase aims to replicate the Firebase Web SDK as closely as possible. Becaus
* [Messaging](docs/api/cloud-messaging.md)
* [Crash](docs/api/crash.md)
* [Transactions](docs/api/transactions.md)
* [FAQs / Troubleshooting](docs/faqs.md)
<hr>

View File

@ -9,6 +9,7 @@ import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
// play services
import com.google.android.gms.common.ConnectionResult;
@ -27,6 +28,16 @@ public class RNFirebaseModule extends ReactContextBaseJavaModule implements Life
return TAG;
}
@ReactMethod
public void promptPlayServices() {
GoogleApiAvailability gapi = GoogleApiAvailability.getInstance();
int status = gapi.isGooglePlayServicesAvailable(getReactApplicationContext());
if (status != ConnectionResult.SUCCESS && gapi.isUserResolvableError(status)) {
gapi.getErrorDialog(getCurrentActivity(), status, 2404).show();
}
}
private WritableMap getPlayServicesStatus() {
GoogleApiAvailability gapi = GoogleApiAvailability.getInstance();
final int status = gapi.isGooglePlayServicesAvailable(getReactApplicationContext());

View File

@ -78,12 +78,13 @@ public class Utils {
/**
*
* @param name
* @param refId
* @param listenerId
* @param path
* @param modifiersString
* @param dataSnapshot
* @return
*/
public static WritableMap snapshotToMap(String name, String path, String modifiersString, DataSnapshot dataSnapshot) {
public static WritableMap snapshotToMap(String name, int refId, Integer listenerId, String path, DataSnapshot dataSnapshot) {
WritableMap snapshot = Arguments.createMap();
WritableMap eventMap = Arguments.createMap();
@ -106,10 +107,13 @@ public class Utils {
snapshot.putArray("childKeys", Utils.getChildKeys(dataSnapshot));
mapPutValue("priority", dataSnapshot.getPriority(), snapshot);
eventMap.putInt("refId", refId);
if (listenerId != null) {
eventMap.putInt("listenerId", listenerId);
}
eventMap.putString("path", path);
eventMap.putMap("snapshot", snapshot);
eventMap.putString("eventName", name);
eventMap.putString("modifiersString", modifiersString);
return eventMap;
}

View File

@ -439,9 +439,9 @@ public class RNFirebaseAuth extends ReactContextBaseJavaModule {
profileBuilder.setDisplayName(displayName);
}
if (props.hasKey("photoUri")) {
String photoUriStr = props.getString("photoUri");
Uri uri = Uri.parse(photoUriStr);
if (props.hasKey("photoURL")) {
String photoURLStr = props.getString("photoURL");
Uri uri = Uri.parse(photoURLStr);
profileBuilder.setPhotoUri(uri);
}

View File

@ -34,7 +34,7 @@ import io.invertase.firebase.Utils;
public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
private static final String TAG = "RNFirebaseDatabase";
private HashMap<String, RNFirebaseDatabaseReference> mDBListeners = new HashMap<>();
private HashMap<Integer, RNFirebaseDatabaseReference> mReferences = new HashMap<>();
private HashMap<String, RNFirebaseTransactionHandler> mTransactionHandlers = new HashMap<>();
private FirebaseDatabase mFirebaseDatabase;
@ -264,7 +264,7 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
}
/**
*
*
* @param id
* @param updates
*/
@ -279,61 +279,60 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
}
@ReactMethod
public void on(final String path, final String modifiersString, final ReadableArray modifiersArray, final String eventName, final Callback callback) {
RNFirebaseDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString);
public void on(final int refId, final String path, final ReadableArray modifiers, final int listenerId, final String eventName, final Callback callback) {
RNFirebaseDatabaseReference ref = this.getDBHandle(refId, path, modifiers);
if (eventName.equals("value")) {
ref.addValueEventListener();
ref.addValueEventListener(listenerId);
} else {
ref.addChildEventListener(eventName);
ref.addChildEventListener(listenerId, eventName);
}
WritableMap resp = Arguments.createMap();
resp.putString("status", "success");
resp.putInt("refId", refId);
resp.putString("handle", path);
callback.invoke(null, resp);
}
@ReactMethod
public void once(final String path, final String modifiersString, final ReadableArray modifiersArray, final String eventName, final Callback callback) {
RNFirebaseDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString);
public void once(final int refId, final String path, final ReadableArray modifiers, final String eventName, final Callback callback) {
RNFirebaseDatabaseReference ref = this.getDBHandle(refId, path, modifiers);
ref.addOnceValueEventListener(callback);
}
/**
* At the time of this writing, off() only gets called when there are no more subscribers to a given path.
* `mListeners` might therefore be out of sync (though javascript isnt listening for those eventTypes, so
* `mListeners` might therefore be out of sync (though javascript isnt listening for those eventNames, so
* it doesn't really matter- just polluting the RN bridge a little more than necessary.
* off() should therefore clean *everything* up
*/
@ReactMethod
public void off(
final String path,
final String modifiersString,
final String eventName,
final int refId,
final ReadableArray listeners,
final Callback callback) {
String key = this.getDBListenerKey(path, modifiersString);
RNFirebaseDatabaseReference r = mDBListeners.get(key);
RNFirebaseDatabaseReference r = mReferences.get(refId);
if (r != null) {
if (eventName == null || "".equals(eventName)) {
r.cleanup();
mDBListeners.remove(key);
} else {
r.removeEventListener(eventName);
List<Object> listenersList = Utils.recursivelyDeconstructReadableArray(listeners);
for (Object l : listenersList) {
Map<String, Object> listener = (Map) l;
int listenerId = ((Double) listener.get("listenerId")).intValue();
String eventName = (String) listener.get("eventName");
r.removeEventListener(listenerId, eventName);
if (!r.hasListeners()) {
mDBListeners.remove(key);
mReferences.remove(refId);
}
}
}
Log.d(TAG, "Removed listener " + path + "/" + modifiersString);
Log.d(TAG, "Removed listeners refId: " + refId + " ; count: " + listeners.size());
WritableMap resp = Arguments.createMap();
resp.putString("handle", path);
resp.putInt("refId", refId);
resp.putString("status", "success");
resp.putString("modifiersString", modifiersString);
//TODO: Remaining listeners
callback.invoke(null, resp);
}
@ -440,23 +439,19 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
}
}
private RNFirebaseDatabaseReference getDBHandle(final String path, final ReadableArray modifiersArray, final String modifiersString) {
String key = this.getDBListenerKey(path, modifiersString);
RNFirebaseDatabaseReference r = mDBListeners.get(key);
private RNFirebaseDatabaseReference getDBHandle(final int refId, final String path,
final ReadableArray modifiers) {
RNFirebaseDatabaseReference r = mReferences.get(refId);
if (r == null) {
ReactContext ctx = getReactApplicationContext();
r = new RNFirebaseDatabaseReference(ctx, mFirebaseDatabase, path, modifiersArray, modifiersString);
mDBListeners.put(key, r);
r = new RNFirebaseDatabaseReference(ctx, mFirebaseDatabase, refId, path, modifiers);
mReferences.put(refId, r);
}
return r;
}
private String getDBListenerKey(String path, String modifiersString) {
return path + " | " + modifiersString;
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();

View File

@ -1,9 +1,11 @@
package io.invertase.firebase.database;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import android.util.Log;
import java.util.Map;
import java.util.Set;
import com.facebook.react.bridge.Callback;
@ -25,81 +27,87 @@ public class RNFirebaseDatabaseReference {
private static final String TAG = "RNFirebaseDBReference";
private Query mQuery;
private int mRefId;
private String mPath;
private String mModifiersString;
private ChildEventListener mEventListener;
private ValueEventListener mValueListener;
private Map<Integer, ChildEventListener> mChildEventListeners = new HashMap<>();
private Map<Integer, ValueEventListener> mValueEventListeners = new HashMap<>();
private ReactContext mReactContext;
private Set<String> childEventListeners = new HashSet<>();
public RNFirebaseDatabaseReference(final ReactContext context,
final FirebaseDatabase firebaseDatabase,
final String path,
final ReadableArray modifiersArray,
final String modifiersString) {
final FirebaseDatabase firebaseDatabase,
final int refId,
final String path,
final ReadableArray modifiersArray) {
mReactContext = context;
mRefId = refId;
mPath = path;
mModifiersString = modifiersString;
mQuery = this.buildDatabaseQueryAtPathAndModifiers(firebaseDatabase, path, modifiersArray);
}
public void addChildEventListener(final String eventName) {
if (mEventListener == null) {
mEventListener = new ChildEventListener() {
public void addChildEventListener(final int listenerId, final String eventName) {
if (!mChildEventListeners.containsKey(listenerId)) {
ChildEventListener childEventListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
handleDatabaseEvent("child_added", dataSnapshot);
if ("child_added".equals(eventName)) {
handleDatabaseEvent("child_added", listenerId, dataSnapshot);
}
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
handleDatabaseEvent("child_changed", dataSnapshot);
if ("child_changed".equals(eventName)) {
handleDatabaseEvent("child_changed", listenerId, dataSnapshot);
}
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
handleDatabaseEvent("child_removed", dataSnapshot);
if ("child_removed".equals(eventName)) {
handleDatabaseEvent("child_removed", listenerId, dataSnapshot);
}
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
handleDatabaseEvent("child_moved", dataSnapshot);
if ("child_moved".equals(eventName)) {
handleDatabaseEvent("child_moved", listenerId, dataSnapshot);
}
}
@Override
public void onCancelled(DatabaseError error) {
removeChildEventListener();
handleDatabaseError(error);
removeChildEventListener(listenerId);
handleDatabaseError(listenerId, error);
}
};
mQuery.addChildEventListener(mEventListener);
Log.d(TAG, "Added ChildEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
mChildEventListeners.put(listenerId, childEventListener);
mQuery.addChildEventListener(childEventListener);
Log.d(TAG, "Added ChildEventListener for refId: " + mRefId + " listenerId: " + listenerId);
} else {
Log.w(TAG, "Trying to add duplicate ChildEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
Log.d(TAG, "ChildEventListener for refId: " + mRefId + " listenerId: " + listenerId + " already exists");
}
//Keep track of the events that the JS is interested in knowing about
childEventListeners.add(eventName);
}
public void addValueEventListener() {
if (mValueListener == null) {
mValueListener = new ValueEventListener() {
public void addValueEventListener(final int listenerId) {
if (!mValueEventListeners.containsKey(listenerId)) {
ValueEventListener valueEventListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
handleDatabaseEvent("value", dataSnapshot);
handleDatabaseEvent("value", listenerId, dataSnapshot);
}
@Override
public void onCancelled(DatabaseError error) {
removeValueEventListener();
handleDatabaseError(error);
removeValueEventListener(listenerId);
handleDatabaseError(listenerId, error);
}
};
mQuery.addValueEventListener(mValueListener);
Log.d(TAG, "Added ValueEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
//this.setListeningTo(mPath, modifiersString, "value");
mValueEventListeners.put(listenerId, valueEventListener);
mQuery.addValueEventListener(valueEventListener);
Log.d(TAG, "Added ValueEventListener for refId: " + mRefId + " listenerId: " + listenerId);
} else {
Log.w(TAG, "Trying to add duplicate ValueEventListener for path: " + mPath + " with modifiers: "+ mModifiersString);
Log.d(TAG, "ValueEventListener for refId: " + mRefId + " listenerId: " + listenerId + " already exists");
}
}
@ -107,63 +115,59 @@ public class RNFirebaseDatabaseReference {
final ValueEventListener onceValueEventListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
WritableMap data = Utils.snapshotToMap("value", mPath, mModifiersString, dataSnapshot);
WritableMap data = Utils.snapshotToMap("value", mRefId, null, mPath, dataSnapshot);
callback.invoke(null, data);
}
@Override
public void onCancelled(DatabaseError error) {
WritableMap err = Arguments.createMap();
err.putInt("refId", mRefId);
err.putString("path", mPath);
err.putInt("code", error.getCode());
err.putString("modifiers", mModifiersString);
err.putString("details", error.getDetails());
err.putString("message", error.getMessage());
callback.invoke(err);
}
};
mQuery.addListenerForSingleValueEvent(onceValueEventListener);
Log.d(TAG, "Added OnceValueEventListener for path: " + mPath + " with modifiers " + mModifiersString);
Log.d(TAG, "Added OnceValueEventListener for refId: " + mRefId);
}
public void removeEventListener(String eventName) {
public void removeEventListener(int listenerId, String eventName) {
if ("value".equals(eventName)) {
this.removeValueEventListener();
this.removeValueEventListener(listenerId);
} else {
childEventListeners.remove(eventName);
if (childEventListeners.isEmpty()) {
this.removeChildEventListener();
}
this.removeChildEventListener(listenerId);
}
}
public boolean hasListeners() {
return mEventListener != null || mValueListener != null;
return !mChildEventListeners.isEmpty() || !mValueEventListeners.isEmpty();
}
public void cleanup() {
Log.d(TAG, "cleaning up database reference " + this);
childEventListeners.clear();
this.removeChildEventListener();
this.removeValueEventListener();
this.removeChildEventListener(null);
this.removeValueEventListener(null);
}
private void removeChildEventListener() {
if (mEventListener != null) {
mQuery.removeEventListener(mEventListener);
mEventListener = null;
private void removeChildEventListener(Integer listenerId) {
ChildEventListener listener = mChildEventListeners.remove(listenerId);
if (listener != null) {
mQuery.removeEventListener(listener);
}
}
private void removeValueEventListener() {
if (mValueListener != null) {
mQuery.removeEventListener(mValueListener);
mValueListener = null;
private void removeValueEventListener(Integer listenerId) {
ValueEventListener listener = mValueEventListeners.remove(listenerId);
if (listener != null) {
mQuery.removeEventListener(listener);
}
}
private void handleDatabaseEvent(final String name, final DataSnapshot dataSnapshot) {
WritableMap data = Utils.snapshotToMap(name, mPath, mModifiersString, dataSnapshot);
private void handleDatabaseEvent(final String name, final Integer listenerId, final DataSnapshot dataSnapshot) {
WritableMap data = Utils.snapshotToMap(name, mRefId, listenerId, mPath, dataSnapshot);
WritableMap evt = Arguments.createMap();
evt.putString("eventName", name);
evt.putMap("body", data);
@ -171,12 +175,15 @@ public class RNFirebaseDatabaseReference {
Utils.sendEvent(mReactContext, "database_event", evt);
}
private void handleDatabaseError(final DatabaseError error) {
private void handleDatabaseError(final Integer listenerId, final DatabaseError error) {
WritableMap errMap = Arguments.createMap();
errMap.putInt("refId", mRefId);
if (listenerId != null) {
errMap.putInt("listenerId", listenerId);
}
errMap.putString("path", mPath);
errMap.putInt("code", error.getCode());
errMap.putString("modifiers", mModifiersString);
errMap.putString("details", error.getDetails());
errMap.putString("message", error.getMessage());
@ -187,104 +194,102 @@ public class RNFirebaseDatabaseReference {
final String path,
final ReadableArray modifiers) {
Query query = firebaseDatabase.getReference(path);
List<Object> strModifiers = Utils.recursivelyDeconstructReadableArray(modifiers);
List<Object> modifiersList = Utils.recursivelyDeconstructReadableArray(modifiers);
for (Object strModifier : strModifiers) {
String str = (String) strModifier;
for (Object m : modifiersList) {
Map<String, Object> modifier = (Map) m;
String type = (String) modifier.get("type");
String name = (String) modifier.get("name");
String[] strArr = str.split(":");
String methStr = strArr[0];
if (methStr.equalsIgnoreCase("orderByKey")) {
query = query.orderByKey();
} else if (methStr.equalsIgnoreCase("orderByValue")) {
query = query.orderByValue();
} else if (methStr.equalsIgnoreCase("orderByPriority")) {
query = query.orderByPriority();
} else if (methStr.contains("orderByChild")) {
String key = strArr[1];
Log.d(TAG, "orderByChild: " + key);
query = query.orderByChild(key);
} else if (methStr.contains("limitToLast")) {
String key = strArr[1];
int limit = Integer.parseInt(key);
Log.d(TAG, "limitToLast: " + limit);
query = query.limitToLast(limit);
} else if (methStr.contains("limitToFirst")) {
String key = strArr[1];
int limit = Integer.parseInt(key);
Log.d(TAG, "limitToFirst: " + limit);
query = query.limitToFirst(limit);
} else if (methStr.contains("equalTo")) {
String value = strArr[1];
String type = strArr[2];
if ("number".equals(type)) {
double doubleValue = Double.parseDouble(value);
if (strArr.length > 3) {
query = query.equalTo(doubleValue, strArr[3]);
} else {
query = query.equalTo(doubleValue);
}
} else if ("boolean".equals(type)) {
boolean booleanValue = Boolean.parseBoolean(value);
if (strArr.length > 3) {
query = query.equalTo(booleanValue, strArr[3]);
} else {
query = query.equalTo(booleanValue);
}
} else {
if (strArr.length > 3) {
query = query.equalTo(value, strArr[3]);
} else {
query = query.equalTo(value);
}
if ("orderBy".equals(type)) {
if ("orderByKey".equals(name)) {
query = query.orderByKey();
} else if ("orderByPriority".equals(name)) {
query = query.orderByPriority();
} else if ("orderByValue".equals(name)) {
query = query.orderByValue();
} else if ("orderByChild".equals(name)) {
String key = (String) modifier.get("key");
query = query.orderByChild(key);
}
} else if (methStr.contains("endAt")) {
String value = strArr[1];
String type = strArr[2];
if ("number".equals(type)) {
double doubleValue = Double.parseDouble(value);
if (strArr.length > 3) {
query = query.endAt(doubleValue, strArr[3]);
} else {
query = query.endAt(doubleValue);
}
} else if ("boolean".equals(type)) {
boolean booleanValue = Boolean.parseBoolean(value);
if (strArr.length > 3) {
query = query.endAt(booleanValue, strArr[3]);
} else {
query = query.endAt(booleanValue);
}
} else {
if (strArr.length > 3) {
query = query.endAt(value, strArr[3]);
} else {
query = query.endAt(value);
}
} else if ("limit".equals(type)) {
int limit = (Integer) modifier.get("limit");
if ("limitToLast".equals(name)) {
query = query.limitToLast(limit);
} else if ("limitToFirst".equals(name)) {
query = query.limitToFirst(limit);
}
} else if (methStr.contains("startAt")) {
String value = strArr[1];
String type = strArr[2];
if ("number".equals(type)) {
double doubleValue = Double.parseDouble(value);
if (strArr.length > 3) {
query = query.startAt(doubleValue, strArr[3]);
} else {
query = query.startAt(doubleValue);
} else if ("filter".equals(type)) {
String valueType = (String) modifier.get("valueType");
String key = (String) modifier.get("key");
if ("equalTo".equals(name)) {
if ("number".equals(valueType)) {
double value = (Double) modifier.get("value");
if (key == null) {
query = query.equalTo(value);
} else {
query = query.equalTo(value, key);
}
} else if ("boolean".equals(valueType)) {
boolean value = (Boolean) modifier.get("value");
if (key == null) {
query = query.equalTo(value);
} else {
query = query.equalTo(value, key);
}
} else if ("string".equals(valueType)) {
String value = (String) modifier.get("value");
if (key == null) {
query = query.equalTo(value);
} else {
query = query.equalTo(value, key);
}
}
} else if ("boolean".equals(type)) {
boolean booleanValue = Boolean.parseBoolean(value);
if (strArr.length > 3) {
query = query.startAt(booleanValue, strArr[3]);
} else {
query = query.startAt(booleanValue);
} else if ("endAt".equals(name)) {
if ("number".equals(valueType)) {
double value = (Double) modifier.get("value");
if (key == null) {
query = query.equalTo(value);
} else {
query = query.equalTo(value, key);
}
} else if ("boolean".equals(valueType)) {
boolean value = (Boolean) modifier.get("value");
if (key == null) {
query = query.equalTo(value);
} else {
query = query.equalTo(value, key);
}
} else if ("string".equals(valueType)) {
String value = (String) modifier.get("value");
if (key == null) {
query = query.equalTo(value);
} else {
query = query.equalTo(value, key);
}
}
} else {
if (strArr.length > 3) {
query = query.startAt(value, strArr[3]);
} else {
query = query.startAt(value);
} else if ("startAt".equals(name)) {
if ("number".equals(valueType)) {
double value = (Double) modifier.get("value");
if (key == null) {
query = query.equalTo(value);
} else {
query = query.equalTo(value, key);
}
} else if ("boolean".equals(valueType)) {
boolean value = (Boolean) modifier.get("value");
if (key == null) {
query = query.equalTo(value);
} else {
query = query.equalTo(value, key);
}
} else if ("string".equals(valueType)) {
String value = (String) modifier.get("value");
if (key == null) {
query = query.equalTo(value);
} else {
query = query.equalTo(value, key);
}
}
}
}

View File

@ -156,35 +156,30 @@ public class RNFirebaseMessaging extends ReactContextBaseJavaModule implements L
}
@ReactMethod
public void send(String senderId, ReadableMap payload) throws Exception {
public void send(ReadableMap remoteMessage) {
FirebaseMessaging fm = FirebaseMessaging.getInstance();
RemoteMessage.Builder message = new RemoteMessage.Builder(senderId + "@gcm.googleapis.com")
.setMessageId(UUID.randomUUID().toString());
RemoteMessage.Builder message = new RemoteMessage.Builder(remoteMessage.getString("sender"));
ReadableMapKeySetIterator iterator = payload.keySetIterator();
message.setTtl(remoteMessage.getInt("ttl"));
message.setMessageId(remoteMessage.getString("id"));
message.setMessageType(remoteMessage.getString("type"));
if (remoteMessage.hasKey("collapseKey")) {
message.setCollapseKey(remoteMessage.getString("collapseKey"));
}
// get data keys and values and add to builder
// js side ensures all data values are strings
// so no need to check types
ReadableMap data = remoteMessage.getMap("data");
ReadableMapKeySetIterator iterator = data.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
String value = getStringFromReadableMap(payload, key);
String value = data.getString(key);
message.addData(key, value);
}
fm.send(message.build());
}
private String getStringFromReadableMap(ReadableMap map, String key) throws Exception {
switch (map.getType(key)) {
case String:
return map.getString(key);
case Number:
try {
return String.valueOf(map.getInt(key));
} catch (Exception e) {
return String.valueOf(map.getDouble(key));
}
case Boolean:
return String.valueOf(map.getBoolean(key));
default:
throw new Exception("Unknown data type: " + map.getType(key).name() + " for message key " + key);
}
fm.send(message.build());
}
private void registerMessageHandler() {

View File

@ -356,7 +356,12 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
Log.d(TAG, "putFile success " + taskSnapshot);
WritableMap resp = getUploadTaskAsMap(taskSnapshot);
sendJSEvent(STORAGE_STATE_CHANGED, path, resp);
// to avoid readable map already consumed errors
resp = getUploadTaskAsMap(taskSnapshot);
sendJSEvent(STORAGE_UPLOAD_SUCCESS, path, resp);
resp = getUploadTaskAsMap(taskSnapshot);
promise.resolve(resp);
}

View File

@ -1,6 +1,6 @@
# Authentication
RNFirebase handles authentication for us out of the box, both with email/password-based authentication and through oauth providers (with a separate library to handle oauth providers).
RNFirebase handles authentication for us out of the box, both with email/password-based authentication and through oauth providers (with a separate library to handle oauth providers, see [examples](#examples)).
> Authentication requires Google Play services to be installed on Android.
@ -284,3 +284,41 @@ firebase.auth().currentUser
.then()
.catch();
```
## Examples
### Facebook authentication with react-native-fbsdk and signInWithCredential
```javascript
import { AccessToken, LoginManager } from 'react-native-fbsdk';
// ... somewhere in your login screen component
LoginManager
.logInWithReadPermissions(['public_profile', 'email'])
.then((result) => {
if (result.isCancelled) {
return Promise.resolve('cancelled');
}
console.log(`Login success with permissions: ${result.grantedPermissions.toString()}`);
// get the access token
return AccessToken.getCurrentAccessToken();
})
.then(data => {
// create a new firebase credential with the token
const credential = firebase.auth.FacebookAuthProvider.credential(data.accessToken);
// login with credential
return firebase.auth().signInWithCredential(credential);
})
.then((currentUser) => {
if (currentUser === 'cancelled') {
console.log('Login cancelled');
} else {
// now signed in
console.warn(JSON.stringify(currentUser.toJSON()));
}
})
.catch((error) => {
console.log(`Login fail with error: ${error}`);
});
```

View File

@ -1,4 +1,3 @@
# Storage
RNFirebase mimics the [Web Firebase SDK Storage](https://firebase.google.com/docs/storage/web/start), whilst

View File

@ -1,5 +1,23 @@
# Transactions
Transactions are currently an experimental feature as they can not be integrated as easily as the other Firebase features.
Transactions are currently an experimental feature as they can not be integrated as easily as the other Firebase features. Please see the [Firebase documentation](https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction) for full implemtation details.
TODO
## Example
```javascript
const ref = firebase.database().ref('user/posts');
ref.transaction((posts) => {
return (posts || 0) + 1;
}, (error, committed, snapshot) => {
if (error) {
console.log('Something went wrong', error);
} else if (!committed) {
console.log('Aborted'); // Returning undefined will trigger this
} else {
console.log('User posts incremented by 1');
}
console.log('User posts is now: ', snapshot.val());
});
```

126
docs/faqs.md Normal file
View File

@ -0,0 +1,126 @@
# FAQs / Troubleshooting
### Comparison to Firestack
Firestack was a great start to integrating Firebase and React Native, however has underlying issues which needed to be fixed.
A V3 fork of Firestack was created to help address issues such as lack of standardisation with the Firebase Web SDK,
and missing core features (crash reporting, transactions etc). The volume of pull requests with fixes/features soon became
too large to manage on the existing repository, whilst trying to maintain backwards compatibility.
RNFirebase was re-written from the ground up, addressing these issues with core focus being around matching the Web SDK as
closely as possible and fixing the major bugs/issues along the way.
### How do I integrate Redux with RNFirebase
As every project has different requirements & structure, RNFirebase *currently* has no built in methods for Redux integration.
As RNFirebase can be used outside of a Components context, you do have free reign to integrate it as you see fit. For example,
with [`redux-thunk`](https://github.com/gaearon/redux-thunk) you dispatch updates to your store with updates from Firebase:
```javascript
class MyApp extends React.Component {
componentDidMount() {
this.props.dispatch(onAuthStateChanged());
}
...
}
connect()(MyApp);
```
```javascript
export function onAuthStateChanged() {
return (dispatch) => {
firebase.auth().onAuthStateChanged((user) => {
dispatch({
type: 'AUTH_STATE_CHANGE',
user,
});
});
};
}
```
### [Android] Google Play Services related issues
The firebase SDK requires a certain version of Google Play Services installed on Android in order to function properly.
If the version of Google Play Services installed on your device is incorrect or non existent, React Native Firebase will throw a red box error, and your app will possibly crash as well. The red box error will have a numerical code associated with it. These codes can be found here:
https://developers.google.com/android/reference/com/google/android/gms/common/ConnectionResult#SERVICE_VERSION_UPDATE_REQUIRED
Here is a quick guide to some of the most common errors encountered:
code 2 - Google Play Services is required to run this application but no valid installation was found:
The emulator/device you're using does not have the Play Services SDK installed.
- Emulator: Open SDK manager, under 'SDK Tools' ensure "Google Play services, rev X" is installed. Once installed,
create a new emulator image. When selecting your system image, ensure the target comes "with Google APIs".
- Device: Play Services needs to be downloaded from the Google Play Store.
code 9 - The version of the Google Play services installed on this device is not authentic:
This error applies to modified or 'shimmed' versions of Google Play Services which you might be using in a third
party emulator such as GenyMotion.
Using this kind of workaround with Google Play Services can be problematic, so we
recommend using the native Android Studio emulators to reduce the chance of these complications.
### [Android] Turning off Google Play Services availability errors
G.P.S errors can be turned off using a config option like so:
```javascript
const firebase = RNFirebase.initializeApp({
errorOnMissingPlayServices: false,
});
```
This will stop your app from immediately red-boxing or crashing, but won't solve the underlying issue of G.P.S not being available or of the correct version. This will mean certain functionalities won't work properly and your app may even crash.
### [Android] Checking for Google Play Services availability with React Native Firebase
React Native Firebase actually has a useful helper object for checking G.P.S availability:
```javascript
const availability = firebase.googleApiAvailability;
```
The availability object would then have the following properties that you can run checks against:
```javascript
isAvailable: boolean
```
and if not available (isAvailable === false):
```javascript
isUserResolvableError: boolean
```
This variable indicates whether or not the end user can fix the issue, for example by downloading the required version of Google Play Services. In a case such as a GenyMotion emulator, this would return false for missing G.P.S, as the end user can't add the package directly.
```javascript
error: string
```
This error will match the messages and error codes mentioned above, and can be found here:
https://developers.google.com/android/reference/com/google/android/gms/common/ConnectionResult#SERVICE_VERSION_UPDATE_REQUIRED
### [Android] Duplicate Dex Files error (build time error)
A common build time error when using libraries that require google play services is of the form:
'Failed on android with com.android.dex.DexException: Multiple dex files... '
This error (https://github.com/invertase/react-native-firebase/issues/48) occurs because different versions of google play services or google play services modules are being required by different libraries.
The process to fix this is fairly manual and depends on your specific combination of external libraries. Essentially what's required is to check the app level build.gradle file of each external library and establish which ones have a Google Play Services dependency.
You then need to find the lowest common version of each G.P.S module dependency, require that in the app level build.gradle file of your own project, and exclude it from being required by the modules themselves. This will force the use of a consistent version of the G.P.S module.
It's not a good idea to modify the version within the library's build.gradle, as this will be overwritten when you update the library, which will lead to the build breaking again.
A good break down of this process can be found here:
https://medium.com/@suchydan/how-to-solve-google-play-services-version-collision-in-gradle-dependencies-ef086ae5c75f

View File

@ -10,42 +10,11 @@ Each platform uses a different setup method after creating the project.
## iOS
After creating a Firebase project, click on the [Add Firebase to your iOS app](http://d.pr/i/3sEL.png) and follow the steps from there to add the configuration file. You do _not_ need to set up a cocoapods project (this is already done through RNFirebase). Make sure not to forget the `Copy Files` phase in iOS.
[Download the Firebase config file](https://support.google.com/firebase/answer/7015592) and place it in your app directory next to your app source code:
![GoogleService-Info.plist](http://d.pr/i/1eGev.png)
Once you download the configuration file, make sure you place it in the root of your Xcode project. Every different Bundle ID (aka, even different project variants needs their own configuration file).
Lastly, due to some dependencies requirements, RNFirebase supports iOS versions 8.0 and up. Make sure to update the minimum version of your iOS app to `8.0`.
See the [ios setup guide](./installation.ios.md).
## Android
There are several ways to setup Firebase on Android. The _easiest_ way is to pass the configuration settings in JavaScript. In that way, there is no setup for the native platform.
### google-services.json setup
If you prefer to include the default settings in the source of your app, download the `google-services.json` file provided by Firebase in the _Add Firebase to Android_ platform menu in your Firebase configuration console.
Next you'll have to add the google-services gradle plugin in order to parse it.
Add the google-services gradle plugin as a dependency in the *project* level build.gradle
`android/build.gradle`
```java
buildscript {
// ...
dependencies {
// ...
classpath 'com.google.gms:google-services:3.0.0'
}
}
```
In your app build.gradle file, add the gradle plugin at the VERY BOTTOM of the file (below all dependencies)
`android/app/build.gradle`
```java
apply plugin: 'com.google.gms.google-services'
```
See the [android setup guide](./installation.android.md).
## Usage
@ -58,32 +27,28 @@ import RNFirebase from 'react-native-firebase'
We need to tell the Firebase library we want to _configure_ the project. RNFirebase provides a way to configure both the native and the JavaScript side of the project at the same time with a single command:
```javascript
const firebase = new RNFirebase();
const firebase = RNFirebase.initializeApp({
// config options
});
```
We can 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.
### Configuration Options
| option | type | Default Value | Description |
|----------------|----------|-------------------------|----------------------------------------|
| debug | bool | false | When set to true, RNFirebase will log messages to the console and fire `debug` events we can listen to in `js` |
| persistence | bool | false | When set to true, database persistence will be enabled. |
| bundleID | string | Default from app `[NSBundle mainBundle]` | The bundle ID for the app to be bundled with |
| googleAppID | string | "" | The Google App ID that is used to uniquely identify an instance of an app. |
| databaseURL | string | "" | The database root (i.e. https://my-app.firebaseio.com) |
| deepLinkURLScheme | string | "" | URL scheme to set up durable deep link service |
| storageBucket | string | "" | The Google Cloud storage bucket name |
| androidClientID | string | "" | The Android client ID used in Google AppInvite when an iOS app has it's android version |
| GCMSenderID | string | "" | The Project number from the Google Developer's console used to configure Google Cloud Messaging |
| trackingID | string | "" | The tracking ID for Google Analytics |
| clientID | string | "" | The OAuth2 client ID for iOS application used to authenticate Google Users for signing in with Google |
| APIKey | string | "" | The secret iOS API key used for authenticating requests from our app |
For instance:
```javascript
import RNFirebase from 'react-native-firebase';
const configurationOptions = {
debug: true
};
const firebase = new RNFirebase(configurationOptions);
firebase.on('debug', msg => console.log('Received debug message', msg))
const firebase = RNFirebase.initializeApp(configurationOptions);
export default firebase;
```

View File

@ -1,14 +1,31 @@
# Android Installation
The simplest way of installing on Android is to use the react-native link CLI command & rebuild the project:
### 1 - Setup google-services.json
Download the `google-services.json` file provided by Firebase in the _Add Firebase to Android_ platform menu in your Firebase configuration console. This file should be downloaded to `YOUR_PROJECT/android/app/google-services.json`.
```
react-native link react-native-firebase
Next you'll have to add the google-services gradle plugin in order to parse it.
Add the google-services gradle plugin as a dependency in the *project* level build.gradle
`android/build.gradle`
```java
buildscript {
// ...
dependencies {
// ...
classpath 'com.google.gms:google-services:3.0.0'
}
}
```
## Manually
In your app build.gradle file, add the gradle plugin at the VERY BOTTOM of the file (below all dependencies)
`android/app/build.gradle`
```java
apply plugin: 'com.google.gms.google-services'
```
To install `react-native-firebase` manually in our project, we'll need to import the package from `io.invertase.firebase` in our project's `android/app/src/main/java/com/[app name]/MainApplication.java` and list it as a package for ReactNative in the `getPackages()` function:
### 2 - Link RNFirebase
To install `react-native-firebase` in your project, you'll need to import the package from `io.invertase.firebase` in your project's `android/app/src/main/java/com/[app name]/MainApplication.java` and list it as a package for ReactNative in the `getPackages()` function:
```java
package com.youcompany.application;
@ -29,8 +46,7 @@ public class MainApplication extends Application implements ReactApplication {
// ...
}
```
We'll also need to list it in our `android/app/build.gradle` file as a dependency that we want React Native to compile. In the `dependencies` listing, add the `compile` line:
You'll also need to list it in our `android/app/build.gradle` file as a dependency that we want React Native to compile. In the `dependencies` listing, add the `compile` line:
```java
dependencies {
@ -45,10 +61,12 @@ include ':react-native-firebase'
project(':react-native-firebase').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-firebase/android')
```
### 3 - Cloud Messaging (optional)
If you plan on using [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/), add the following to `android/app/src/main/AndroidManifest.xml`.
Add permissions:
```
```xml
<manifest ...>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
@ -56,7 +74,7 @@ Add permissions:
```
Set app [launch mode](https://inthecheesefactory.com/blog/understand-android-activity-launchmode/en) inside application props:
```
```xml
<application
...
android:launchMode="singleTop"
@ -64,7 +82,7 @@ Set app [launch mode](https://inthecheesefactory.com/blog/understand-android-act
```
Add messaging service:
```
```xml
<application ...>
<service
android:name="io.invertase.firebase.messaging.MessagingService"
@ -82,7 +100,7 @@ Add messaging service:
```
If you would like to schedule local notifications then you also need to add the following:
```
```xml
<receiver android:name="io.invertase.firebase.messaging.RNFirebaseLocalMessagingPublisher"/>
<receiver android:enabled="true" android:exported="true"android:name="io.invertase.firebase.messaging.RNFirebaseSystemBootEventReceiver">
<intent-filter>

View File

@ -1,12 +1,10 @@
# iOS Installation
## iOS Installation
## Firebase
### 1 - Setup google-services.plist and dependencies
Setup the `google-services.plist` file and Firebase ios frameworks first; check out the relevant Firebase docs [here](https://firebase.google.com/docs/ios/setup#frameworks).
### Setup
Setup the Firebase ios frameworks first; check out the relevant Firebase docs [here](https://firebase.google.com/docs/ios/setup#frameworks).
### Initialisation
You need to add the following to the top of `ios/[YOUR APP NAME]]/AppDelegate.m`:
#### 1.1 - Initialisation
Make sure you've added the following to the top of your `ios/[YOUR APP NAME]]/AppDelegate.m` file:
`#import <Firebase.h>`
@ -14,14 +12,14 @@ and this to the `didFinishLaunchingWithOptions:(NSDictionary *)launchOptions` me
`[FIRApp configure];`
## RNFirebase
There are multiple ways to install RNFirebase dependent on how your project is currently setup:
### 2 - Link RNFirebase
There are multiple ways to install RNFirebase depending on how your project is currently setup:
### 1) Existing Cocoapods setup, including React Native as a pod
#### 2.1 - Existing Cocoapods setup, including React Native as a pod
Simply add the following to your `Podfile`:
```ruby
# Required by RNFirebase
# Required by RNFirebase - you should already have some of these from step 1.
pod 'Firebase/Auth'
pod 'Firebase/Analytics'
pod 'Firebase/AppIndexing'
@ -35,7 +33,7 @@ pod 'Firebase/Storage'
pod 'RNFirebase', :path => '../node_modules/react-native-firebase'
```
### 2) Automatically with react-native-cli
#### 2.2 - Via react-native-cli link
React native ships with a `link` command that can be used to link the projects together, which can help automate the process of linking our package environments.
```bash
@ -48,12 +46,12 @@ Update the newly installed pods once the linking is done:
cd ios && pod update --verbose
```
#### cocoapods
##### cocoapods
We've automated the process of setting up with cocoapods. This will happen automatically upon linking the package with `react-native-cli`.
**Remember to use the `ios/[YOUR APP NAME].xcworkspace` instead of the `ios/[YOUR APP NAME].xcproj` file from now on**.
### 3) Manually
#### 2.3 - Manually
If you prefer not to use `react-native link`, we can manually link the package together with the following steps, after `npm install`:

View File

@ -1 +0,0 @@

View File

@ -1,6 +0,0 @@
/**
* @flow
*/
import firebase from './lib/firebase';
export default firebase;

View File

@ -1,5 +0,0 @@
/**
* @flow
*/
import firebase from './lib/firebase';
export default firebase;

View File

@ -1,3 +1,3 @@
import Firebase from './firebase';
import Firebase from './lib/firebase';
export default Firebase;

View File

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
29C199451EA7A851007B6BF8 /* RNFirebaseCrash.m in Sources */ = {isa = PBXBuildFile; fileRef = 29C199441EA7A851007B6BF8 /* RNFirebaseCrash.m */; };
D90882D61D89C18C00FB6742 /* RNFirebaseMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = D90882D51D89C18C00FB6742 /* RNFirebaseMessaging.m */; };
D950369E1D19C77400F7094D /* RNFirebase.m in Sources */ = {isa = PBXBuildFile; fileRef = D950369D1D19C77400F7094D /* RNFirebase.m */; };
D962903F1D6D15B00099A3EC /* RNFirebaseErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = D962903E1D6D15B00099A3EC /* RNFirebaseErrors.m */; };
@ -30,6 +31,8 @@
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRNFirebase.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNFirebase.a; sourceTree = BUILT_PRODUCTS_DIR; };
29C199431EA7A851007B6BF8 /* RNFirebaseCrash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNFirebaseCrash.h; path = RNFirebase/RNFirebaseCrash.h; sourceTree = "<group>"; };
29C199441EA7A851007B6BF8 /* RNFirebaseCrash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNFirebaseCrash.m; path = RNFirebase/RNFirebaseCrash.m; sourceTree = "<group>"; };
D90882D41D89C18C00FB6742 /* RNFirebaseMessaging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNFirebaseMessaging.h; path = RNFirebase/RNFirebaseMessaging.h; sourceTree = "<group>"; };
D90882D51D89C18C00FB6742 /* RNFirebaseMessaging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNFirebaseMessaging.m; path = RNFirebase/RNFirebaseMessaging.m; sourceTree = "<group>"; };
D950369C1D19C77400F7094D /* RNFirebase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNFirebase.h; path = RNFirebase/RNFirebase.h; sourceTree = "<group>"; };
@ -83,6 +86,8 @@
D90882D51D89C18C00FB6742 /* RNFirebaseMessaging.m */,
D9D62E7E1D6D8717003D826D /* RNFirebaseAuth.h */,
D9D62E7F1D6D8717003D826D /* RNFirebaseAuth.m */,
29C199431EA7A851007B6BF8 /* RNFirebaseCrash.h */,
29C199441EA7A851007B6BF8 /* RNFirebaseCrash.m */,
D96290391D6D152A0099A3EC /* RNFirebaseEvents.h */,
D962903D1D6D15B00099A3EC /* RNFirebaseErrors.h */,
D962903E1D6D15B00099A3EC /* RNFirebaseErrors.m */,
@ -158,6 +163,7 @@
D962903F1D6D15B00099A3EC /* RNFirebaseErrors.m in Sources */,
D950369E1D19C77400F7094D /* RNFirebase.m in Sources */,
D90882D61D89C18C00FB6742 /* RNFirebaseMessaging.m in Sources */,
29C199451EA7A851007B6BF8 /* RNFirebaseCrash.m in Sources */,
D96290851D6D28B80099A3EC /* RNFirebaseDatabase.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -5,64 +5,61 @@
@interface RNFirebaseDBReference : NSObject
@property RCTEventEmitter *emitter;
@property FIRDatabaseQuery *query;
@property NSNumber *refId;
@property NSString *path;
@property NSString *modifiersString;
@property NSMutableDictionary *listeners;
@property FIRDatabaseHandle childAddedHandler;
@property FIRDatabaseHandle childModifiedHandler;
@property FIRDatabaseHandle childRemovedHandler;
@property FIRDatabaseHandle childMovedHandler;
@property FIRDatabaseHandle childValueHandler;
+ (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot;
@end
@implementation RNFirebaseDBReference
- (id) initWithPathAndModifiers:(RCTEventEmitter *) emitter
database:(FIRDatabase *) database
refId:(NSNumber *) refId
path:(NSString *) path
modifiers:(NSArray *) modifiers
modifiersString:(NSString *) modifiersString
{
self = [super init];
if (self) {
_emitter = emitter;
_refId = refId;
_path = path;
_modifiersString = modifiersString;
_query = [self buildQueryAtPathWithModifiers:database path:path modifiers:modifiers];
_listeners = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void) addEventHandler:(NSString *) eventName
- (void) addEventHandler:(NSNumber *) listenerId
eventName:(NSString *) eventName
{
if (![self isListeningTo:eventName]) {
if (![_listeners objectForKey:listenerId]) {
id withBlock = ^(FIRDataSnapshot * _Nonnull snapshot) {
NSDictionary *props = [RNFirebaseDBReference snapshotToDict:snapshot];
[self sendJSEvent:DATABASE_DATA_EVENT
title:eventName
props: @{
@"eventName": eventName,
@"refId": _refId,
@"listenerId": listenerId,
@"path": _path,
@"modifiersString": _modifiersString,
@"snapshot": props
}];
};
id errorBlock = ^(NSError * _Nonnull error) {
NSLog(@"Error onDBEvent: %@", [error debugDescription]);
[self unsetListeningOn:eventName];
[self removeEventHandler:listenerId eventName:eventName];
[self getAndSendDatabaseError:error
path:_path
modifiersString:_modifiersString];
listenerId:listenerId];
};
int eventType = [self eventTypeFromName:eventName];
FIRDatabaseHandle handle = [_query observeEventType:eventType
withBlock:withBlock
withCancelBlock:errorBlock];
[self setEventHandler:handle forName:eventName];
[_listeners setObject:@(handle) forKey:listenerId];
} else {
NSLog(@"Warning Trying to add duplicate listener for type: %@ with modifiers: %@ for path: %@", eventName, _modifiersString, _path);
NSLog(@"Warning Trying to add duplicate listener for refId: %@ listenerId: %@", _refId, listenerId);
}
}
@ -74,7 +71,7 @@
callback(@[[NSNull null], @{
@"eventName": @"value",
@"path": _path,
@"modifiersString": _modifiersString,
@"refId": _refId,
@"snapshot": props
}]);
}
@ -83,7 +80,7 @@
callback(@[@{
@"eventName": DATABASE_ERROR_EVENT,
@"path": _path,
@"modifiers": _modifiersString,
@"refId": _refId,
@"code": @([error code]),
@"details": [error debugDescription],
@"message": [error localizedDescription],
@ -92,44 +89,14 @@
}];
}
- (void) removeEventHandler:(NSString *) name
- (void) removeEventHandler:(NSNumber *) listenerId
eventName:(NSString *) eventName
{
int eventType = [self eventTypeFromName:name];
switch (eventType) {
case FIRDataEventTypeValue:
if (self.childValueHandler != nil) {
[_query removeObserverWithHandle:self.childValueHandler];
self.childValueHandler = nil;
}
break;
case FIRDataEventTypeChildAdded:
if (self.childAddedHandler != nil) {
[_query removeObserverWithHandle:self.childAddedHandler];
self.childAddedHandler = nil;
}
break;
case FIRDataEventTypeChildChanged:
if (self.childModifiedHandler != nil) {
[_query removeObserverWithHandle:self.childModifiedHandler];
self.childModifiedHandler = nil;
}
break;
case FIRDataEventTypeChildRemoved:
if (self.childRemovedHandler != nil) {
[_query removeObserverWithHandle:self.childRemovedHandler];
self.childRemovedHandler = nil;
}
break;
case FIRDataEventTypeChildMoved:
if (self.childMovedHandler != nil) {
[_query removeObserverWithHandle:self.childMovedHandler];
self.childMovedHandler = nil;
}
break;
default:
break;
FIRDatabaseHandle handle = [[_listeners objectForKey:listenerId] integerValue];
if (handle) {
[_listeners removeObjectForKey:listenerId];
[_query removeObserverWithHandle:handle];
}
[self unsetListeningOn:name];
}
+ (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot
@ -159,21 +126,19 @@
}
- (NSDictionary *) getAndSendDatabaseError:(NSError *) error
path:(NSString *) path
modifiersString:(NSString *) modifiersString
listenerId:(NSNumber *) listenerId
{
NSDictionary *event = @{
@"eventName": DATABASE_ERROR_EVENT,
@"path": path,
@"modifiers": modifiersString,
@"path": _path,
@"refId": _refId,
@"listenerId": listenerId,
@"code": @([error code]),
@"details": [error debugDescription],
@"message": [error localizedDescription],
@"description": [error description]
};
// [self sendJSEvent:DATABASE_ERROR_EVENT title:DATABASE_ERROR_EVENT props: event];
@try {
[_emitter sendEventWithName:DATABASE_ERROR_EVENT body:event];
}
@ -181,7 +146,7 @@
NSLog(@"An error occurred in getAndSendDatabaseError: %@", [err debugDescription]);
NSLog(@"Tried to send: %@ with %@", DATABASE_ERROR_EVENT, event);
}
return event;
}
@ -194,70 +159,59 @@
}
}
- (FIRDatabaseQuery *) buildQueryAtPathWithModifiers:(FIRDatabase*) database
path:(NSString*) path
modifiers:(NSArray *) modifiers
{
FIRDatabaseQuery *query = [[database reference] child:path];
for (NSString *str in modifiers) {
if ([str isEqualToString:@"orderByKey"]) {
query = [query queryOrderedByKey];
} else if ([str isEqualToString:@"orderByPriority"]) {
query = [query queryOrderedByPriority];
} else if ([str isEqualToString:@"orderByValue"]) {
query = [query queryOrderedByValue];
} else if ([str containsString:@"orderByChild"]) {
NSArray *args = [str componentsSeparatedByString:@":"];
NSString *key = args[1];
query = [query queryOrderedByChild:key];
} else if ([str containsString:@"limitToLast"]) {
NSArray *args = [str componentsSeparatedByString:@":"];
NSString *key = args[1];
NSUInteger limit = key.integerValue;
query = [query queryLimitedToLast:limit];
} else if ([str containsString:@"limitToFirst"]) {
NSArray *args = [str componentsSeparatedByString:@":"];
NSString *key = args[1];
NSUInteger limit = key.integerValue;
query = [query queryLimitedToFirst:limit];
} else if ([str containsString:@"equalTo"]) {
NSArray *args = [str componentsSeparatedByString:@":"];
int size = (int)[args count];;
id value = [self getIdValue:args[1] type:args[2]];
if (size > 3) {
NSString *key = args[3];
query = [query queryEqualToValue:value
childKey:key];
} else {
query = [query queryEqualToValue:value];
for (NSDictionary *modifier in modifiers) {
NSString *type = [modifier valueForKey:@"type"];
NSString *name = [modifier valueForKey:@"name"];
if ([type isEqualToString:@"orderBy"]) {
if ([name isEqualToString:@"orderByKey"]) {
query = [query queryOrderedByKey];
} else if ([name isEqualToString:@"orderByPriority"]) {
query = [query queryOrderedByPriority];
} else if ([name isEqualToString:@"orderByValue"]) {
query = [query queryOrderedByValue];
} else if ([name isEqualToString:@"orderByChild"]) {
NSString *key = [modifier valueForKey:@"key"];
query = [query queryOrderedByChild:key];
}
} else if ([str containsString:@"endAt"]) {
NSArray *args = [str componentsSeparatedByString:@":"];
int size = (int)[args count];;
id value = [self getIdValue:args[1] type:args[2]];
if (size > 3) {
NSString *key = args[3];
query = [query queryEndingAtValue:value
childKey:key];
} else {
query = [query queryEndingAtValue:value];
} else if ([type isEqualToString:@"limit"]) {
int limit = [[modifier valueForKey:@"limit"] integerValue];
if ([name isEqualToString:@"limitToLast"]) {
query = [query queryLimitedToLast:limit];
} else if ([name isEqualToString:@"limitToFirst"]) {
query = [query queryLimitedToFirst:limit];
}
} else if ([str containsString:@"startAt"]) {
NSArray *args = [str componentsSeparatedByString:@":"];
id value = [self getIdValue:args[1] type:args[2]];
int size = (int)[args count];;
if (size > 3) {
NSString *key = args[3];
query = [query queryStartingAtValue:value
childKey:key];
} else {
query = [query queryStartingAtValue:value];
} else if ([type isEqualToString:@"filter"]) {
NSString* valueType = [modifier valueForKey:@"valueType"];
NSString* key = [modifier valueForKey:@"key"];
id value = [self getIdValue:[modifier valueForKey:@"value"] type:valueType];
if ([name isEqualToString:@"equalTo"]) {
if (key != nil) {
query = [query queryEqualToValue:value childKey:key];
} else {
query = [query queryEqualToValue:value];
}
} else if ([name isEqualToString:@"endAt"]) {
if (key != nil) {
query = [query queryEndingAtValue:value childKey:key];
} else {
query = [query queryEndingAtValue:value];
}
} else if ([name isEqualToString:@"startAt"]) {
if (key != nil) {
query = [query queryStartingAtValue:value childKey:key];
} else {
query = [query queryStartingAtValue:value];
}
}
}
}
return query;
}
@ -273,62 +227,15 @@
}
}
- (void) setEventHandler:(FIRDatabaseHandle) handle
forName:(NSString *) name
{
int eventType = [self eventTypeFromName:name];
switch (eventType) {
case FIRDataEventTypeValue:
self.childValueHandler = handle;
break;
case FIRDataEventTypeChildAdded:
self.childAddedHandler = handle;
break;
case FIRDataEventTypeChildChanged:
self.childModifiedHandler = handle;
break;
case FIRDataEventTypeChildRemoved:
self.childRemovedHandler = handle;
break;
case FIRDataEventTypeChildMoved:
self.childMovedHandler = handle;
break;
default:
break;
}
[self setListeningOn:name withHandle:handle];
}
- (void) setListeningOn:(NSString *) name
withHandle:(FIRDatabaseHandle) handle
{
[_listeners setValue:@(handle) forKey:name];
}
- (void) unsetListeningOn:(NSString *) name
{
[_listeners removeObjectForKey:name];
}
- (BOOL) isListeningTo:(NSString *) name
{
return [_listeners valueForKey:name] != nil;
}
- (BOOL) hasListeners
{
return [[_listeners allKeys] count] > 0;
}
- (NSArray *) listenerKeys
{
return [_listeners allKeys];
}
- (int) eventTypeFromName:(NSString *)name
{
int eventType = FIRDataEventTypeValue;
if ([name isEqualToString:DATABASE_VALUE_EVENT]) {
eventType = FIRDataEventTypeValue;
} else if ([name isEqualToString:DATABASE_CHILD_ADDED_EVENT]) {
@ -343,24 +250,6 @@
return eventType;
}
- (void) cleanup {
if (self.childValueHandler > 0) {
[self removeEventHandler:DATABASE_VALUE_EVENT];
}
if (self.childAddedHandler > 0) {
[self removeEventHandler:DATABASE_CHILD_ADDED_EVENT];
}
if (self.childModifiedHandler > 0) {
[self removeEventHandler:DATABASE_CHILD_MODIFIED_EVENT];
}
if (self.childRemovedHandler > 0) {
[self removeEventHandler:DATABASE_CHILD_REMOVED_EVENT];
}
if (self.childMovedHandler > 0) {
[self removeEventHandler:DATABASE_CHILD_MOVED_EVENT];
}
}
@end
@ -393,27 +282,27 @@ RCT_EXPORT_METHOD(startTransaction:(NSString *) path identifier:(NSString *) ide
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[transactionState setObject:sema forKey:@"semaphore"];
FIRDatabaseReference *ref = [self getPathRef:path];
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) {
dispatch_barrier_async(_transactionQueue, ^{
[_transactions setValue:transactionState forKey:identifier];
[self sendTransactionEvent:DATABASE_TRANSACTION_EVENT body:@{ @"id": identifier, @"type": @"update", @"value": currentData.value }];
});
// wait for the js event handler to call tryCommitTransaction
// this wait occurs on the Firebase Worker Queue
// so if the tryCommitTransaction fails to signal the semaphore
// no further blocks will be executed by Firebase until the timeout expires
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC);
BOOL timedout = dispatch_semaphore_wait(sema, delayTime) != 0;
BOOL abort = [transactionState valueForKey:@"abort"] || timedout;
id value = [transactionState valueForKey:@"value"];
dispatch_barrier_async(_transactionQueue, ^{
[_transactions removeObjectForKey:identifier];
});
if (abort) {
return [FIRTransactionResult abort];
} else {
@ -442,34 +331,34 @@ RCT_EXPORT_METHOD(startTransaction:(NSString *) path identifier:(NSString *) ide
RCT_EXPORT_METHOD(tryCommitTransaction:(NSString *) identifier withData:(NSDictionary *) data) {
__block NSMutableDictionary *transactionState;
dispatch_sync(_transactionQueue, ^{
transactionState = [_transactions objectForKey: identifier];
});
if (!transactionState) {
NSLog(@"tryCommitTransaction for unknown ID %@", identifier);
return;
}
dispatch_semaphore_t sema = [transactionState valueForKey:@"semaphore"];
BOOL abort = [[data valueForKey:@"abort"] boolValue];
if (abort) {
[transactionState setValue:@true forKey:@"abort"];
} else {
id newValue = [data valueForKey:@"value"];
[transactionState setValue:newValue forKey:@"value"];
}
dispatch_semaphore_signal(sema);
}
RCT_EXPORT_METHOD(enablePersistence:(BOOL) enable
callback:(RCTResponseSenderBlock) callback)
{
BOOL isEnabled = [FIRDatabase database].persistenceEnabled;
if ( isEnabled != enable) {
[FIRDatabase database].persistenceEnabled = enable;
@ -526,10 +415,10 @@ RCT_EXPORT_METHOD(push:(NSString *) path
{
FIRDatabaseReference *ref = [self getPathRef:path];
FIRDatabaseReference *newRef = [ref childByAutoId];
NSURL *url = [NSURL URLWithString:newRef.URL];
NSString *newPath = [url path];
if ([data count] > 0) {
[newRef setValue:[data valueForKey:@"value"] withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
if (error != nil) {
@ -540,7 +429,7 @@ RCT_EXPORT_METHOD(push:(NSString *) path
@"message": [error localizedDescription],
@"description": [error description]
};
callback(@[evt]);
} else {
callback(@[[NSNull null], @{
@ -558,84 +447,65 @@ RCT_EXPORT_METHOD(push:(NSString *) path
}
RCT_EXPORT_METHOD(on:(NSString *) path
modifiersString:(NSString *) modifiersString
RCT_EXPORT_METHOD(on:(nonnull NSNumber *) refId
path:(NSString *) path
modifiers:(NSArray *) modifiers
listenerId:(nonnull NSNumber *) listenerId
name:(NSString *) eventName
callback:(RCTResponseSenderBlock) callback)
{
RNFirebaseDBReference *ref = [self getDBHandle:path modifiers:modifiers modifiersString:modifiersString];
[ref addEventHandler:eventName];
RNFirebaseDBReference *ref = [self getDBHandle:refId path:path modifiers:modifiers];
[ref addEventHandler:listenerId eventName:eventName];
callback(@[[NSNull null], @{
@"status": @"success",
@"refId": refId,
@"handle": path
}]);
}
RCT_EXPORT_METHOD(once:(NSString *) path
modifiersString:(NSString *) modifiersString
RCT_EXPORT_METHOD(once:(nonnull NSNumber *) refId
path:(NSString *) path
modifiers:(NSArray *) modifiers
name:(NSString *) name
callback:(RCTResponseSenderBlock) callback)
{
RNFirebaseDBReference *ref = [self getDBHandle:path modifiers:modifiers modifiersString:modifiersString];
[ref addSingleEventHandler:callback];
}
RCT_EXPORT_METHOD(off:(NSString *)path
modifiersString:(NSString *) modifiersString
eventName:(NSString *) eventName
callback:(RCTResponseSenderBlock) callback)
{
NSString *key = [self getDBListenerKey:path withModifiers:modifiersString];
NSArray *listenerKeys;
RNFirebaseDBReference *ref = [_dbReferences objectForKey:key];
if (ref == nil) {
listenerKeys = @[];
} else {
if (eventName == nil || [eventName isEqualToString:@""]) {
[ref cleanup];
[_dbReferences removeObjectForKey:key];
} else {
[ref removeEventHandler:eventName];
RNFirebaseDBReference *ref = [self getDBHandle:refId path:path modifiers:modifiers];
[ref addSingleEventHandler:callback];
}
RCT_EXPORT_METHOD(off:(nonnull NSNumber *) refId
listeners:(NSArray *) listeners
callback:(RCTResponseSenderBlock) callback)
{
RNFirebaseDBReference *ref = [_dbReferences objectForKey:refId];
if (ref != nil) {
for (NSDictionary *listener in listeners) {
NSNumber *listenerId = [listener valueForKey:@"listenerId"];
NSString *eventName = [listener valueForKey:@"eventName"];
[ref removeEventHandler:listenerId eventName:eventName];
if (![ref hasListeners]) {
[_dbReferences removeObjectForKey:key];
[_dbReferences removeObjectForKey:refId];
}
}
listenerKeys = [ref listenerKeys];
}
callback(@[[NSNull null], @{
@"result": @"success",
@"handle": path,
@"modifiersString": modifiersString,
@"remainingListeners": listenerKeys,
@"status": @"success",
@"refId": refId,
}]);
}
// On disconnect
RCT_EXPORT_METHOD(onDisconnectSetObject:(NSString *) path
RCT_EXPORT_METHOD(onDisconnectSet:(NSString *) path
props:(NSDictionary *) props
callback:(RCTResponseSenderBlock) callback)
{
FIRDatabaseReference *ref = [self getPathRef:path];
[ref onDisconnectSetValue:props
[ref onDisconnectSetValue:props[@"value"]
withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
[self handleCallback:@"onDisconnectSetObject" callback:callback databaseError:error];
}];
}
RCT_EXPORT_METHOD(onDisconnectSetString:(NSString *) path
val:(NSString *) val
callback:(RCTResponseSenderBlock) callback)
{
FIRDatabaseReference *ref = [self getPathRef:path];
[ref onDisconnectSetValue:val
withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
[self handleCallback:@"onDisconnectSetString" callback:callback databaseError:error];
}];
}
RCT_EXPORT_METHOD(onDisconnectRemove:(NSString *) path
callback:(RCTResponseSenderBlock) callback)
{
@ -691,30 +561,23 @@ RCT_EXPORT_METHOD(goOnline)
}
}
- (RNFirebaseDBReference *) getDBHandle:(NSString *) path
modifiers:modifiers
modifiersString:modifiersString
- (RNFirebaseDBReference *) getDBHandle:(NSNumber *) refId
path:(NSString *) path
modifiers:(NSArray *) modifiers
{
NSString *key = [self getDBListenerKey:path withModifiers:modifiersString];
RNFirebaseDBReference *ref = [_dbReferences objectForKey:key];
RNFirebaseDBReference *ref = [_dbReferences objectForKey:refId];
if (ref == nil) {
ref = [[RNFirebaseDBReference alloc] initWithPathAndModifiers:self
database:[FIRDatabase database]
refId:refId
path:path
modifiers:modifiers
modifiersString:modifiersString];
[_dbReferences setObject:ref forKey:key];
modifiers:modifiers];
[_dbReferences setObject:ref forKey:refId];
}
return ref;
}
- (NSString *) getDBListenerKey:(NSString *) path
withModifiers:(NSString *) modifiersString
{
return [NSString stringWithFormat:@"%@ | %@", path, modifiersString, nil];
}
// Not sure how to get away from this... yet
- (NSArray<NSString *> *)supportedEvents {
return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT, DATABASE_TRANSACTION_EVENT];

View File

@ -278,12 +278,12 @@ RCT_EXPORT_METHOD(requestPermissions:(RCTPromiseResolveBlock)resolve rejecter:(R
[[UNUserNotificationCenter currentNotificationCenter]
requestAuthorizationWithOptions:authOptions
completionHandler:^(BOOL granted, NSError * _Nullable error) {
resolve(@{@"granted":@(granted)});
resolve(@{@"granted":@(granted)});
}
];
#endif
}
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
@ -408,23 +408,12 @@ RCT_EXPORT_METHOD(getBadgeNumber: (RCTPromiseResolveBlock)resolve rejecter:(RCTP
resolve(@([RCTSharedApplication() applicationIconBadgeNumber]));
}
RCT_EXPORT_METHOD(send:(NSString*)senderId withPayload:(NSDictionary *)message)
{
NSMutableDictionary * mMessage = [message mutableCopy];
NSMutableDictionary * upstreamMessage = [[NSMutableDictionary alloc] init];
for (NSString* key in mMessage) {
upstreamMessage[key] = [NSString stringWithFormat:@"%@", [mMessage valueForKey:key]];
}
NSDictionary *imMessage = [NSDictionary dictionaryWithDictionary:upstreamMessage];
int64_t ttl = 3600;
NSString * receiver = [NSString stringWithFormat:@"%@@gcm.googleapis.com", senderId];
NSUUID *uuid = [NSUUID UUID];
NSString * messageID = [uuid UUIDString];
[[FIRMessaging messaging]sendMessage:imMessage to:receiver withMessageID:messageID timeToLive:ttl];
RCT_EXPORT_METHOD(send:(NSDictionary *)remoteMessage) {
int64_t ttl = @([[remoteMessage valueForKey:@"ttl"] intValue]).doubleValue;
NSString * mId = [remoteMessage valueForKey:@"id"];
NSString * receiver = [remoteMessage valueForKey:@"sender"];
NSDictionary * data = [remoteMessage valueForKey:@"data"];
[[FIRMessaging messaging]sendMessage:data to:receiver withMessageID:mId timeToLive:ttl];
}
RCT_EXPORT_METHOD(finishRemoteNotification: (NSString *)completionHandlerId fetchResult:(UIBackgroundFetchResult)result){

View File

@ -349,6 +349,7 @@ RCT_EXPORT_METHOD(putFile:(NSString *) path localPath:(NSString *)localPath meta
[uploadTask observeStatus:FIRStorageTaskStatusSuccess handler:^(FIRStorageTaskSnapshot *snapshot) {
// upload completed successfully
NSDictionary *resp = [self getUploadTaskAsDictionary:snapshot];
[self sendJSEvent:STORAGE_EVENT path:path title:STORAGE_STATE_CHANGED props:resp];
[self sendJSEvent:STORAGE_EVENT path:path title:STORAGE_UPLOAD_SUCCESS props:resp];
resolve(resp);
}];

View File

@ -11,11 +11,11 @@ import { isObject } from './utils';
import Auth, { statics as AuthStatics } from './modules/auth';
import Storage, { statics as StorageStatics } from './modules/storage';
import Database, { statics as DatabaseStatics } from './modules/database';
import Messaging from './modules/messaging';
import Messaging, { statics as MessagingStatics } from './modules/messaging';
import Analytics from './modules/analytics';
import Crash from './modules/crash';
const instances = { default: null };
const instances: Object = { default: null };
const FirebaseModule = NativeModules.RNFirebase;
/**
@ -34,6 +34,19 @@ export default class Firebase {
_remoteConfig: ?Object;
_crash: ?Object;
auth: Function;
storage: Function;
database: Function;
messaging: Function;
eventHandlers: Object;
debug: boolean;
options: {
errorOnMissingPlayServices: boolean,
debug?: boolean,
persistence?: boolean
};
/**
*
* @param options
@ -41,7 +54,7 @@ export default class Firebase {
constructor(options: Object = {}) {
this.eventHandlers = {};
this.debug = options.debug || false;
this.options = Object.assign({ errorOnMissingPlayServices: true }, options);
this.options = Object.assign({ errorOnMissingPlayServices: true, promptOnMissingPlayServices: true }, options);
if (this.debug) {
Log.enable(this.debug);
@ -49,16 +62,25 @@ export default class Firebase {
this._log = new Log('firebase');
this._auth = new Auth(this, this.options);
if (this.options.errorOnMissingPlayServices && !this.googleApiAvailability.isAvailable) {
throw new Error(`Google Play Services is required to run this application but no valid installation was found (Code ${this.googleApiAvailability.status}).`);
if (!this.googleApiAvailability.isAvailable) {
if (this.options.promptOnMissingPlayServices && this.googleApiAvailability.isUserResolvableError) {
FirebaseModule.promptPlayServices();
} else {
const error = `Google Play Services is required to run this application but no valid installation was found (Code ${this.googleApiAvailability.status}).`;
if (this.options.errorOnMissingPlayServices) {
throw new Error(error);
} else {
console.warn(error);
}
}
}
this.auth = this._staticsOrInstance('auth', StorageStatics, Auth);
this.auth = this._staticsOrInstance('auth', AuthStatics, Auth);
this.storage = this._staticsOrInstance('storage', StorageStatics, Storage);
this.database = this._staticsOrInstance('database', DatabaseStatics, Database);
this.messaging = this._staticsOrInstance('messaging', MessagingStatics, Messaging);
// init auth to stat listeners
// init auth to start listeners
this.auth();
}
@ -92,13 +114,6 @@ export default class Firebase {
return this._analytics;
}
messaging() {
if (!this._messaging) {
this._messaging = new Messaging(this);
}
return this._messaging;
}
crash() {
if (!this._crash) {
this._crash = new Crash(this);
@ -134,19 +149,21 @@ export default class Firebase {
* @returns {function()}
* @private
*/
_staticsOrInstance(name, statics, InstanceClass) {
_staticsOrInstance(name, statics, InstanceClass): Function {
const getInstance = () => {
const internalPropName = `_${name}`;
// $FlowFixMe
if (!this[internalPropName]) {
// $FlowFixMe
this[internalPropName] = new InstanceClass(this);
}
// $FlowFixMe
return this[internalPropName];
};
Object.assign(getInstance, statics || {});
return getInstance;
}
}

View File

@ -15,9 +15,35 @@ declare type CredentialType = {
secret: string
};
declare type DatabaseListener = {
listenerId: number;
eventName: string;
successCallback: Function;
failureCallback?: Function;
};
declare type DatabaseModifier = {
type: 'orderBy' | 'limit' | 'filter';
name?: string;
key?: string;
limit?: number;
value?: any;
valueType?: string;
};
declare type GoogleApiAvailabilityType = {
status: number,
isAvailable: boolean,
isUserResolvableError?: boolean,
error?: string
};
declare class FirebaseError {
message: string,
name: string,
code: string,
stack: string,
path: string,
details: string,
modifiers: string
};

View File

@ -42,7 +42,7 @@ export default class Auth extends Base {
if (auth && auth.user && !this._user) this._user = new User(this, auth);
else if ((!auth || !auth.user) && this._user) this._user = null;
else if (this._user) this._user._updateValues(auth);
if (emit) this.emit('onAuthStateChanged', this._authResult.user || null);
if (emit) this.emit('onAuthStateChanged', this._user);
return auth ? this._user : null;
}

View File

@ -4,7 +4,7 @@ import { Base } from './../base';
const FirebaseCrash = NativeModules.RNFirebaseCrash;
export default class Analytics extends Base {
export default class Crash extends Base {
/**
* Logs a message that will appear in a subsequent crash report.
* @param {string} message
@ -33,7 +33,7 @@ export default class Analytics extends Base {
* @param {Error} error
* @param maxStackSize
*/
report(error: Error, maxStackSize: Number = 10): void {
report(error: FirebaseError, maxStackSize: number = 10): void {
if (!error || !error.code || !error.message) return;
let errorMessage = `Message: ${error.message}\r\n`;

View File

@ -12,6 +12,7 @@ const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
*/
export default class Disconnect {
ref: Reference;
path: string;
/**
*

View File

@ -19,9 +19,8 @@ const FirebaseDatabaseEvt = new NativeEventEmitter(FirebaseDatabase);
export default class Database extends Base {
constructor(firebase: Object, options: Object = {}) {
super(firebase, options);
this.subscriptions = {};
this.references = {};
this.serverTimeOffset = 0;
this.errorSubscriptions = {};
this.persistenceEnabled = false;
this.namespace = 'firebase:database';
this.transaction = new TransactionHandler(firebase, this, FirebaseDatabaseEvt);
@ -68,20 +67,12 @@ export default class Database extends Base {
* @param errorCb
* @returns {*}
*/
on(path: string, modifiersString: string, modifiers: Array<string>, eventName: string, cb: () => void, errorCb: () => void) {
const handle = this._handle(path, modifiersString);
this.log.debug('adding on listener', handle);
if (!this.subscriptions[handle]) this.subscriptions[handle] = {};
if (!this.subscriptions[handle][eventName]) this.subscriptions[handle][eventName] = [];
this.subscriptions[handle][eventName].push(cb);
if (errorCb) {
if (!this.errorSubscriptions[handle]) this.errorSubscriptions[handle] = [];
this.errorSubscriptions[handle].push(errorCb);
}
return promisify('on', FirebaseDatabase)(path, modifiersString, modifiers, eventName);
on(ref: Reference, listener: DatabaseListener) {
const { refId, path, query } = ref;
const { listenerId, eventName } = listener;
this.log.debug('on() : ', ref.refId, listenerId, eventName);
this.references[refId] = ref;
return promisify('on', FirebaseDatabase)(refId, path, query.getModifiers(), listenerId, eventName);
}
/**
@ -92,49 +83,30 @@ export default class Database extends Base {
* @param origCB
* @returns {*}
*/
off(path: string, modifiersString: string, eventName?: string, origCB?: () => void) {
const handle = this._handle(path, modifiersString);
this.log.debug('off() : ', handle, eventName);
off(refId: number, listeners: Array<DatabaseListener>, remainingListenersCount: number) {
this.log.debug('off() : ', refId, listeners);
if (!this.subscriptions[handle] || (eventName && !this.subscriptions[handle][eventName])) {
this.log.warn('off() called, but not currently listening at that location (bad path)', handle, eventName);
return Promise.resolve();
}
// Delete the reference if there are no more listeners
if (remainingListenersCount === 0) delete this.references[refId];
if (eventName && origCB) {
const i = this.subscriptions[handle][eventName].indexOf(origCB);
if (listeners.length === 0) return Promise.resolve();
if (i === -1) {
this.log.warn('off() called, but the callback specified is not listening at that location (bad path)', handle, eventName);
return Promise.resolve();
}
this.subscriptions[handle][eventName].splice(i, 1);
if (this.subscriptions[handle][eventName].length > 0) return Promise.resolve();
} else if (eventName) {
this.subscriptions[handle][eventName] = [];
} else {
this.subscriptions[handle] = {};
}
this.errorSubscriptions[handle] = [];
return promisify('off', FirebaseDatabase)(path, modifiersString, eventName);
return promisify('off', FirebaseDatabase)(refId, listeners.map(listener => ({
listenerId: listener.listenerId,
eventName: listener.eventName,
})));
}
/**
* Removes all event handlers and their native subscriptions
* Removes all references and their native listeners
* @returns {Promise.<*>}
*/
cleanup() {
const promises = [];
Object.keys(this.subscriptions).forEach((handle) => {
Object.keys(this.subscriptions[handle]).forEach((eventName) => {
const separator = handle.indexOf('|');
const path = handle.substring(0, separator);
const modifiersString = handle.substring(separator + 1);
promises.push(this.off(path, modifiersString, eventName));
});
Object.keys(this.references).forEach((refId) => {
const ref = this.references[refId];
promises.push(this.off(Number(refId), Object.values(ref.listeners), 0));
});
return Promise.all(promises);
}
@ -169,18 +141,6 @@ export default class Database extends Base {
return Promise.reject({ status: 'Already enabled' });
}
/**
*
* @param path
* @param modifiersString
* @returns {string}
* @private
*/
_handle(path: string = '', modifiersString: string = '') {
return `${path}|${modifiersString}`;
}
/**
*
* @param event
@ -188,18 +148,14 @@ export default class Database extends Base {
*/
_handleDatabaseEvent(event: Object) {
const body = event.body || {};
const { path, modifiersString, eventName, snapshot } = body;
const handle = this._handle(path, modifiersString);
this.log.debug('_handleDatabaseEvent: ', handle, eventName, snapshot && snapshot.key);
if (this.subscriptions[handle] && this.subscriptions[handle][eventName]) {
this.subscriptions[handle][eventName].forEach((cb) => {
cb(new Snapshot(new Reference(this, path, modifiersString.split('|')), snapshot), body);
});
const { refId, listenerId, path, eventName, snapshot } = body;
this.log.debug('_handleDatabaseEvent: ', refId, listenerId, path, eventName, snapshot && snapshot.key);
if (this.references[refId] && this.references[refId].listeners[listenerId]) {
const cb = this.references[refId].listeners[listenerId].successCallback;
cb(new Snapshot(this.references[refId], snapshot));
} else {
FirebaseDatabase.off(path, modifiersString, eventName, () => {
this.log.debug('_handleDatabaseEvent: No JS listener registered, removed native listener', handle, eventName);
FirebaseDatabase.off(refId, [{ listenerId, eventName }], () => {
this.log.debug('_handleDatabaseEvent: No JS listener registered, removed native listener', refId, listenerId, eventName);
});
}
}
@ -218,7 +174,8 @@ export default class Database extends Base {
firebaseMessage = `${firebaseMessage} at /${path}\r\n`;
}
const firebaseError = new Error(firebaseMessage);
// $FlowFixMe
const firebaseError: FirebaseError = new Error(firebaseMessage);
firebaseError.code = code;
firebaseError.path = path;
@ -234,13 +191,15 @@ export default class Database extends Base {
* @private
*/
_handleDatabaseError(error: Object = {}) {
const { path, modifiers } = error;
const handle = this._handle(path, modifiers);
const { refId, listenerId, path } = error;
const firebaseError = this._toFirebaseError(error);
this.log.debug('_handleDatabaseError ->', handle, 'database_error', error);
this.log.debug('_handleDatabaseError ->', refId, listenerId, path, 'database_error', error);
if (this.errorSubscriptions[handle]) this.errorSubscriptions[handle].forEach(listener => listener(firebaseError));
if (this.references[refId] && this.references[refId].listeners[listenerId]) {
const failureCb = this.references[refId].listeners[listenerId].failureCallback;
if (failureCb) failureCb(firebaseError);
}
}
}
@ -249,4 +208,3 @@ export const statics = {
TIMESTAMP: FirebaseDatabase.serverValueTimestamp || { '.sv': 'timestamp' },
},
};

View File

@ -9,47 +9,41 @@ import Reference from './reference.js';
* @class Query
*/
export default class Query extends ReferenceBase {
static ref: Reference;
modifiers: Array<DatabaseModifier>;
static modifiers: Array<string>;
ref: Reference;
constructor(ref: Reference, path: string, existingModifiers?: Array<string>) {
constructor(ref: Reference, path: string, existingModifiers?: Array<DatabaseModifier>) {
super(ref.database, path);
this.log.debug('creating Query ', path, existingModifiers);
this.ref = ref;
this.modifiers = existingModifiers ? [...existingModifiers] : [];
}
setOrderBy(name: string, key?: string) {
if (key) {
this.modifiers.push(`${name}:${key}`);
} else {
this.modifiers.push(name);
}
orderBy(name: string, key?: string) {
this.modifiers.push({
type: 'orderBy',
name,
key,
});
}
setLimit(name: string, limit: number) {
this.modifiers.push(`${name}:${limit}`);
limit(name: string, limit: number) {
this.modifiers.push({
type: 'limit',
name,
limit,
});
}
setFilter(name: string, value: any, key?:string) {
if (key) {
this.modifiers.push(`${name}:${value}:${typeof value}:${key}`);
} else {
this.modifiers.push(`${name}:${value}:${typeof value}`);
}
filter(name: string, value: any, key?:string) {
this.modifiers.push({
type: 'filter',
name,
value,
valueType: typeof value,
key,
});
}
getModifiers(): Array<string> {
getModifiers(): Array<DatabaseModifier> {
return [...this.modifiers];
}
getModifiersString(): string {
if (!this.modifiers || !Array.isArray(this.modifiers)) {
return '';
}
return this.modifiers.join('|');
}
}

View File

@ -10,6 +10,8 @@ import { ReferenceBase } from './../base';
import { promisify, isFunction, isObject, tryJSONParse, tryJSONStringify, generatePushID } from './../../utils';
const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
// Unique Reference ID for native events
let refId = 1;
/**
* @link https://firebase.google.com/docs/reference/js/firebase.database.Reference
@ -17,15 +19,19 @@ const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
*/
export default class Reference extends ReferenceBase {
refId: number;
listeners: { [listenerId: number]: DatabaseListener };
database: FirebaseDatabase;
query: Query;
constructor(database: FirebaseDatabase, path: string, existingModifiers?: Array<string>) {
constructor(database: FirebaseDatabase, path: string, existingModifiers?: Array<DatabaseModifier>) {
super(database.firebase, path);
this.refId = refId++;
this.listeners = {};
this.database = database;
this.namespace = 'firebase:db:ref';
this.query = new Query(this, path, existingModifiers);
this.log.debug('Created new Reference', this.database._handle(path, existingModifiers));
this.log.debug('Created new Reference', this.refId, this.path);
}
/**
@ -81,7 +87,6 @@ export default class Reference extends ReferenceBase {
const path = this.path;
const _value = this._serializeAnyType(value);
return promisify('push', FirebaseDatabase)(path, _value)
.then(({ ref }) => {
const newRef = new Reference(this.database, ref);
@ -95,36 +100,37 @@ export default class Reference extends ReferenceBase {
/**
*
* @param eventType
* @param eventName
* @param successCallback
* @param failureCallback
* @param context TODO
* @returns {*}
*/
on(eventType: string, successCallback: () => any, failureCallback: () => any) {
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');
const path = this.path;
const modifiers = this.query.getModifiers();
const modifiersString = this.query.getModifiersString();
this.log.debug('adding reference.on', path, modifiersString, eventType);
this.database.on(path, modifiersString, modifiers, eventType, successCallback, failureCallback);
this.log.debug('adding reference.on', this.refId, eventName);
const listener = {
listenerId: Object.keys(this.listeners).length + 1,
eventName,
successCallback,
failureCallback,
};
this.listeners[listener.listenerId] = listener;
this.database.on(this, listener);
return successCallback;
}
/**
*
* @param eventType
* @param eventName
* @param successCallback
* @param failureCallback
* @param context TODO
* @returns {Promise.<TResult>}
*/
once(eventType: string = 'value', successCallback: (snapshot: Object) => void, failureCallback: (error: Error) => void) {
const path = this.path;
const modifiers = this.query.getModifiers();
const modifiersString = this.query.getModifiersString();
return promisify('once', FirebaseDatabase)(path, modifiersString, modifiers, eventType)
once(eventName: string = 'value', successCallback: (snapshot: Object) => void, failureCallback: (error: FirebaseError) => void) {
return promisify('once', FirebaseDatabase)(this.refId, this.path, this.query.getModifiers(), eventName)
.then(({ snapshot }) => new Snapshot(this, snapshot))
.then((snapshot) => {
if (isFunction(successCallback)) successCallback(snapshot);
@ -139,15 +145,35 @@ export default class Reference extends ReferenceBase {
/**
*
* @param eventType
* @param eventName
* @param origCB
* @returns {*}
*/
off(eventType?: string = '', origCB?: () => any) {
const path = this.path;
const modifiersString = this.query.getModifiersString();
this.log.debug('ref.off(): ', path, modifiersString, eventType);
return this.database.off(path, modifiersString, eventType, origCB);
off(eventName?: string = '', origCB?: () => any) {
this.log.debug('ref.off(): ', this.refId, eventName);
let listenersToRemove;
if (eventName && origCB) {
listenersToRemove = Object.values(this.listeners).filter((listener) => {
return listener.eventName === eventName && listener.successCallback === origCB;
});
// Only remove a single listener as per the web spec
if (listenersToRemove.length > 1) listenersToRemove = [listenersToRemove[0]];
} else if (eventName) {
listenersToRemove = Object.values(this.listeners).filter((listener) => {
return listener.eventName === eventName;
});
} else if (origCB) {
listenersToRemove = Object.values(this.listeners).filter((listener) => {
return listener.successCallback === origCB;
});
} else {
listenersToRemove = Object.values(this.listeners);
}
// Remove the listeners from the reference to prevent memory leaks
listenersToRemove.forEach((listener) => {
delete this.listeners[listener.listenerId];
});
return this.database.off(this.refId, listenersToRemove, Object.keys(this.listeners).length);
}
/**
@ -157,19 +183,21 @@ export default class Reference extends ReferenceBase {
* @param onComplete
* @param applyLocally
*/
transaction(transactionUpdate, onComplete?: () => any, applyLocally: boolean = false) {
transaction(transactionUpdate: Function, onComplete, applyLocally: boolean = false) {
if (!isFunction(transactionUpdate)) return Promise.reject(new Error('Missing transactionUpdate function argument.'));
return new Promise((resolve, reject) => {
const onCompleteWrapper = (error, committed, snapshotData) => {
if (error) {
if (isFunction(onComplete)) onComplete(error, committed, null);
if (typeof onComplete === 'function') {
onComplete(error, committed, null);
}
return reject(error);
}
const snapshot = new Snapshot(this, snapshotData);
if (isFunction(onComplete)) {
if (typeof onComplete === 'function') {
onComplete(null, committed, snapshot);
}
@ -225,7 +253,7 @@ export default class Reference extends ReferenceBase {
*/
orderBy(name: string, key?: string): Reference {
const newRef = new Reference(this.database, this.path, this.query.getModifiers());
newRef.query.setOrderBy(name, key);
newRef.query.orderBy(name, key);
return newRef;
}
@ -259,7 +287,7 @@ export default class Reference extends ReferenceBase {
*/
limit(name: string, limit: number): Reference {
const newRef = new Reference(this.database, this.path, this.query.getModifiers());
newRef.query.setLimit(name, limit);
newRef.query.limit(name, limit);
return newRef;
}
@ -306,7 +334,7 @@ export default class Reference extends ReferenceBase {
*/
filter(name: string, value: any, key?: string): Reference {
const newRef = new Reference(this.database, this.path, this.query.getModifiers());
newRef.query.setFilter(name, value, key);
newRef.query.filter(name, value, key);
return newRef;
}
@ -335,6 +363,17 @@ export default class Reference extends ReferenceBase {
return this.path;
}
/**
* Returns whether another Reference represent the same location and are from the
* same instance of firebase.app.App - multiple firebase apps not currently supported.
* @param {Reference} otherRef - Other reference to compare to this one
* @return {Boolean} Whether otherReference is equal to this one
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#isEqual}
*/
isEqual(otherRef: Reference): boolean {
return !!otherRef && otherRef.constructor === Reference && otherRef.key === this.key;
}
/**
* GETTERS
*/
@ -348,6 +387,14 @@ export default class Reference extends ReferenceBase {
return new Reference(this.database, this.path.substring(0, this.path.lastIndexOf('/')));
}
/**
* A reference to itself
* @type {!Reference}
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#ref}
*/
get ref(): Reference {
return this;
}
/**
* Returns a ref to the root of db - '/'

View File

@ -62,7 +62,7 @@ export default class Snapshot {
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#exists
* @returns {boolean}
*/
exists(): Boolean {
exists(): boolean {
return this._value !== null;
}
@ -71,7 +71,7 @@ export default class Snapshot {
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#forEach
* @param action
*/
forEach(action: (key: any) => any): Boolean {
forEach(action: (key: any) => any): boolean {
if (!this._childKeys.length) return false;
let cancelled = false;
@ -94,7 +94,7 @@ export default class Snapshot {
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#getPriority
* @returns {String|Number|null}
*/
getPriority(): String|Number|null {
getPriority(): string | number | null {
return this._priority;
}
@ -104,7 +104,7 @@ export default class Snapshot {
* @param path
* @returns {Boolean}
*/
hasChild(path: string): Boolean {
hasChild(path: string): boolean {
return deepExists(this._value, path);
}
@ -113,7 +113,7 @@ export default class Snapshot {
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#hasChildren
* @returns {boolean}
*/
hasChildren(): Boolean {
hasChildren(): boolean {
return this.numChildren() > 0;
}
@ -122,7 +122,7 @@ export default class Snapshot {
* @link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#numChildren
* @returns {Number}
*/
numChildren(): Number {
numChildren(): number {
if (!isObject(this._value)) return 0;
return Object.keys(this._value).length;
}

View File

@ -13,7 +13,7 @@ const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
* @class Database
*/
export default class TransactionHandler extends Base {
constructor(firebase: Object, database: Object, FirebaseDatabaseEvt) {
constructor(firebase: Object, database: Object, FirebaseDatabaseEvt: Object) {
super(firebase, {});
this.transactions = {};
this.database = database;
@ -32,7 +32,12 @@ export default class TransactionHandler extends Base {
* @param onComplete
* @param applyLocally
*/
add(reference, transactionUpdater, onComplete, applyLocally = false) {
add(
reference: Object,
transactionUpdater: Function,
onComplete?: Function,
applyLocally?: boolean = false
) {
const id = this._generateTransactionId();
this.transactions[id] = {
@ -45,7 +50,7 @@ export default class TransactionHandler extends Base {
started: true,
};
FirebaseDatabase.startTransaction(reference.path, id, applyLocally || false);
FirebaseDatabase.startTransaction(reference.path, id, applyLocally);
}
/**
@ -57,7 +62,7 @@ export default class TransactionHandler extends Base {
* @returns {string}
* @private
*/
_generateTransactionId() {
_generateTransactionId(): string {
return generatePushID(this.database.serverTimeOffset);
}
@ -72,7 +77,7 @@ export default class TransactionHandler extends Base {
case 'update':
return this._handleUpdate(event);
case 'error':
return this._handleError(error);
return this._handleError(event);
case 'complete':
return this._handleComplete(event);
default:

View File

@ -0,0 +1,80 @@
import { isObject, generatePushID } from './../../utils';
export default class RemoteMessage {
constructor(sender: String) {
this.properties = {
id: generatePushID(),
ttl: 3600,
// add the googleapis sender id part if not already added.
sender: `${sender}`.includes('@') ? sender : `${sender}@gcm.googleapis.com`,
type: 'remote',
data: {},
};
}
/**
*
* @param ttl
* @returns {RemoteMessage}
*/
setTtl(ttl: Number): RemoteMessage {
this.properties.ttl = ttl;
return this;
}
/**
*
* @param id
*/
setId(id: string): RemoteMessage {
this.properties.id = `${id}`;
return this;
}
/**
*
* @param type
* @returns {RemoteMessage}
*/
setType(type: string): RemoteMessage {
this.properties.type = `${type}`;
return this;
}
/**
*
* @param key
* @returns {RemoteMessage}
*/
setCollapseKey(key: string): RemoteMessage {
this.properties.collapseKey = `${key}`;
return this;
}
/**
*
* @param data
* @returns {RemoteMessage}
*/
setData(data: Object = {}) {
if (!isObject(data)) {
throw new Error(`RemoteMessage:setData expects an object as the first parameter but got type '${typeof data}'.`);
}
const props = Object.keys(data);
// coerce all property values to strings as
// remote message data only supports strings
for (let i = 0, len = props.length; i < len; i++) {
const prop = props[i];
this.properties.data[prop] = `${data[prop]}`;
}
return this;
}
toJSON() {
return Object.assign({}, this.properties);
}
}

View File

@ -1,40 +1,32 @@
import { NativeModules, DeviceEventEmitter, Platform } from 'react-native';
import { Base } from './../base';
import RemoteMessage from './RemoteMessage';
const FirebaseMessaging = NativeModules.RNFirebaseMessaging;
export const EVENT_TYPE = {
const EVENT_TYPE = {
RefreshToken: 'FCMTokenRefreshed',
Notification: 'FCMNotificationReceived',
};
export const NOTIFICATION_TYPE = {
const NOTIFICATION_TYPE = {
Remote: 'remote_notification',
NotificationResponse: 'notification_response',
WillPresent: 'will_present_notification',
Local: 'local_notification',
};
export const REMOTE_NOTIFICATION_RESULT = {
const REMOTE_NOTIFICATION_RESULT = {
NewData: 'UIBackgroundFetchResultNewData',
NoData: 'UIBackgroundFetchResultNoData',
ResultFailed: 'UIBackgroundFetchResultFailed',
};
export const WILL_PRESENT_RESULT = {
const WILL_PRESENT_RESULT = {
All: 'UNNotificationPresentationOptionAll',
None: 'UNNotificationPresentationOptionNone',
};
type RemoteMessage = {
id: string,
type: string,
ttl?: number,
sender: string,
collapseKey?: string,
data: Object,
};
/**
* IOS only finish function
* @param data
@ -246,10 +238,21 @@ export default class Messaging extends Base {
/**
* Send an upstream message
* @param senderId
* @param payload
* @param remoteMessage
*/
send(senderId, payload: RemoteMessage) {
return FirebaseMessaging.send(senderId, payload);
send(remoteMessage: RemoteMessage) {
if (!(remoteMessage instanceof RemoteMessage)) {
throw new Error('messaging().send requires an instance of RemoteMessage as the first argument.');
}
return FirebaseMessaging.send(remoteMessage.toJSON());
}
}
export const statics = {
EVENT_TYPE,
NOTIFICATION_TYPE,
REMOTE_NOTIFICATION_RESULT,
WILL_PRESENT_RESULT,
RemoteMessage,
};

View File

@ -1,7 +1,7 @@
/* @flow */
import { NativeModules } from 'react-native';
import { ReferenceBase } from './../base';
import { ReferenceBase } from '../base';
import StorageTask, { UPLOAD_TASK, DOWNLOAD_TASK } from './task';
import Storage from './';
@ -16,7 +16,7 @@ export default class StorageReference extends ReferenceBase {
this.storage = storage;
}
get fullPath() {
get fullPath(): string {
return this.path;
}
@ -76,6 +76,14 @@ export default class StorageReference extends ReferenceBase {
return new StorageTask(DOWNLOAD_TASK, FirebaseStorage.downloadFile(this.path, filePath), this);
}
/**
* Alias to putFile
* @returns {StorageReference.putFile}
*/
get put() {
return this.putFile;
}
/**
* Upload a file path
* @param {string} filePath The local path of the file

View File

@ -1,22 +1,105 @@
/* @flow */
import { statics as StorageStatics } from './';
import { isObject, isFunction } from './../../utils';
import StorageReference from './reference';
export const UPLOAD_TASK = 'upload';
export const DOWNLOAD_TASK = 'download';
declare type UploadTaskSnapshotType = {
bytesTransferred: number,
downloadURL: string|null,
metadata: Object, // TODO flow type def for https://firebase.google.com/docs/reference/js/firebase.storage.FullMetadata.html
ref: StorageReference,
state: StorageStatics.TaskState.RUNNING
|StorageStatics.TaskState.PAUSED
|StorageStatics.TaskState.SUCCESS
|StorageStatics.TaskState.CANCELLED
|StorageStatics.TaskState.ERROR,
task: StorageTask,
totalBytes: number,
};
declare type FuncSnapshotType = null|(snapshot: UploadTaskSnapshotType) => any;
declare type FuncErrorType = null|(error: Error) => any;
declare type NextOrObserverType = null
|{ next?: FuncSnapshotType, error?: FuncErrorType, complete?:FuncSnapshotType }
|FuncSnapshotType;
/**
* @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask
*/
export default class StorageTask {
constructor(type: string, promise, storageRef) {
constructor(type: UPLOAD_TASK|DOWNLOAD_TASK, promise: Promise, storageRef: StorageReference) {
this.type = type;
this.ref = storageRef;
this.promise = promise;
this.storage = storageRef.storage;
this.path = storageRef.path;
// 'proxy' original promise
this.then = promise.then.bind(promise);
this.then = promise.then(this._interceptSnapshotEvent);
this.catch = promise.catch.bind(promise);
}
/**
* Intercepts a native snapshot result object attaches ref / task instances
* and calls the original function
* @returns {Promise.<T>}
* @private
*/
_interceptSnapshotEvent(f: Function|null|undefined): null|() => any {
if (!isFunction(f)) return null;
return (snapshot) => {
const _snapshot = Object.assign({}, snapshot);
_snapshot.task = this;
_snapshot.ref = this.ref;
return f(_snapshot);
};
}
/**
* Intercepts a error object form native and converts to a JS Error
* @param f
* @returns {*}
* @private
*/
_interceptErrorEvent(f: Function|null|undefined): null|() => any {
if (!isFunction(f)) return null;
return (error) => {
const _error = new Error(error.message);
_error.code = error.code;
return f(_error);
};
}
/**
*
* @param nextOrObserver
* @param error
* @param complete
* @returns {function()}
* @private
*/
_subscribe(nextOrObserver: NextOrObserverType, error: FuncErrorType, complete: FuncSnapshotType): Function {
const observer = isObject(nextOrObserver);
const _error = this._interceptErrorEvent(observer ? nextOrObserver.error : error);
const _next = this._interceptSnapshotEvent(observer ? nextOrObserver.next : nextOrObserver);
const _complete = this._interceptSnapshotEvent(observer ? nextOrObserver.complete : complete);
if (_next) this.storage._addListener(this.path, StorageStatics.TaskEvent.STATE_CHANGED, _next);
if (_error) this.storage._addListener(this.path, `${this.type}_failure`, _error);
if (_complete) this.storage._addListener(this.path, `${this.type}_success`, _complete);
return () => {
if (_next) this.storage._removeListener(this.path, StorageStatics.TaskEvent.STATE_CHANGED, _next);
if (_error) this.storage._removeListener(this.path, `${this.type}_failure`, _error);
if (_complete) this.storage._removeListener(this.path, `${this.type}_success`, _complete);
};
}
/**
*
* @param event
@ -25,21 +108,24 @@ export default class StorageTask {
* @param complete
* @returns {function()}
*/
on(event = 'state_changed', nextOrObserver, error, complete) {
if (nextOrObserver) this.storage._addListener(this.path, 'state_changed', nextOrObserver);
if (error) this.storage._addListener(this.path, `${this.type}_failure`, error);
if (complete) this.storage._addListener(this.path, `${this.type}_success`, complete);
// off
// todo support add callback syntax as per https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#on
return () => {
if (nextOrObserver) this.storage._removeListener(this.path, 'state_changed', nextOrObserver);
if (error) this.storage._removeListener(this.path, `${this.type}_failure`, error);
if (complete) this.storage._removeListener(this.path, `${this.type}_success`, complete);
};
on(event: string = StorageStatics.TaskEvent.STATE_CHANGED, nextOrObserver: NextOrObserverType, error: FuncErrorType, complete: FuncSnapshotType): Function {
if (!event) {
throw new Error('StorageTask.on listener is missing required string argument \'event\'.');
}
if (event !== StorageStatics.TaskEvent.STATE_CHANGED) {
throw new Error(`StorageTask.on event argument must be a string with a value of '${StorageStatics.TaskEvent.STATE_CHANGED}'`);
}
// if only event provided return the subscriber function
if (!nextOrObserver && !error && !complete) {
return this._subscribe.bind(this);
}
return this._subscribe(nextOrObserver, error, complete);
}
pause() {
// todo
throw new Error('.pause() is not currently supported by react-native-firebase');
}

View File

@ -1,3 +1,6 @@
/**
* @flow
*/
// modeled after base64 web-safe chars, but ordered by ASCII
const PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
const hasOwnProperty = Object.hasOwnProperty;
@ -8,7 +11,8 @@ const _handler = (resolve, reject, errorPrefix, err, resp) => {
// resolve / reject after events etc
setImmediate(() => {
if (err) {
const firebaseError = new Error(err.message);
// $FlowFixMe
const firebaseError: FirebaseError = new Error(err.message);
if (isObject(err)) {
Object.keys(err).forEach(key => Object.defineProperty(firebaseError, key, { value: err[key] }));
@ -23,7 +27,7 @@ const _handler = (resolve, reject, errorPrefix, err, resp) => {
});
};
export function toWebSDKErrorCode(code, prefix) {
export function toWebSDKErrorCode(code: any, prefix: string): string {
if (!code || typeof code !== 'string') return '';
return code.toLowerCase().replace('error_', prefix).replace(/_/g, '-');
}
@ -36,7 +40,11 @@ export function toWebSDKErrorCode(code, prefix) {
* @param joiner
* @returns {*}
*/
export function deepGet(object, path, joiner = '/') {
export function deepGet(
object: Object,
path: string,
joiner?: string = '/'
): any {
const keys = path.split(joiner);
let i = 0;
@ -60,7 +68,11 @@ export function deepGet(object, path, joiner = '/') {
* @param joiner
* @returns {*}
*/
export function deepExists(object, path, joiner = '/') {
export function deepExists(
object: Object,
path: string,
joiner?: string = '/'
): boolean {
const keys = path.split(joiner);
let i = 0;
@ -81,7 +93,7 @@ export function deepExists(object, path, joiner = '/') {
* @param item
* @returns {boolean}
*/
export function isObject(item) {
export function isObject(item: any): boolean {
return (item && typeof item === 'object' && !Array.isArray(item) && item !== null);
}
@ -90,8 +102,8 @@ export function isObject(item) {
* @param item
* @returns {*|boolean}
*/
export function isFunction(item) {
return (item && typeof item === 'function');
export function isFunction(item?: any): boolean {
return Boolean(item && typeof item === 'function');
}
/**
@ -99,9 +111,9 @@ export function isFunction(item) {
* @param string
* @returns {*}
*/
export function tryJSONParse(string) {
export function tryJSONParse(string: string | null): any {
try {
return JSON.parse(string);
return string && JSON.parse(string);
} catch (jsonError) {
return string;
}
@ -112,7 +124,7 @@ export function tryJSONParse(string) {
* @param data
* @returns {*}
*/
export function tryJSONStringify(data) {
export function tryJSONStringify(data: any): string | null {
try {
return JSON.stringify(data);
} catch (jsonError) {
@ -149,7 +161,11 @@ export function noop(): void {
* @param NativeModule
* @param errorPrefix
*/
export function promisify(fn: Function, NativeModule: Object, errorPrefix): Function<Promise> {
export function promisify(
fn: Function | string,
NativeModule: Object,
errorPrefix?: string
): (any) => Promise<> {
return (...args) => {
return new Promise((resolve, reject) => {
const _fn = typeof fn === 'function' ? fn : NativeModule[fn];
@ -168,7 +184,12 @@ export function promisify(fn: Function, NativeModule: Object, errorPrefix): Func
* @param callback
* @private
*/
function _delayChunk(collection, chunkSize, operation, callback): void {
function _delayChunk(
collection: Array<*>,
chunkSize: number,
operation: Function,
callback: Function
): void {
const length = collection.length;
const iterations = Math.ceil(length / chunkSize);
@ -196,21 +217,28 @@ function _delayChunk(collection, chunkSize, operation, callback): void {
* @param iterator
* @param cb
*/
export function each(array: Array, chunkSize?: number, iterator: Function, cb: Function): void {
export function each(
array: Array<*>,
chunkSize: number | Function,
iterator: Function,
cb?: Function
): void {
if (typeof chunkSize === 'function') {
cb = iterator;
iterator = chunkSize;
chunkSize = DEFAULT_CHUNK_SIZE;
}
_delayChunk(array, chunkSize, (slice, start) => {
for (let ii = 0, jj = slice.length; ii < jj; ii += 1) {
iterator(slice[ii], start + ii);
}
}, cb);
if (cb) {
_delayChunk(array, chunkSize, (slice, start) => {
for (let ii = 0, jj = slice.length; ii < jj; ii += 1) {
iterator(slice[ii], start + ii);
}
}, cb);
}
}
export function typeOf(value) {
export function typeOf(value: any): string {
if (value === null) return 'null';
if (Array.isArray(value)) return 'array';
return typeof value;
@ -224,7 +252,12 @@ export function typeOf(value) {
* @param cb
* @returns {*}
*/
export function map(array: Array, chunkSize?: number, iterator: Function, cb: Function): void {
export function map(
array: Array<*>,
chunkSize: number | Function,
iterator: Function,
cb?: Function
): void {
if (typeof chunkSize === 'function') {
cb = iterator;
iterator = chunkSize;
@ -237,7 +270,7 @@ export function map(array: Array, chunkSize?: number, iterator: Function, cb: Fu
result.push(iterator(slice[ii], start + ii, array));
}
return result;
}, () => cb(result));
}, () => cb && cb(result));
}

View File

@ -1,6 +1,6 @@
{
"name": "react-native-firebase",
"version": "1.0.0-alpha10",
"version": "1.0.0-alpha12",
"author": "Invertase <contact@invertase.io> (http://invertase.io)",
"description": "A react native firebase library supporting both android and ios native firebase SDK's",
"main": "index",
@ -9,7 +9,8 @@
"dev": "npm run compile -- --watch",
"lint": "eslint ./src",
"publish_pages": "gh-pages -d public/",
"watchcpx": "echo 'See https://github.com/wix/wml for watching changes. \r\n'"
"watchcpx": "echo 'See https://github.com/wix/wml for watching changes. \r\n'",
"flow": "flow"
},
"repository": {
"type": "git",
@ -68,7 +69,7 @@
"eslint-plugin-import": "^2.0.1",
"eslint-plugin-jsx-a11y": "^2.2.3",
"eslint-plugin-react": "^6.4.1",
"flow-bin": "^0.35.0",
"flow-bin": "^0.40.0",
"react": "^15.3.0",
"react-dom": "^15.3.0",
"react-native": "^0.42.0"

9
tests/.babelrc Normal file
View File

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

6
tests/.buckconfig Normal file
View File

@ -0,0 +1,6 @@
[android]
target = Google Inc.:Google APIs:23
[maven_repositories]
central = https://repo1.maven.org/maven2

10
tests/.editorconfig Normal file
View File

@ -0,0 +1,10 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

40
tests/.eslintrc Normal file
View File

@ -0,0 +1,40 @@
{
"extends": "airbnb",
"parser": "babel-eslint",
"ecmaFeatures": {
"jsx": true
},
"plugins": [
"flowtype"
],
"env": {
"es6": true,
"jasmine": true
},
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
},
"rules": {
"class-methods-use-this": 0,
"no-underscore-dangle": 0,
"no-use-before-define": 0,
"arrow-body-style": 0,
"import/prefer-default-export": 0,
"radix": 0,
"new-cap": 0,
"max-len": 0,
"no-continue": 0,
"no-console": 0,
"global-require": 0,
"import/extensions": 0,
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": 0,
"react/jsx-filename-extension": 0
},
"globals": {
"__DEV__": true,
"window": true
}
}

47
tests/.flowconfig Normal file
View File

@ -0,0 +1,47 @@
[ignore]
; We fork some components by platform
.*/*[.]android.js
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/
; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.*
; Ignore duplicate module providers
; For RN Apps installed via npm, "Libraries" folder is inside
; "node_modules/react-native" but in the source repo it is in the root
.*/Libraries/react-native/React.js
.*/Libraries/react-native/ReactNative.js
[include]
[libs]
node_modules/react-native/Libraries/react-native/react-native-interface.js
node_modules/react-native/flow
flow/
[options]
emoji=true
module.system=haste
experimental.strict_type_args=true
munge_underscores=true
module.name_mapper='^~\/\(.*\)$' -> '<PROJECT_ROOT>/\1'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-8]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-8]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
unsafe.enable_getters_and_setters=true
[version]
^0.38.0

1
tests/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.pbxproj -text

1
tests/.watchmanconfig Normal file
View File

@ -0,0 +1 @@
{}

363
tests/README.md Normal file
View File

@ -0,0 +1,363 @@
# react-native-firebase test suite
An **iOS** and **Android** React Native app built to test [`react-native-firebase`](https://github.com/invertase/react-native-firebase).
## Install
1. Clone the test application
```bash
git clone https://github.com/invertase/react-native-firebase.git
```
2. Install the dependencies listed in `package.json`
```bash
cd react-native-firebase/tests/ && npm install
```
### iOS Installation
3. Install the test application's CocoaPods.
* See [troubleshooting](#installing-podfiles) if this doesn't work for you.
```bash
npm run ios:pod:install
```
4. Start the React Native packager
```bash
npm run start
```
5. In another terminal window, install the app on your emulator:
```bash
npm run ios:dev
```
### Android Installation
6. Start your emulator through Android Studio: Tools > Android > AVD Manager
> You will need a version of the Android emulator that has the Play Store installed (you should be able to find it on the emulator's home screen or on the list of apps).
7. Start the React Native packager if you haven't already in the iOS instructions.
```bash
npm run start
```
8. Run the test app on your Android emulator:
```bash
npm run android:dev
```
## Documentation
`react-native-firebase` aims to match the Firebase Web API wherever possible. As a result, the tests are largely derived from the [Firebase Web API documentation](https://firebase.google.com/docs/reference/js/).
## Tests
Tests are bootstrapped and ran when the app is booted. The status of each test suite and individual test will update as and when a test has completed or errored.
### Running tests
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)
### Adding test
To add tests to an existing test suite, you need to pass a function to `addTests`.
#### Synchronous tests
Synchronous tests are created by passing a function to `it`. The next test is run immediately after the last line is executed.
```javascript
testSuite.addTests(({ describe, it }) => {
describe('synchronous test', () => {
it('does something correctly', () => {
});
});
});
```
#### Asynchronous tests
Tests can be asynchronous if they return a promise. The test suite waits for the promise to resolve before executing the next test.
```javascript
testSuite.addTests(({ describe, it }) => {
describe('async successful test', () => {
it('does something correctly', () => {
return new Promise((resolve, reject) => {
// ...
resolve();
});
});
});
});
```
Asynchronous tests can also be created using the `async` function syntax:
```javascript
testSuite.addTests(({ describe, it }) => {
describe('async successful test', () => {
it('does something correctly', async () => {
// ...
await somethingAsynchronous();
});
});
});
```
> When rejecting, always ensure a valid [JavaScript Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) is provided.
### Creating a new test suite
A test suite groups together test categories under the same Firebase feature. e.g. *Realtime Database* tests.
To add a new test suite:
1. Create a new directory within `src/tests`.
2. Create an `index.js` file.
In this file, you need to create an instance of `TestSuite` - see [TestSuite constructor](#testsuite-constructor).
```javascript
import TestSuite from 'lib/TestSuite';
const MyNewSuite = new TestSuite('Realtime Database Storage', 'Upload/Download storage tests');
export default MyNewSuite;
```
3. `addTests` is then used as many times as is necessary to add tests to the test suite, accepting a function that defines one or more tests.
4. The test suite must then be imported into `src/tests/index.js` and added to `testSuiteInstances` in order for it to be included in the list of test suites available to run in the app.
## TestSuite API
### TestSuite Constructor
The TestSuite constructor accepts 3 arguments:
- **name**: String containing the name of the test suite. e.g. 'Realtime Storage'
- **description**: String containing description of the test suite
- **firebase**: This is the object exported from `src/firebase` and contains both the native and web firebase instances.
```javascript
import firebase from '../firebase';
new TestSuite('Realtime Database Storage', 'firebase.database()', firebase);
```
### Test Definition
#### describe()
The `describe()` function takes 2 - 3 arguments:
- **description**: String describing the context or target of all the tests defined in `testDefinitions`
- **options**: (Optional) object of options:
* **focus**: Boolean marking all the tests defined in `testDefinitions` (and any others marked as focused) as the only one(s) that should run
* **pending**: Boolean marking all the tests defined in `testDefinitions` as excluded from running in the test suite
- **testDefinitions**: Function that defines 1 or more tests by calling `it`, `xit` or `fit`
```javascript
function testCategory({ describe }) {
describe('a feature', () => {
it('does something synchronously', () => {
});
});
}
export default testCategory;
```
`describe()` statements can be arbitrarily nested.
#### context()
`context()` is an alias for `describe()` provided as syntactical sugar. `xcontext()` and `fcontext()` work similar to `xdescribe()` and `fdescribe()`, respectively.
#### it()
The `it()` function takes 2 - 3 arguments:
- **description**: String describing the test defined in `testDefinition`
- **options**: (Optional) object of options:
* **focus**: Boolean marking the test defined in `testDefinition` (and any others marked as focused) as the only one(s) that should run
* **pending**: Boolean marking the test defined in `testDefinition` as excluded from running in the test suite
* **timeout**: Time in milliseconds a test is allowed to execute before it's considered to have timed out. Default is 5000ms (5 seconds).
- **testDefinition**: Function that defines a test with one or more assertions. Can be a synchronous or asynchronous function. Functions that return a promise cause the test environment to wait for the promise to be resolved before proceding to the next test.
```javascript
it('does something synchronously', () => {
});
it('does something asynchronously', async () => {
});
it('does something else asynchronously', () => {
return new Promise(/* ... */);
});
```
`it()` statements can *not* be nested.
#### xdescribe() & xit()
##### Pending Tests
You can mark all tests within a `describe` statement as pending by using the `xdescribe` function instead. The test will appear greyed out and will not be run as part of the test suite.
You can mark a single test as pending by using `xit` as you would `it`.
Tests should only be marked as pending temporarily, and should not normally be committed to the test suite unless they are fully implemented.
#### fdescribe() & fit()
##### Focused Tests
You can mark all tests within a `describe` statement as focused by using the `fdescribe` function instead. Tests that are focused will be the only ones that appear and run in the test suite until all tests are removed from being focused. This is useful for running and working on a few tests at a time.
You can mark a single test as focused by using `fit` as you would `it`.
#### Test Assertions
The assertion library Should.js is used in the tests. The complete list of available assertions is available in the [Should.js documentation](https://shouldjs.github.io).
#### Lifecycle methods
Four lifecycle methods are provided for each test context:
- **before** - Run once, before the current test context executes
- **beforeEach** - Run before every test in the current test context
- **after** - Run once, after the current test context has finished executing
- **afterEach** - Run after every test in the current test context
A new test context is created when the test suite encounters any of `describe`, `xdescribe`, `fdescribe`, `context`, `xcontext` or `fcontext`, and close again when it reaches the end of the block. Test contexts can be nested and lifecycle hooks set for parent contexts apply for all descendents.
Each lifecycle hook accepts either a synchronous function, a function that returns a promise or an `async` function.
```javascript
function testCategory({ before, beforeEach, afterEach, after }) {
before(() => console.log('Before all tests start.'));
beforeEach(() => console.log('Before every test starts.'));
describe('sync successful test', function() {
// ...
});
afterEach(() => console.log('After each test starts.'));
after(() => console.log('After all tests are complete, with success or error.'));
}
```
An optional hash of options can also be passed as the first argument, defining one or more of the following values:
* **timeout**: Time in milliseconds a hook is allowed to execute before it's considered to have timed out. Default is 5000ms (5 seconds).
#### Accessing Firebase
`react-native-firebase` is available `firebase.native`:
```javascript
function testCategory({ describe, firebase }) {
describe('sync successful test', 'category', function() {
firebase.native.database();
});
}
```
If you need to access the web API for Firebase to compare with the functionality of `react-native-firebase`, you can access it on `firebase.web`.
> All tests should be written in terms of `react-native-firebase`'s behaviour and should **not** include direct comparisons with the web API. It's available for reference, only.
## Development
### Running test suite against latest version of react-native-firebase
You can use the node module `wml` to automatically copy changes you make to `react-native-firebase` over to the test application so you can run the test suite against them.
1. Install `wml` globally:
```bash
npm install wml -g
```
2. Configure `wml` to copy changes from `react-native-firebase` to `react-native-firebase/tests/node_modules/react-native-firebase` is:
```bash
wml add /full/path/to/react-native-firebase /full/path/to/react-native-firebase/tests/node_modules/react-native-firebase
```
3. Start `wml`:
```bash
wml start
```
> JavaScript changes require restarting the React Native packager to take effect
> Java changes will need to be rebuilt in Android Studio
> Objective-C changes need to be rebuilt in Xcode
4. Stop `wml` when you are finished:
```bash
wml stop
```
### Debugging or viewing internals of the test suite
`react-native-firebase/tests` is compatible with [react-native-debugger](https://github.com/jhen0409/react-native-debugger) and is the recommended way to view the internal state of the test suite for development or troubleshooting.
It allows you to view state and prop values of the React component tree, view the actions and contents of the Redux store and view and interact with the debugging console.
Make sure **Remote JS Debugging** when running the application and close any chrome debugging windows that appear and start React Native Debugger.
### Running the internal tests
`react-native-firebase-tests` has its own tests to verify the testing framework is working as expected. These are run from the command line:
```bash
npm run internal-tests
```
## Troubleshooting
### Invalid React.podspec file: no implicit conversion of nil into String
This error occurs if you are using ruby version 2.1.2. Upgrade your version of ruby and try again.
### Unable to resolve module ../../../node_modules/react-native/packager/...
Run the packager separately, clearing the cache:
```bash
npm start -- --reset-cache
```

View File

@ -0,0 +1,287 @@
import 'should-sinon';
import TestSuite from '../lib/TestSuite';
function asynchronousHooksTests({ it: _it, describe: _describe }) {
_describe('before hooks:', () => {
_it('can return a promise that is resolved before executing other hooks and tests', async () => {
let valueBySecondHook = null;
let valueByTest = null;
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, before }) => {
let resolved = false;
before(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolved = true;
resolve();
}, 500);
});
});
before(() => {
valueBySecondHook = resolved;
});
it('', () => {
valueByTest = resolved;
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
valueBySecondHook.should.equal(true);
valueByTest.should.equal(true);
});
_it('can be an asynchronous function that is awaited before executing other hooks and tests', async () => {
let valueBySecondHook = null;
let valueByTest = null;
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, before }) => {
let resolved = false;
before(async () => {
await new Promise((resolve) => {
setTimeout(() => {
resolved = true;
resolve();
}, 500);
});
});
before(() => {
valueBySecondHook = resolved;
});
it('', () => {
valueByTest = resolved;
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
valueBySecondHook.should.equal(true);
valueByTest.should.equal(true);
});
});
_describe('beforeEach hooks:', () => {
_it('can return a promise that is resolved before executing other hooks and tests', async () => {
let valueBySecondHook = null;
let valueByTest = null;
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, beforeEach }) => {
let resolved = false;
beforeEach(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolved = true;
resolve();
}, 500);
});
});
beforeEach(() => {
valueBySecondHook = resolved;
});
it('', () => {
valueByTest = resolved;
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
valueBySecondHook.should.equal(true);
valueByTest.should.equal(true);
});
_it('can be an asynchronous function that is awaited before executing other hooks and tests', async () => {
let valueBySecondHook = null;
let valueByTest = null;
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, beforeEach }) => {
let resolved = false;
beforeEach(async () => {
await new Promise((resolve) => {
setTimeout(() => {
resolved = true;
resolve();
}, 500);
});
});
beforeEach(() => {
valueBySecondHook = resolved;
});
it('', () => {
valueByTest = resolved;
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
valueBySecondHook.should.equal(true);
valueByTest.should.equal(true);
});
});
_describe('afterEach hooks:', () => {
_it('can return a promise that is resolved before executing other hooks and tests', async () => {
let valueBySecondHook = null;
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, afterEach }) => {
let resolved = false;
afterEach(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolved = true;
resolve();
}, 500);
});
});
afterEach(() => {
valueBySecondHook = resolved;
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
valueBySecondHook.should.equal(true);
});
_it('can be an asynchronous function that is awaited before executing other hooks and tests', async () => {
let valueBySecondHook = null;
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, afterEach }) => {
let resolved = false;
afterEach(async () => {
await new Promise((resolve) => {
setTimeout(() => {
resolved = true;
resolve();
}, 500);
});
});
afterEach(() => {
valueBySecondHook = resolved;
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
valueBySecondHook.should.equal(true);
});
});
_describe('after hooks:', () => {
_it('can return a promise that is resolved before executing other hooks and tests', async () => {
let valueBySecondHook = null;
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, after }) => {
let resolved = false;
after(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolved = true;
resolve();
}, 500);
});
});
after(() => {
valueBySecondHook = resolved;
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
valueBySecondHook.should.equal(true);
});
_it('can be an asynchronous function that is awaited before executing other hooks and tests', async () => {
let valueBySecondHook = null;
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, after }) => {
let resolved = false;
after(async () => {
await new Promise((resolve) => {
setTimeout(() => {
resolved = true;
resolve();
}, 500);
});
});
after(() => {
valueBySecondHook = resolved;
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
valueBySecondHook.should.equal(true);
});
});
}
export default asynchronousHooksTests;

View File

@ -0,0 +1,661 @@
import 'should-sinon';
import TestSuite from '../lib/TestSuite';
function failingHookTests({ it: _it, describe: _describe }) {
_describe('before hooks:', () => {
_it('capture promise rejections and marks all tests as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, before }) => {
before(() => {
return new Promise((resolve, reject) => {
reject('failure');
});
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('2 tests has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" before Hook: failure');
});
_it('capture errors thrown in promises and marks all tests as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, before }) => {
before(() => {
return new Promise(() => {
true.should.equal(false);
});
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('2 tests has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" before Hook: AssertionError: expected true to be false');
});
_it('captures errors thrown in asynchronous functions and marks all tests as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, before }) => {
before(async () => {
await new Promise(() => {
true.should.equal(false);
});
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('2 tests has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" before Hook: AssertionError: expected true to be false');
});
_it('captures errors thrown in synchronous functions and marks all tests as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, before }) => {
before(() => {
true.should.equal(false);
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('2 tests has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" before Hook: AssertionError: expected true to be false');
});
});
_describe('beforeEach hooks:', () => {
_it('capture promise rejections and marks test that follows as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
let testRuns = 0;
testSuite.addTests(({ it, beforeEach }) => {
beforeEach(() => {
return new Promise((resolve, reject) => {
if (testRuns > 0) {
reject('failure');
} else {
testRuns += 1;
resolve();
}
});
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" beforeEach Hook: failure');
});
_it('capture errors thrown in promises and marks test that follows as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
let testRuns = 0;
testSuite.addTests(({ it, beforeEach }) => {
beforeEach(() => {
return new Promise((resolve) => {
if (testRuns > 0) {
true.should.equal(false);
} else {
testRuns += 1;
resolve();
}
});
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" beforeEach Hook: AssertionError: expected true to be false');
});
_it('captures errors thrown in asynchronous functions and marks test that follows as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
let testRuns = 0;
testSuite.addTests(({ it, beforeEach }) => {
beforeEach(async () => {
await new Promise((resolve) => {
if (testRuns > 0) {
true.should.equal(false);
} else {
testRuns += 1;
resolve();
}
});
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" beforeEach Hook: AssertionError: expected true to be false');
});
_it('captures errors thrown in synchronous functions and marks test that follows as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
let testRuns = 0;
testSuite.addTests(({ it, beforeEach }) => {
beforeEach(() => {
if (testRuns > 0) {
true.should.equal(false);
} else {
testRuns += 1;
}
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" beforeEach Hook: AssertionError: expected true to be false');
});
});
_describe('afterEach hooks:', () => {
_it('capture promise rejections and marks test that proceeded as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
let testRuns = 0;
testSuite.addTests(({ it, afterEach }) => {
afterEach(() => {
return new Promise((resolve, reject) => {
if (testRuns > 0) {
reject('failure');
} else {
testRuns += 1;
resolve();
}
});
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" afterEach Hook: failure');
});
_it('capture errors thrown in promises and marks test that proceeded as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
let testRuns = 0;
testSuite.addTests(({ it, afterEach }) => {
afterEach(() => {
return new Promise((resolve) => {
if (testRuns > 0) {
true.should.equal(false);
} else {
testRuns += 1;
resolve();
}
});
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" afterEach Hook: AssertionError: expected true to be false');
});
_it('captures errors thrown in asynchronous functions and marks test that proceeded as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
let testRuns = 0;
testSuite.addTests(({ it, afterEach }) => {
afterEach(async () => {
await new Promise((resolve) => {
if (testRuns > 0) {
true.should.equal(false);
} else {
testRuns += 1;
resolve();
}
});
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" afterEach Hook: AssertionError: expected true to be false');
});
_it('captures errors thrown in synchronous functions and marks test that proceeded as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
let testRuns = 0;
testSuite.addTests(({ it, afterEach }) => {
afterEach(() => {
if (testRuns > 0) {
true.should.equal(false);
} else {
testRuns += 1;
}
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" afterEach Hook: AssertionError: expected true to be false');
});
});
_describe('after hooks:', () => {
_it('capture promise rejections and marks all tests as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, after }) => {
after(() => {
return new Promise((resolve, reject) => {
reject('failure');
});
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('2 tests has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" after Hook: failure');
});
_it('capture errors thrown in promises and marks all tests as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, after }) => {
after(() => {
return new Promise(() => {
true.should.equal(false);
});
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('2 tests has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" after Hook: AssertionError: expected true to be false');
});
_it('captures errors thrown in asynchronous functions and marks all tests as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, after }) => {
after(async () => {
await new Promise(() => {
true.should.equal(false);
});
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('2 tests has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" after Hook: AssertionError: expected true to be false');
});
_it('captures errors thrown in synchronous functions and marks all tests as failed', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, after }) => {
after(() => {
true.should.equal(false);
});
it('', () => { });
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('2 tests has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" after Hook: AssertionError: expected true to be false');
});
});
}
export default failingHookTests;

View File

@ -0,0 +1,278 @@
import sinon from 'sinon';
import 'should-sinon';
import TestSuite from '../lib/TestSuite';
function hookScopeTests({ it: _it, describe: _describe }) {
_describe('before hooks:', () => {
_it('apply only to the scope they are defined in and any child scopes', async () => {
const testSuite = new TestSuite('', '', {});
let value = 0;
let valueWhenOtherTestRuns = null;
let valueWhenSiblingTestRuns = null;
let valueWhenChildTestRuns = null;
testSuite.addTests(({ it, before, context }) => {
context('', () => {
before(() => {
value = 1;
});
it('', () => {
valueWhenSiblingTestRuns = value;
});
context('', () => {
it('', () => {
valueWhenChildTestRuns = value;
});
});
});
it('', () => {
valueWhenOtherTestRuns = value;
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
valueWhenOtherTestRuns.should.equal(0);
valueWhenSiblingTestRuns.should.equal(1);
valueWhenChildTestRuns.should.equal(1);
});
_it('only run once for the scope they apply', async () => {
const testSuite = new TestSuite('', '', {});
const beforeHook = sinon.spy();
testSuite.addTests(({ it, before, context }) => {
context('', () => {
before(beforeHook);
it('', () => { });
context('', () => {
it('', () => { });
});
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
beforeHook.should.be.calledOnce();
});
});
_describe('beforeEach hooks:', () => {
_it('apply only to the scope they are defined in and any child scopes', async () => {
const testSuite = new TestSuite('', '', {});
let value = 0;
let valueWhenOtherTestRuns = null;
let valueWhenSiblingTestRuns = null;
let valueWhenChildTestRuns = null;
testSuite.addTests(({ it, beforeEach, context }) => {
context('', () => {
beforeEach(() => {
value = 1;
});
it('', () => {
valueWhenSiblingTestRuns = value;
});
context('', () => {
it('', () => {
valueWhenChildTestRuns = value;
});
});
});
it('', () => {
valueWhenOtherTestRuns = value;
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
valueWhenOtherTestRuns.should.equal(0);
valueWhenSiblingTestRuns.should.equal(1);
valueWhenChildTestRuns.should.equal(1);
});
_it('are called once for every test in its scope', async () => {
const testSuite = new TestSuite('', '', {});
const beforeEachHook = sinon.spy();
testSuite.addTests(({ it, beforeEach, context }) => {
context('', () => {
beforeEach(beforeEachHook);
it('', () => { });
context('', () => {
it('', () => { });
});
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
beforeEachHook.should.be.calledTwice();
});
});
_describe('afterEach hooks:', () => {
_it('apply only to the scope they are defined in and any child scopes', async () => {
const testSuite = new TestSuite('', '', {});
let value = 0;
testSuite.addTests(({ it, afterEach, context }) => {
context('', () => {
it('', () => {
value += 1;
});
context('', () => {
it('', () => {
value += 1;
});
});
afterEach(() => {
value -= 1;
});
});
it('', () => {
value += 1;
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
value.should.equal(1);
});
_it('are called once for every test in its scope', async () => {
const testSuite = new TestSuite('', '', {});
const afterEachHook = sinon.spy();
testSuite.addTests(({ it, afterEach, context }) => {
context('', () => {
afterEach(afterEachHook);
it('', () => { });
context('', () => {
it('', () => { });
});
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
afterEachHook.should.be.calledTwice();
});
});
_describe('after hooks:', () => {
_it('apply only to the scope they are defined in and any child scopes', async () => {
const testSuite = new TestSuite('', '', {});
let value = 0;
testSuite.addTests(({ it, after, context }) => {
context('', () => {
it('', () => {
value += 1;
});
context('', () => {
it('', () => {
value += 1;
});
});
after(() => {
value -= 1;
});
});
it('', () => {
value += 1;
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
value.should.equal(2);
});
_it('are called once for every test in its scope', async () => {
const testSuite = new TestSuite('', '', {});
const afterHook = sinon.spy();
testSuite.addTests(({ it, after, context }) => {
context('', () => {
it('', () => { });
context('', () => {
it('', () => { });
});
after(afterHook);
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
afterHook.should.be.calledOnce();
});
});
}
export default hookScopeTests;

View File

@ -0,0 +1,310 @@
import sinon from 'sinon';
import 'should-sinon';
import assert from 'assert';
import TestSuite from '../lib/TestSuite';
function hooksCallOrderTests({ it: _it, describe: _describe }) {
_describe('before hooks:', () => {
_it('calls before hooks defined in the same context in the order they are defined', async () => {
const testSuite = new TestSuite('', '', {});
const beforeCallbackA = sinon.spy();
const beforeCallbackB = sinon.spy();
testSuite.addTests(({ it, before }) => {
before(beforeCallbackA);
before(beforeCallbackB);
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
beforeCallbackA.should.have.been.called();
beforeCallbackB.should.have.been.called();
assert(beforeCallbackB.calledAfter(beforeCallbackA));
});
_it('calls before hooks defined in child contexts after those in parent contexts', async () => {
const testSuite = new TestSuite('', '', {});
const beforeCallbackA = sinon.spy();
const beforeCallbackB = sinon.spy();
testSuite.addTests(({ it, before, context }) => {
before(beforeCallbackA);
context('', () => {
before(beforeCallbackB);
it('', () => { });
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
beforeCallbackA.should.have.been.called();
beforeCallbackB.should.have.been.called();
assert(beforeCallbackB.calledAfter(beforeCallbackA));
});
});
_describe('beforeEach hooks:', () => {
_it('calls beforeEach hooks defined in the same context in the order they are defined', async () => {
const testSuite = new TestSuite('', '', {});
const beforeEachCallbackA = sinon.spy();
const beforeEachCallbackB = sinon.spy();
testSuite.addTests(({ it, beforeEach }) => {
beforeEach(beforeEachCallbackA);
beforeEach(beforeEachCallbackB);
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
beforeEachCallbackA.should.have.been.called();
beforeEachCallbackB.should.have.been.called();
assert(beforeEachCallbackB.calledAfter(beforeEachCallbackA));
});
_it('calls beforeEach hooks defined in child contexts after those in parent contexts', async () => {
const testSuite = new TestSuite('', '', {});
const beforeEachCallbackA = sinon.spy();
const beforeEachCallbackB = sinon.spy();
testSuite.addTests(({ it, beforeEach, context }) => {
beforeEach(beforeEachCallbackA);
context('', () => {
beforeEach(beforeEachCallbackB);
it('', () => { });
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
beforeEachCallbackA.should.have.been.called();
beforeEachCallbackB.should.have.been.called();
assert(beforeEachCallbackB.calledAfter(beforeEachCallbackA));
});
_it('calls beforeEach hooks after before hooks', async () => {
const testSuite = new TestSuite('', '', {});
const beforeCallbackA = sinon.spy();
const beforeEachCallbackB = sinon.spy();
testSuite.addTests(({ it, before, beforeEach }) => {
before(beforeCallbackA);
beforeEach(beforeEachCallbackB);
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
beforeCallbackA.should.have.been.called();
beforeEachCallbackB.should.have.been.called();
assert(beforeEachCallbackB.calledAfter(beforeCallbackA));
});
});
_describe('after hooks:', () => {
_it('calls after hooks defined in the same context in the order they are defined', async () => {
const testSuite = new TestSuite('', '', {});
const afterCallbackA = sinon.spy();
const afterCallbackB = sinon.spy();
testSuite.addTests(({ it, after }) => {
after(afterCallbackA);
after(afterCallbackB);
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
afterCallbackA.should.have.been.called();
afterCallbackB.should.have.been.called();
assert(afterCallbackB.calledAfter(afterCallbackA));
});
_it('calls after hooks defined in child contexts before those in parent contexts', async () => {
const testSuite = new TestSuite('', '', {});
const afterCallbackA = sinon.spy();
const afterCallbackB = sinon.spy();
testSuite.addTests(({ it, after, context }) => {
after(afterCallbackA);
context('', () => {
after(afterCallbackB);
it('', () => { });
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
afterCallbackA.should.have.been.called();
afterCallbackB.should.have.been.called();
assert(afterCallbackA.calledAfter(afterCallbackB));
});
});
_describe('afterEach hooks:', () => {
_it('calls afterEach hooks defined in the same context in the order they are defined', async () => {
const testSuite = new TestSuite('', '', {});
const afterEachCallbackA = sinon.spy();
const afterEachCallbackB = sinon.spy();
testSuite.addTests(({ it, afterEach }) => {
afterEach(afterEachCallbackA);
afterEach(afterEachCallbackB);
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
afterEachCallbackA.should.have.been.called();
afterEachCallbackB.should.have.been.called();
assert(afterEachCallbackB.calledAfter(afterEachCallbackA));
});
_it('calls afterEach hooks defined in child contexts before those in parent contexts', async () => {
const testSuite = new TestSuite('', '', {});
const afterEachCallbackA = sinon.spy();
const afterEachCallbackB = sinon.spy();
testSuite.addTests(({ it, afterEach, context }) => {
afterEach(afterEachCallbackA);
context('', () => {
afterEach(afterEachCallbackB);
it('', () => { });
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
afterEachCallbackA.should.have.been.called();
afterEachCallbackB.should.have.been.called();
assert(afterEachCallbackA.calledAfter(afterEachCallbackB));
});
_it('calls afterEach hooks before after hooks', async () => {
const testSuite = new TestSuite('', '', {});
const afterCallbackA = sinon.spy();
const afterEachCallbackB = sinon.spy();
testSuite.addTests(({ it, after, afterEach }) => {
after(afterCallbackA);
afterEach(afterEachCallbackB);
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
afterCallbackA.should.have.been.called();
afterEachCallbackB.should.have.been.called();
assert(afterCallbackA.calledAfter(afterEachCallbackB));
});
});
_describe('when there are no tests in a context or any of its children', () => {
_it('then doesn\'t call any hooks', async () => {
const testSuite = new TestSuite('', '', {});
const beforeCallback = sinon.spy();
const beforeEachCallback = sinon.spy();
const afterCallback = sinon.spy();
const afterEachCallback = sinon.spy();
testSuite.addTests(({ before, beforeEach, after, afterEach, context }) => {
context('', () => {
before(beforeCallback);
beforeEach(beforeEachCallback);
afterEach(afterEachCallback);
after(afterCallback);
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
beforeCallback.should.not.have.been.called();
beforeEachCallback.should.not.have.been.called();
afterEachCallback.should.not.have.been.called();
afterCallback.should.not.have.been.called();
});
});
}
export default hooksCallOrderTests;

View File

@ -0,0 +1,17 @@
import TestSuite from '../lib/TestSuite';
import hooksCallOrderTest from './hooksCallOrderTests';
import asynchronousHooksTests from './asynchronousHooksTests';
import hookScopeTests from './hookScopeTests';
import failingHookTests from './failingHookTests';
import timingOutHookTests from './timingOutHookTests';
const suite = new TestSuite('Internal', 'Lifecycle methods', {});
suite.addTests(hooksCallOrderTest);
suite.addTests(asynchronousHooksTests);
suite.addTests(hookScopeTests);
suite.addTests(failingHookTests);
suite.addTests(timingOutHookTests);
export default suite;

View File

@ -0,0 +1,327 @@
import 'should-sinon';
import TestSuite from '../lib/TestSuite';
function failingHookTests({ it: _it, describe: _describe }) {
_describe('before hooks:', () => {
_it('timeout after 5 seconds by default', { timeout: 7000 }, async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, before }) => {
before(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 6000);
});
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" before Hook: TimeoutError: before hook took longer than 5000ms. This can be extended with the timeout option.');
});
_it('allows manually setting timeout', { timeout: 7000 }, async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, before }) => {
before({ timeout: 500 }, () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 600);
});
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" before Hook: TimeoutError: before hook took longer than 500ms. This can be extended with the timeout option.');
});
});
_describe('beforeEach hooks:', () => {
_it('timeout after 5 seconds by default', { timeout: 7000 }, async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, beforeEach }) => {
beforeEach(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 6000);
});
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" beforeEach Hook: TimeoutError: beforeEach hook took longer than 5000ms. This can be extended with the timeout option.');
});
_it('allows manually setting timeout', { timeout: 7000 }, async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, beforeEach }) => {
beforeEach({ timeout: 500 }, () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 600);
});
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" beforeEach Hook: TimeoutError: beforeEach hook took longer than 500ms. This can be extended with the timeout option.');
});
});
_describe('afterEach hooks:', () => {
_it('timeout after 5 seconds by default', { timeout: 7000 }, async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, afterEach }) => {
afterEach(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 6000);
});
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" afterEach Hook: TimeoutError: afterEach hook took longer than 5000ms. This can be extended with the timeout option.');
});
_it('allows manually setting timeout', { timeout: 7000 }, async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, afterEach }) => {
afterEach({ timeout: 500 }, () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 600);
});
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" afterEach Hook: TimeoutError: afterEach hook took longer than 500ms. This can be extended with the timeout option.');
});
});
_describe('after hooks:', () => {
_it('timeout after 5 seconds by default', { timeout: 7000 }, async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, after }) => {
after(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 6000);
});
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" after Hook: TimeoutError: after hook took longer than 5000ms. This can be extended with the timeout option.');
});
_it('allows manually setting timeout', { timeout: 7000 }, async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it, after }) => {
after({ timeout: 500 }, () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 600);
});
});
it('', () => { });
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('Error occurred in "" after Hook: TimeoutError: after hook took longer than 500ms. This can be extended with the timeout option.');
});
});
}
export default failingHookTests;

View File

@ -0,0 +1,75 @@
import 'babel-core/register';
import 'babel-polyfill';
import Promise from 'bluebird';
import 'colors';
import RunStatus from './lib/RunStatus';
import LifeCycleTestSuite from './hooks/index';
import TestsTestSuite from './tests/index';
let successfulTests = 0;
let failingTests = 0;
const testErrors = {};
const suites = [
LifeCycleTestSuite,
TestsTestSuite,
];
suites.forEach((suite) => {
suite.setStore({
getState: () => { return {}; },
}, (testSuiteAction) => {
if (testSuiteAction.message) {
console.error(testSuiteAction.message.red);
testErrors[suite.description] = {
message: testSuiteAction.message,
stackTrace: testSuiteAction.stackTrace,
};
}
},
(testAction) => {
const test = suite.testDefinitions.tests[testAction.testId];
const testContext = suite.testDefinitions.testContexts[test.testContextId];
const description = (() => {
if (testContext.name && !test.description.startsWith(testContext.name)) {
return `${testContext.name} ${test.description}`;
}
return test.description;
})();
if (testAction.status === RunStatus.OK) {
console.log(`${description}`.green);
successfulTests += 1;
} else if (testAction.status === RunStatus.ERR) {
console.log(`${description}`.red);
testErrors[test.description] = {
message: testAction.message,
stackTrace: testAction.stackTrace,
};
failingTests += 1;
}
});
});
Promise.each(suites, (suite) => {
console.log(`\n\n${suite.name} ${suite.description}:\n\r`);
return suite.run();
}).then(() => {
console.log(`\n${successfulTests} tests passed.`);
if (failingTests) {
console.log(`${failingTests} tests failed.`);
}
if (Object.keys(testErrors).length > 0) {
console.log('\nErrors:'.red);
Object.keys(testErrors).forEach((failingTestDescription) => {
const error = testErrors[failingTestDescription];
console.error(`\n${failingTestDescription}: ${error.message} \n${error.stackTrace}`.red);
});
}
});

View File

@ -0,0 +1,82 @@
import 'should-sinon';
import TestSuite from '../lib/TestSuite';
function asynchronousTestTests({ it: _it, describe: _describe }) {
_describe('tests', () => {
_it('can return a promise that is resolved before executing hooks and other tests', async () => {
let valueBySecondTest = null;
let valueByHook = null;
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, after }) => {
let resolved = false;
it('', () => {
return new Promise((resolve) => {
setTimeout(() => {
resolved = true;
resolve();
}, 500);
});
});
it('', () => {
valueBySecondTest = resolved;
});
after(() => {
valueByHook = resolved;
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
valueBySecondTest.should.equal(true);
valueByHook.should.equal(true);
});
_it('can be an asynchronous function that is awaited before executing hooks and other tests', async () => {
let valueBySecondTest = null;
let valueByHook = null;
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, after }) => {
let resolved = false;
it('', async () => {
await new Promise((resolve) => {
setTimeout(() => {
resolved = true;
resolve();
}, 500);
});
});
it('', () => {
valueBySecondTest = resolved;
});
after(() => {
valueByHook = resolved;
});
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run();
valueBySecondTest.should.equal(true);
valueByHook.should.equal(true);
});
});
}
export default asynchronousTestTests;

View File

@ -0,0 +1,146 @@
import 'should-sinon';
import TestSuite from '../lib/TestSuite';
function failingTestTests({ it: _it, describe: _describe }) {
_describe('running a test that is a function that returns a promise', () => {
_it('correctly reports a failure when the promise is rejected', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it }) => {
it('', () => {
return new Promise((resolve, reject) => {
reject('failure');
});
});
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('failure');
});
_it('correctly reports a failure when an error is thrown', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it }) => {
it('', () => {
return new Promise(() => {
false.should.equal(true);
});
});
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('AssertionError: expected false to be true');
});
});
_describe('running an async function test', () => {
_it('correctly reports a failure when an error is thrown', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it }) => {
it('', async () => {
false.should.equal(true);
});
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('AssertionError: expected false to be true');
});
});
_describe('running an synchronous function test', () => {
_it('correctly reports a failure when an error is thrown', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it }) => {
it('', () => {
false.should.equal(true);
});
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('AssertionError: expected false to be true');
});
});
}
export default failingTestTests;

View File

@ -0,0 +1,84 @@
import sinon from 'sinon';
import 'should-sinon';
import TestSuite from '../lib/TestSuite';
function focusedTestTests({ it: _it, describe: _describe }) {
_describe('when fit is used instead of it', () => {
_it('a test is marked as focused', async () => {
const focusedTest = sinon.spy();
const otherTest = sinon.spy();
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, fit }) => {
fit('', focusedTest);
it('', otherTest);
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run(Object.keys(testSuite.testDefinitions.focusedTestIds));
focusedTest.should.be.called();
otherTest.should.not.be.called();
});
});
_describe('when fdescribe is used instead of describe', () => {
_it('child tests are marked as focused', async () => {
const focusedTest = sinon.spy();
const otherTest = sinon.spy();
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, fdescribe }) => {
fdescribe('', () => {
it('', focusedTest);
});
it('', otherTest);
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run(Object.keys(testSuite.testDefinitions.focusedTestIds));
focusedTest.should.be.called();
otherTest.should.not.be.called();
});
});
_describe('when fcontext is used instead of context', () => {
_it('child tests are marked as focused', async () => {
const focusedTest = sinon.spy();
const otherTest = sinon.spy();
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, fcontext }) => {
fcontext('', () => {
it('', focusedTest);
});
it('', otherTest);
});
testSuite.setStore({
getState: () => { return {}; },
});
await testSuite.run(Object.keys(testSuite.testDefinitions.focusedTestIds));
focusedTest.should.be.called();
otherTest.should.not.be.called();
});
});
}
export default focusedTestTests;

View File

@ -0,0 +1,17 @@
import TestSuite from '../lib/TestSuite';
import asynchronousTestTests from './asynchronousTestTests';
import focusedTestTests from './focusedTestTests';
import pendingTestTests from './pendingTestTests';
import failingTestTests from './failingTestTests';
import timingOutTests from './timingOutTests';
const suite = new TestSuite('Internal', 'Test Definitions', {});
suite.addTests(asynchronousTestTests);
suite.addTests(focusedTestTests);
suite.addTests(pendingTestTests);
suite.addTests(failingTestTests);
suite.addTests(timingOutTests);
export default suite;

View File

@ -0,0 +1,108 @@
import sinon from 'sinon';
import 'should-sinon';
import TestSuite from '../lib/TestSuite';
function pendingTestTests({ it: _it, describe: _describe }) {
_describe('when xit is used instead of it', () => {
_it('a test is marked as pending', async () => {
const pendingTest = sinon.spy();
const otherTest = sinon.spy();
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, xit }) => {
xit('', pendingTest);
it('', otherTest);
});
testSuite.setStore({
getState: () => { return {}; },
});
const testIdsToRun = Object.keys(testSuite.testDefinitions.tests).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();
});
});
_describe('when xdescribe is used instead of describe', () => {
_it('child tests are marked as pending', async () => {
const pendingTest = sinon.spy();
const otherTest = sinon.spy();
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, xdescribe }) => {
xdescribe('', () => {
it('', pendingTest);
});
it('', otherTest);
});
testSuite.setStore({
getState: () => { return {}; },
});
const testIdsToRun = Object.keys(testSuite.testDefinitions.tests).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();
});
});
_describe('when xcontext is used instead of context', () => {
_it('child tests are marked as pending', async () => {
const pendingTest = sinon.spy();
const otherTest = sinon.spy();
const testSuite = new TestSuite('', '', {});
testSuite.addTests(({ it, xcontext }) => {
xcontext('', () => {
it('', pendingTest);
});
it('', otherTest);
});
testSuite.setStore({
getState: () => { return {}; },
});
const testIdsToRun = Object.keys(testSuite.testDefinitions.tests).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();
});
});
}
export default pendingTestTests;

View File

@ -0,0 +1,84 @@
import 'should-sinon';
import TestSuite from '../lib/TestSuite';
function timingOutTests({ it: _it, describe: _describe }) {
_describe('tests', () => {
_it('time out after 5 seconds by default', { timeout: 7000 }, async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it }) => {
it('', () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 6000);
});
});
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('TimeoutError: Test took longer than 5000ms. This can be extended with the timeout option.');
});
_it('can set custom timeout', async () => {
const testSuite = new TestSuite('', '', {});
const testSuiteStatuses = [];
const testStatuses = [];
testSuite.addTests(({ it }) => {
it('', { timeout: 500 }, () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
});
});
});
testSuite.setStore({
getState: () => { return {}; },
}, (value) => {
testSuiteStatuses.push(value);
}, (value) => {
testStatuses.push(value);
});
await testSuite.run();
const lastTestSuiteStatus = testSuiteStatuses[testSuiteStatuses.length - 1];
lastTestSuiteStatus.progress.should.equal(100);
lastTestSuiteStatus.status.should.equal('error');
lastTestSuiteStatus.message.should.equal('1 test has error(s).');
const lastTestStatus = testStatuses[testStatuses.length - 1];
lastTestStatus.status.should.equal('error');
lastTestStatus.message.should.equal('TimeoutError: Test took longer than 500ms. This can be extended with the timeout option.');
});
});
}
export default timingOutTests;

66
tests/android/app/BUCK Normal file
View File

@ -0,0 +1,66 @@
import re
# To learn about Buck see [Docs](https://buckbuild.com/).
# To run your application with Buck:
# - install Buck
# - `npm start` - to start the packager
# - `cd android`
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
# - `buck install -r android/app` - compile, install and run application
#
lib_deps = []
for jarfile in glob(['libs/*.jar']):
name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile)
lib_deps.append(':' + name)
prebuilt_jar(
name = name,
binary_jar = jarfile,
)
for aarfile in glob(['libs/*.aar']):
name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile)
lib_deps.append(':' + name)
android_prebuilt_aar(
name = name,
aar = aarfile,
)
android_library(
name = 'all-libs',
exported_deps = lib_deps
)
android_library(
name = 'app-code',
srcs = glob([
'src/main/java/**/*.java',
]),
deps = [
':all-libs',
':build_config',
':res',
],
)
android_build_config(
name = 'build_config',
package = 'com.reactnativefirebasedemo',
)
android_resource(
name = 'res',
res = 'src/main/res',
package = 'com.reactnativefirebasedemo',
)
android_binary(
name = 'app',
package_type = 'debug',
manifest = 'src/main/AndroidManifest.xml',
keystore = '//android/keystores:debug',
deps = [
':app-code',
],
)

View File

@ -0,0 +1,144 @@
apply plugin: "com.android.application"
import com.android.build.OutputFile
/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
* These basically call `react-native bundle` with the correct arguments during the Android build
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
* bundle directly from the development server. Below you can see all the possible configurations
* and their defaults. If you decide to add a configuration block, make sure to add it before the
* `apply from: "../../node_modules/react-native/react.gradle"` line.
*
* project.ext.react = [
* // the name of the generated asset file containing your JS bundle
* bundleAssetName: "index.android.bundle",
*
* // the entry file for bundle generation
* entryFile: "index.android.js",
*
* // whether to bundle JS and assets in debug mode
* bundleInDebug: false,
*
* // whether to bundle JS and assets in release mode
* bundleInRelease: true,
*
* // whether to bundle JS and assets in another build variant (if configured).
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
* // The configuration property can be in the following formats
* // 'bundleIn${productFlavor}${buildType}'
* // 'bundleIn${buildType}'
* // bundleInFreeDebug: true,
* // bundleInPaidRelease: true,
* // bundleInBeta: true,
*
* // the root of your project, i.e. where "package.json" lives
* root: "../../",
*
* // where to put the JS bundle asset in debug mode
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
*
* // where to put the JS bundle asset in release mode
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in debug mode
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in release mode
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
*
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
* // for example, you might want to remove it from here.
* inputExcludes: ["android/**", "ios/**"],
*
* // override which node gets called and with what additional arguments
* nodeExecutableAndArgs: ["node"]
*
* // supply additional arguments to the packager
* extraPackagerArgs: []
* ]
*/
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
android {
compileSdkVersion 25
buildToolsVersion '25.0.2'
defaultConfig {
applicationId "com.reactnativefirebasedemo"
minSdkVersion 16
targetSdkVersion 25
versionCode 1
versionName "1.0"
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86"
}
}
buildTypes {
release {
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
def versionCodes = ["armeabi-v7a": 1, "x86": 2]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}
}
dependencies {
compile project(':react-native-firebase')
compile project(':react-native-vector-icons')
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:24.0.0"
compile "com.facebook.react:react-native:+" // From node_modules
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}
apply plugin: 'com.google.gms.google-services'

View File

@ -0,0 +1,42 @@
{
"project_info": {
"project_number": "305229645282",
"firebase_url": "https://rnfirebase-b9ad4.firebaseio.com",
"project_id": "rnfirebase-b9ad4",
"storage_bucket": "rnfirebase-b9ad4.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:305229645282:android:efe37851d57e1d05",
"android_client_info": {
"package_name": "com.reactnativefirebasedemo"
}
},
"oauth_client": [
{
"client_id": "305229645282-j8ij0jev9ut24odmlk9i215pas808ugn.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCzbBYFyX8d6VdSu7T4s10IWYbPc-dguwM"
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"ads_service": {
"status": 2
}
}
}
],
"configuration_version": "1"
}

66
tests/android/app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,66 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Disabling obfuscation is useful if you collect stack traces from production crashes
# (unless you are using a system that supports de-obfuscate the stack traces).
-dontobfuscate
# React Native
# Keep our interfaces so they can be used by other ProGuard rules.
# See http://sourceforge.net/p/proguard/bugs/466/
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
# Do not strip any method/class that is annotated with @DoNotStrip
-keep @com.facebook.proguard.annotations.DoNotStrip class *
-keep @com.facebook.common.internal.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.proguard.annotations.DoNotStrip *;
@com.facebook.common.internal.DoNotStrip *;
}
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
void set*(***);
*** get*();
}
-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
-keep class * extends com.facebook.react.bridge.NativeModule { *; }
-keepclassmembers,includedescriptorclasses class * { native <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.UIProp <fields>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
-dontwarn com.facebook.react.**
# okhttp
-keepattributes Signature
-keepattributes *Annotation*
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn okhttp3.**
# okio
-keep class sun.misc.Unsafe { *; }
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.**

View File

@ -0,0 +1,51 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactnativefirebasedemo"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="22" />
<application
android:name=".MainApplication"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/AppTheme">
<service
android:name="io.invertase.firebase.messaging.MessagingService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service android:name="io.invertase.firebase.messaging.InstanceIdService" android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
</intent-filter>
</service>
<activity
android:name=".MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,15 @@
package com.reactnativefirebasedemo;
import com.facebook.react.ReactActivity;
public class MainActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "ReactNativeFirebaseDemo";
}
}

View File

@ -0,0 +1,44 @@
package com.reactnativefirebasedemo;
import android.app.Application;
import com.facebook.react.ReactApplication;
import io.invertase.firebase.RNFirebasePackage;
import com.oblador.vectoricons.VectorIconsPackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import java.util.Arrays;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new VectorIconsPackage(),
new RNFirebasePackage()
);
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">ReactNativeFirebaseDemo</string>
</resources>

View File

@ -0,0 +1,8 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -0,0 +1,48 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'com.google.gms:google-services:3.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenLocal()
jcenter()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
}
}
subprojects {
ext {
compileSdk = 25
buildTools = "25.0.2"
minSdk = 16
targetSdk = 25
}
afterEvaluate { project ->
if (!project.name.equalsIgnoreCase("app")
&& project.hasProperty("android")) {
android {
compileSdkVersion compileSdk
buildToolsVersion buildTools
defaultConfig {
minSdkVersion minSdk
targetSdkVersion targetSdk
}
}
}
}
}

View File

@ -0,0 +1,20 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useDeprecatedNdk=true

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Tue Mar 07 13:10:12 GMT 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip

160
tests/android/gradlew vendored Executable file
View File

@ -0,0 +1,160 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
tests/android/gradlew.bat vendored Normal file
View File

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,8 @@
keystore(
name = 'debug',
store = 'debug.keystore',
properties = 'debug.keystore.properties',
visibility = [
'PUBLIC',
],
)

View File

@ -0,0 +1,4 @@
key.store=debug.keystore
key.alias=androiddebugkey
key.store.password=android
key.alias.password=android

View File

@ -0,0 +1,7 @@
rootProject.name = 'ReactNativeFirebaseDemo'
include ':react-native-firebase'
project(':react-native-firebase').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-firebase/android')
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
include ':app'

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

5
tests/index.android.js Normal file
View File

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

4
tests/index.ios.js Normal file
View File

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

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AD_UNIT_ID_FOR_BANNER_TEST</key>
<string>ca-app-pub-3940256099942544/2934735716</string>
<key>AD_UNIT_ID_FOR_INTERSTITIAL_TEST</key>
<string>ca-app-pub-3940256099942544/4411468910</string>
<key>CLIENT_ID</key>
<string>305229645282-22imndi01abc2p6esgtu1i1m9mqrd0ib.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.305229645282-22imndi01abc2p6esgtu1i1m9mqrd0ib</string>
<key>API_KEY</key>
<string>AIzaSyAcdVLG5dRzA1ck_fa_xd4Z0cY7cga7S5A</string>
<key>GCM_SENDER_ID</key>
<string>305229645282</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.invertase.ReactNativeFirebaseDemo</string>
<key>PROJECT_ID</key>
<string>rnfirebase-b9ad4</string>
<key>STORAGE_BUCKET</key>
<string>rnfirebase-b9ad4.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<true/>
<key>IS_ANALYTICS_ENABLED</key>
<false/>
<key>IS_APPINVITE_ENABLED</key>
<false/>
<key>IS_GCM_ENABLED</key>
<true/>
<key>IS_SIGNIN_ENABLED</key>
<true/>
<key>GOOGLE_APP_ID</key>
<string>1:305229645282:ios:7b45748cb1117d2d</string>
<key>DATABASE_URL</key>
<string>https://rnfirebase-b9ad4.firebaseio.com</string>
</dict>
</plist>

Some files were not shown because too many files have changed in this diff Show More