mirror of
https://github.com/zhigang1992/react-native-firebase.git
synced 2026-04-03 22:48:36 +08:00
[android][database] database improvements (#1619)
- [ANDROID] [BUGFIX] [DATABASE] - Database listeners now correctly tearing down between RN reloads. (Fixes #1498 #1611 #1609) - [JS] [BUGFIX] [DATABASE] - Fixed an issue where `Reference.toString()` incorrectly contains `//` instead of `/` when joining the parent and child paths. - [JS] [BUGFIX] [DATABASE] - Rework `.push()` behaviour to match WebSDK and correctly return a Reference instance in all scenarios. (Fixes #893 #1464 #1572) - [JS] [ENHANCEMENT] [UTILS] - Added a `firebase.utils().database.cleanup()` utility method which removes all database listeners.
This commit is contained in:
@@ -25,6 +25,7 @@ import com.google.firebase.database.ServerValue;
|
||||
import com.google.firebase.database.Transaction;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -36,18 +37,18 @@ import io.invertase.firebase.Utils;
|
||||
public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "RNFirebaseDatabase";
|
||||
private static boolean enableLogging = false;
|
||||
private static ReactApplicationContext reactApplicationContext = null;
|
||||
private static HashMap<String, Boolean> loggingLevelSet = new HashMap<>();
|
||||
private HashMap<String, RNFirebaseDatabaseReference> references = new HashMap<>();
|
||||
private SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>();
|
||||
private static HashMap<String, RNFirebaseDatabaseReference> references = new HashMap<>();
|
||||
private static SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>();
|
||||
|
||||
RNFirebaseDatabase(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* REACT NATIVE METHODS
|
||||
*/
|
||||
static ReactApplicationContext getReactApplicationContextInstance() {
|
||||
return reactApplicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve null or reject with a js like error if databaseError exists
|
||||
@@ -68,6 +69,11 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* REACT NATIVE METHODS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get a database instance for a specific firebase app instance
|
||||
*
|
||||
@@ -253,6 +259,26 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||
return errorMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
Log.d(TAG, "RNFirebaseDatabase:initialized");
|
||||
reactApplicationContext = getReactApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCatalystInstanceDestroy() {
|
||||
super.onCatalystInstanceDestroy();
|
||||
|
||||
Iterator refIterator = references.entrySet().iterator();
|
||||
while (refIterator.hasNext()) {
|
||||
Map.Entry pair = (Map.Entry) refIterator.next();
|
||||
RNFirebaseDatabaseReference nativeRef = (RNFirebaseDatabaseReference) pair.getValue();
|
||||
nativeRef.removeAllEventListeners();
|
||||
refIterator.remove(); // avoids a ConcurrentModificationException
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param appName
|
||||
*/
|
||||
@@ -792,7 +818,6 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||
ReadableArray modifiers
|
||||
) {
|
||||
return new RNFirebaseDatabaseReference(
|
||||
getReactApplicationContext(),
|
||||
appName,
|
||||
dbURL,
|
||||
key,
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.google.firebase.database.ValueEventListener;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -33,7 +34,6 @@ class RNFirebaseDatabaseReference {
|
||||
private Query query;
|
||||
private String appName;
|
||||
private String dbURL;
|
||||
private ReactContext reactContext;
|
||||
private HashMap<String, ChildEventListener> childEventListeners = new HashMap<>();
|
||||
private HashMap<String, ValueEventListener> valueEventListeners = new HashMap<>();
|
||||
|
||||
@@ -41,14 +41,12 @@ class RNFirebaseDatabaseReference {
|
||||
* RNFirebase wrapper around FirebaseDatabaseReference,
|
||||
* handles Query generation and event listeners.
|
||||
*
|
||||
* @param context
|
||||
* @param app
|
||||
* @param refKey
|
||||
* @param refPath
|
||||
* @param modifiersArray
|
||||
*/
|
||||
RNFirebaseDatabaseReference(
|
||||
ReactContext context,
|
||||
String app,
|
||||
String url,
|
||||
String refKey,
|
||||
@@ -59,10 +57,32 @@ class RNFirebaseDatabaseReference {
|
||||
query = null;
|
||||
appName = app;
|
||||
dbURL = url;
|
||||
reactContext = context;
|
||||
buildDatabaseQueryAtPathAndModifiers(refPath, modifiersArray);
|
||||
}
|
||||
|
||||
void removeAllEventListeners() {
|
||||
if (hasListeners()) {
|
||||
Iterator valueIterator = valueEventListeners.entrySet().iterator();
|
||||
|
||||
while (valueIterator.hasNext()) {
|
||||
Map.Entry pair = (Map.Entry) valueIterator.next();
|
||||
ValueEventListener valueEventListener = (ValueEventListener) pair.getValue();
|
||||
query.removeEventListener(valueEventListener);
|
||||
valueIterator.remove();
|
||||
}
|
||||
|
||||
Iterator childIterator = childEventListeners.entrySet().iterator();
|
||||
|
||||
while (childIterator.hasNext()) {
|
||||
Map.Entry pair = (Map.Entry) childIterator.next();
|
||||
ChildEventListener childEventListener = (ChildEventListener) pair.getValue();
|
||||
query.removeEventListener(childEventListener);
|
||||
childIterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used outside of class for keepSynced etc.
|
||||
*
|
||||
@@ -141,7 +161,6 @@ class RNFirebaseDatabaseReference {
|
||||
*/
|
||||
private void addOnceValueEventListener(final Promise promise) {
|
||||
@SuppressLint("StaticFieldLeak") final DataSnapshotToMapAsyncTask asyncTask = new DataSnapshotToMapAsyncTask(
|
||||
reactContext,
|
||||
this
|
||||
) {
|
||||
@Override
|
||||
@@ -338,7 +357,7 @@ class RNFirebaseDatabaseReference {
|
||||
@Nullable String previousChildName
|
||||
) {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
DataSnapshotToMapAsyncTask asyncTask = new DataSnapshotToMapAsyncTask(reactContext, this) {
|
||||
DataSnapshotToMapAsyncTask asyncTask = new DataSnapshotToMapAsyncTask(this) {
|
||||
@Override
|
||||
protected void onPostExecute(WritableMap data) {
|
||||
if (this.isAvailable()) {
|
||||
@@ -347,7 +366,11 @@ class RNFirebaseDatabaseReference {
|
||||
event.putString("key", key);
|
||||
event.putString("eventType", eventType);
|
||||
event.putMap("registration", Utils.readableMapToWritableMap(registration));
|
||||
Utils.sendEvent(reactContext, "database_sync_event", event);
|
||||
Utils.sendEvent(
|
||||
RNFirebaseDatabase.getReactApplicationContextInstance(),
|
||||
"database_sync_event",
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -367,7 +390,11 @@ class RNFirebaseDatabaseReference {
|
||||
event.putMap("error", RNFirebaseDatabase.getJSError(error));
|
||||
event.putMap("registration", Utils.readableMapToWritableMap(registration));
|
||||
|
||||
Utils.sendEvent(reactContext, "database_sync_event", event);
|
||||
Utils.sendEvent(
|
||||
RNFirebaseDatabase.getReactApplicationContextInstance(),
|
||||
"database_sync_event",
|
||||
event
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -554,13 +581,10 @@ class RNFirebaseDatabaseReference {
|
||||
* Introduced due to https://github.com/invertase/react-native-firebase/issues/1284
|
||||
*/
|
||||
private static class DataSnapshotToMapAsyncTask extends AsyncTask<Object, Void, WritableMap> {
|
||||
|
||||
private WeakReference<ReactContext> reactContextWeakReference;
|
||||
private WeakReference<RNFirebaseDatabaseReference> referenceWeakReference;
|
||||
|
||||
DataSnapshotToMapAsyncTask(ReactContext context, RNFirebaseDatabaseReference reference) {
|
||||
DataSnapshotToMapAsyncTask(RNFirebaseDatabaseReference reference) {
|
||||
referenceWeakReference = new WeakReference<>(reference);
|
||||
reactContextWeakReference = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -572,8 +596,7 @@ class RNFirebaseDatabaseReference {
|
||||
return RNFirebaseDatabaseUtils.snapshotToMap(dataSnapshot, previousChildName);
|
||||
} catch (RuntimeException e) {
|
||||
if (isAvailable()) {
|
||||
reactContextWeakReference
|
||||
.get()
|
||||
RNFirebaseDatabase.getReactApplicationContextInstance()
|
||||
.handleException(e);
|
||||
}
|
||||
throw e;
|
||||
@@ -586,7 +609,7 @@ class RNFirebaseDatabaseReference {
|
||||
}
|
||||
|
||||
Boolean isAvailable() {
|
||||
return reactContextWeakReference.get() != null && referenceWeakReference.get() != null;
|
||||
return RNFirebaseDatabase.getReactApplicationContextInstance() != null && referenceWeakReference.get() != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,19 +77,20 @@ type DatabaseListener = {
|
||||
export default class Reference extends ReferenceBase {
|
||||
_database: Database;
|
||||
|
||||
_promise: ?Promise<*>;
|
||||
|
||||
_query: Query;
|
||||
|
||||
_refListeners: { [listenerId: number]: DatabaseListener };
|
||||
|
||||
then: (a?: any) => Promise<any>;
|
||||
|
||||
catch: (a?: any) => Promise<any>;
|
||||
|
||||
constructor(
|
||||
database: Database,
|
||||
path: string,
|
||||
existingModifiers?: Array<DatabaseModifier>
|
||||
) {
|
||||
super(path);
|
||||
this._promise = null;
|
||||
this._refListeners = {};
|
||||
this._database = database;
|
||||
this._query = new Query(this, existingModifiers);
|
||||
@@ -303,34 +304,26 @@ export default class Reference extends ReferenceBase {
|
||||
* @returns {*}
|
||||
*/
|
||||
push(value: any, onComplete?: Function): Reference | Promise<void> {
|
||||
if (value === null || value === undefined) {
|
||||
return new Reference(
|
||||
this._database,
|
||||
`${this.path}/${generatePushID(this._database._serverTimeOffset)}`
|
||||
);
|
||||
const name = generatePushID(this._database._serverTimeOffset);
|
||||
|
||||
const pushRef = this.child(name);
|
||||
const thennablePushRef = this.child(name);
|
||||
|
||||
let promise;
|
||||
if (value != null) {
|
||||
promise = thennablePushRef.set(value, onComplete).then(() => pushRef);
|
||||
} else {
|
||||
promise = Promise.resolve(pushRef);
|
||||
}
|
||||
|
||||
const newRef = new Reference(
|
||||
this._database,
|
||||
`${this.path}/${generatePushID(this._database._serverTimeOffset)}`
|
||||
);
|
||||
const promise = newRef.set(value);
|
||||
thennablePushRef.then = promise.then.bind(promise);
|
||||
thennablePushRef.catch = promise.catch.bind(promise);
|
||||
|
||||
// if callback provided then internally call the set promise with value
|
||||
if (isFunction(onComplete)) {
|
||||
return (
|
||||
promise
|
||||
// $FlowExpectedError: Reports that onComplete can change to null despite the null check: https://github.com/facebook/flow/issues/1655
|
||||
.then(() => onComplete(null, newRef))
|
||||
// $FlowExpectedError: Reports that onComplete can change to null despite the null check: https://github.com/facebook/flow/issues/1655
|
||||
.catch(error => onComplete(error, null))
|
||||
);
|
||||
promise.catch(() => {});
|
||||
}
|
||||
|
||||
// otherwise attach promise to 'thenable' reference and return the
|
||||
// new reference
|
||||
newRef._setThenable(promise);
|
||||
return newRef;
|
||||
return thennablePushRef;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -500,7 +493,7 @@ export default class Reference extends ReferenceBase {
|
||||
* @returns {string}
|
||||
*/
|
||||
toString(): string {
|
||||
return `${this._database.databaseUrl}/${this.path}`;
|
||||
return `${this._database.databaseUrl}${this.path}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -566,47 +559,6 @@ export default class Reference extends ReferenceBase {
|
||||
return new Reference(this._database, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Access then method of promise if set
|
||||
* @return {*}
|
||||
*/
|
||||
then(fnResolve: any => any, fnReject: any => any) {
|
||||
if (isFunction(fnResolve) && this._promise && this._promise.then) {
|
||||
return this._promise.then.bind(this._promise)(
|
||||
result => {
|
||||
this._promise = null;
|
||||
return fnResolve(result);
|
||||
},
|
||||
possibleErr => {
|
||||
this._promise = null;
|
||||
|
||||
if (isFunction(fnReject)) {
|
||||
return fnReject(possibleErr);
|
||||
}
|
||||
|
||||
throw possibleErr;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error("Cannot read property 'then' of undefined.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Access catch method of promise if set
|
||||
* @return {*}
|
||||
*/
|
||||
catch(fnReject: any => any) {
|
||||
if (isFunction(fnReject) && this._promise && this._promise.catch) {
|
||||
return this._promise.catch.bind(this._promise)(possibleErr => {
|
||||
this._promise = null;
|
||||
return fnReject(possibleErr);
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error("Cannot read property 'catch' of undefined.");
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNALS
|
||||
*/
|
||||
@@ -635,15 +587,6 @@ export default class Reference extends ReferenceBase {
|
||||
}$${this._query.queryIdentifier()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the promise this 'thenable' reference relates to
|
||||
* @param promise
|
||||
* @private
|
||||
*/
|
||||
_setThenable(promise: Promise<*>) {
|
||||
this._promise = promise;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param obj
|
||||
@@ -812,7 +755,7 @@ export default class Reference extends ReferenceBase {
|
||||
},
|
||||
});
|
||||
|
||||
// increment number of listeners - just s short way of making
|
||||
// increment number of listeners - just a short way of making
|
||||
// every registration unique per .on() call
|
||||
listeners += 1;
|
||||
|
||||
@@ -903,12 +846,3 @@ export default class Reference extends ReferenceBase {
|
||||
return SyncTree.removeListenersForRegistrations(registrations);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// class ThenableReference<+R> extends Reference {
|
||||
// then<U>(
|
||||
// onFulfill?: (value: R) => Promise<U> | U,
|
||||
// onReject?: (error: any) => Promise<U> | U
|
||||
// ): Promise<U>;
|
||||
// catch<U>(onReject?: (error: any) => Promise<U> | U): Promise<R | U>;
|
||||
// }
|
||||
|
||||
12
src/modules/utils/database.js
Normal file
12
src/modules/utils/database.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import SyncTree from '../../utils/SyncTree';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Removes all database listeners (JS & Native)
|
||||
*/
|
||||
cleanup(): void {
|
||||
SyncTree.removeListenersForRegistrations(
|
||||
Object.keys(SyncTree._reverseLookup)
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import INTERNALS from '../../utils/internals';
|
||||
import { isIOS } from '../../utils';
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import type App from '../core/app';
|
||||
import DatabaseUtils from './database';
|
||||
|
||||
const FirebaseCoreModule = NativeModules.RNFirebase;
|
||||
|
||||
@@ -28,6 +29,10 @@ export default class RNFirebaseUtils extends ModuleBase {
|
||||
});
|
||||
}
|
||||
|
||||
get database(): DatabaseUtils {
|
||||
return DatabaseUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
13
tests/app.js
13
tests/app.js
@@ -1,6 +1,15 @@
|
||||
/* eslint-disable import/extensions,import/no-unresolved */
|
||||
/* eslint-disable import/extensions,import/no-unresolved,import/first */
|
||||
import React, { Component } from 'react';
|
||||
import { AppRegistry, Text, View, Image, StyleSheet } from 'react-native';
|
||||
import {
|
||||
AppRegistry,
|
||||
Text,
|
||||
View,
|
||||
Image,
|
||||
StyleSheet,
|
||||
YellowBox,
|
||||
} from 'react-native';
|
||||
|
||||
YellowBox.ignoreWarnings(['Require cycle:']);
|
||||
|
||||
import firebase from 'react-native-firebase';
|
||||
import jet from 'jet/platform/react-native';
|
||||
|
||||
279
tests/e2e/database/issueSpecific.e2e.js
Normal file
279
tests/e2e/database/issueSpecific.e2e.js
Normal file
@@ -0,0 +1,279 @@
|
||||
const { CONTENTS, setDatabaseContents } = TestHelpers.database;
|
||||
|
||||
describe('database()', () => {
|
||||
beforeEach(() => setDatabaseContents());
|
||||
|
||||
describe('issue_100', () => {
|
||||
describe('array-like values should', () => {
|
||||
it('return null in returned array at positions where a key is missing', async () => {
|
||||
const ref = firebase.database().ref('tests/issues/100');
|
||||
|
||||
const snapshot = await ref.once('value');
|
||||
|
||||
snapshot
|
||||
.val()
|
||||
.should.eql(
|
||||
jet.contextify([
|
||||
null,
|
||||
jet.contextify(CONTENTS.ISSUES[100][1]),
|
||||
jet.contextify(CONTENTS.ISSUES[100][2]),
|
||||
jet.contextify(CONTENTS.ISSUES[100][3]),
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('issue_108', () => {
|
||||
describe('filters using floats', () => {
|
||||
it('return correct results', async () => {
|
||||
const ref = firebase.database().ref('tests/issues/108');
|
||||
|
||||
const snapshot = await ref
|
||||
.orderByChild('latitude')
|
||||
.startAt(34.00867000999119)
|
||||
.endAt(34.17462960866099)
|
||||
.once('value');
|
||||
|
||||
const val = snapshot.val();
|
||||
|
||||
val.foobar.should.eql(jet.contextify(CONTENTS.ISSUES[108].foobar));
|
||||
should.equal(Object.keys(val).length, 1);
|
||||
});
|
||||
|
||||
it('return correct results when not using float values', async () => {
|
||||
const ref = firebase.database().ref('tests/issues/108');
|
||||
|
||||
const snapshot = await ref
|
||||
.orderByChild('latitude')
|
||||
.equalTo(37)
|
||||
.once('value');
|
||||
|
||||
const val = snapshot.val();
|
||||
|
||||
val.notAFloat.should.eql(
|
||||
jet.contextify(CONTENTS.ISSUES[108].notAFloat)
|
||||
);
|
||||
|
||||
should.equal(Object.keys(val).length, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
xdescribe('issue_171', () => {
|
||||
describe('non array-like values should', () => {
|
||||
it('return as objects', async () => {
|
||||
const ref = firebase.database().ref('tests/issues/171');
|
||||
const snapshot = await ref.once('value');
|
||||
|
||||
snapshot.val().should.eql(jet.contextify(CONTENTS.ISSUES[171]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('issue_489', () => {
|
||||
describe('long numbers should', () => {
|
||||
it('return as longs', async () => {
|
||||
const long1Ref = firebase.database().ref('tests/issues/489/long1');
|
||||
const long2Ref = firebase.database().ref('tests/issues/489/long2');
|
||||
const long2 = 1234567890123456;
|
||||
|
||||
let snapshot = await long1Ref.once('value');
|
||||
snapshot.val().should.eql(CONTENTS.ISSUES[489].long1);
|
||||
|
||||
await long2Ref.set(long2);
|
||||
snapshot = await long2Ref.once('value');
|
||||
snapshot.val().should.eql(long2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('issue_521', () => {
|
||||
describe('orderByChild (numerical field) and limitToLast', () => {
|
||||
it('once() returns correct results', async () => {
|
||||
const ref = firebase.database().ref('tests/issues/521');
|
||||
|
||||
const snapshot = await ref
|
||||
.orderByChild('number')
|
||||
.limitToLast(1)
|
||||
.once('value');
|
||||
|
||||
const val = snapshot.val();
|
||||
|
||||
val.key3.should.eql(jet.contextify(CONTENTS.ISSUES[521].key3));
|
||||
should.equal(Object.keys(val).length, 1);
|
||||
});
|
||||
|
||||
it('on() returns correct initial results', async () => {
|
||||
const ref = firebase
|
||||
.database()
|
||||
.ref('tests/issues/521')
|
||||
.orderByChild('number')
|
||||
.limitToLast(2);
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
await new Promise(resolve => {
|
||||
ref.on('value', snapshot => {
|
||||
callback(snapshot.val());
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
callback.should.be.calledWith({
|
||||
key2: CONTENTS.ISSUES[521].key2,
|
||||
key3: CONTENTS.ISSUES[521].key3,
|
||||
});
|
||||
|
||||
callback.should.be.calledOnce();
|
||||
});
|
||||
|
||||
it('on() returns correct subsequent results', async () => {
|
||||
const ref = firebase
|
||||
.database()
|
||||
.ref('tests/issues/521')
|
||||
.orderByChild('number')
|
||||
.limitToLast(2);
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
await new Promise(resolve => {
|
||||
ref.on('value', snapshot => {
|
||||
callback(snapshot.val());
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
callback.should.be.calledWith({
|
||||
key2: CONTENTS.ISSUES[521].key2,
|
||||
key3: CONTENTS.ISSUES[521].key3,
|
||||
});
|
||||
|
||||
callback.should.be.calledOnce();
|
||||
|
||||
const newDataValue = {
|
||||
name: 'Item 4',
|
||||
number: 4,
|
||||
string: 'item4',
|
||||
};
|
||||
|
||||
const newRef = firebase.database().ref('tests/issues/521/key4');
|
||||
|
||||
await newRef.set(newDataValue);
|
||||
await sleep(5);
|
||||
|
||||
callback.should.be.calledWith({
|
||||
key3: CONTENTS.ISSUES[521].key3,
|
||||
key4: newDataValue,
|
||||
});
|
||||
|
||||
callback.should.be.calledTwice();
|
||||
});
|
||||
});
|
||||
|
||||
describe('orderByChild (string field) and limitToLast', () => {
|
||||
it('once() returns correct results', async () => {
|
||||
const ref = firebase.database().ref('tests/issues/521');
|
||||
|
||||
const snapshot = await ref
|
||||
.orderByChild('string')
|
||||
.limitToLast(1)
|
||||
.once('value');
|
||||
|
||||
const val = snapshot.val();
|
||||
|
||||
val.key3.should.eql(jet.contextify(CONTENTS.ISSUES[521].key3));
|
||||
should.equal(Object.keys(val).length, 1);
|
||||
});
|
||||
|
||||
it('on() returns correct initial results', async () => {
|
||||
const ref = firebase
|
||||
.database()
|
||||
.ref('tests/issues/521')
|
||||
.orderByChild('string')
|
||||
.limitToLast(2);
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
await new Promise(resolve => {
|
||||
ref.on('value', snapshot => {
|
||||
callback(snapshot.val());
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
callback.should.be.calledWith({
|
||||
key2: CONTENTS.ISSUES[521].key2,
|
||||
key3: CONTENTS.ISSUES[521].key3,
|
||||
});
|
||||
|
||||
callback.should.be.calledOnce();
|
||||
});
|
||||
|
||||
it('on() returns correct subsequent results', async () => {
|
||||
const ref = firebase
|
||||
.database()
|
||||
.ref('tests/issues/521')
|
||||
.orderByChild('string')
|
||||
.limitToLast(2);
|
||||
|
||||
const callback = sinon.spy();
|
||||
|
||||
await new Promise(resolve => {
|
||||
ref.on('value', snapshot => {
|
||||
callback(snapshot.val());
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
callback.should.be.calledWith({
|
||||
key2: CONTENTS.ISSUES[521].key2,
|
||||
key3: CONTENTS.ISSUES[521].key3,
|
||||
});
|
||||
|
||||
callback.should.be.calledOnce();
|
||||
|
||||
const newDataValue = {
|
||||
name: 'Item 4',
|
||||
number: 4,
|
||||
string: 'item4',
|
||||
};
|
||||
|
||||
const newRef = firebase.database().ref('tests/issues/521/key4');
|
||||
await newRef.set(newDataValue);
|
||||
await sleep(5);
|
||||
|
||||
callback.should.be.calledWith({
|
||||
key3: CONTENTS.ISSUES[521].key3,
|
||||
key4: newDataValue,
|
||||
});
|
||||
|
||||
callback.should.be.calledTwice();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('issue_679', () => {
|
||||
describe('path from snapshot reference', () => {
|
||||
it('should match web SDK', async () => {
|
||||
const nativeRef = firebase.database().ref('tests/issues/679');
|
||||
const webRef = firebaseAdmin.database().ref('tests/issues/679');
|
||||
const nativeRef2 = firebase.database().ref('tests/issues/679/');
|
||||
const webRef2 = firebaseAdmin.database().ref('tests/issues/679/');
|
||||
|
||||
webRef.toString().should.equal(nativeRef.toString());
|
||||
webRef2.toString().should.equal(nativeRef2.toString());
|
||||
});
|
||||
|
||||
it('should be correct when returned from native', async () => {
|
||||
const nativeRef = firebase.database().ref('tests/issues/679/');
|
||||
const webRef = firebaseAdmin.database().ref('tests/issues/679/');
|
||||
|
||||
const nativeSnapshot = await nativeRef.once('value');
|
||||
const webSnapshot = await webRef.once('value');
|
||||
|
||||
webSnapshot.ref.toString().should.equal(nativeSnapshot.ref.toString());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
39
tests/e2e/database/ref/child.e2e.js
Normal file
39
tests/e2e/database/ref/child.e2e.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const { setDatabaseContents } = TestHelpers.database;
|
||||
|
||||
describe('database()', () => {
|
||||
before(() => setDatabaseContents());
|
||||
|
||||
describe('ref().child', () => {
|
||||
describe('when passed a shallow path', () => {
|
||||
it('returns correct child ref', () => {
|
||||
const ref = firebase.database().ref('tests');
|
||||
const childRef = ref.child('tests');
|
||||
childRef.key.should.eql('tests');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when passed a nested path', () => {
|
||||
it('returns correct child ref', () => {
|
||||
const ref = firebase.database().ref('tests');
|
||||
const grandChildRef = ref.child('tests/number');
|
||||
grandChildRef.key.should.eql('number');
|
||||
});
|
||||
});
|
||||
|
||||
describe("when passed a path that doesn't exist", () => {
|
||||
it('creates a reference, anyway', () => {
|
||||
const ref = firebase.database().ref('tests');
|
||||
const grandChildRef = ref.child('doesnt/exist');
|
||||
grandChildRef.key.should.eql('exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when passed an invalid path', () => {
|
||||
it('creates a reference, anyway', () => {
|
||||
const ref = firebase.database().ref('tests');
|
||||
const grandChildRef = ref.child('does$&nt/exist');
|
||||
grandChildRef.key.should.eql('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
24
tests/e2e/database/ref/factory.e2e.js
Normal file
24
tests/e2e/database/ref/factory.e2e.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const { CONTENTS, setDatabaseContents } = TestHelpers.database;
|
||||
|
||||
describe('database()', () => {
|
||||
before(() => setDatabaseContents());
|
||||
|
||||
describe('ref()', () => {
|
||||
it('returns root reference when provided no path', () => {
|
||||
const ref = firebase.database().ref();
|
||||
(ref.key === null).should.be.true();
|
||||
(ref.parent === null).should.be.true();
|
||||
});
|
||||
|
||||
it('returns reference to data at path', async () => {
|
||||
const ref = firebase.database().ref('tests/types/number');
|
||||
|
||||
let valueAtRef;
|
||||
await ref.once('value', snapshot => {
|
||||
valueAtRef = snapshot.val();
|
||||
});
|
||||
|
||||
valueAtRef.should.eql(CONTENTS.DEFAULT.number);
|
||||
});
|
||||
});
|
||||
});
|
||||
32
tests/e2e/database/ref/isEqual.e2e.js
Normal file
32
tests/e2e/database/ref/isEqual.e2e.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const { setDatabaseContents } = TestHelpers.database;
|
||||
|
||||
describe('database()', () => {
|
||||
before(() => setDatabaseContents());
|
||||
|
||||
describe('ref().isEqual()', () => {
|
||||
before(() => {
|
||||
this.ref = firebase.database().ref('tests/types');
|
||||
});
|
||||
|
||||
it('returns true when the reference is for the same location', () => {
|
||||
const ref = firebase.database().ref();
|
||||
ref.ref.should.eql(ref);
|
||||
|
||||
const ref2 = firebase.database().ref('tests/types');
|
||||
this.ref.isEqual(ref2).should.eql(true);
|
||||
});
|
||||
|
||||
it('returns false when the reference is for a different location', () => {
|
||||
const ref2 = firebase.database().ref('tests/types/number');
|
||||
this.ref.isEqual(ref2).should.eql(false);
|
||||
});
|
||||
|
||||
it('returns false when the reference is null', () => {
|
||||
this.ref.isEqual(null).should.eql(false);
|
||||
});
|
||||
|
||||
it('returns false when the reference is not a Reference', () => {
|
||||
this.ref.isEqual(1).should.eql(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
16
tests/e2e/database/ref/key.e2e.js
Normal file
16
tests/e2e/database/ref/key.e2e.js
Normal file
@@ -0,0 +1,16 @@
|
||||
describe('database()', () => {
|
||||
describe('ref().key', () => {
|
||||
it('returns null for root ref', () => {
|
||||
const ref = firebase.database().ref();
|
||||
(ref.key === null).should.be.true();
|
||||
});
|
||||
|
||||
it('returns correct key for path', () => {
|
||||
const ref = firebase.database().ref('tests/types/number');
|
||||
const arrayItemRef = firebase.database().ref('tests/types/array/1');
|
||||
|
||||
ref.key.should.eql('number');
|
||||
arrayItemRef.key.should.eql('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
19
tests/e2e/database/ref/parent.e2e.js
Normal file
19
tests/e2e/database/ref/parent.e2e.js
Normal file
@@ -0,0 +1,19 @@
|
||||
describe('database()', () => {
|
||||
describe('ref().parent', () => {
|
||||
describe('on the root ref', () => {
|
||||
it('returns null', () => {
|
||||
const ref = firebase.database().ref();
|
||||
(ref.parent === null).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
describe('on a non-root ref', () => {
|
||||
it('returns correct parent', () => {
|
||||
const ref = firebase.database().ref('tests/types/number');
|
||||
const parentRef = firebase.database().ref('tests/types');
|
||||
|
||||
ref.parent.key.should.eql(parentRef.key);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
33
tests/e2e/database/ref/priority.e2e.js
Normal file
33
tests/e2e/database/ref/priority.e2e.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const { CONTENTS, setDatabaseContents } = TestHelpers.database;
|
||||
|
||||
describe('database()', () => {
|
||||
before(() => setDatabaseContents());
|
||||
|
||||
describe('ref().priority', () => {
|
||||
it('setPriority() should correctly set a priority for all non-null values', async () => {
|
||||
await Promise.all(
|
||||
Object.keys(CONTENTS.DEFAULT).map(async dataRef => {
|
||||
const ref = firebase.database().ref(`tests/types/${dataRef}`);
|
||||
|
||||
await ref.setPriority(1);
|
||||
|
||||
await ref.once('value').then(snapshot => {
|
||||
if (snapshot.val() !== null) {
|
||||
snapshot.getPriority().should.eql(1);
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('setWithPriority() should correctly set the priority', async () => {
|
||||
const ref = firebase.database().ref('tests/types/number');
|
||||
|
||||
await ref.setWithPriority(CONTENTS.DEFAULT.number, '2');
|
||||
|
||||
await ref.once('value').then(snapshot => {
|
||||
snapshot.getPriority().should.eql('2');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
126
tests/e2e/database/ref/push.e2e.js
Normal file
126
tests/e2e/database/ref/push.e2e.js
Normal file
@@ -0,0 +1,126 @@
|
||||
const { CONTENTS, setDatabaseContents } = TestHelpers.database;
|
||||
|
||||
describe('database()', () => {
|
||||
before(() => setDatabaseContents());
|
||||
|
||||
describe('ref().push()', () => {
|
||||
it('returns a ref that can be used to set value later', async () => {
|
||||
const ref = firebase.database().ref('tests/types/array');
|
||||
|
||||
let originalListValue;
|
||||
await ref.once('value', snapshot => {
|
||||
originalListValue = snapshot.val();
|
||||
});
|
||||
await sleep(5);
|
||||
|
||||
originalListValue.should.eql(jet.contextify(CONTENTS.DEFAULT.array));
|
||||
|
||||
const newItemRef = ref.push();
|
||||
const valueToAddToList = CONTENTS.NEW.number;
|
||||
await newItemRef.set(valueToAddToList);
|
||||
|
||||
let newItemValue;
|
||||
await newItemRef.once('value', snapshot => {
|
||||
newItemValue = snapshot.val();
|
||||
});
|
||||
await sleep(5);
|
||||
|
||||
newItemValue.should.eql(valueToAddToList);
|
||||
|
||||
let newListValue;
|
||||
await ref.once('value', snapshot => {
|
||||
newListValue = snapshot.val();
|
||||
});
|
||||
await sleep(5);
|
||||
|
||||
const originalListAsObject = {
|
||||
...originalListValue,
|
||||
[newItemRef.key]: valueToAddToList,
|
||||
};
|
||||
|
||||
newListValue.should.eql(jet.contextify(originalListAsObject));
|
||||
});
|
||||
|
||||
it('allows setting value immediately', async () => {
|
||||
let snapshot;
|
||||
|
||||
const ref = firebase.database().ref('tests/types/array');
|
||||
const valueToAddToList = CONTENTS.NEW.number;
|
||||
|
||||
snapshot = await ref.once('value');
|
||||
const originalListValue = snapshot.val();
|
||||
const newItemRef = ref.push(valueToAddToList);
|
||||
|
||||
snapshot = await newItemRef.once('value');
|
||||
const newItemValue = snapshot.val();
|
||||
newItemValue.should.eql(valueToAddToList);
|
||||
|
||||
snapshot = await firebase
|
||||
.database()
|
||||
.ref('tests/types/array')
|
||||
.once('value');
|
||||
const newListValue = snapshot.val();
|
||||
|
||||
const originalListAsObject = {
|
||||
...originalListValue,
|
||||
[newItemRef.key]: valueToAddToList,
|
||||
};
|
||||
|
||||
newListValue.should.eql(jet.contextify(originalListAsObject));
|
||||
});
|
||||
|
||||
// https://github.com/invertase/react-native-firebase/issues/893
|
||||
it('correctly returns the reference', async () => {
|
||||
let result;
|
||||
const path = 'tests/types/array';
|
||||
const valueToAddToList = CONTENTS.NEW.number;
|
||||
const Reference = jet.require('src/modules/database/Reference');
|
||||
|
||||
// 1
|
||||
const ref1 = firebase
|
||||
.database()
|
||||
.ref(path)
|
||||
.push();
|
||||
|
||||
should.exist(ref1, 'ref1 did not return a Reference instance');
|
||||
ref1.key.should.be.a.String();
|
||||
ref1.should.be.instanceOf(Reference);
|
||||
result = await ref1.set(valueToAddToList);
|
||||
should.not.exist(result);
|
||||
|
||||
// 2
|
||||
const ref2 = await firebase
|
||||
.database()
|
||||
.ref(path)
|
||||
.push(valueToAddToList);
|
||||
|
||||
should.exist(ref2, 'ref2 did not return a Reference instance');
|
||||
ref2.key.should.be.a.String();
|
||||
ref2.should.be.instanceOf(Reference);
|
||||
|
||||
// 3
|
||||
const ref3 = await firebase
|
||||
.database()
|
||||
.ref(path)
|
||||
.push();
|
||||
|
||||
should.exist(ref3, 'ref3 did not return a Reference instance');
|
||||
ref3.key.should.be.a.String();
|
||||
ref3.should.be.instanceOf(Reference);
|
||||
|
||||
result = await ref3.set(valueToAddToList);
|
||||
should.not.exist(result);
|
||||
});
|
||||
|
||||
it('calls an onComplete callback', async () => {
|
||||
const callback = sinon.spy();
|
||||
const ref = firebase.database().ref('tests/types/array');
|
||||
|
||||
const valueToAddToList = CONTENTS.NEW.number;
|
||||
const newItemRef = await ref.push(valueToAddToList, callback);
|
||||
|
||||
callback.should.be.calledWith(null);
|
||||
newItemRef.parent.path.should.equal('tests/types/array');
|
||||
});
|
||||
});
|
||||
});
|
||||
21
tests/e2e/database/ref/query.e2e.js
Normal file
21
tests/e2e/database/ref/query.e2e.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const { CONTENTS, setDatabaseContents } = TestHelpers.database;
|
||||
|
||||
describe('database()', () => {
|
||||
before(() => setDatabaseContents());
|
||||
|
||||
describe('ref() query', () => {
|
||||
it('orderByChild().equalTo()', async () => {
|
||||
const snapshot = await firebase
|
||||
.database()
|
||||
.ref('tests/query')
|
||||
.orderByChild('search')
|
||||
.equalTo('foo')
|
||||
.once('value');
|
||||
|
||||
const val = snapshot.val();
|
||||
CONTENTS.QUERY[0].should.eql({ ...val[0] });
|
||||
});
|
||||
|
||||
// TODO more query tests
|
||||
});
|
||||
});
|
||||
97
tests/e2e/database/rnReload.e2e.js
Normal file
97
tests/e2e/database/rnReload.e2e.js
Normal file
@@ -0,0 +1,97 @@
|
||||
const { CONTENTS, setDatabaseContents } = TestHelpers.database;
|
||||
|
||||
describe('database()', () => {
|
||||
before(() => setDatabaseContents());
|
||||
|
||||
describe('ref().once()', () => {
|
||||
it('same reference path works after React Native reload', async () => {
|
||||
let ref;
|
||||
let snapshot;
|
||||
const path = 'tests/types/number';
|
||||
const dataTypeValue = CONTENTS.DEFAULT.number;
|
||||
|
||||
// before reload
|
||||
ref = firebase.database().ref(path);
|
||||
snapshot = await ref.once('value');
|
||||
snapshot.val().should.eql(dataTypeValue);
|
||||
|
||||
// RELOAD
|
||||
await device.reloadReactNative();
|
||||
|
||||
// after reload
|
||||
ref = firebase.database().ref(path);
|
||||
snapshot = await ref.once('value');
|
||||
snapshot.val().should.eql(dataTypeValue);
|
||||
}).timeout(15000);
|
||||
|
||||
it(':android: same reference path works after app backgrounded', async () => {
|
||||
let ref;
|
||||
let snapshot;
|
||||
const path = 'tests/types/number';
|
||||
const dataTypeValue = CONTENTS.DEFAULT.number;
|
||||
|
||||
// before
|
||||
ref = firebase.database().ref(path);
|
||||
snapshot = await ref.once('value');
|
||||
snapshot.val().should.eql(dataTypeValue);
|
||||
|
||||
await device.sendToHome();
|
||||
await sleep(250);
|
||||
await device.launchApp({ newInstance: false });
|
||||
await sleep(250);
|
||||
|
||||
// after
|
||||
ref = firebase.database().ref(path);
|
||||
snapshot = await ref.once('value');
|
||||
snapshot.val().should.eql(dataTypeValue);
|
||||
}).timeout(15000);
|
||||
});
|
||||
|
||||
describe('ref().on()', () => {
|
||||
it('same reference path works after React Native reload', async () => {
|
||||
let ref;
|
||||
let snapshot;
|
||||
const path = 'tests/types/number';
|
||||
const dataTypeValue = CONTENTS.DEFAULT.number;
|
||||
|
||||
// before reload
|
||||
ref = firebase.database().ref(path);
|
||||
snapshot = await new Promise(resolve => ref.on('value', resolve));
|
||||
snapshot.val().should.eql(dataTypeValue);
|
||||
|
||||
// RELOAD
|
||||
await device.reloadReactNative();
|
||||
|
||||
// after reload
|
||||
ref = firebase.database().ref(path);
|
||||
snapshot = await new Promise(resolve => ref.on('value', resolve));
|
||||
snapshot.val().should.eql(dataTypeValue);
|
||||
|
||||
firebase.utils().database.cleanup();
|
||||
}).timeout(15000);
|
||||
|
||||
it(':android: same reference path works after app backgrounded', async () => {
|
||||
let ref;
|
||||
let snapshot;
|
||||
const path = 'tests/types/number';
|
||||
const dataTypeValue = CONTENTS.DEFAULT.number;
|
||||
|
||||
// before background
|
||||
ref = firebase.database().ref(path);
|
||||
snapshot = await new Promise(resolve => ref.on('value', resolve));
|
||||
snapshot.val().should.eql(dataTypeValue);
|
||||
|
||||
await device.sendToHome();
|
||||
await sleep(250);
|
||||
await device.launchApp({ newInstance: false });
|
||||
await sleep(250);
|
||||
|
||||
// after background
|
||||
ref = firebase.database().ref(path);
|
||||
snapshot = await new Promise(resolve => ref.on('value', resolve));
|
||||
snapshot.val().should.eql(dataTypeValue);
|
||||
|
||||
firebase.utils().database.cleanup();
|
||||
}).timeout(15000);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
const { setDatabaseContents } = TestHelpers.database;
|
||||
|
||||
// TODO use testRunId in refs to prevent multiple test instances interfering with each other
|
||||
describe('database()', () => {
|
||||
describe('Snapshot', () => {
|
||||
before(() => setDatabaseContents());
|
||||
|
||||
@@ -13,6 +13,7 @@ module.exports = {
|
||||
666
|
||||
),
|
||||
database.ref('tests/query').set(CONTENTS.QUERY),
|
||||
database.ref('tests/issues').set(CONTENTS.ISSUES),
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -62,6 +62,7 @@ console.log = (...args) => {
|
||||
args[0] &&
|
||||
typeof args[0] === 'string' &&
|
||||
(args[0].toLowerCase().includes('deprecated') ||
|
||||
args[0].toLowerCase().includes('require cycle') ||
|
||||
args[0].toLowerCase().includes('restrictions in the native sdk'))
|
||||
) {
|
||||
return undefined;
|
||||
|
||||
@@ -254,7 +254,7 @@ PODS:
|
||||
- React/Core
|
||||
- React/fishhook
|
||||
- React/RCTBlob
|
||||
- RNFirebase (5.0.0):
|
||||
- RNFirebase (5.1.0-rc1):
|
||||
- Firebase/Core
|
||||
- React
|
||||
- yoga (0.57.1.React)
|
||||
@@ -325,7 +325,7 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native"
|
||||
RNFirebase:
|
||||
:path: "../../ios/RNFirebase.podspec"
|
||||
:version: "~> 5.0.0"
|
||||
:version: "~> 5.1.0-rc1"
|
||||
yoga:
|
||||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"detox": "^9.0.4",
|
||||
"fbjs": "^0.8.16",
|
||||
"firebase-admin": "^5.12.0",
|
||||
"jet": "^0.1.0",
|
||||
"jet": "^0.2.0",
|
||||
"jsonwebtoken": "^8.2.1",
|
||||
"mocha": "^5.2.0",
|
||||
"prop-types": "^15.6.1",
|
||||
|
||||
Reference in New Issue
Block a user