diff --git a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestore.java b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestore.java index 1fc4d4d1..a55a4a6b 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestore.java +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestore.java @@ -57,9 +57,10 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { @ReactMethod public void collectionOnSnapshot(String appName, String path, ReadableArray filters, - ReadableArray orders, ReadableMap options, String listenerId) { + ReadableArray orders, ReadableMap options, String listenerId, + ReadableMap queryListenOptions) { RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath(appName, path, filters, orders, options); - ref.onSnapshot(listenerId); + ref.onSnapshot(listenerId, queryListenOptions); } @@ -145,9 +146,10 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule { } @ReactMethod - public void documentOnSnapshot(String appName, String path, String listenerId) { + public void documentOnSnapshot(String appName, String path, String listenerId, + ReadableMap docListenOptions) { RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); - ref.onSnapshot(listenerId); + ref.onSnapshot(listenerId, docListenOptions); } @ReactMethod diff --git a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java index e8512d19..263cde63 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java @@ -12,10 +12,12 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; +import com.google.firebase.firestore.DocumentListenOptions; import com.google.firebase.firestore.EventListener; import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.ListenerRegistration; import com.google.firebase.firestore.Query; +import com.google.firebase.firestore.QueryListenOptions; import com.google.firebase.firestore.QuerySnapshot; import java.util.HashMap; @@ -71,7 +73,7 @@ public class RNFirebaseFirestoreCollectionReference { } } - public void onSnapshot(final String listenerId) { + public void onSnapshot(final String listenerId, final ReadableMap queryListenOptions) { if (!collectionSnapshotListeners.containsKey(listenerId)) { final EventListener listener = new EventListener() { @Override @@ -87,7 +89,19 @@ public class RNFirebaseFirestoreCollectionReference { } } }; - ListenerRegistration listenerRegistration = this.query.addSnapshotListener(listener); + QueryListenOptions options = new QueryListenOptions(); + if (queryListenOptions != null) { + if (queryListenOptions.hasKey("includeDocumentMetadataChanges") + && queryListenOptions.getBoolean("includeDocumentMetadataChanges")) { + options.includeDocumentMetadataChanges(); + } + if (queryListenOptions.hasKey("includeQueryMetadataChanges") + && queryListenOptions.getBoolean("includeQueryMetadataChanges")) { + options.includeQueryMetadataChanges(); + } + } + + ListenerRegistration listenerRegistration = this.query.addSnapshotListener(options, listener); collectionSnapshotListeners.put(listenerId, listenerRegistration); } } diff --git a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreDocumentReference.java b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreDocumentReference.java index f35d2056..026d04f5 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreDocumentReference.java +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreDocumentReference.java @@ -10,6 +10,7 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; +import com.google.firebase.firestore.DocumentListenOptions; import com.google.firebase.firestore.DocumentReference; import com.google.firebase.firestore.DocumentSnapshot; import com.google.firebase.firestore.EventListener; @@ -85,7 +86,7 @@ public class RNFirebaseFirestoreDocumentReference { } } - public void onSnapshot(final String listenerId) { + public void onSnapshot(final String listenerId, final ReadableMap docListenOptions) { if (!documentSnapshotListeners.containsKey(listenerId)) { final EventListener listener = new EventListener() { @Override @@ -101,7 +102,11 @@ public class RNFirebaseFirestoreDocumentReference { } } }; - ListenerRegistration listenerRegistration = this.ref.addSnapshotListener(listener); + DocumentListenOptions options = new DocumentListenOptions(); + if (docListenOptions != null && docListenOptions.hasKey("includeMetadataChanges") && docListenOptions.getBoolean("includeMetadataChanges")) { + options.includeMetadataChanges(); + } + ListenerRegistration listenerRegistration = this.ref.addSnapshotListener(options, listener); documentSnapshotListeners.put(listenerId, listenerRegistration); } } diff --git a/index.d.ts b/index.d.ts index 9f50442f..9272a526 100644 --- a/index.d.ts +++ b/index.d.ts @@ -51,6 +51,8 @@ declare module "react-native-firebase" { static initializeApp(options?: any | RNFirebase.configurationOptions, name?: string): FireBase; + static app(name?: string): FireBase; + [key: string]: any; } diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestore.m b/ios/RNFirebase/firestore/RNFirebaseFirestore.m index 9a7df690..ed056a1c 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestore.m +++ b/ios/RNFirebase/firestore/RNFirebaseFirestore.m @@ -42,9 +42,10 @@ RCT_EXPORT_METHOD(collectionOnSnapshot:(NSString *) appName filters:(NSArray *) filters orders:(NSArray *) orders options:(NSDictionary *) options - listenerId:(nonnull NSString *) listenerId) { + listenerId:(nonnull NSString *) listenerId + queryListenOptions:(NSDictionary *) queryListenOptions) { RNFirebaseFirestoreCollectionReference *ref = [self getCollectionForAppPath:appName path:path filters:filters orders:orders options:options]; - [ref onSnapshot:listenerId]; + [ref onSnapshot:listenerId queryListenOptions:queryListenOptions]; } RCT_EXPORT_METHOD(documentBatch:(NSString *) appName @@ -128,9 +129,10 @@ RCT_EXPORT_METHOD(documentOffSnapshot:(NSString *) appName RCT_EXPORT_METHOD(documentOnSnapshot:(NSString *) appName path:(NSString *) path - listenerId:(nonnull NSString *) listenerId) { + listenerId:(nonnull NSString *) listenerId + docListenOptions:(NSDictionary *) docListenOptions) { RNFirebaseFirestoreDocumentReference *ref = [self getDocumentForAppPath:appName path:path]; - [ref onSnapshot:listenerId]; + [ref onSnapshot:listenerId docListenOptions:docListenOptions]; } RCT_EXPORT_METHOD(documentSet:(NSString *) appName diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.h b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.h index b03904b4..7371f2b1 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.h +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.h @@ -22,7 +22,7 @@ - (id)initWithPathAndModifiers:(RCTEventEmitter *)emitter app:(NSString *)app path:(NSString *)path filters:(NSArray *)filters orders:(NSArray *)orders options:(NSDictionary *)options; - (void)get:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; + (void)offSnapshot:(NSString *)listenerId; -- (void)onSnapshot:(NSString *)listenerId; +- (void)onSnapshot:(NSString *)listenerId queryListenOptions:(NSDictionary *) queryListenOptions; + (NSDictionary *)snapshotToDictionary:(FIRQuerySnapshot *)querySnapshot; @end diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m index 1cc0e3ab..64f162bd 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m @@ -49,7 +49,8 @@ static NSMutableDictionary *_listeners; } } -- (void)onSnapshot:(NSString *) listenerId { +- (void)onSnapshot:(NSString *) listenerId +queryListenOptions:(NSDictionary *) queryListenOptions { if (_listeners[listenerId] == nil) { id listenerBlock = ^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) { if (error) { @@ -63,7 +64,18 @@ static NSMutableDictionary *_listeners; [self handleQuerySnapshotEvent:listenerId querySnapshot:snapshot]; } }; - id listener = [_query addSnapshotListener:listenerBlock]; + + FIRQueryListenOptions *options = [[FIRQueryListenOptions alloc] init]; + if (queryListenOptions) { + if (queryListenOptions[@"includeDocumentMetadataChanges"]) { + [options includeDocumentMetadataChanges:TRUE]; + } + if (queryListenOptions[@"includeQueryMetadataChanges"]) { + [options includeQueryMetadataChanges:TRUE]; + } + } + + id listener = [_query addSnapshotListenerWithOptions:options listener:listenerBlock]; _listeners[listenerId] = listener; } } diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h index 53fdab87..238388f6 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.h @@ -22,7 +22,7 @@ - (void)delete:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; - (void)get:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; + (void)offSnapshot:(NSString *)listenerId; -- (void)onSnapshot:(NSString *)listenerId; +- (void)onSnapshot:(NSString *)listenerId docListenOptions:(NSDictionary *) docListenOptions; - (void)set:(NSDictionary *)data options:(NSDictionary *)options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; - (void)update:(NSDictionary *)data resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; - (BOOL)hasListeners; diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m index 3f3c0d16..0a458731 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreDocumentReference.m @@ -61,7 +61,8 @@ static NSMutableDictionary *_listeners; } } -- (void)onSnapshot:(NSString *) listenerId { +- (void)onSnapshot:(NSString *) listenerId + docListenOptions:(NSDictionary *) docListenOptions { if (_listeners[listenerId] == nil) { id listenerBlock = ^(FIRDocumentSnapshot * _Nullable snapshot, NSError * _Nullable error) { if (error) { @@ -75,8 +76,11 @@ static NSMutableDictionary *_listeners; [self handleDocumentSnapshotEvent:listenerId documentSnapshot:snapshot]; } }; - - id listener = [_ref addSnapshotListener:listenerBlock]; + FIRDocumentListenOptions *options = [[FIRDocumentListenOptions alloc] init]; + if (docListenOptions && docListenOptions[@"includeMetadataChanges"]) { + [options includeMetadataChanges:TRUE]; + } + id listener = [_ref addSnapshotListenerWithOptions:options listener:listenerBlock]; _listeners[listenerId] = listener; } } diff --git a/lib/modules/firestore/DocumentReference.js b/lib/modules/firestore/DocumentReference.js index c5cc9634..986f6165 100644 --- a/lib/modules/firestore/DocumentReference.js +++ b/lib/modules/firestore/DocumentReference.js @@ -5,12 +5,21 @@ import CollectionReference from './CollectionReference'; import DocumentSnapshot from './DocumentSnapshot'; import Path from './Path'; -import { firestoreAutoId } from '../../utils'; +import { firestoreAutoId, isFunction, isObject, isString } from '../../utils'; export type WriteOptions = { merge?: boolean, } +type DocumentListenOptions = { + includeMetadataChanges: boolean, +} + +type Observer = { + next: (DocumentSnapshot) => void, + error?: (Object) => void, +} + /** * @class DocumentReference */ @@ -60,13 +69,64 @@ export default class DocumentReference { .then(result => new DocumentSnapshot(this._firestore, result)); } - onSnapshot(onNext: Function, onError?: Function): () => void { - // TODO: Validation + onSnapshot( + optionsOrObserverOrOnNext: DocumentListenOptions | Observer | (DocumentSnapshot) => void, + observerOrOnNextOrOnError?: Observer | (DocumentSnapshot) => void | (Object) => void, + onError?: (Object) => void + ) { + let observer = {}; + let docListenOptions = {}; + // Called with: onNext, ?onError + if (isFunction(optionsOrObserverOrOnNext)) { + observer.next = optionsOrObserverOrOnNext; + if (observerOrOnNextOrOnError && !isFunction(observerOrOnNextOrOnError)) { + throw new Error('DocumentReference.onSnapshot failed: Second argument must be a valid function.'); + } + observer.error = observerOrOnNextOrOnError; + } else if (optionsOrObserverOrOnNext && isObject(optionsOrObserverOrOnNext)) { + // Called with: Observer + if (optionsOrObserverOrOnNext.next) { + if (isFunction(optionsOrObserverOrOnNext.next)) { + if (optionsOrObserverOrOnNext.error && !isFunction(optionsOrObserverOrOnNext.error)) { + throw new Error('DocumentReference.onSnapshot failed: Observer.error must be a valid function.'); + } + observer = optionsOrObserverOrOnNext; + } else { + throw new Error('DocumentReference.onSnapshot failed: Observer.next must be a valid function.'); + } + } else if (optionsOrObserverOrOnNext.includeMetadataChanges) { + docListenOptions = optionsOrObserverOrOnNext; + // Called with: Options, onNext, ?onError + if (isFunction(observerOrOnNextOrOnError)) { + observer.next = observerOrOnNextOrOnError; + if (onError && !isFunction(onError)) { + throw new Error('DocumentReference.onSnapshot failed: Third argument must be a valid function.'); + } + observer.error = onError; + // Called with Options, Observer + } else if (observerOrOnNextOrOnError && isObject(observerOrOnNextOrOnError) && observerOrOnNextOrOnError.next) { + if (isFunction(observerOrOnNextOrOnError.next)) { + if (observerOrOnNextOrOnError.error && !isFunction(observerOrOnNextOrOnError.error)) { + throw new Error('DocumentReference.onSnapshot failed: Observer.error must be a valid function.'); + } + observer = observerOrOnNextOrOnError; + } else { + throw new Error('DocumentReference.onSnapshot failed: Observer.next must be a valid function.'); + } + } else { + throw new Error('DocumentReference.onSnapshot failed: Second argument must be a function or observer.'); + } + } else { + throw new Error('DocumentReference.onSnapshot failed: First argument must be a function, observer or options.'); + } + } else { + throw new Error('DocumentReference.onSnapshot failed: Called with invalid arguments.'); + } const listenerId = firestoreAutoId(); const listener = (nativeDocumentSnapshot) => { const documentSnapshot = new DocumentSnapshot(this, nativeDocumentSnapshot); - onNext(documentSnapshot); + observer.next(documentSnapshot); }; // Listen to snapshot events @@ -76,16 +136,16 @@ export default class DocumentReference { ); // Listen for snapshot error events - if (onError) { + if (observer.error) { this._firestore.on( this._firestore._getAppEventName(`onDocumentSnapshotError:${listenerId}`), - onError, + observer.error, ); } // Add the native listener this._firestore._native - .documentOnSnapshot(this.path, listenerId); + .documentOnSnapshot(this.path, listenerId, docListenOptions); // Return an unsubscribe method return this._offDocumentSnapshot.bind(this, listenerId, listener); @@ -96,7 +156,25 @@ export default class DocumentReference { .documentSet(this.path, data, writeOptions); } - update(data: Object): Promise { + update(...args: Object | string[]): Promise { + let data = {}; + if (args.length === 1) { + if (!isObject(args[0])) { + throw new Error('DocumentReference.update failed: If using a single argument, it must be an object.'); + } + data = args[0]; + } else if (args.length % 2 === 1) { + throw new Error('DocumentReference.update failed: Must have either a single object argument, or equal numbers of key/value pairs.'); + } else { + for (let i = 0; i < args.length; i += 2) { + const key = args[i]; + const value = args[i + 1]; + if (!isString(key)) { + throw new Error(`DocumentReference.update failed: Argument at index ${i} must be a string`); + } + data[key] = value; + } + } return this._firestore._native .documentUpdate(this.path, data); } diff --git a/lib/modules/firestore/Query.js b/lib/modules/firestore/Query.js index cdf5daa5..74d1f449 100644 --- a/lib/modules/firestore/Query.js +++ b/lib/modules/firestore/Query.js @@ -5,8 +5,7 @@ import DocumentSnapshot from './DocumentSnapshot'; import Path from './Path'; import QuerySnapshot from './QuerySnapshot'; -import INTERNALS from '../../internals'; -import { firestoreAutoId } from '../../utils'; +import { firestoreAutoId, isFunction, isObject } from '../../utils'; const DIRECTIONS = { ASC: 'ASCENDING', @@ -44,6 +43,15 @@ type QueryOptions = { startAt?: any[], } export type Operator = '<' | '<=' | '=' | '==' | '>' | '>='; +type QueryListenOptions = { + includeQueryMetadataChanges: boolean, + includeQueryMetadataChanges: boolean, +} + +type Observer = { + next: (DocumentSnapshot) => void, + error?: (Object) => void, +} /** * @class Query @@ -116,13 +124,65 @@ export default class Query { this._fieldOrders, options); } - onSnapshot(onNext: () => any, onError?: () => any): () => void { - // TODO: Validation + onSnapshot( + optionsOrObserverOrOnNext: QueryListenOptions | Observer | (DocumentSnapshot) => void, + observerOrOnNextOrOnError?: Observer | (DocumentSnapshot) => void | (Object) => void, + onError?: (Object) => void, + ) { + let observer = {}; + let queryListenOptions = {}; + // Called with: onNext, ?onError + if (isFunction(optionsOrObserverOrOnNext)) { + observer.next = optionsOrObserverOrOnNext; + if (observerOrOnNextOrOnError && !isFunction(observerOrOnNextOrOnError)) { + throw new Error('Query.onSnapshot failed: Second argument must be a valid function.'); + } + observer.error = observerOrOnNextOrOnError; + } else if (optionsOrObserverOrOnNext && isObject(optionsOrObserverOrOnNext)) { + // Called with: Observer + if (optionsOrObserverOrOnNext.next) { + if (isFunction(optionsOrObserverOrOnNext.next)) { + if (optionsOrObserverOrOnNext.error && !isFunction(optionsOrObserverOrOnNext.error)) { + throw new Error('Query.onSnapshot failed: Observer.error must be a valid function.'); + } + observer = optionsOrObserverOrOnNext; + } else { + throw new Error('Query.onSnapshot failed: Observer.next must be a valid function.'); + } + } else if (optionsOrObserverOrOnNext.includeDocumentMetadataChanges || optionsOrObserverOrOnNext.includeQueryMetadataChanges) { + queryListenOptions = optionsOrObserverOrOnNext; + // Called with: Options, onNext, ?onError + if (isFunction(observerOrOnNextOrOnError)) { + observer.next = observerOrOnNextOrOnError; + if (onError && !isFunction(onError)) { + throw new Error('Query.onSnapshot failed: Third argument must be a valid function.'); + } + observer.error = onError; + // Called with Options, Observer + } else if (observerOrOnNextOrOnError && isObject(observerOrOnNextOrOnError) && observerOrOnNextOrOnError.next) { + if (isFunction(observerOrOnNextOrOnError.next)) { + if (observerOrOnNextOrOnError.error && !isFunction(observerOrOnNextOrOnError.error)) { + throw new Error('Query.onSnapshot failed: Observer.error must be a valid function.'); + } + observer = observerOrOnNextOrOnError; + } else { + throw new Error('Query.onSnapshot failed: Observer.next must be a valid function.'); + } + } else { + throw new Error('Query.onSnapshot failed: Second argument must be a function or observer.'); + } + } else { + throw new Error('Query.onSnapshot failed: First argument must be a function, observer or options.'); + } + } else { + throw new Error('Query.onSnapshot failed: Called with invalid arguments.'); + } + const listenerId = firestoreAutoId(); const listener = (nativeQuerySnapshot) => { const querySnapshot = new QuerySnapshot(this._firestore, this, nativeQuerySnapshot); - onNext(querySnapshot); + observer.next(querySnapshot); }; // Listen to snapshot events @@ -132,10 +192,10 @@ export default class Query { ); // Listen for snapshot error events - if (onError) { + if (observer.error) { this._firestore.on( this._firestore._getAppEventName(`onQuerySnapshotError:${listenerId}`), - onError, + observer.error, ); } @@ -146,7 +206,8 @@ export default class Query { this._fieldFilters, this._fieldOrders, this._queryOptions, - listenerId + listenerId, + queryListenOptions, ); // Return an unsubscribe method diff --git a/lib/modules/firestore/WriteBatch.js b/lib/modules/firestore/WriteBatch.js index 7ada92d9..0ed02094 100644 --- a/lib/modules/firestore/WriteBatch.js +++ b/lib/modules/firestore/WriteBatch.js @@ -3,6 +3,7 @@ * WriteBatch representation wrapper */ import DocumentReference from './DocumentReference'; +import { isObject, isString } from '../../utils'; import type { WriteOptions } from './DocumentReference'; @@ -58,11 +59,27 @@ export default class WriteBatch { return this; } - // TODO: Update to new method signature - update(docRef: DocumentReference, data: Object): WriteBatch { + update(docRef: DocumentReference, ...args: Object | string[]): WriteBatch { // TODO: Validation // validate.isDocumentReference('docRef', docRef); - // validate.isDocument('data', data, true); + let data = {}; + if (args.length === 1) { + if (!isObject(args[0])) { + throw new Error('DocumentReference.update failed: If using two arguments, the second must be an object.'); + } + data = args[0]; + } else if (args.length % 2 === 1) { + throw new Error('DocumentReference.update failed: Must have a document reference, followed by either a single object argument, or equal numbers of key/value pairs.'); + } else { + for (let i = 0; i < args.length; i += 2) { + const key = args[i]; + const value = args[i + 1]; + if (!isString(key)) { + throw new Error(`DocumentReference.update failed: Argument at index ${i + 1} must be a string`); + } + data[key] = value; + } + } this._writes.push({ data, diff --git a/tests/ios/Podfile.lock b/tests/ios/Podfile.lock index eae603f0..198a5f69 100644 --- a/tests/ios/Podfile.lock +++ b/tests/ios/Podfile.lock @@ -129,30 +129,30 @@ PODS: - nanopb/decode (0.3.8) - nanopb/encode (0.3.8) - Protobuf (3.4.0) - - React (0.49.0-rc.6): - - React/Core (= 0.49.0-rc.6) - - React/BatchedBridge (0.49.0-rc.6): + - React (0.49.1): + - React/Core (= 0.49.1) + - React/BatchedBridge (0.49.1): - React/Core - React/cxxreact_legacy - - React/Core (0.49.0-rc.6): - - yoga (= 0.49.0-rc.6.React) - - React/cxxreact_legacy (0.49.0-rc.6): + - React/Core (0.49.1): + - yoga (= 0.49.1.React) + - React/cxxreact_legacy (0.49.1): - React/jschelpers_legacy - - React/fishhook (0.49.0-rc.6) - - React/jschelpers_legacy (0.49.0-rc.6) - - React/RCTBlob (0.49.0-rc.6): + - React/fishhook (0.49.1) + - React/jschelpers_legacy (0.49.1) + - React/RCTBlob (0.49.1): - React/Core - - React/RCTNetwork (0.49.0-rc.6): + - React/RCTNetwork (0.49.1): - React/Core - - React/RCTText (0.49.0-rc.6): + - React/RCTText (0.49.1): - React/Core - - React/RCTWebSocket (0.49.0-rc.6): + - React/RCTWebSocket (0.49.1): - React/Core - React/fishhook - React/RCTBlob - RNFirebase (3.0.0): - React - - yoga (0.49.0-rc.6.React) + - yoga (0.49.1.React) DEPENDENCIES: - Firebase/AdMob @@ -207,9 +207,9 @@ SPEC CHECKSUMS: leveldb-library: 10fb39c39e243db4af1828441162405bbcec1404 nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3 Protobuf: 03eef2ee0b674770735cf79d9c4d3659cf6908e8 - React: e6ef6a41ec6dd1b7941417d60ca582bf5e9c739d - RNFirebase: 2ceda3aef595ea1379bf5bfcc1cd9ee7d6c05d3b - yoga: f9485d2ebf0ca773db2d727ea71b1aa8c9f3e075 + React: cf892fb84b7d06bf5fea7f328e554c6dcabe85ee + RNFirebase: 901a473c68fcbaa28125c56a911923f2fbe5d61b + yoga: 3abf02d6d9aeeb139b4c930eb1367feae690a35a PODFILE CHECKSUM: b5674be55653f5dda937c8b794d0479900643d45 diff --git a/tests/src/tests/firestore/collectionReferenceTests.js b/tests/src/tests/firestore/collectionReferenceTests.js index ab2a1f92..1145c03d 100644 --- a/tests/src/tests/firestore/collectionReferenceTests.js +++ b/tests/src/tests/firestore/collectionReferenceTests.js @@ -53,262 +53,360 @@ function collectionReferenceTests({ describe, it, context, firebase }) { }); context('onSnapshot()', () => { - it('calls callback with the initial data and then when document changes', () => { - return new Promise(async (resolve) => { - const collectionRef = firebase.native.firestore().collection('document-tests'); - const currentDocValue = { name: 'doc1' }; - const newDocValue = { name: 'updated' }; + it('calls callback with the initial data and then when document changes', async () => { + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; - const callback = sinon.spy(); + const callback = sinon.spy(); - // Test + // Test - let unsubscribe; - await new Promise((resolve2) => { - unsubscribe = collectionRef.onSnapshot((snapshot) => { - snapshot.forEach(doc => callback(doc.data())); - resolve2(); - }); + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); }); - - callback.should.be.calledWith(currentDocValue); - - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - await docRef.set(newDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - // Assertions - - callback.should.be.calledWith(newDocValue); - callback.should.be.calledTwice(); - - // Tear down - - unsubscribe(); - - resolve(); }); + + callback.should.be.calledWith(currentDocValue); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDocValue); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); }); }); context('onSnapshot()', () => { - it('calls callback with the initial data and then when document is added', () => { - return new Promise(async (resolve) => { - const collectionRef = firebase.native.firestore().collection('document-tests'); - const currentDocValue = { name: 'doc1' }; - const newDocValue = { name: 'updated' }; + it('calls callback with the initial data and then when document is added', async () => { + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; - const callback = sinon.spy(); + const callback = sinon.spy(); - // Test + // Test - let unsubscribe; - await new Promise((resolve2) => { - unsubscribe = collectionRef.onSnapshot((snapshot) => { - snapshot.forEach(doc => callback(doc.data())); - resolve2(); - }); + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); }); - - callback.should.be.calledWith(currentDocValue); - - const docRef = firebase.native.firestore().doc('document-tests/doc2'); - await docRef.set(newDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - // Assertions - - callback.should.be.calledWith(currentDocValue); - callback.should.be.calledWith(newDocValue); - callback.should.be.calledThrice(); - - // Tear down - - unsubscribe(); - - resolve(); }); + + callback.should.be.calledWith(currentDocValue); + + const docRef = firebase.native.firestore().doc('document-tests/doc2'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(currentDocValue); + callback.should.be.calledWith(newDocValue); + callback.should.be.calledThrice(); + + // Tear down + + unsubscribe(); }); }); context('onSnapshot()', () => { it('doesn\'t call callback when the ref is updated with the same value', async () => { - return new Promise(async (resolve) => { - const collectionRef = firebase.native.firestore().collection('document-tests'); - const currentDocValue = { name: 'doc1' }; + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; - const callback = sinon.spy(); + const callback = sinon.spy(); - // Test + // Test - let unsubscribe; - await new Promise((resolve2) => { - unsubscribe = collectionRef.onSnapshot((snapshot) => { + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); + }); + }); + + callback.should.be.calledWith(currentDocValue); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(currentDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledOnce(); // Callback is not called again + + // Tear down + + unsubscribe(); + }); + }); + + context('onSnapshot()', () => { + it('allows binding multiple callbacks to the same ref', async () => { + // Setup + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; + + const callbackA = sinon.spy(); + const callbackB = sinon.spy(); + + // Test + let unsubscribeA; + let unsubscribeB; + await new Promise((resolve2) => { + unsubscribeA = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callbackA(doc.data())); + resolve2(); + }); + }); + await new Promise((resolve2) => { + unsubscribeB = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callbackB(doc.data())); + resolve2(); + }); + }); + + callbackA.should.be.calledWith(currentDocValue); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(currentDocValue); + callbackB.should.be.calledOnce(); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledWith(newDocValue); + callbackB.should.be.calledWith(newDocValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Tear down + + unsubscribeA(); + unsubscribeB(); + }); + }); + + context('onSnapshot()', () => { + it('listener stops listening when unsubscribed', async () => { + // Setup + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; + + const callbackA = sinon.spy(); + const callbackB = sinon.spy(); + + // Test + let unsubscribeA; + let unsubscribeB; + await new Promise((resolve2) => { + unsubscribeA = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callbackA(doc.data())); + resolve2(); + }); + }); + await new Promise((resolve2) => { + unsubscribeB = collectionRef.onSnapshot((snapshot) => { + snapshot.forEach(doc => callbackB(doc.data())); + resolve2(); + }); + }); + + callbackA.should.be.calledWith(currentDocValue); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(currentDocValue); + callbackB.should.be.calledOnce(); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledWith(newDocValue); + callbackB.should.be.calledWith(newDocValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Unsubscribe A + + unsubscribeA(); + + await docRef.set(currentDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackB.should.be.calledWith(currentDocValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + + // Unsubscribe B + + unsubscribeB(); + + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + }); + }); + + context('onSnapshot()', () => { + it('supports options and callback', async () => { + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = collectionRef.onSnapshot({ includeQueryMetadataChanges: true, includeDocumentMetadataChanges: true }, (snapshot) => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); + }); + }); + + callback.should.be.calledWith(currentDocValue); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDocValue); + + // Tear down + + unsubscribe(); + }); + }); + + context('onSnapshot()', () => { + it('supports observer', async () => { + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise((resolve2) => { + const observer = { + next: (snapshot) => { snapshot.forEach(doc => callback(doc.data())); resolve2(); - }); - }); - - callback.should.be.calledWith(currentDocValue); - - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - await docRef.set(currentDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - // Assertions - - callback.should.be.calledOnce(); // Callback is not called again - - // Tear down - - unsubscribe(); - - resolve(); + }, + }; + unsubscribe = collectionRef.onSnapshot(observer); }); + + callback.should.be.calledWith(currentDocValue); + + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDocValue); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); }); }); context('onSnapshot()', () => { - it('allows binding multiple callbacks to the same ref', () => { - return new Promise(async (resolve) => { - // Setup - const collectionRef = firebase.native.firestore().collection('document-tests'); - const currentDocValue = { name: 'doc1' }; - const newDocValue = { name: 'updated' }; + it('supports options and observer', async () => { + const collectionRef = firebase.native.firestore().collection('document-tests'); + const currentDocValue = { name: 'doc1' }; + const newDocValue = { name: 'updated' }; - const callbackA = sinon.spy(); - const callbackB = sinon.spy(); + const callback = sinon.spy(); - // Test - let unsubscribeA; - let unsubscribeB; - await new Promise((resolve2) => { - unsubscribeA = collectionRef.onSnapshot((snapshot) => { - snapshot.forEach(doc => callbackA(doc.data())); + // Test + + let unsubscribe; + await new Promise((resolve2) => { + const observer = { + next: (snapshot) => { + snapshot.forEach(doc => callback(doc.data())); resolve2(); - }); - }); - await new Promise((resolve2) => { - unsubscribeB = collectionRef.onSnapshot((snapshot) => { - snapshot.forEach(doc => callbackB(doc.data())); - resolve2(); - }); - }); - - callbackA.should.be.calledWith(currentDocValue); - callbackA.should.be.calledOnce(); - - callbackB.should.be.calledWith(currentDocValue); - callbackB.should.be.calledOnce(); - - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - await docRef.set(newDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackA.should.be.calledWith(newDocValue); - callbackB.should.be.calledWith(newDocValue); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledTwice(); - - // Tear down - - unsubscribeA(); - unsubscribeB(); - - resolve(); + }, + }; + unsubscribe = collectionRef.onSnapshot({ includeQueryMetadataChanges: true, includeDocumentMetadataChanges: true }, observer); }); - }); - }); - context('onSnapshot()', () => { - it('listener stops listening when unsubscribed', () => { - return new Promise(async (resolve) => { - // Setup - const collectionRef = firebase.native.firestore().collection('document-tests'); - const currentDocValue = { name: 'doc1' }; - const newDocValue = { name: 'updated' }; + callback.should.be.calledWith(currentDocValue); - const callbackA = sinon.spy(); - const callbackB = sinon.spy(); + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + await docRef.set(newDocValue); - // Test - let unsubscribeA; - let unsubscribeB; - await new Promise((resolve2) => { - unsubscribeA = collectionRef.onSnapshot((snapshot) => { - snapshot.forEach(doc => callbackA(doc.data())); - resolve2(); - }); - }); - await new Promise((resolve2) => { - unsubscribeB = collectionRef.onSnapshot((snapshot) => { - snapshot.forEach(doc => callbackB(doc.data())); - resolve2(); - }); - }); - - callbackA.should.be.calledWith(currentDocValue); - callbackA.should.be.calledOnce(); - - callbackB.should.be.calledWith(currentDocValue); - callbackB.should.be.calledOnce(); - - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - await docRef.set(newDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackA.should.be.calledWith(newDocValue); - callbackB.should.be.calledWith(newDocValue); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledTwice(); - - // Unsubscribe A - - unsubscribeA(); - - await docRef.set(currentDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackB.should.be.calledWith(currentDocValue); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledThrice(); - - // Unsubscribe B - - unsubscribeB(); - - await docRef.set(newDocValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledThrice(); - - resolve(); + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); }); + + // Assertions + + callback.should.be.calledWith(newDocValue); + + // Tear down + + unsubscribe(); }); }); diff --git a/tests/src/tests/firestore/documentReferenceTests.js b/tests/src/tests/firestore/documentReferenceTests.js index e527c226..4fbd4ca4 100644 --- a/tests/src/tests/firestore/documentReferenceTests.js +++ b/tests/src/tests/firestore/documentReferenceTests.js @@ -2,7 +2,7 @@ import sinon from 'sinon'; import 'should-sinon'; import should from 'should'; -function collectionReferenceTests({ describe, it, context, firebase }) { +function documentReferenceTests({ describe, it, context, firebase }) { describe('DocumentReference', () => { context('class', () => { it('should return instance methods', () => { @@ -29,219 +29,324 @@ function collectionReferenceTests({ describe, it, context, firebase }) { }); context('onSnapshot()', () => { - it('calls callback with the initial data and then when value changes', () => { - return new Promise(async (resolve) => { - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - const currentDataValue = { name: 'doc1' }; - const newDataValue = { name: 'updated' }; + it('calls callback with the initial data and then when value changes', async () => { + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; - const callback = sinon.spy(); + const callback = sinon.spy(); - // Test + // Test - let unsubscribe; - await new Promise((resolve2) => { - unsubscribe = docRef.onSnapshot((snapshot) => { - callback(snapshot.data()); - resolve2(); - }); + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = docRef.onSnapshot((snapshot) => { + callback(snapshot.data()); + resolve2(); }); - - callback.should.be.calledWith(currentDataValue); - - // Update the document - - await docRef.set(newDataValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - // Assertions - - callback.should.be.calledWith(newDataValue); - callback.should.be.calledTwice(); - - // Tear down - - unsubscribe(); - - resolve(); }); + + callback.should.be.calledWith(currentDataValue); + + // Update the document + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDataValue); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); }); }); context('onSnapshot()', () => { it('doesn\'t call callback when the ref is updated with the same value', async () => { - return new Promise(async (resolve) => { - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - const currentDataValue = { name: 'doc1' }; + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; - const callback = sinon.spy(); + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = docRef.onSnapshot((snapshot) => { + callback(snapshot.data()); + resolve2(); + }); + }); + + callback.should.be.calledWith(currentDataValue); + + await docRef.set(currentDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledOnce(); // Callback is not called again + + // Tear down + + unsubscribe(); + }); + }); + + context('onSnapshot()', () => { + it('allows binding multiple callbacks to the same ref', async () => { + // Setup + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; + + const callbackA = sinon.spy(); + const callbackB = sinon.spy(); + + // Test + let unsubscribeA; + let unsubscribeB; + await new Promise((resolve2) => { + unsubscribeA = docRef.onSnapshot((snapshot) => { + callbackA(snapshot.data()); + resolve2(); + }); + }); + + await new Promise((resolve2) => { + unsubscribeB = docRef.onSnapshot((snapshot) => { + callbackB(snapshot.data()); + resolve2(); + }); + }); + + callbackA.should.be.calledWith(currentDataValue); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(currentDataValue); + callbackB.should.be.calledOnce(); + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledWith(newDataValue); + callbackB.should.be.calledWith(newDataValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Tear down + + unsubscribeA(); + unsubscribeB(); + }); + }); + + context('onSnapshot()', () => { + it('listener stops listening when unsubscribed', async () => { + // Setup + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; + + const callbackA = sinon.spy(); + const callbackB = sinon.spy(); + + // Test + let unsubscribeA; + let unsubscribeB; + await new Promise((resolve2) => { + unsubscribeA = docRef.onSnapshot((snapshot) => { + callbackA(snapshot.data()); + resolve2(); + }); + }); + + await new Promise((resolve2) => { + unsubscribeB = docRef.onSnapshot((snapshot) => { + callbackB(snapshot.data()); + resolve2(); + }); + }); + + callbackA.should.be.calledWith(currentDataValue); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(currentDataValue); + callbackB.should.be.calledOnce(); + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledWith(newDataValue); + callbackB.should.be.calledWith(newDataValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Unsubscribe A + + unsubscribeA(); + + await docRef.set(currentDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackB.should.be.calledWith(currentDataValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + + // Unsubscribe B + + unsubscribeB(); + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + }); + }); + + context('onSnapshot()', () => { + it('supports options and callbacks', async () => { + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; + + const callback = sinon.spy(); // Test - let unsubscribe; - await new Promise((resolve2) => { - unsubscribe = docRef.onSnapshot((snapshot) => { - callback(snapshot.data()); - resolve2(); - }); + let unsubscribe; + await new Promise((resolve2) => { + unsubscribe = docRef.onSnapshot({ includeMetadataChanges: true }, (snapshot) => { + callback(snapshot.data()); + resolve2(); }); + }); - callback.should.be.calledWith(currentDataValue); + callback.should.be.calledWith(currentDataValue); - await docRef.set(currentDataValue); + // Update the document - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); // Assertions - callback.should.be.calledOnce(); // Callback is not called again + callback.should.be.calledWith(newDataValue); // Tear down - unsubscribe(); - - resolve(); - }); + unsubscribe(); }); }); context('onSnapshot()', () => { - it('allows binding multiple callbacks to the same ref', () => { - return new Promise(async (resolve) => { - // Setup - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - const currentDataValue = { name: 'doc1' }; - const newDataValue = { name: 'updated' }; + it('supports observer', async () => { + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; - const callbackA = sinon.spy(); - const callbackB = sinon.spy(); + const callback = sinon.spy(); - // Test - let unsubscribeA; - let unsubscribeB; - await new Promise((resolve2) => { - unsubscribeA = docRef.onSnapshot((snapshot) => { - callbackA(snapshot.data()); + // Test + + let unsubscribe; + await new Promise((resolve2) => { + const observer = { + next: (snapshot) => { + callback(snapshot.data()); resolve2(); - }); - }); - - await new Promise((resolve2) => { - unsubscribeB = docRef.onSnapshot((snapshot) => { - callbackB(snapshot.data()); - resolve2(); - }); - }); - - callbackA.should.be.calledWith(currentDataValue); - callbackA.should.be.calledOnce(); - - callbackB.should.be.calledWith(currentDataValue); - callbackB.should.be.calledOnce(); - - await docRef.set(newDataValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackA.should.be.calledWith(newDataValue); - callbackB.should.be.calledWith(newDataValue); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledTwice(); - - // Tear down - - unsubscribeA(); - unsubscribeB(); - - resolve(); + }, + }; + unsubscribe = docRef.onSnapshot(observer); }); + + callback.should.be.calledWith(currentDataValue); + + // Update the document + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDataValue); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); }); }); context('onSnapshot()', () => { - it('listener stops listening when unsubscribed', () => { - return new Promise(async (resolve) => { - // Setup - const docRef = firebase.native.firestore().doc('document-tests/doc1'); - const currentDataValue = { name: 'doc1' }; - const newDataValue = { name: 'updated' }; + it('supports options and observer', async () => { + const docRef = firebase.native.firestore().doc('document-tests/doc1'); + const currentDataValue = { name: 'doc1' }; + const newDataValue = { name: 'updated' }; - const callbackA = sinon.spy(); - const callbackB = sinon.spy(); + const callback = sinon.spy(); - // Test - let unsubscribeA; - let unsubscribeB; - await new Promise((resolve2) => { - unsubscribeA = docRef.onSnapshot((snapshot) => { - callbackA(snapshot.data()); + // Test + + let unsubscribe; + await new Promise((resolve2) => { + const observer = { + next: (snapshot) => { + callback(snapshot.data()); resolve2(); - }); - }); - - await new Promise((resolve2) => { - unsubscribeB = docRef.onSnapshot((snapshot) => { - callbackB(snapshot.data()); - resolve2(); - }); - }); - - callbackA.should.be.calledWith(currentDataValue); - callbackA.should.be.calledOnce(); - - callbackB.should.be.calledWith(currentDataValue); - callbackB.should.be.calledOnce(); - - await docRef.set(newDataValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackA.should.be.calledWith(newDataValue); - callbackB.should.be.calledWith(newDataValue); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledTwice(); - - // Unsubscribe A - - unsubscribeA(); - - await docRef.set(currentDataValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackB.should.be.calledWith(currentDataValue); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledThrice(); - - // Unsubscribe B - - unsubscribeB(); - - await docRef.set(newDataValue); - - await new Promise((resolve2) => { - setTimeout(() => resolve2(), 5); - }); - - callbackA.should.be.calledTwice(); - callbackB.should.be.calledThrice(); - - resolve(); + }, + }; + unsubscribe = docRef.onSnapshot({ includeMetadataChanges: true }, observer); }); + + callback.should.be.calledWith(currentDataValue); + + // Update the document + + await docRef.set(newDataValue); + + await new Promise((resolve2) => { + setTimeout(() => resolve2(), 5); + }); + + // Assertions + + callback.should.be.calledWith(newDataValue); + + // Tear down + + unsubscribe(); }); }); @@ -283,10 +388,22 @@ function collectionReferenceTests({ describe, it, context, firebase }) { }); context('update()', () => { - it('should update Document', () => { + it('should update Document using object', () => { return firebase.native.firestore() .doc('document-tests/doc1') - .set({ name: 'updated' }) + .update({ name: 'updated' }) + .then(async () => { + const doc = await firebase.native.firestore().doc('document-tests/doc1').get(); + doc.data().name.should.equal('updated'); + }); + }); + }); + + context('update()', () => { + it('should update Document using key/value pairs', () => { + return firebase.native.firestore() + .doc('document-tests/doc1') + .update('name', 'updated') .then(async () => { const doc = await firebase.native.firestore().doc('document-tests/doc1').get(); doc.data().name.should.equal('updated'); @@ -296,4 +413,4 @@ function collectionReferenceTests({ describe, it, context, firebase }) { }); } -export default collectionReferenceTests; +export default documentReferenceTests; diff --git a/tests/src/tests/firestore/firestoreTests.js b/tests/src/tests/firestore/firestoreTests.js index 26a29893..29fc9703 100644 --- a/tests/src/tests/firestore/firestoreTests.js +++ b/tests/src/tests/firestore/firestoreTests.js @@ -36,7 +36,7 @@ function firestoreTests({ describe, it, context, firebase }) { .set(nycRef, { name: 'New York City' }) .set(sfRef, { name: 'San Francisco' }) .update(nycRef, { population: 1000000 }) - .update(sfRef, { name: 'San Fran' }) + .update(sfRef, 'name', 'San Fran') .set(lRef, { population: 3000000 }, { merge: true }) .delete(ayRef) .commit()