From 68fa17ed5636e31af98f6b25e86481608c85dc22 Mon Sep 17 00:00:00 2001 From: Salakar Date: Thu, 20 Sep 2018 10:31:24 +0100 Subject: [PATCH 01/12] 5.0.0-rc3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1af66b65..8a2a94fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-firebase", - "version": "5.0.0-rc2", + "version": "5.0.0-rc3", "author": "Invertase (http://invertase.io)", "description": "A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Admob, Analytics, Auth, Crash Reporting, Cloud Firestore, Database, Dynamic Links, Functions, Messaging (FCM), Remote Config, Storage and more.", "main": "dist/index.js", From 38b172835a5625f3fcde72399cb0280e5bf8d8c1 Mon Sep 17 00:00:00 2001 From: Salakar Date: Thu, 20 Sep 2018 13:19:20 +0100 Subject: [PATCH 02/12] [firestore][ios] 'array-contains' support --- .../RNFirebaseFirestoreCollectionReference.m | 406 +++++++++--------- 1 file changed, 208 insertions(+), 198 deletions(-) diff --git a/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m index 5b66c034..95540bc7 100644 --- a/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m +++ b/ios/RNFirebase/firestore/RNFirebaseFirestoreCollectionReference.m @@ -6,254 +6,264 @@ static NSMutableDictionary *_listeners; -- (id)initWithPathAndModifiers:(RCTEventEmitter *) emitter - appDisplayName:(NSString *) appDisplayName - path:(NSString *) path - filters:(NSArray *) filters - orders:(NSArray *) orders - options:(NSDictionary *) options { - self = [super init]; - if (self) { - _emitter = emitter; - _appDisplayName = appDisplayName; - _path = path; - _filters = filters; - _orders = orders; - _options = options; - _query = [self buildQuery]; - } - // Initialise the static listeners object if required - if (!_listeners) { - _listeners = [[NSMutableDictionary alloc] init]; - } - return self; +- (id)initWithPathAndModifiers:(RCTEventEmitter *)emitter + appDisplayName:(NSString *)appDisplayName + path:(NSString *)path + filters:(NSArray *)filters + orders:(NSArray *)orders + options:(NSDictionary *)options { + self = [super init]; + if (self) { + _emitter = emitter; + _appDisplayName = appDisplayName; + _path = path; + _filters = filters; + _orders = orders; + _options = options; + _query = [self buildQuery]; + } + // Initialise the static listeners object if required + if (!_listeners) { + _listeners = [[NSMutableDictionary alloc] init]; + } + return self; } -- (void)get:(NSDictionary *) getOptions - resolver:(RCTPromiseResolveBlock) resolve - rejecter:(RCTPromiseRejectBlock) reject { - FIRFirestoreSource source; - if (getOptions && getOptions[@"source"]) { - if ([getOptions[@"source"] isEqualToString:@"server"]) { - source = FIRFirestoreSourceServer; - } else if ([getOptions[@"source"] isEqualToString:@"cache"]) { - source = FIRFirestoreSourceCache; - } else { - source = FIRFirestoreSourceDefault; - } +- (void)get:(NSDictionary *)getOptions + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject { + FIRFirestoreSource source; + if (getOptions && getOptions[@"source"]) { + if ([getOptions[@"source"] isEqualToString:@"server"]) { + source = FIRFirestoreSourceServer; + } else if ([getOptions[@"source"] isEqualToString:@"cache"]) { + source = FIRFirestoreSourceCache; } else { - source = FIRFirestoreSourceDefault; + source = FIRFirestoreSourceDefault; } - [_query getDocumentsWithSource:source completion:^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) { - if (error) { - [RNFirebaseFirestore promiseRejectException:reject error:error]; - } else { - NSDictionary *data = [RNFirebaseFirestoreCollectionReference snapshotToDictionary:snapshot]; - resolve(data); - } - }]; + } else { + source = FIRFirestoreSourceDefault; + } + [_query getDocumentsWithSource:source completion:^(FIRQuerySnapshot *_Nullable snapshot, NSError *_Nullable error) { + if (error) { + [RNFirebaseFirestore promiseRejectException:reject error:error]; + } else { + NSDictionary *data = [RNFirebaseFirestoreCollectionReference snapshotToDictionary:snapshot]; + resolve(data); + } + }]; } -+ (void)offSnapshot:(NSString *) listenerId { - id listener = _listeners[listenerId]; - if (listener) { - [_listeners removeObjectForKey:listenerId]; - [listener remove]; - } ++ (void)offSnapshot:(NSString *)listenerId { + id listener = _listeners[listenerId]; + if (listener) { + [_listeners removeObjectForKey:listenerId]; + [listener remove]; + } } -- (void)onSnapshot:(NSString *) listenerId -queryListenOptions:(NSDictionary *) queryListenOptions { - if (_listeners[listenerId] == nil) { - id listenerBlock = ^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) { - if (error) { - id listener = _listeners[listenerId]; - if (listener) { - [_listeners removeObjectForKey:listenerId]; - [listener remove]; - } - [self handleQuerySnapshotError:listenerId error:error]; - } else { - [self handleQuerySnapshotEvent:listenerId querySnapshot:snapshot]; - } - }; - - bool includeMetadataChanges; - if (queryListenOptions && queryListenOptions[@"includeMetadataChanges"]) { - includeMetadataChanges = true; - } else { - includeMetadataChanges = false; +- (void)onSnapshot:(NSString *)listenerId +queryListenOptions:(NSDictionary *)queryListenOptions { + if (_listeners[listenerId] == nil) { + id listenerBlock = ^(FIRQuerySnapshot *_Nullable snapshot, NSError *_Nullable error) { + if (error) { + id listener = _listeners[listenerId]; + if (listener) { + [_listeners removeObjectForKey:listenerId]; + [listener remove]; } + [self handleQuerySnapshotError:listenerId error:error]; + } else { + [self handleQuerySnapshotEvent:listenerId querySnapshot:snapshot]; + } + }; - id listener = [_query addSnapshotListenerWithIncludeMetadataChanges:includeMetadataChanges listener:listenerBlock]; - _listeners[listenerId] = listener; + bool includeMetadataChanges; + if (queryListenOptions && queryListenOptions[@"includeMetadataChanges"]) { + includeMetadataChanges = true; + } else { + includeMetadataChanges = false; } + + id + listener = [_query addSnapshotListenerWithIncludeMetadataChanges:includeMetadataChanges listener:listenerBlock]; + _listeners[listenerId] = listener; + } } - (FIRQuery *)buildQuery { - FIRFirestore *firestore = [RNFirebaseFirestore getFirestoreForApp:_appDisplayName]; - FIRQuery *query = (FIRQuery*)[firestore collectionWithPath:_path]; - query = [self applyFilters:firestore query:query]; - query = [self applyOrders:query]; - query = [self applyOptions:firestore query:query]; + FIRFirestore *firestore = [RNFirebaseFirestore getFirestoreForApp:_appDisplayName]; + FIRQuery *query = (FIRQuery *) [firestore collectionWithPath:_path]; + query = [self applyFilters:firestore query:query]; + query = [self applyOrders:query]; + query = [self applyOptions:firestore query:query]; - return query; + return query; } -- (FIRQuery *)applyFilters:(FIRFirestore *) firestore - query:(FIRQuery *) query { - for (NSDictionary *filter in _filters) { - NSDictionary *fieldPathDictionary = filter[@"fieldPath"]; - NSString *fieldPathType = fieldPathDictionary[@"type"]; - NSString *operator = filter[@"operator"]; - NSDictionary *jsValue = filter[@"value"]; - id value = [RNFirebaseFirestoreDocumentReference parseJSTypeMap:firestore jsTypeMap:jsValue]; +- (FIRQuery *)applyFilters:(FIRFirestore *)firestore + query:(FIRQuery *)query { + for (NSDictionary *filter in _filters) { + NSDictionary *fieldPathDictionary = filter[@"fieldPath"]; + NSString *fieldPathType = fieldPathDictionary[@"type"]; + NSString *operator = filter[@"operator"]; + NSDictionary *jsValue = filter[@"value"]; + id value = [RNFirebaseFirestoreDocumentReference parseJSTypeMap:firestore jsTypeMap:jsValue]; - if ([fieldPathType isEqualToString:@"string"]) { - NSString *fieldPath = fieldPathDictionary[@"string"]; - if ([operator isEqualToString:@"EQUAL"]) { - query = [query queryWhereField:fieldPath isEqualTo:value]; - } else if ([operator isEqualToString:@"GREATER_THAN"]) { - query = [query queryWhereField:fieldPath isGreaterThan:value]; - } else if ([operator isEqualToString:@"GREATER_THAN_OR_EQUAL"]) { - query = [query queryWhereField:fieldPath isGreaterThanOrEqualTo:value]; - } else if ([operator isEqualToString:@"LESS_THAN"]) { - query = [query queryWhereField:fieldPath isLessThan:value]; - } else if ([operator isEqualToString:@"LESS_THAN_OR_EQUAL"]) { - query = [query queryWhereField:fieldPath isLessThanOrEqualTo:value]; - } - } else { - NSArray *fieldPathElements = fieldPathDictionary[@"elements"]; - FIRFieldPath *fieldPath = [[FIRFieldPath alloc] initWithFields:fieldPathElements]; - if ([operator isEqualToString:@"EQUAL"]) { - query = [query queryWhereFieldPath:fieldPath isEqualTo:value]; - } else if ([operator isEqualToString:@"GREATER_THAN"]) { - query = [query queryWhereFieldPath:fieldPath isGreaterThan:value]; - } else if ([operator isEqualToString:@"GREATER_THAN_OR_EQUAL"]) { - query = [query queryWhereFieldPath:fieldPath isGreaterThanOrEqualTo:value]; - } else if ([operator isEqualToString:@"LESS_THAN"]) { - query = [query queryWhereFieldPath:fieldPath isLessThan:value]; - } else if ([operator isEqualToString:@"LESS_THAN_OR_EQUAL"]) { - query = [query queryWhereFieldPath:fieldPath isLessThanOrEqualTo:value]; - } - } + if ([fieldPathType isEqualToString:@"string"]) { + NSString *fieldPath = fieldPathDictionary[@"string"]; + if ([operator isEqualToString:@"EQUAL"]) { + query = [query queryWhereField:fieldPath isEqualTo:value]; + } else if ([operator isEqualToString:@"GREATER_THAN"]) { + query = [query queryWhereField:fieldPath isGreaterThan:value]; + } else if ([operator isEqualToString:@"GREATER_THAN_OR_EQUAL"]) { + query = [query queryWhereField:fieldPath isGreaterThanOrEqualTo:value]; + } else if ([operator isEqualToString:@"LESS_THAN"]) { + query = [query queryWhereField:fieldPath isLessThan:value]; + } else if ([operator isEqualToString:@"LESS_THAN_OR_EQUAL"]) { + query = [query queryWhereField:fieldPath isLessThanOrEqualTo:value]; + } else if ([operator isEqualToString:@"ARRAY_CONTAINS"]) { + query = [query queryWhereField:fieldPath arrayContains:value]; + } + } else { + NSArray *fieldPathElements = fieldPathDictionary[@"elements"]; + FIRFieldPath *fieldPath = [[FIRFieldPath alloc] initWithFields:fieldPathElements]; + if ([operator isEqualToString:@"EQUAL"]) { + query = [query queryWhereFieldPath:fieldPath isEqualTo:value]; + } else if ([operator isEqualToString:@"GREATER_THAN"]) { + query = [query queryWhereFieldPath:fieldPath isGreaterThan:value]; + } else if ([operator isEqualToString:@"GREATER_THAN_OR_EQUAL"]) { + query = [query queryWhereFieldPath:fieldPath isGreaterThanOrEqualTo:value]; + } else if ([operator isEqualToString:@"LESS_THAN"]) { + query = [query queryWhereFieldPath:fieldPath isLessThan:value]; + } else if ([operator isEqualToString:@"LESS_THAN_OR_EQUAL"]) { + query = [query queryWhereFieldPath:fieldPath isLessThanOrEqualTo:value]; + } else if ([operator isEqualToString:@"ARRAY_CONTAINS"]) { + query = [query queryWhereFieldPath:fieldPath arrayContains:value]; + } } - return query; + } + + return query; } -- (FIRQuery *)applyOrders:(FIRQuery *) query { - for (NSDictionary *order in _orders) { - NSString *direction = order[@"direction"]; - NSDictionary *fieldPathDictionary = order[@"fieldPath"]; - NSString *fieldPathType = fieldPathDictionary[@"type"]; +- (FIRQuery *)applyOrders:(FIRQuery *)query { + for (NSDictionary *order in _orders) { + NSString *direction = order[@"direction"]; + NSDictionary *fieldPathDictionary = order[@"fieldPath"]; + NSString *fieldPathType = fieldPathDictionary[@"type"]; - if ([fieldPathType isEqualToString:@"string"]) { - NSString *fieldPath = fieldPathDictionary[@"string"]; - query = [query queryOrderedByField:fieldPath descending:([direction isEqualToString:@"DESCENDING"])]; - } else { - NSArray *fieldPathElements = fieldPathDictionary[@"elements"]; - FIRFieldPath *fieldPath = [[FIRFieldPath alloc] initWithFields:fieldPathElements]; - query = [query queryOrderedByFieldPath:fieldPath descending:([direction isEqualToString:@"DESCENDING"])]; - } + if ([fieldPathType isEqualToString:@"string"]) { + NSString *fieldPath = fieldPathDictionary[@"string"]; + query = [query queryOrderedByField:fieldPath descending:([direction isEqualToString:@"DESCENDING"])]; + } else { + NSArray *fieldPathElements = fieldPathDictionary[@"elements"]; + FIRFieldPath *fieldPath = [[FIRFieldPath alloc] initWithFields:fieldPathElements]; + query = [query queryOrderedByFieldPath:fieldPath descending:([direction isEqualToString:@"DESCENDING"])]; } - return query; + } + return query; } -- (FIRQuery *)applyOptions:(FIRFirestore *) firestore - query:(FIRQuery *) query { - if (_options[@"endAt"]) { - query = [query queryEndingAtValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"endAt"]]]; - } - if (_options[@"endBefore"]) { - query = [query queryEndingBeforeValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"endBefore"]]]; - } - if (_options[@"limit"]) { - query = [query queryLimitedTo:[_options[@"limit"] intValue]]; - } - if (_options[@"offset"]) { - // iOS doesn't support offset - } - if (_options[@"selectFields"]) { - // iOS doesn't support selectFields - } - if (_options[@"startAfter"]) { - query = [query queryStartingAfterValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"startAfter"]]]; - } - if (_options[@"startAt"]) { - query = [query queryStartingAtValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"startAt"]]]; - } - return query; +- (FIRQuery *)applyOptions:(FIRFirestore *)firestore + query:(FIRQuery *)query { + if (_options[@"endAt"]) { + query = + [query queryEndingAtValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"endAt"]]]; + } + if (_options[@"endBefore"]) { + query = + [query queryEndingBeforeValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"endBefore"]]]; + } + if (_options[@"limit"]) { + query = [query queryLimitedTo:[_options[@"limit"] intValue]]; + } + if (_options[@"offset"]) { + // iOS doesn't support offset + } + if (_options[@"selectFields"]) { + // iOS doesn't support selectFields + } + if (_options[@"startAfter"]) { + query = + [query queryStartingAfterValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"startAfter"]]]; + } + if (_options[@"startAt"]) { + query = + [query queryStartingAtValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"startAt"]]]; + } + return query; } - (void)handleQuerySnapshotError:(NSString *)listenerId error:(NSError *)error { - NSMutableDictionary *event = [[NSMutableDictionary alloc] init]; - [event setValue:_appDisplayName forKey:@"appName"]; - [event setValue:_path forKey:@"path"]; - [event setValue:listenerId forKey:@"listenerId"]; - [event setValue:[RNFirebaseFirestore getJSError:error] forKey:@"error"]; + NSMutableDictionary *event = [[NSMutableDictionary alloc] init]; + [event setValue:_appDisplayName forKey:@"appName"]; + [event setValue:_path forKey:@"path"]; + [event setValue:listenerId forKey:@"listenerId"]; + [event setValue:[RNFirebaseFirestore getJSError:error] forKey:@"error"]; - [RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_COLLECTION_SYNC_EVENT body:event]; + [RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_COLLECTION_SYNC_EVENT body:event]; } - (void)handleQuerySnapshotEvent:(NSString *)listenerId querySnapshot:(FIRQuerySnapshot *)querySnapshot { - NSMutableDictionary *event = [[NSMutableDictionary alloc] init]; - [event setValue:_appDisplayName forKey:@"appName"]; - [event setValue:_path forKey:@"path"]; - [event setValue:listenerId forKey:@"listenerId"]; - [event setValue:[RNFirebaseFirestoreCollectionReference snapshotToDictionary:querySnapshot] forKey:@"querySnapshot"]; + NSMutableDictionary *event = [[NSMutableDictionary alloc] init]; + [event setValue:_appDisplayName forKey:@"appName"]; + [event setValue:_path forKey:@"path"]; + [event setValue:listenerId forKey:@"listenerId"]; + [event setValue:[RNFirebaseFirestoreCollectionReference snapshotToDictionary:querySnapshot] forKey:@"querySnapshot"]; - [RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_COLLECTION_SYNC_EVENT body:event]; + [RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_COLLECTION_SYNC_EVENT body:event]; } + (NSDictionary *)snapshotToDictionary:(FIRQuerySnapshot *)querySnapshot { - NSMutableDictionary *snapshot = [[NSMutableDictionary alloc] init]; - [snapshot setValue:[self documentChangesToArray:querySnapshot.documentChanges] forKey:@"changes"]; - [snapshot setValue:[self documentSnapshotsToArray:querySnapshot.documents] forKey:@"documents"]; - if (querySnapshot.metadata) { - NSMutableDictionary *metadata = [[NSMutableDictionary alloc] init]; - [metadata setValue:@(querySnapshot.metadata.fromCache) forKey:@"fromCache"]; - [metadata setValue:@(querySnapshot.metadata.hasPendingWrites) forKey:@"hasPendingWrites"]; - [snapshot setValue:metadata forKey:@"metadata"]; - } + NSMutableDictionary *snapshot = [[NSMutableDictionary alloc] init]; + [snapshot setValue:[self documentChangesToArray:querySnapshot.documentChanges] forKey:@"changes"]; + [snapshot setValue:[self documentSnapshotsToArray:querySnapshot.documents] forKey:@"documents"]; + if (querySnapshot.metadata) { + NSMutableDictionary *metadata = [[NSMutableDictionary alloc] init]; + [metadata setValue:@(querySnapshot.metadata.fromCache) forKey:@"fromCache"]; + [metadata setValue:@(querySnapshot.metadata.hasPendingWrites) forKey:@"hasPendingWrites"]; + [snapshot setValue:metadata forKey:@"metadata"]; + } - return snapshot; + return snapshot; } -+ (NSArray *)documentChangesToArray:(NSArray *) documentChanges { - NSMutableArray *changes = [[NSMutableArray alloc] init]; - for (FIRDocumentChange *change in documentChanges) { - [changes addObject:[self documentChangeToDictionary:change]]; - } ++ (NSArray *)documentChangesToArray:(NSArray *)documentChanges { + NSMutableArray *changes = [[NSMutableArray alloc] init]; + for (FIRDocumentChange *change in documentChanges) { + [changes addObject:[self documentChangeToDictionary:change]]; + } - return changes; + return changes; } + (NSDictionary *)documentChangeToDictionary:(FIRDocumentChange *)documentChange { - NSMutableDictionary *change = [[NSMutableDictionary alloc] init]; - [change setValue:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:documentChange.document] forKey:@"document"]; - [change setValue:@(documentChange.newIndex) forKey:@"newIndex"]; - [change setValue:@(documentChange.oldIndex) forKey:@"oldIndex"]; + NSMutableDictionary *change = [[NSMutableDictionary alloc] init]; + [change setValue:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:documentChange.document] forKey:@"document"]; + [change setValue:@(documentChange.newIndex) forKey:@"newIndex"]; + [change setValue:@(documentChange.oldIndex) forKey:@"oldIndex"]; - if (documentChange.type == FIRDocumentChangeTypeAdded) { - [change setValue:@"added" forKey:@"type"]; - } else if (documentChange.type == FIRDocumentChangeTypeRemoved) { - [change setValue:@"removed" forKey:@"type"]; - } else if (documentChange.type == FIRDocumentChangeTypeModified) { - [change setValue:@"modified" forKey:@"type"]; - } + if (documentChange.type == FIRDocumentChangeTypeAdded) { + [change setValue:@"added" forKey:@"type"]; + } else if (documentChange.type == FIRDocumentChangeTypeRemoved) { + [change setValue:@"removed" forKey:@"type"]; + } else if (documentChange.type == FIRDocumentChangeTypeModified) { + [change setValue:@"modified" forKey:@"type"]; + } - return change; + return change; } -+ (NSArray *)documentSnapshotsToArray:(NSArray *) documentSnapshots { - NSMutableArray *snapshots = [[NSMutableArray alloc] init]; - for (FIRDocumentSnapshot *snapshot in documentSnapshots) { - [snapshots addObject:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:snapshot]]; - } ++ (NSArray *)documentSnapshotsToArray:(NSArray *)documentSnapshots { + NSMutableArray *snapshots = [[NSMutableArray alloc] init]; + for (FIRDocumentSnapshot *snapshot in documentSnapshots) { + [snapshots addObject:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:snapshot]]; + } - return snapshots; + return snapshots; } #endif From 12eaea7ff449720dd88a2d2c44ecf2ac099b152b Mon Sep 17 00:00:00 2001 From: Salakar Date: Thu, 20 Sep 2018 13:19:37 +0100 Subject: [PATCH 03/12] [firestore][android] 'array-contains' support --- .../firestore/RNFirebaseFirestoreCollectionReference.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 eddb36cc..cfc59456 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java +++ b/android/src/main/java/io/invertase/firebase/firestore/RNFirebaseFirestoreCollectionReference.java @@ -189,6 +189,9 @@ class RNFirebaseFirestoreCollectionReference { case "LESS_THAN_OR_EQUAL": query = query.whereLessThanOrEqualTo(fieldPath, value); break; + case "ARRAY_CONTAINS": + query = query.whereArrayContains(fieldPath, value); + break; } } else { ReadableArray fieldPathElements = fieldPathMap.getArray("elements"); @@ -213,6 +216,9 @@ class RNFirebaseFirestoreCollectionReference { case "LESS_THAN_OR_EQUAL": query = query.whereLessThanOrEqualTo(fieldPath, value); break; + case "ARRAY_CONTAINS": + query = query.whereArrayContains(fieldPath, value); + break; } } } From 19283b92d04665de524eec0a90f730cd3dc83a0d Mon Sep 17 00:00:00 2001 From: Salakar Date: Thu, 20 Sep 2018 13:20:25 +0100 Subject: [PATCH 04/12] [firestore][js] 'array-contains' support --- src/modules/firestore/Query.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/firestore/Query.js b/src/modules/firestore/Query.js index 10911ed9..f40f96a9 100644 --- a/src/modules/firestore/Query.js +++ b/src/modules/firestore/Query.js @@ -34,6 +34,7 @@ const OPERATORS: { [QueryOperator]: string } = { '>=': 'GREATER_THAN_OR_EQUAL', '<': 'LESS_THAN', '<=': 'LESS_THAN_OR_EQUAL', + 'array-contains': 'ARRAY_CONTAINS', }; type NativeFieldPath = {| @@ -200,6 +201,7 @@ export default class Query { observerOrOnNextOrOnError?: Observer | ObserverOnNext | ObserverOnError, onError?: ObserverOnError ) { + // TODO refactor this 💩 let observer: Observer; let metadataChanges = {}; // Called with: onNext, ?onError @@ -458,6 +460,7 @@ export default class Query { /** * Remove query snapshot listener + * @param listenerId * @param listener */ _offCollectionSnapshot(listenerId: string, listener: Function) { From bf78fd8775c8652ac468b5be8787adab257d5a8d Mon Sep 17 00:00:00 2001 From: Salakar Date: Thu, 20 Sep 2018 13:20:55 +0100 Subject: [PATCH 05/12] [firestore][types] 'array-contains' support --- src/index.d.ts | 2 +- src/modules/firestore/types.js | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/index.d.ts b/src/index.d.ts index dfd392dc..df9ddd54 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -2670,7 +2670,7 @@ declare module 'react-native-firebase' { } type QueryDirection = 'asc' | 'ASC' | 'desc' | 'DESC'; - type QueryOperator = '=' | '==' | '>' | '>=' | '<' | '<='; + type QueryOperator = '=' | '==' | '>' | '>=' | '<' | '<=' | 'array-contains'; interface TypeMap { type: diff --git a/src/modules/firestore/types.js b/src/modules/firestore/types.js index fa97342b..8f7c67aa 100644 --- a/src/modules/firestore/types.js +++ b/src/modules/firestore/types.js @@ -8,7 +8,14 @@ export type MetadataChanges = {| export type QueryDirection = 'DESC' | 'desc' | 'ASC' | 'asc'; -export type QueryOperator = '<' | '<=' | '=' | '==' | '>' | '>='; +export type QueryOperator = + | '<' + | '<=' + | '=' + | '==' + | '>' + | '>=' + | 'array-contains'; export type GetOptions = { source: 'default' | 'server' | 'cache', From 0c9435af33b7915f4a9acf169c62e8a925921068 Mon Sep 17 00:00:00 2001 From: Salakar Date: Thu, 20 Sep 2018 13:21:47 +0100 Subject: [PATCH 06/12] [tests][firestore] cleanup tests & helpers - wip --- tests/e2e/firestore/collection/cursors.e2e.js | 532 ++++++++++++++++ tests/e2e/firestore/collection/limit.e2e.js | 61 ++ tests/e2e/firestore/collection/where.e2e.js | 121 ++++ .../e2e/firestore/collectionReference.e2e.js | 602 +----------------- tests/helpers/firestore.js | 16 +- 5 files changed, 727 insertions(+), 605 deletions(-) create mode 100644 tests/e2e/firestore/collection/cursors.e2e.js create mode 100644 tests/e2e/firestore/collection/limit.e2e.js create mode 100644 tests/e2e/firestore/collection/where.e2e.js diff --git a/tests/e2e/firestore/collection/cursors.e2e.js b/tests/e2e/firestore/collection/cursors.e2e.js new file mode 100644 index 00000000..05777cc0 --- /dev/null +++ b/tests/e2e/firestore/collection/cursors.e2e.js @@ -0,0 +1,532 @@ +const { + COL_DOC_1, + cleanCollection, + TEST_COLLECTION_NAME_DYNAMIC, +} = TestHelpers.firestore; + +describe('firestore()', () => { + before(async () => { + await cleanCollection(TEST_COLLECTION_NAME_DYNAMIC); + + const collection = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC); + + await Promise.all([ + collection.doc('col1').set({ ...COL_DOC_1(), foo: 'bar0' }), + collection.doc('col2').set({ + ...COL_DOC_1(), + foo: 'bar1', + daz: 234, + object: { daz: 234 }, + timestamp: new jet.context.window.Date(2017, 2, 11, 10, 0, 0), + }), + collection.doc('col3').set({ + ...COL_DOC_1(), + foo: 'bar2', + daz: 345, + object: { daz: 345 }, + timestamp: new jet.context.window.Date(2017, 2, 12, 10, 0, 0), + }), + collection.doc('col4').set({ + ...COL_DOC_1(), + foo: 'bar3', + daz: 456, + object: { daz: 456 }, + timestamp: new jet.context.window.Date(2017, 2, 13, 10, 0, 0), + }), + collection.doc('col5').set({ + ...COL_DOC_1(), + foo: 'bar4', + daz: 567, + object: { daz: 567 }, + timestamp: new jet.context.window.Date(2017, 2, 14, 10, 0, 0), + }), + ]); + }); + + describe('CollectionReference', () => { + describe('cursors', () => { + describe('endAt', () => { + it('handles dates', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('timestamp') + .endAt(new jet.context.window.Date(2017, 2, 12, 10, 0, 0)) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 3); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 123, + 234, + 345, + ]); + })); + + it('handles numbers', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('daz') + .endAt(345) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 3); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 123, + 234, + 345, + ]); + })); + + it('handles strings', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .endAt('bar2') + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 3); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 123, + 234, + 345, + ]); + })); + + it('handles snapshots', async () => { + const collectionSnapshot = await firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .get(); + + return firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .endAt(collectionSnapshot.docs[2]) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 3); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 123, + 234, + 345, + ]); + }); + }); + + it('works with FieldPath', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy(new firebase.firestore.FieldPath('timestamp')) + .endAt(new jet.context.window.Date(2017, 2, 12, 10, 0, 0)) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 3); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 123, + 234, + 345, + ]); + })); + + it('handles snapshots with FieldPath', async () => { + const collectionSnapshot = await firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy(new firebase.firestore.FieldPath('foo')) + .get(); + return firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .endAt(collectionSnapshot.docs[2]) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 3); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 123, + 234, + 345, + ]); + }); + }); + }); + + describe('endBefore', () => { + it('handles dates', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('timestamp') + .endBefore(new jet.context.window.Date(2017, 2, 12, 10, 0, 0)) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 2); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 123, + 234, + ]); + })); + + it('handles numbers', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('daz') + .endBefore(345) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 2); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 123, + 234, + ]); + })); + + it('handles strings', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .endBefore('bar2') + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 2); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 123, + 234, + ]); + })); + + it('handles snapshots', async () => { + const collectionSnapshot = await firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .get(); + + return firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .endBefore(collectionSnapshot.docs[2]) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 2); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 123, + 234, + ]); + }); + }); + + it('works with FieldPath', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy(new firebase.firestore.FieldPath('timestamp')) + .endBefore(new jet.context.window.Date(2017, 2, 12, 10, 0, 0)) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 2); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 123, + 234, + ]); + })); + + it('handles snapshots with FieldPath', async () => { + const collectionSnapshot = await firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy(new firebase.firestore.FieldPath('foo')) + .get(); + return firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .endBefore(collectionSnapshot.docs[2]) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 2); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 123, + 234, + ]); + }); + }); + }); + + describe('startAt', () => { + it('handles dates', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('timestamp') + .startAt(new jet.context.window.Date(2017, 2, 12, 10, 0, 0)) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 3); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 345, + 456, + 567, + ]); + })); + + it('handles numbers', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('daz') + .startAt(345) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 3); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 345, + 456, + 567, + ]); + })); + + it('handles strings', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .startAt('bar2') + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 3); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 345, + 456, + 567, + ]); + })); + + it('handles snapshots', async () => { + const collectionSnapshot = await firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .get(); + return firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .startAt(collectionSnapshot.docs[2]) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 3); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 345, + 456, + 567, + ]); + }); + }); + + it('works with FieldPath', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy(new firebase.firestore.FieldPath('timestamp')) + .startAt(new jet.context.window.Date(2017, 2, 12, 10, 0, 0)) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 3); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 345, + 456, + 567, + ]); + })); + + it('handles snapshots with FieldPath', async () => { + const collectionSnapshot = await firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy(new firebase.firestore.FieldPath('foo')) + .get(); + return firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .startAt(collectionSnapshot.docs[2]) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 3); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 345, + 456, + 567, + ]); + }); + }); + }); + + describe('startAfter', () => { + it('handles dates', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('timestamp') + .startAfter(new jet.context.window.Date(2017, 2, 12, 10, 0, 0)) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 2); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 456, + 567, + ]); + })); + + it('handles numbers', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('daz') + .startAfter(345) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 2); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 456, + 567, + ]); + })); + + it('handles strings', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .startAfter('bar2') + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 2); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 456, + 567, + ]); + })); + + it('handles snapshot', async () => { + const collectionSnapshot = await firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .get(); + return firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .startAfter(collectionSnapshot.docs[2]) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 2); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 456, + 567, + ]); + }); + }); + + it('works with FieldPath', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy(new firebase.firestore.FieldPath('timestamp')) + .startAfter(new jet.context.window.Date(2017, 2, 12, 10, 0, 0)) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 2); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 456, + 567, + ]); + })); + + it('handles snapshots with FieldPath', async () => { + const collectionSnapshot = await firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy(new firebase.firestore.FieldPath('foo')) + .get(); + + return firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .orderBy('foo') + .startAfter(collectionSnapshot.docs[2]) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 2); + should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ + 456, + 567, + ]); + }); + }); + }); + + describe('orderBy()', () => { + it('errors if called after startAt', () => { + (() => { + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .startAt({}) + .orderBy('test'); + }).should.throw( + 'Cannot specify an orderBy() constraint after calling startAt(), startAfter(), endBefore() or endAt().' + ); + }); + + it('errors if called after startAfter', () => { + (() => { + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .startAfter({}) + .orderBy('test'); + }).should.throw( + 'Cannot specify an orderBy() constraint after calling startAt(), startAfter(), endBefore() or endAt().' + ); + }); + + it('errors if called after endBefore', () => { + (() => { + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .endBefore({}) + .orderBy('test'); + }).should.throw( + 'Cannot specify an orderBy() constraint after calling startAt(), startAfter(), endBefore() or endAt().' + ); + }); + + it('errors if called after endAt', () => { + (() => { + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .endAt({}) + .orderBy('test'); + }).should.throw( + 'Cannot specify an orderBy() constraint after calling startAt(), startAfter(), endBefore() or endAt().' + ); + }); + }); + }); + }); +}); diff --git a/tests/e2e/firestore/collection/limit.e2e.js b/tests/e2e/firestore/collection/limit.e2e.js new file mode 100644 index 00000000..6b96d3ed --- /dev/null +++ b/tests/e2e/firestore/collection/limit.e2e.js @@ -0,0 +1,61 @@ +const { + COL_DOC_1, + cleanCollection, + TEST_COLLECTION_NAME_DYNAMIC, +} = TestHelpers.firestore; + +describe('firestore()', () => { + before(async () => { + await cleanCollection(TEST_COLLECTION_NAME_DYNAMIC); + + const collection = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC); + + await Promise.all([ + collection.doc('col1').set(COL_DOC_1()), + collection.doc('col2').set({ ...COL_DOC_1(), daz: 2 }), + collection.doc('col3').set({ ...COL_DOC_1(), daz: 3 }), + collection.doc('col4').set({ ...COL_DOC_1(), daz: 4 }), + collection.doc('col5').set({ ...COL_DOC_1(), daz: 5 }), + ]); + }); + + describe('CollectionReference', () => { + describe('limit()', () => { + it('correctly works with get()', async () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .limit(3) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 3); + })); + + it('correctly works with onSnapshot()', async () => { + const collectionRef = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .limit(3); + + const callback = sinon.spy(); + + // Test + let unsubscribe; + await new Promise(resolve2 => { + unsubscribe = collectionRef.onSnapshot(snapshot => { + callback(snapshot.size); + resolve2(); + }); + }); + + // Assertions + callback.should.be.calledWith(3); + + // Tear down + unsubscribe(); + }); + }); + }); +}); diff --git a/tests/e2e/firestore/collection/where.e2e.js b/tests/e2e/firestore/collection/where.e2e.js new file mode 100644 index 00000000..f8307f72 --- /dev/null +++ b/tests/e2e/firestore/collection/where.e2e.js @@ -0,0 +1,121 @@ +const { + COL_DOC_1, + COL_DOC_1_PATH, + TEST_COLLECTION_NAME_DYNAMIC, + resetTestCollectionDoc, +} = TestHelpers.firestore; + +describe('firestore()', () => { + describe('CollectionReference', () => { + before(() => resetTestCollectionDoc(COL_DOC_1_PATH, COL_DOC_1())); + describe('where()', () => { + it('correctly handles == boolean values', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .where('baz', '==', true) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 1); + querySnapshot.forEach(documentSnapshot => { + should.equal(documentSnapshot.data().baz, true); + }); + })); + + it('correctly handles == string values', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .where('foo', '==', 'bar') + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 1); + querySnapshot.forEach(documentSnapshot => { + should.equal(documentSnapshot.data().foo, 'bar'); + }); + })); + + it('correctly handles == null values', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .where('naz', '==', null) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 1); + querySnapshot.forEach(documentSnapshot => { + should.equal(documentSnapshot.data().naz, null); + }); + })); + + it('correctly handles == date values', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .where('timestamp', '==', COL_DOC_1().timestamp) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 1); + })); + + it('correctly handles == geopoint values', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .where('geopoint', '==', COL_DOC_1().geopoint) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 1); + })); + + it('correctly handles >= number values', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .where('daz', '>=', 123) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 1); + querySnapshot.forEach(documentSnapshot => { + should.equal(documentSnapshot.data().daz, 123); + }); + })); + + it('correctly handles >= geopoint values', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .where('geopoint', '>=', new firebase.firestore.GeoPoint(-1, -1)) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 1); + })); + + it('correctly handles <= float values', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .where('gaz', '<=', 12.1234666) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 1); + querySnapshot.forEach(documentSnapshot => { + should.equal(documentSnapshot.data().gaz, 12.1234567); + }); + })); + + it('correctly handles FieldPath', () => + firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .where(new firebase.firestore.FieldPath('baz'), '==', true) + .get() + .then(querySnapshot => { + should.equal(querySnapshot.size, 1); + querySnapshot.forEach(documentSnapshot => { + should.equal(documentSnapshot.data().baz, true); + }); + })); + }); + }); +}); diff --git a/tests/e2e/firestore/collectionReference.e2e.js b/tests/e2e/firestore/collectionReference.e2e.js index d42eb78f..9765beeb 100644 --- a/tests/e2e/firestore/collectionReference.e2e.js +++ b/tests/e2e/firestore/collectionReference.e2e.js @@ -4,6 +4,7 @@ const { COL_DOC_1_ID, COL_DOC_1_PATH, TEST_COLLECTION_NAME, + TEST_COLLECTION_NAME_DYNAMIC, testCollection, cleanCollection, testCollectionDoc, @@ -657,607 +658,6 @@ describe('firestore()', () => { // }); // }); - // // Where - // describe('where()', () => { - // it('correctly handles == boolean values', () => - // firebase - // .firestore() - // .collection('collection-tests') - // .where('baz', '==', true) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 1); - // querySnapshot.forEach(documentSnapshot => { - // should.equal(documentSnapshot.data().baz, true); - // }); - // })); - - // it('correctly handles == string values', () => - // firebase - // .firestore() - // .collection('collection-tests') - // .where('foo', '==', 'bar') - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 1); - // querySnapshot.forEach(documentSnapshot => { - // should.equal(documentSnapshot.data().foo, 'bar'); - // }); - // })); - - // it('correctly handles == null values', () => - // firebase - // .firestore() - // .collection('collection-tests') - // .where('naz', '==', null) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 1); - // querySnapshot.forEach(documentSnapshot => { - // should.equal(documentSnapshot.data().naz, null); - // }); - // })); - - // it('correctly handles == date values', () => - // firebase - // .firestore() - // .collection('collection-tests') - // .where('timestamp', '==', COL_DOC_1.timestamp) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 1); - // })); - - // it('correctly handles == geopoint values', () => - // firebase - // .firestore() - // .collection('collection-tests') - // .where('geopoint', '==', COL_DOC_1.geopoint) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 1); - // })); - - // it('correctly handles >= number values', () => - // firebase - // .firestore() - // .collection('collection-tests') - // .where('daz', '>=', 123) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 1); - // querySnapshot.forEach(documentSnapshot => { - // should.equal(documentSnapshot.data().daz, 123); - // }); - // })); - - // it('correctly handles >= geopoint values', () => - // firebase - // .firestore() - // .collection('collection-tests') - // .where('geopoint', '>=', new firebase.firestore.GeoPoint(-1, -1)) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 1); - // })); - - // it('correctly handles <= float values', () => - // firebase - // .firestore() - // .collection('collection-tests') - // .where('gaz', '<=', 12.1234666) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 1); - // querySnapshot.forEach(documentSnapshot => { - // should.equal(documentSnapshot.data().gaz, 12.1234567); - // }); - // })); - - // it('correctly handles FieldPath', () => - // firebase - // .firestore() - // .collection('collection-tests') - // .where(new firebase.firestore.FieldPath('baz'), '==', true) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 1); - // querySnapshot.forEach(documentSnapshot => { - // should.equal(documentSnapshot.data().baz, true); - // }); - // })); - // }); - - // describe('limit', () => { - // let collectionTests; - // before(async () => { - // collectionTests = firebase.firestore().collection('collection-tests2'); - // await Promise.all([ - // collectionTests.doc('col1').set(COL_DOC_1), - // collectionTests.doc('col2').set({ ...COL_DOC_1, daz: 234 }), - // collectionTests.doc('col3').set({ ...COL_DOC_1, daz: 234 }), - // collectionTests.doc('col4').set({ ...COL_DOC_1, daz: 234 }), - // collectionTests.doc('col5').set({ ...COL_DOC_1, daz: 234 }), - // ]); - // }); - - // it('correctly works with get()', async () => - // collectionTests - // .limit(3) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 3); - // return cleanCollection(collectionTests); - // })); - - // it('correctly works with onSnapshot()', async () => { - // const collectionRef = collectionTests.limit(3); - // const callback = sinon.spy(); - - // // Test - - // let unsubscribe; - // await new Promise(resolve2 => { - // unsubscribe = collectionRef.onSnapshot(snapshot => { - // callback(snapshot.size); - // resolve2(); - // }); - // }); - - // // Assertions - - // callback.should.be.calledWith(3); - - // // Tear down - - // unsubscribe(); - // }); - - // after(() => cleanCollection(collectionTests)); - // }); - - // describe('cursors', () => { - // let collectionTests; - // before(async () => { - // collectionTests = firebase.firestore().collection('collection-tests2'); - // await Promise.all([ - // collectionTests.doc('col1').set({ ...COL_DOC_1, foo: 'bar0' }), - // collectionTests.doc('col2').set({ - // ...COL_DOC_1, - // foo: 'bar1', - // daz: 234, - // object: { daz: 234 }, - // timestamp: new Date(2017, 2, 11, 10, 0, 0), - // }), - // collectionTests.doc('col3').set({ - // ...COL_DOC_1, - // foo: 'bar2', - // daz: 345, - // object: { daz: 345 }, - // timestamp: new Date(2017, 2, 12, 10, 0, 0), - // }), - // collectionTests.doc('col4').set({ - // ...COL_DOC_1, - // foo: 'bar3', - // daz: 456, - // object: { daz: 456 }, - // timestamp: new Date(2017, 2, 13, 10, 0, 0), - // }), - // collectionTests.doc('col5').set({ - // ...COL_DOC_1, - // foo: 'bar4', - // daz: 567, - // object: { daz: 567 }, - // timestamp: new Date(2017, 2, 14, 10, 0, 0), - // }), - // ]); - // }); - - // describe('endAt', () => { - // it('handles dates', () => - // collectionTests - // .orderBy('timestamp') - // .endAt(new Date(2017, 2, 12, 10, 0, 0)) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 3); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 123, - // 234, - // 345, - // ]); - // })); - - // it('handles numbers', () => - // collectionTests - // .orderBy('daz') - // .endAt(345) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 3); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 123, - // 234, - // 345, - // ]); - // })); - - // it('handles strings', () => - // collectionTests - // .orderBy('foo') - // .endAt('bar2') - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 3); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 123, - // 234, - // 345, - // ]); - // })); - - // it('handles snapshots', async () => { - // const collectionSnapshot = await collectionTests.orderBy('foo').get(); - // return collectionTests - // .orderBy('foo') - // .endAt(collectionSnapshot.docs[2]) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 3); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 123, - // 234, - // 345, - // ]); - // }); - // }); - - // it('works with FieldPath', () => - // collectionTests - // .orderBy(new firebase.firestore.FieldPath('timestamp')) - // .endAt(new Date(2017, 2, 12, 10, 0, 0)) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 3); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 123, - // 234, - // 345, - // ]); - // })); - - // it('handles snapshots with FieldPath', async () => { - // const collectionSnapshot = await collectionTests - // .orderBy(new firebase.firestore.FieldPath('foo')) - // .get(); - // return collectionTests - // .orderBy('foo') - // .endAt(collectionSnapshot.docs[2]) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 3); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 123, - // 234, - // 345, - // ]); - // }); - // }); - // }); - - // describe('endBefore', () => { - // it('handles dates', () => - // collectionTests - // .orderBy('timestamp') - // .endBefore(new Date(2017, 2, 12, 10, 0, 0)) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 2); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 123, - // 234, - // ]); - // })); - - // it('handles numbers', () => - // collectionTests - // .orderBy('daz') - // .endBefore(345) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 2); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 123, - // 234, - // ]); - // })); - - // it('handles strings', () => - // collectionTests - // .orderBy('foo') - // .endBefore('bar2') - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 2); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 123, - // 234, - // ]); - // })); - - // it('handles snapshots', async () => { - // const collectionSnapshot = await collectionTests.orderBy('foo').get(); - // return collectionTests - // .orderBy('foo') - // .endBefore(collectionSnapshot.docs[2]) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 2); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 123, - // 234, - // ]); - // }); - // }); - - // it('works with FieldPath', () => - // collectionTests - // .orderBy(new firebase.firestore.FieldPath('timestamp')) - // .endBefore(new Date(2017, 2, 12, 10, 0, 0)) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 2); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 123, - // 234, - // ]); - // })); - - // it('handles snapshots with FieldPath', async () => { - // const collectionSnapshot = await collectionTests - // .orderBy(new firebase.firestore.FieldPath('foo')) - // .get(); - // return collectionTests - // .orderBy('foo') - // .endBefore(collectionSnapshot.docs[2]) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 2); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 123, - // 234, - // ]); - // }); - // }); - // }); - - // describe('startAt', () => { - // it('handles dates', () => - // collectionTests - // .orderBy('timestamp') - // .startAt(new Date(2017, 2, 12, 10, 0, 0)) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 3); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 345, - // 456, - // 567, - // ]); - // })); - - // it('handles numbers', () => - // collectionTests - // .orderBy('daz') - // .startAt(345) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 3); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 345, - // 456, - // 567, - // ]); - // })); - - // it('handles strings', () => - // collectionTests - // .orderBy('foo') - // .startAt('bar2') - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 3); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 345, - // 456, - // 567, - // ]); - // })); - - // it('handles snapshots', async () => { - // const collectionSnapshot = await collectionTests.orderBy('foo').get(); - // return collectionTests - // .orderBy('foo') - // .startAt(collectionSnapshot.docs[2]) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 3); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 345, - // 456, - // 567, - // ]); - // }); - // }); - - // it('works with FieldPath', () => - // collectionTests - // .orderBy(new firebase.firestore.FieldPath('timestamp')) - // .startAt(new Date(2017, 2, 12, 10, 0, 0)) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 3); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 345, - // 456, - // 567, - // ]); - // })); - - // it('handles snapshots with FieldPath', async () => { - // const collectionSnapshot = await collectionTests - // .orderBy(new firebase.firestore.FieldPath('foo')) - // .get(); - // return collectionTests - // .orderBy('foo') - // .startAt(collectionSnapshot.docs[2]) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 3); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 345, - // 456, - // 567, - // ]); - // }); - // }); - // }); - - // describe('startAfter', () => { - // it('handles dates', () => - // collectionTests - // .orderBy('timestamp') - // .startAfter(new Date(2017, 2, 12, 10, 0, 0)) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 2); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 456, - // 567, - // ]); - // })); - - // it('handles numbers', () => - // collectionTests - // .orderBy('daz') - // .startAfter(345) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 2); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 456, - // 567, - // ]); - // })); - - // it('handles strings', () => - // collectionTests - // .orderBy('foo') - // .startAfter('bar2') - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 2); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 456, - // 567, - // ]); - // })); - - // it('handles snapshot', async () => { - // const collectionSnapshot = await collectionTests.orderBy('foo').get(); - // return collectionTests - // .orderBy('foo') - // .startAfter(collectionSnapshot.docs[2]) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 2); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 456, - // 567, - // ]); - // }); - // }); - - // it('works with FieldPath', () => - // collectionTests - // .orderBy(new firebase.firestore.FieldPath('timestamp')) - // .startAfter(new Date(2017, 2, 12, 10, 0, 0)) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 2); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 456, - // 567, - // ]); - // })); - - // it('handles snapshots with FieldPath', async () => { - // const collectionSnapshot = await collectionTests - // .orderBy(new firebase.firestore.FieldPath('foo')) - // .get(); - // return collectionTests - // .orderBy('foo') - // .startAfter(collectionSnapshot.docs[2]) - // .get() - // .then(querySnapshot => { - // should.equal(querySnapshot.size, 2); - // should.deepEqual(querySnapshot.docs.map(doc => doc.data().daz), [ - // 456, - // 567, - // ]); - // }); - // }); - // }); - - // describe('orderBy()', () => { - // it('errors if called after startAt', () => { - // (() => { - // firebase - // .firestore() - // .collection('collections') - // .startAt({}) - // .orderBy('test'); - // }).should.throw( - // 'Cannot specify an orderBy() constraint after calling startAt(), startAfter(), endBefore() or endAt().' - // ); - // }); - - // it('errors if called after startAfter', () => { - // (() => { - // firebase - // .firestore() - // .collection('collections') - // .startAfter({}) - // .orderBy('test'); - // }).should.throw( - // 'Cannot specify an orderBy() constraint after calling startAt(), startAfter(), endBefore() or endAt().' - // ); - // }); - - // it('errors if called after endBefore', () => { - // (() => { - // firebase - // .firestore() - // .collection('collections') - // .endBefore({}) - // .orderBy('test'); - // }).should.throw( - // 'Cannot specify an orderBy() constraint after calling startAt(), startAfter(), endBefore() or endAt().' - // ); - // }); - - // it('errors if called after endAt', () => { - // (() => { - // firebase - // .firestore() - // .collection('collections') - // .endAt({}) - // .orderBy('test'); - // }).should.throw( - // 'Cannot specify an orderBy() constraint after calling startAt(), startAfter(), endBefore() or endAt().' - // ); - // }); - // }); // describe('onSnapshot()', () => { // it('gets called correctly', async () => { diff --git a/tests/helpers/firestore.js b/tests/helpers/firestore.js index f2fae4cc..22593a8a 100644 --- a/tests/helpers/firestore.js +++ b/tests/helpers/firestore.js @@ -1,5 +1,7 @@ const TEST_COLLECTION_NAME = 'tests'; const TEST2_COLLECTION_NAME = 'tests2'; +const TEST_COLLECTION_NAME_DYNAMIC = `tests${Math.floor(Math.random() * 31) + + 2}`; // const TEST3_COLLECTION_NAME = 'tests3'; let shouldCleanup = false; @@ -11,15 +13,15 @@ module.exports = { await Promise.all([ module.exports.cleanCollection(TEST_COLLECTION_NAME), module.exports.cleanCollection(TEST2_COLLECTION_NAME), + module.exports.cleanCollection(TEST_COLLECTION_NAME_DYNAMIC), ]); - // await module.exports.cleanCollection(`${TEST_COLLECTION_NAME}3`); - // await module.exports.cleanCollection(`${TEST_COLLECTION_NAME}4`); return Promise.resolve(); }, TEST_COLLECTION_NAME, TEST2_COLLECTION_NAME, + TEST_COLLECTION_NAME_DYNAMIC, // TEST3_COLLECTION_NAME, DOC_1: { name: 'doc1' }, @@ -32,12 +34,15 @@ module.exports = { COL_DOC_1() { shouldCleanup = true; return { + testRunId, baz: true, daz: 123, foo: 'bar', gaz: 12.1234567, geopoint: new firebase.firestore.GeoPoint(0, 0), naz: null, + arrNumber: [1, 2, 3, 4], + arrString: ['a', 'b', 'c', 'd'], object: { daz: 123, }, @@ -63,7 +68,7 @@ module.exports = { }, COL_DOC_1_ID: `col1${testRunId}`, - COL_DOC_1_PATH: `${TEST_COLLECTION_NAME}/col1${testRunId}`, + COL_DOC_1_PATH: `${TEST_COLLECTION_NAME_DYNAMIC}/col1${testRunId}`, COL2_DOC_1_ID: `doc1${testRunId}`, COL2_DOC_1_PATH: `${TEST2_COLLECTION_NAME}/doc1${testRunId}`, @@ -92,7 +97,8 @@ module.exports = { if ( ref.path.includes(testRunId) || - new Date(docsToDelete[i].createTime) <= yesterday + new Date(docsToDelete[i].createTime) <= yesterday || + collectionName === TEST_COLLECTION_NAME_DYNAMIC ) { batch.delete(ref); } @@ -148,6 +154,8 @@ module.exports = { shouldCleanup = true; const _doc = doc || module.exports.COL_DOC_1(); + await module.exports.cleanCollection(TEST_COLLECTION_NAME_DYNAMIC); + await firebase .firestore() .doc(path || module.exports.COL_DOC_1_PATH) From eb1f0acda6cbd1e98d56db88c197bbbc98e5e66e Mon Sep 17 00:00:00 2001 From: Salakar Date: Thu, 20 Sep 2018 14:16:09 +0100 Subject: [PATCH 07/12] [tests] update podfile lock --- tests/ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ios/Podfile.lock b/tests/ios/Podfile.lock index 75c9ce48..d25928c3 100644 --- a/tests/ios/Podfile.lock +++ b/tests/ios/Podfile.lock @@ -236,7 +236,7 @@ PODS: - React/Core - React/fishhook - React/RCTBlob - - RNFirebase (5.0.0-rc2): + - RNFirebase (5.0.0-rc3): - Firebase/Core - React - yoga (0.57.0.React) From f5ba4bfb7b30eaab7bdefd997c9fc8f1497f1b3a Mon Sep 17 00:00:00 2001 From: Salakar Date: Thu, 20 Sep 2018 16:22:38 +0100 Subject: [PATCH 08/12] [firestore][internals] remove `_offCollectionSnapshot` - not required as subscriptions already return an instance with a remove --- src/modules/firestore/Query.js | 46 +++++++++++++--------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/src/modules/firestore/Query.js b/src/modules/firestore/Query.js index f40f96a9..3ec14fda 100644 --- a/src/modules/firestore/Query.js +++ b/src/modules/firestore/Query.js @@ -7,7 +7,6 @@ import FieldPath from './FieldPath'; import QuerySnapshot from './QuerySnapshot'; import { buildNativeArray, buildTypeMap } from './utils/serialize'; import { getAppEventName, SharedEventEmitter } from '../../utils/events'; -import { getLogger } from '../../utils/log'; import { firestoreAutoId, isFunction, isObject } from '../../utils'; import { getNativeModule } from '../../utils/native'; @@ -311,14 +310,15 @@ export default class Query { }; // Listen to snapshot events - SharedEventEmitter.addListener( + const snapshotSubscription = SharedEventEmitter.addListener( getAppEventName(this._firestore, `onQuerySnapshot:${listenerId}`), listener ); // Listen for snapshot error events + let errorSubscription; if (observer.error) { - SharedEventEmitter.addListener( + errorSubscription = SharedEventEmitter.addListener( getAppEventName(this._firestore, `onQuerySnapshotError:${listenerId}`), observer.error ); @@ -334,8 +334,19 @@ export default class Query { metadataChanges ); - // Return an unsubscribe method - return this._offCollectionSnapshot.bind(this, listenerId, listener); + // return an unsubscribe method + return () => { + snapshotSubscription.remove(); + if (errorSubscription) errorSubscription.remove(); + // cancel native listener + getNativeModule(this._firestore).collectionOffSnapshot( + this._referencePath.relativeName, + this._fieldFilters, + this._fieldOrders, + this._queryOptions, + listenerId + ); + }; } orderBy( @@ -416,6 +427,7 @@ export default class Query { operator: OPERATORS[opStr], value: nativeValue, }; + const combinedFilters = this._fieldFilters.concat(newFilter); return new Query( this.firestore, @@ -457,28 +469,4 @@ export default class Query { return buildNativeArray(values); } - - /** - * Remove query snapshot listener - * @param listenerId - * @param listener - */ - _offCollectionSnapshot(listenerId: string, listener: Function) { - getLogger(this._firestore).info('Removing onQuerySnapshot listener'); - SharedEventEmitter.removeListener( - getAppEventName(this._firestore, `onQuerySnapshot:${listenerId}`), - listener - ); - SharedEventEmitter.removeListener( - getAppEventName(this._firestore, `onQuerySnapshotError:${listenerId}`), - listener - ); - getNativeModule(this._firestore).collectionOffSnapshot( - this._referencePath.relativeName, - this._fieldFilters, - this._fieldOrders, - this._queryOptions, - listenerId - ); - } } From 5c514077090b53819c996d02c01979d9ad8c16f1 Mon Sep 17 00:00:00 2001 From: Salakar Date: Thu, 20 Sep 2018 16:25:49 +0100 Subject: [PATCH 09/12] [tests][firestore] re-write onSnapshot tests to remove race conditions --- .../e2e/firestore/collection/snapshot.e2e.js | 520 ++++++++++++++++ .../e2e/firestore/collectionReference.e2e.js | 565 ------------------ tests/helpers/firestore.js | 12 +- 3 files changed, 526 insertions(+), 571 deletions(-) create mode 100644 tests/e2e/firestore/collection/snapshot.e2e.js diff --git a/tests/e2e/firestore/collection/snapshot.e2e.js b/tests/e2e/firestore/collection/snapshot.e2e.js new file mode 100644 index 00000000..5900ab10 --- /dev/null +++ b/tests/e2e/firestore/collection/snapshot.e2e.js @@ -0,0 +1,520 @@ +const { + COL_DOC_1, + DOC_2_PATH, + COL_DOC_1_PATH, + cleanCollection, + resetTestCollectionDoc, + TEST_COLLECTION_NAME_DYNAMIC, +} = TestHelpers.firestore; + +describe('firestore()', () => { + describe('CollectionReference', () => { + describe('onSnapshot()', () => { + beforeEach(async () => { + await sleep(50); + await cleanCollection(TEST_COLLECTION_NAME_DYNAMIC); + await sleep(50); + }); + + it('QuerySnapshot has correct properties', async () => { + const collection = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC); + + const snapshot = await collection.get(); + snapshot.docChanges.should.be.an.Array(); + snapshot.empty.should.equal(true); + snapshot.metadata.should.be.an.Object(); + snapshot.query.should.be.an.Object(); + }); + + it('DocumentChange has correct properties', async () => { + const collection = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC); + + await resetTestCollectionDoc(); + + // Test + let changes; + let unsubscribe; + await new Promise(resolve => { + unsubscribe = collection.onSnapshot(snapshot => { + changes = snapshot.docChanges; + resolve(); + }); + }); + + // Assertions + changes.should.be.a.Array(); + changes[0].doc.should.be.an.Object(); + changes[0].newIndex.should.be.a.Number(); + changes[0].oldIndex.should.be.a.Number(); + changes[0].type.should.be.a.String(); + + // Tear down + unsubscribe(); + }); + + it('calls callback with the initial data and then when document changes', async () => { + const callback = sinon.spy(); + const collection = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC); + + const newDocValue = { ...COL_DOC_1(), foo: 'updated' }; + + // Test + let unsubscribe; + let resolved = false; + await new Promise(resolve => { + unsubscribe = collection.onSnapshot(snapshot => { + if (snapshot && snapshot.docs.length) { + callback(snapshot.docs[0].data()); + } else { + callback(null); + } + + if (!resolved) { + resolved = true; + resolve(); + } + }); + }); + + callback.should.be.calledOnce(); + + await firebase + .firestore() + .doc(COL_DOC_1_PATH) + .set(newDocValue); + + await sleep(25); + + // Assertions + callback.should.be.calledTwice(); + callback.getCall(1).args[0].foo.should.equal('updated'); + + // Tear down + unsubscribe(); + }); + + // crappy race condition somewhere =/ will come back to it later + it('calls callback with the initial data and then when document is added', async () => { + const colDoc = await resetTestCollectionDoc(); + await sleep(50); + + const collectionRef = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC); + + const newDocValue = { foo: 'updated' }; + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise(resolve2 => { + unsubscribe = collectionRef.onSnapshot(snapshot => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); + }); + }); + + callback.should.be.calledWith(colDoc); + + const docRef = firebase.firestore().doc(DOC_2_PATH); + await docRef.set(newDocValue); + + await sleep(25); + + // Assertions + + callback.should.be.calledWith(colDoc); + callback.should.be.calledWith(newDocValue); + callback.should.be.calledThrice(); + + // Tear down + + unsubscribe(); + }); + + it("doesn't call callback when the ref is updated with the same value", async () => { + const colDoc = await resetTestCollectionDoc(); + await sleep(50); + + const collectionRef = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC); + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise(resolve2 => { + unsubscribe = collectionRef.onSnapshot(snapshot => { + snapshot.forEach(doc => { + callback(doc.data()); + }); + resolve2(); + }); + }); + + callback.should.be.calledWith(colDoc); + + const docRef = firebase.firestore().doc(COL_DOC_1_PATH); + await docRef.set(colDoc); + + await sleep(150); + + // Assertions + + callback.should.be.calledOnce(); // Callback is not called again + + // Tear down + + unsubscribe(); + }); + + it('allows binding multiple callbacks to the same ref', async () => { + const colDoc = await resetTestCollectionDoc(); + await sleep(50); + + // Setup + const collectionRef = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC); + + const newDocValue = { ...colDoc, foo: '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(colDoc); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(colDoc); + callbackB.should.be.calledOnce(); + + const docRef = firebase.firestore().doc(COL_DOC_1_PATH); + await docRef.set(newDocValue); + + await sleep(25); + + callbackA.should.be.calledWith(newDocValue); + callbackB.should.be.calledWith(newDocValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Tear down + + unsubscribeA(); + unsubscribeB(); + }); + + it('listener stops listening when unsubscribed', async () => { + const colDoc = await resetTestCollectionDoc(); + await sleep(50); + + // Setup + const collectionRef = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC); + const newDocValue = { ...colDoc, foo: '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(colDoc); + callbackA.should.be.calledOnce(); + + callbackB.should.be.calledWith(colDoc); + callbackB.should.be.calledOnce(); + + const docRef = firebase.firestore().doc(COL_DOC_1_PATH); + await docRef.set(newDocValue); + + await sleep(25); + + callbackA.should.be.calledWith(newDocValue); + callbackB.should.be.calledWith(newDocValue); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledTwice(); + + // Unsubscribe A + + unsubscribeA(); + + await docRef.set(colDoc); + + await sleep(25); + + callbackB.should.be.calledWith(colDoc); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + + // Unsubscribe B + + unsubscribeB(); + + await docRef.set(newDocValue); + + await sleep(25); + + callbackA.should.be.calledTwice(); + callbackB.should.be.calledThrice(); + }); + + it('supports options and callback', async () => { + const colDoc = await resetTestCollectionDoc(); + await sleep(50); + + const collectionRef = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC); + const newDocValue = { ...colDoc, foo: 'updated' }; + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise(resolve2 => { + unsubscribe = collectionRef.onSnapshot( + { + includeMetadataChanges: true, + }, + snapshot => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); + } + ); + }); + + callback.should.be.calledWith(colDoc); + + const docRef = firebase.firestore().doc(COL_DOC_1_PATH); + await docRef.set(newDocValue); + + await sleep(25); + + // Assertions + + callback.should.be.calledWith(newDocValue); + + // Tear down + + unsubscribe(); + }); + + it('supports observer', async () => { + const colDoc = await resetTestCollectionDoc(); + await sleep(50); + + const collectionRef = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC); + const newDocValue = { ...colDoc, foo: 'updated' }; + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise(resolve2 => { + const observer = { + next: snapshot => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); + }, + }; + unsubscribe = collectionRef.onSnapshot(observer); + }); + + callback.should.be.calledWith(colDoc); + + const docRef = firebase.firestore().doc(COL_DOC_1_PATH); + await docRef.set(newDocValue); + + await sleep(25); + + // Assertions + + callback.should.be.calledWith(newDocValue); + callback.should.be.calledTwice(); + + // Tear down + + unsubscribe(); + }); + + it('supports options and observer', async () => { + const colDoc = await resetTestCollectionDoc(); + await sleep(50); + + const collectionRef = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC); + const newDocValue = { ...colDoc, foo: 'updated' }; + + const callback = sinon.spy(); + + // Test + + let unsubscribe; + await new Promise(resolve2 => { + const observer = { + next: snapshot => { + snapshot.forEach(doc => callback(doc.data())); + resolve2(); + }, + error: () => {}, + }; + unsubscribe = collectionRef.onSnapshot( + { + includeMetadataChanges: true, + }, + observer + ); + }); + + callback.should.be.calledWith(colDoc); + + const docRef = firebase.firestore().doc(COL_DOC_1_PATH); + await docRef.set(newDocValue); + + await sleep(25); + + // Assertions + + callback.should.be.calledWith(newDocValue); + + // Tear down + + unsubscribe(); + }); + + it('errors when invalid parameters supplied', async () => { + const colRef = firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC); + + (() => { + colRef.onSnapshot(() => {}, 'error'); + }).should.throw( + 'Query.onSnapshot failed: Second argument must be a valid function.' + ); + (() => { + colRef.onSnapshot({ + next: () => {}, + error: 'error', + }); + }).should.throw( + 'Query.onSnapshot failed: Observer.error must be a valid function.' + ); + (() => { + colRef.onSnapshot({ + next: 'error', + }); + }).should.throw( + 'Query.onSnapshot failed: Observer.next must be a valid function.' + ); + (() => { + colRef.onSnapshot( + { + includeMetadataChanges: true, + }, + () => {}, + 'error' + ); + }).should.throw( + 'Query.onSnapshot failed: Third argument must be a valid function.' + ); + (() => { + colRef.onSnapshot( + { + includeMetadataChanges: true, + }, + { + next: () => {}, + error: 'error', + } + ); + }).should.throw( + 'Query.onSnapshot failed: Observer.error must be a valid function.' + ); + (() => { + colRef.onSnapshot( + { + includeMetadataChanges: true, + }, + { + next: 'error', + } + ); + }).should.throw( + 'Query.onSnapshot failed: Observer.next must be a valid function.' + ); + (() => { + colRef.onSnapshot( + { + includeMetadataChanges: true, + }, + 'error' + ); + }).should.throw( + 'Query.onSnapshot failed: Second argument must be a function or observer.' + ); + (() => { + colRef.onSnapshot({ + error: 'error', + }); + }).should.throw( + 'Query.onSnapshot failed: First argument must be a function, observer or options.' + ); + (() => { + colRef.onSnapshot(); + }).should.throw( + 'Query.onSnapshot failed: Called with invalid arguments.' + ); + }); + }); + }); +}); diff --git a/tests/e2e/firestore/collectionReference.e2e.js b/tests/e2e/firestore/collectionReference.e2e.js index 9765beeb..95e7d4b2 100644 --- a/tests/e2e/firestore/collectionReference.e2e.js +++ b/tests/e2e/firestore/collectionReference.e2e.js @@ -170,570 +170,5 @@ describe('firestore()', () => { return Promise.resolve(); }); }); - - describe('onSnapshot()', () => { - it('QuerySnapshot has correct properties', async () => { - const collection = testCollection(TEST_COLLECTION_NAME); - const snapshot = await collection.get(); - snapshot.docChanges.should.be.an.Array(); - snapshot.empty.should.equal(false); - snapshot.metadata.should.be.an.Object(); - snapshot.query.should.be.an.Object(); - }); - - it('DocumentChange has correct properties', async () => { - const collection = testCollection(TEST_COLLECTION_NAME); - - // Test - let changes; - let unsubscribe; - await new Promise(resolve => { - unsubscribe = collection.onSnapshot(snapshot => { - changes = snapshot.docChanges; - resolve(); - }); - }); - - // Assertions - changes.should.be.a.Array(); - changes[0].doc.should.be.an.Object(); - changes[0].newIndex.should.be.a.Number(); - changes[0].oldIndex.should.be.a.Number(); - changes[0].type.should.be.a.String(); - - // Tear down - unsubscribe(); - }); - - xit('calls callback with the initial data and then when document changes', async () => { - await cleanCollection(TEST_COLLECTION_NAME); - - const callback = sinon.spy(); - const collection = testCollection(TEST_COLLECTION_NAME); - const newDocValue = { ...COL_DOC_1(), foo: 'updated' }; - - // Test - let unsubscribe; - let resolved = false; - await new Promise(resolve => { - unsubscribe = collection.onSnapshot(snapshot => { - if (snapshot && snapshot.docs.length) { - callback(snapshot.docs[0].data()); - } else { - callback(null); - } - - if (!resolved) { - resolved = true; - resolve(); - } - }); - }); - - callback.should.be.calledOnce(); - - await testCollectionDoc(COL_DOC_1_PATH).set(newDocValue); - await sleep(25); - - // Assertions - callback.should.be.calledTwice(); - callback.getCall(1).args[0].foo.should.equal('updated'); - - // Tear down - unsubscribe(); - }); - - // crappy race condition somewhere =/ will come back to it later - xit('calls callback with the initial data and then when document is added', async () => { - await cleanCollection(TEST_COLLECTION_NAME); - const colDoc = await resetTestCollectionDoc(); - - await sleep(50); - - const collectionRef = firebase - .firestore() - .collection(TEST_COLLECTION_NAME); - - const newDocValue = { foo: 'updated' }; - - const callback = sinon.spy(); - - // Test - - let unsubscribe; - await new Promise(resolve2 => { - unsubscribe = collectionRef.onSnapshot(snapshot => { - snapshot.forEach(doc => callback(doc.data())); - resolve2(); - }); - }); - - callback.should.be.calledWith(colDoc); - - const docRef = firebase.firestore().doc(DOC_2_PATH); - await docRef.set(newDocValue); - - await new Promise(resolve2 => { - setTimeout(() => resolve2(), 5); - }); - - // Assertions - - callback.should.be.calledWith(colDoc); - callback.should.be.calledWith(newDocValue); - callback.should.be.calledThrice(); - - // Tear down - - unsubscribe(); - }); - - // it("doesn't call callback when the ref is updated with the same value", async () => { - // const collectionRef = firebase - // .firestore() - // .collection('collection-tests'); - - // const callback = sinon.spy(); - - // // Test - - // let unsubscribe; - // await new Promise(resolve2 => { - // unsubscribe = collectionRef.onSnapshot(snapshot => { - // snapshot.forEach(doc => callback(doc.data())); - // resolve2(); - // }); - // }); - - // callback.should.be.calledWith(COL_DOC_1); - - // const docRef = firebase.firestore().doc('collection-tests/col1'); - // await docRef.set(COL_DOC_1); - - // await new Promise(resolve2 => { - // setTimeout(() => resolve2(), 5); - // }); - - // // Assertions - - // callback.should.be.calledOnce(); // Callback is not called again - - // // Tear down - - // unsubscribe(); - // }); - - // it('allows binding multiple callbacks to the same ref', async () => { - // // Setup - // const collectionRef = firebase - // .firestore() - // .collection('collection-tests'); - // const newDocValue = { ...COL_DOC_1, foo: '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(COL_DOC_1); - // callbackA.should.be.calledOnce(); - - // callbackB.should.be.calledWith(COL_DOC_1); - // callbackB.should.be.calledOnce(); - - // const docRef = firebase.firestore().doc('collection-tests/col1'); - // 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(); - // }); - - // it('listener stops listening when unsubscribed', async () => { - // // Setup - // const collectionRef = firebase - // .firestore() - // .collection('collection-tests'); - // const newDocValue = { ...COL_DOC_1, foo: '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(COL_DOC_1); - // callbackA.should.be.calledOnce(); - - // callbackB.should.be.calledWith(COL_DOC_1); - // callbackB.should.be.calledOnce(); - - // const docRef = firebase.firestore().doc('collection-tests/col1'); - // 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(COL_DOC_1); - - // await new Promise(resolve2 => { - // setTimeout(() => resolve2(), 5); - // }); - - // callbackB.should.be.calledWith(COL_DOC_1); - - // 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(); - // }); - - // it('supports options and callback', async () => { - // const collectionRef = firebase - // .firestore() - // .collection('collection-tests'); - // const newDocValue = { ...COL_DOC_1, foo: 'updated' }; - - // const callback = sinon.spy(); - - // // Test - - // let unsubscribe; - // await new Promise(resolve2 => { - // unsubscribe = collectionRef.onSnapshot( - // { - // includeMetadataChanges: true, - // }, - // snapshot => { - // snapshot.forEach(doc => callback(doc.data())); - // resolve2(); - // } - // ); - // }); - - // callback.should.be.calledWith(COL_DOC_1); - - // const docRef = firebase.firestore().doc('collection-tests/col1'); - // await docRef.set(newDocValue); - - // await new Promise(resolve2 => { - // setTimeout(() => resolve2(), 5); - // }); - - // // Assertions - - // callback.should.be.calledWith(newDocValue); - - // // Tear down - - // unsubscribe(); - // }); - - // it('supports observer', async () => { - // const collectionRef = firebase - // .firestore() - // .collection('collection-tests'); - // const newDocValue = { ...COL_DOC_1, foo: 'updated' }; - - // const callback = sinon.spy(); - - // // Test - - // let unsubscribe; - // await new Promise(resolve2 => { - // const observer = { - // next: snapshot => { - // snapshot.forEach(doc => callback(doc.data())); - // resolve2(); - // }, - // }; - // unsubscribe = collectionRef.onSnapshot(observer); - // }); - - // callback.should.be.calledWith(COL_DOC_1); - - // const docRef = firebase.firestore().doc('collection-tests/col1'); - // await docRef.set(newDocValue); - - // await new Promise(resolve2 => { - // setTimeout(() => resolve2(), 5); - // }); - - // // Assertions - - // callback.should.be.calledWith(newDocValue); - // callback.should.be.calledTwice(); - - // // Tear down - - // unsubscribe(); - // }); - - // it('supports options and observer', async () => { - // const collectionRef = firebase - // .firestore() - // .collection('collection-tests'); - // const newDocValue = { ...COL_DOC_1, foo: 'updated' }; - - // const callback = sinon.spy(); - - // // Test - - // let unsubscribe; - // await new Promise(resolve2 => { - // const observer = { - // next: snapshot => { - // snapshot.forEach(doc => callback(doc.data())); - // resolve2(); - // }, - // error: () => {}, - // }; - // unsubscribe = collectionRef.onSnapshot( - // { - // includeMetadataChanges: true, - // }, - // observer - // ); - // }); - - // callback.should.be.calledWith(COL_DOC_1); - - // const docRef = firebase.firestore().doc('collection-tests/col1'); - // await docRef.set(newDocValue); - - // await new Promise(resolve2 => { - // setTimeout(() => resolve2(), 5); - // }); - - // // Assertions - - // callback.should.be.calledWith(newDocValue); - - // // Tear down - - // unsubscribe(); - // }); - - // it('errors when invalid parameters supplied', async () => { - // const colRef = firebase.firestore().collection('collection-tests'); - - // (() => { - // colRef.onSnapshot(() => {}, 'error'); - // }).should.throw( - // 'Query.onSnapshot failed: Second argument must be a valid function.' - // ); - // (() => { - // colRef.onSnapshot({ - // next: () => {}, - // error: 'error', - // }); - // }).should.throw( - // 'Query.onSnapshot failed: Observer.error must be a valid function.' - // ); - // (() => { - // colRef.onSnapshot({ - // next: 'error', - // }); - // }).should.throw( - // 'Query.onSnapshot failed: Observer.next must be a valid function.' - // ); - // (() => { - // colRef.onSnapshot( - // { - // includeMetadataChanges: true, - // }, - // () => {}, - // 'error' - // ); - // }).should.throw( - // 'Query.onSnapshot failed: Third argument must be a valid function.' - // ); - // (() => { - // colRef.onSnapshot( - // { - // includeMetadataChanges: true, - // }, - // { - // next: () => {}, - // error: 'error', - // } - // ); - // }).should.throw( - // 'Query.onSnapshot failed: Observer.error must be a valid function.' - // ); - // (() => { - // colRef.onSnapshot( - // { - // includeMetadataChanges: true, - // }, - // { - // next: 'error', - // } - // ); - // }).should.throw( - // 'Query.onSnapshot failed: Observer.next must be a valid function.' - // ); - // (() => { - // colRef.onSnapshot( - // { - // includeMetadataChanges: true, - // }, - // 'error' - // ); - // }).should.throw( - // 'Query.onSnapshot failed: Second argument must be a function or observer.' - // ); - // (() => { - // colRef.onSnapshot({ - // error: 'error', - // }); - // }).should.throw( - // 'Query.onSnapshot failed: First argument must be a function, observer or options.' - // ); - // (() => { - // colRef.onSnapshot(); - // }).should.throw( - // 'Query.onSnapshot failed: Called with invalid arguments.' - // ); - // }); - // }); - - - // describe('onSnapshot()', () => { - // it('gets called correctly', async () => { - // const collectionRef = collectionTests - // .orderBy('object.daz') - // .endAt(345); - // const newDocValue = { ...COL_DOC_1, object: { daz: 346 } }; - - // const callback = sinon.spy(); - - // // Test - - // let unsubscribe; - // await new Promise(resolve2 => { - // unsubscribe = collectionRef.onSnapshot(snapshot => { - // callback(snapshot.docs.map(doc => doc.data().daz)); - // resolve2(); - // }); - // }); - - // callback.should.be.calledWith([123, 234, 345]); - - // const docRef = firebase.firestore().doc('collection-tests2/col1'); - // await docRef.set(newDocValue); - - // await new Promise(resolve2 => { - // setTimeout(() => resolve2(), 5); - // }); - - // // Assertions - - // callback.should.be.calledWith([234, 345]); - // callback.should.be.calledTwice(); - - // // Tear down - - // unsubscribe(); - // }); - - // it('gets called correctly when combined with where', async () => { - // const collectionRef = collectionTests - // .where('baz', '==', true) - // .orderBy('daz'); - // const newDocValue = { ...COL_DOC_1, daz: 678 }; - - // const callback = sinon.spy(); - - // // Test - - // let unsubscribe; - // await new Promise(resolve2 => { - // unsubscribe = collectionRef.onSnapshot(snapshot => { - // callback(snapshot.docs.map(doc => doc.data().daz)); - // resolve2(); - // }); - // }); - - // callback.should.be.calledWith([123, 234, 345, 456, 567]); - - // const docRef = firebase.firestore().doc('collection-tests2/col6'); - // await docRef.set(newDocValue); - - // await new Promise(resolve2 => { - // setTimeout(() => resolve2(), 5); - // }); - - // // Assertions - - // callback.should.be.calledWith([123, 234, 345, 456, 567, 678]); - // callback.should.be.calledTwice(); - - // // Tear down - - // unsubscribe(); - // }); - // }); - }); }); }); diff --git a/tests/helpers/firestore.js b/tests/helpers/firestore.js index 22593a8a..6c49b4b9 100644 --- a/tests/helpers/firestore.js +++ b/tests/helpers/firestore.js @@ -1,7 +1,7 @@ const TEST_COLLECTION_NAME = 'tests'; const TEST2_COLLECTION_NAME = 'tests2'; -const TEST_COLLECTION_NAME_DYNAMIC = `tests${Math.floor(Math.random() * 31) + - 2}`; +const TEST_COLLECTION_NAME_DYNAMIC = `tests${Math.floor(Math.random() * 30) + + 1}`; // const TEST3_COLLECTION_NAME = 'tests3'; let shouldCleanup = false; @@ -25,10 +25,10 @@ module.exports = { // TEST3_COLLECTION_NAME, DOC_1: { name: 'doc1' }, - DOC_1_PATH: `tests/doc1${testRunId}`, + DOC_1_PATH: `${TEST_COLLECTION_NAME_DYNAMIC}/doc1${testRunId}`, DOC_2: { name: 'doc2', title: 'Document 2' }, - DOC_2_PATH: `tests/doc2${testRunId}`, + DOC_2_PATH: `${TEST_COLLECTION_NAME_DYNAMIC}/doc2${testRunId}`, // needs to be a fn as firebase may not yet be available COL_DOC_1() { @@ -71,7 +71,7 @@ module.exports = { COL_DOC_1_PATH: `${TEST_COLLECTION_NAME_DYNAMIC}/col1${testRunId}`, COL2_DOC_1_ID: `doc1${testRunId}`, - COL2_DOC_1_PATH: `${TEST2_COLLECTION_NAME}/doc1${testRunId}`, + COL2_DOC_1_PATH: `${TEST_COLLECTION_NAME_DYNAMIC}/doc1${testRunId}`, /** * Removes all documents on the collection for the current testId or @@ -81,7 +81,7 @@ module.exports = { * @return {Promise<*>} */ async cleanCollection(collectionName) { - const firestore = firebaseAdmin.firestore(); + const firestore = firebase.firestore(); const collection = firestore.collection( collectionName || TEST_COLLECTION_NAME ); From 75dc6090478b8ed8ef2bd12ad6232e8321c0ae02 Mon Sep 17 00:00:00 2001 From: Salakar Date: Thu, 20 Sep 2018 16:36:22 +0100 Subject: [PATCH 10/12] [tests][firestore] fix documentReference tests --- tests/e2e/firestore/documentReference.e2e.js | 4 ++-- tests/helpers/firestore.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/e2e/firestore/documentReference.e2e.js b/tests/e2e/firestore/documentReference.e2e.js index 8936b57b..be156f8d 100644 --- a/tests/e2e/firestore/documentReference.e2e.js +++ b/tests/e2e/firestore/documentReference.e2e.js @@ -3,7 +3,7 @@ const { COL2_DOC_1, COL2_DOC_1_ID, COL2_DOC_1_PATH, - TEST2_COLLECTION_NAME, + TEST_COLLECTION_NAME_DYNAMIC, resetTestCollectionDoc, } = TestHelpers.firestore; @@ -31,7 +31,7 @@ describe('firestore()', () => { describe('parent', () => { it('should return parent collection', () => { const document = test2DocRef(COL2_DOC_1_ID); - document.parent.id.should.equal(TEST2_COLLECTION_NAME); + document.parent.id.should.equal(TEST_COLLECTION_NAME_DYNAMIC); }); }); diff --git a/tests/helpers/firestore.js b/tests/helpers/firestore.js index 6c49b4b9..12b26f54 100644 --- a/tests/helpers/firestore.js +++ b/tests/helpers/firestore.js @@ -60,6 +60,8 @@ module.exports = { gaz: 12.1234567, geopoint: new firebase.firestore.GeoPoint(0, 0), naz: null, + arrNumber: [1, 2, 3, 4], + arrString: ['a', 'b', 'c', 'd'], object: { daz: 123, }, @@ -115,7 +117,7 @@ module.exports = { shouldCleanup = true; return firebase .firestore() - .collection(TEST_COLLECTION_NAME) + .collection(TEST_COLLECTION_NAME_DYNAMIC) .doc( docId.startsWith(testRunId) || docId.endsWith(testRunId) ? docId @@ -127,7 +129,7 @@ module.exports = { shouldCleanup = true; return firebase .firestore() - .collection(TEST2_COLLECTION_NAME) + .collection(TEST_COLLECTION_NAME_DYNAMIC) .doc( docId.startsWith(testRunId) || docId.endsWith(testRunId) ? docId @@ -153,9 +155,7 @@ module.exports = { async resetTestCollectionDoc(path, doc) { shouldCleanup = true; const _doc = doc || module.exports.COL_DOC_1(); - await module.exports.cleanCollection(TEST_COLLECTION_NAME_DYNAMIC); - await firebase .firestore() .doc(path || module.exports.COL_DOC_1_PATH) From a8130d7c3d45325e1518af4d41ea62e5fc864c6c Mon Sep 17 00:00:00 2001 From: Salakar Date: Thu, 20 Sep 2018 16:58:40 +0100 Subject: [PATCH 11/12] [tests][firestore] add where `array-contains` tests --- tests/e2e/firestore/collection/where.e2e.js | 70 ++++++++++++++++++--- 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/tests/e2e/firestore/collection/where.e2e.js b/tests/e2e/firestore/collection/where.e2e.js index f8307f72..7377db2b 100644 --- a/tests/e2e/firestore/collection/where.e2e.js +++ b/tests/e2e/firestore/collection/where.e2e.js @@ -8,8 +8,58 @@ const { describe('firestore()', () => { describe('CollectionReference', () => { before(() => resetTestCollectionDoc(COL_DOC_1_PATH, COL_DOC_1())); - describe('where()', () => { - it('correctly handles == boolean values', () => + describe.only('where()', () => { + it('`array-contains` a string value', async () => { + const found = await firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .where('arrString', 'array-contains', 'a') + .get(); + + should.equal(found.size, 1); + found.forEach(documentSnapshot => { + should.deepEqual( + documentSnapshot.data().arrString, + jet.contextify(['a', 'b', 'c', 'd']) + ); + }); + + const notFound = await firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .where('arrString', 'array-contains', 'f') + .get(); + + should.equal(notFound.size, 0); + }); + + it('`array-contains` a number value', async () => { + const found = await firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .where('arrNumber', 'array-contains', 1) + .get(); + + should.equal(found.size, 1); + found.forEach(documentSnapshot => { + should.deepEqual( + documentSnapshot.data().arrNumber, + jet.contextify([1, 2, 3, 4]) + ); + }); + + const notFound = await firebase + .firestore() + .collection(TEST_COLLECTION_NAME_DYNAMIC) + .where('arrNumber', 'array-contains', 5) + .get(); + + should.equal(notFound.size, 0); + }); + + // TODO: below tests should also check the inverse to ensure working as + // TODO: currently there is only one document in the collection so might be false positives in future + it('== boolean value', () => firebase .firestore() .collection(TEST_COLLECTION_NAME_DYNAMIC) @@ -22,7 +72,7 @@ describe('firestore()', () => { }); })); - it('correctly handles == string values', () => + it('== string value', () => firebase .firestore() .collection(TEST_COLLECTION_NAME_DYNAMIC) @@ -35,7 +85,7 @@ describe('firestore()', () => { }); })); - it('correctly handles == null values', () => + it('== null value', () => firebase .firestore() .collection(TEST_COLLECTION_NAME_DYNAMIC) @@ -48,7 +98,7 @@ describe('firestore()', () => { }); })); - it('correctly handles == date values', () => + it('== date value', () => firebase .firestore() .collection(TEST_COLLECTION_NAME_DYNAMIC) @@ -58,7 +108,7 @@ describe('firestore()', () => { should.equal(querySnapshot.size, 1); })); - it('correctly handles == geopoint values', () => + it('== GeoPoint value', () => firebase .firestore() .collection(TEST_COLLECTION_NAME_DYNAMIC) @@ -68,7 +118,7 @@ describe('firestore()', () => { should.equal(querySnapshot.size, 1); })); - it('correctly handles >= number values', () => + it('>= number value', () => firebase .firestore() .collection(TEST_COLLECTION_NAME_DYNAMIC) @@ -81,7 +131,7 @@ describe('firestore()', () => { }); })); - it('correctly handles >= geopoint values', () => + it('>= GeoPoint value', () => firebase .firestore() .collection(TEST_COLLECTION_NAME_DYNAMIC) @@ -91,7 +141,7 @@ describe('firestore()', () => { should.equal(querySnapshot.size, 1); })); - it('correctly handles <= float values', () => + it('<= float value', () => firebase .firestore() .collection(TEST_COLLECTION_NAME_DYNAMIC) @@ -104,7 +154,7 @@ describe('firestore()', () => { }); })); - it('correctly handles FieldPath', () => + it('FieldPath', () => firebase .firestore() .collection(TEST_COLLECTION_NAME_DYNAMIC) From 197a26d8c427bb42227ef5f334d8123ab2819211 Mon Sep 17 00:00:00 2001 From: Salakar Date: Thu, 20 Sep 2018 17:11:01 +0100 Subject: [PATCH 12/12] [tests][firestore] remove `only` --- tests/e2e/firestore/collection/where.e2e.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/firestore/collection/where.e2e.js b/tests/e2e/firestore/collection/where.e2e.js index 7377db2b..b5a83c9c 100644 --- a/tests/e2e/firestore/collection/where.e2e.js +++ b/tests/e2e/firestore/collection/where.e2e.js @@ -8,7 +8,7 @@ const { describe('firestore()', () => { describe('CollectionReference', () => { before(() => resetTestCollectionDoc(COL_DOC_1_PATH, COL_DOC_1())); - describe.only('where()', () => { + describe('where()', () => { it('`array-contains` a string value', async () => { const found = await firebase .firestore()