merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
8f2f078ab3
@ -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
11
.gitignore
vendored
@ -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/
|
||||
|
@ -60,3 +60,4 @@ docs
|
||||
.idea
|
||||
coverage
|
||||
yarn.lock
|
||||
tests
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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<>();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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}`);
|
||||
});
|
||||
```
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
# Storage
|
||||
|
||||
RNFirebase mimics the [Web Firebase SDK Storage](https://firebase.google.com/docs/storage/web/start), whilst
|
||||
|
@ -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
126
docs/faqs.md
Normal 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
|
@ -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:
|
||||
|
||||

|
||||
|
||||
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;
|
||||
```
|
||||
|
@ -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>
|
||||
|
@ -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`:
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
import firebase from './lib/firebase';
|
||||
|
||||
export default firebase;
|
@ -1,5 +0,0 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
import firebase from './lib/firebase';
|
||||
export default firebase;
|
2
index.js
2
index.js
@ -1,3 +1,3 @@
|
||||
import Firebase from './firebase';
|
||||
import Firebase from './lib/firebase';
|
||||
|
||||
export default Firebase;
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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){
|
||||
|
@ -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);
|
||||
}];
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
26
lib/flow.js
26
lib/flow.js
@ -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
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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`;
|
||||
|
@ -12,6 +12,7 @@ const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
|
||||
*/
|
||||
export default class Disconnect {
|
||||
ref: Reference;
|
||||
path: string;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -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' },
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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('|');
|
||||
}
|
||||
}
|
||||
|
@ -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 - '/'
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
80
lib/modules/messaging/RemoteMessage.js
Normal file
80
lib/modules/messaging/RemoteMessage.js
Normal 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);
|
||||
}
|
||||
}
|
@ -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,
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
9
tests/.babelrc
Normal 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
6
tests/.buckconfig
Normal 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
10
tests/.editorconfig
Normal 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
40
tests/.eslintrc
Normal 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
47
tests/.flowconfig
Normal 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
1
tests/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.pbxproj -text
|
1
tests/.watchmanconfig
Normal file
1
tests/.watchmanconfig
Normal file
@ -0,0 +1 @@
|
||||
{}
|
363
tests/README.md
Normal file
363
tests/README.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
|
||||
### 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
|
||||
```
|
287
tests/__tests__/src/hooks/asynchronousHooksTests.js
Normal file
287
tests/__tests__/src/hooks/asynchronousHooksTests.js
Normal 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;
|
661
tests/__tests__/src/hooks/failingHookTests.js
Normal file
661
tests/__tests__/src/hooks/failingHookTests.js
Normal 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;
|
278
tests/__tests__/src/hooks/hookScopeTests.js
Normal file
278
tests/__tests__/src/hooks/hookScopeTests.js
Normal 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;
|
310
tests/__tests__/src/hooks/hooksCallOrderTests.js
Normal file
310
tests/__tests__/src/hooks/hooksCallOrderTests.js
Normal 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;
|
17
tests/__tests__/src/hooks/index.js
Normal file
17
tests/__tests__/src/hooks/index.js
Normal 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;
|
327
tests/__tests__/src/hooks/timingOutHookTests.js
Normal file
327
tests/__tests__/src/hooks/timingOutHookTests.js
Normal 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;
|
75
tests/__tests__/src/index.js
Normal file
75
tests/__tests__/src/index.js
Normal 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);
|
||||
});
|
||||
}
|
||||
});
|
82
tests/__tests__/src/tests/asynchronousTestTests.js
Normal file
82
tests/__tests__/src/tests/asynchronousTestTests.js
Normal 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;
|
146
tests/__tests__/src/tests/failingTestTests.js
Normal file
146
tests/__tests__/src/tests/failingTestTests.js
Normal 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;
|
84
tests/__tests__/src/tests/focusedTestTests.js
Normal file
84
tests/__tests__/src/tests/focusedTestTests.js
Normal 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;
|
17
tests/__tests__/src/tests/index.js
Normal file
17
tests/__tests__/src/tests/index.js
Normal 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;
|
108
tests/__tests__/src/tests/pendingTestTests.js
Normal file
108
tests/__tests__/src/tests/pendingTestTests.js
Normal 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;
|
84
tests/__tests__/src/tests/timingOutTests.js
Normal file
84
tests/__tests__/src/tests/timingOutTests.js
Normal 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
66
tests/android/app/BUCK
Normal 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',
|
||||
],
|
||||
)
|
144
tests/android/app/build.gradle
Normal file
144
tests/android/app/build.gradle
Normal 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'
|
42
tests/android/app/google-services.json
Normal file
42
tests/android/app/google-services.json
Normal 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
66
tests/android/app/proguard-rules.pro
vendored
Normal 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.**
|
51
tests/android/app/src/main/AndroidManifest.xml
Normal file
51
tests/android/app/src/main/AndroidManifest.xml
Normal 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>
|
BIN
tests/android/app/src/main/assets/fonts/Entypo.ttf
Normal file
BIN
tests/android/app/src/main/assets/fonts/Entypo.ttf
Normal file
Binary file not shown.
BIN
tests/android/app/src/main/assets/fonts/EvilIcons.ttf
Normal file
BIN
tests/android/app/src/main/assets/fonts/EvilIcons.ttf
Normal file
Binary file not shown.
BIN
tests/android/app/src/main/assets/fonts/FontAwesome.ttf
Normal file
BIN
tests/android/app/src/main/assets/fonts/FontAwesome.ttf
Normal file
Binary file not shown.
BIN
tests/android/app/src/main/assets/fonts/Foundation.ttf
Normal file
BIN
tests/android/app/src/main/assets/fonts/Foundation.ttf
Normal file
Binary file not shown.
BIN
tests/android/app/src/main/assets/fonts/Ionicons.ttf
Normal file
BIN
tests/android/app/src/main/assets/fonts/Ionicons.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
tests/android/app/src/main/assets/fonts/MaterialIcons.ttf
Normal file
BIN
tests/android/app/src/main/assets/fonts/MaterialIcons.ttf
Normal file
Binary file not shown.
BIN
tests/android/app/src/main/assets/fonts/Octicons.ttf
Normal file
BIN
tests/android/app/src/main/assets/fonts/Octicons.ttf
Normal file
Binary file not shown.
BIN
tests/android/app/src/main/assets/fonts/SimpleLineIcons.ttf
Normal file
BIN
tests/android/app/src/main/assets/fonts/SimpleLineIcons.ttf
Normal file
Binary file not shown.
BIN
tests/android/app/src/main/assets/fonts/Zocial.ttf
Normal file
BIN
tests/android/app/src/main/assets/fonts/Zocial.ttf
Normal file
Binary file not shown.
@ -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";
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
BIN
tests/android/app/src/main/res/drawable-hdpi/ic_launcher.png
Executable file
BIN
tests/android/app/src/main/res/drawable-hdpi/ic_launcher.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
BIN
tests/android/app/src/main/res/drawable-mdpi/ic_launcher.png
Executable file
BIN
tests/android/app/src/main/res/drawable-mdpi/ic_launcher.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
BIN
tests/android/app/src/main/res/drawable-xhdpi/ic_launcher.png
Executable file
BIN
tests/android/app/src/main/res/drawable-xhdpi/ic_launcher.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
BIN
tests/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Executable file
BIN
tests/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
tests/android/app/src/main/res/drawable-xxxhdpi/ic_launcher.png
Executable file
BIN
tests/android/app/src/main/res/drawable-xxxhdpi/ic_launcher.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
3
tests/android/app/src/main/res/values/strings.xml
Normal file
3
tests/android/app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">ReactNativeFirebaseDemo</string>
|
||||
</resources>
|
8
tests/android/app/src/main/res/values/styles.xml
Normal file
8
tests/android/app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
48
tests/android/build.gradle
Normal file
48
tests/android/build.gradle
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
tests/android/gradle.properties
Normal file
20
tests/android/gradle.properties
Normal 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
|
BIN
tests/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
tests/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
tests/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
tests/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
160
tests/android/gradlew
vendored
Executable 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
90
tests/android/gradlew.bat
vendored
Normal 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
|
8
tests/android/keystores/BUCK
Normal file
8
tests/android/keystores/BUCK
Normal file
@ -0,0 +1,8 @@
|
||||
keystore(
|
||||
name = 'debug',
|
||||
store = 'debug.keystore',
|
||||
properties = 'debug.keystore.properties',
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
)
|
4
tests/android/keystores/debug.keystore.properties
Normal file
4
tests/android/keystores/debug.keystore.properties
Normal file
@ -0,0 +1,4 @@
|
||||
key.store=debug.keystore
|
||||
key.alias=androiddebugkey
|
||||
key.store.password=android
|
||||
key.alias.password=android
|
7
tests/android/settings.gradle
Normal file
7
tests/android/settings.gradle
Normal 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'
|
BIN
tests/docs/assets/test-suite-screenshot-android.png
Normal file
BIN
tests/docs/assets/test-suite-screenshot-android.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
5
tests/index.android.js
Normal file
5
tests/index.android.js
Normal 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
4
tests/index.ios.js
Normal file
@ -0,0 +1,4 @@
|
||||
import { AppRegistry } from 'react-native';
|
||||
import bootstrap from './src/main';
|
||||
|
||||
AppRegistry.registerComponent('ReactNativeFirebaseDemo', () => bootstrap);
|
40
tests/ios/GoogleService-Info.plist
Normal file
40
tests/ios/GoogleService-Info.plist
Normal 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
Loading…
x
Reference in New Issue
Block a user