FIRQueryTests.mm 57 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <FirebaseFirestore/FirebaseFirestore.h>
  17. #import <XCTest/XCTest.h>
  18. #import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
  19. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  20. #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
  21. namespace {
  22. NSArray<NSString *> *SortedStringsNotIn(NSSet<NSString *> *set, NSSet<NSString *> *remove) {
  23. NSMutableSet<NSString *> *mutableSet = [NSMutableSet setWithSet:set];
  24. [mutableSet minusSet:remove];
  25. return [mutableSet.allObjects sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
  26. }
  27. } // namespace
  28. @interface FIRQueryTests : FSTIntegrationTestCase
  29. @end
  30. @implementation FIRQueryTests
  31. /**
  32. * Checks that running the query while online (against the backend/emulator) results in the same
  33. * documents as running the query while offline. It also checks that both online and offline
  34. * query result is equal to the expected documents.
  35. *
  36. * @param query The query to check.
  37. * @param expectedDocs Array of document keys that are expected to match the query.
  38. */
  39. - (void)checkOnlineAndOfflineQuery:(FIRQuery *)query matchesResult:(NSArray *)expectedDocs {
  40. FIRQuerySnapshot *docsFromServer = [self readDocumentSetForRef:query
  41. source:FIRFirestoreSourceServer];
  42. FIRQuerySnapshot *docsFromCache = [self readDocumentSetForRef:query
  43. source:FIRFirestoreSourceCache];
  44. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(docsFromServer),
  45. FIRQuerySnapshotGetIDs(docsFromCache));
  46. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(docsFromCache), expectedDocs);
  47. }
  48. - (void)testLimitQueries {
  49. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  50. @"a" : @{@"k" : @"a"},
  51. @"b" : @{@"k" : @"b"},
  52. @"c" : @{@"k" : @"c"}
  53. }];
  54. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[collRef queryLimitedTo:2]];
  55. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ @{@"k" : @"a"}, @{@"k" : @"b"} ]));
  56. }
  57. - (void)testLimitQueriesWithDescendingSortOrder {
  58. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  59. @"a" : @{@"k" : @"a", @"sort" : @0},
  60. @"b" : @{@"k" : @"b", @"sort" : @1},
  61. @"c" : @{@"k" : @"c", @"sort" : @1},
  62. @"d" : @{@"k" : @"d", @"sort" : @2},
  63. }];
  64. FIRQuerySnapshot *snapshot =
  65. [self readDocumentSetForRef:[[collRef queryOrderedByField:@"sort"
  66. descending:YES] queryLimitedTo:2]];
  67. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  68. (@[ @{@"k" : @"d", @"sort" : @2}, @{@"k" : @"c", @"sort" : @1} ]));
  69. }
  70. - (void)testLimitToLastMustAlsoHaveExplicitOrderBy {
  71. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{}];
  72. FIRQuery *query = [collRef queryLimitedToLast:2];
  73. FSTAssertThrows([query getDocumentsWithCompletion:^(FIRQuerySnapshot *, NSError *){
  74. }],
  75. @"limit(toLast:) queries require specifying at least one OrderBy() clause.");
  76. }
  77. // Two queries that mapped to the same target ID are referred to as
  78. // "mirror queries". An example for a mirror query is a limitToLast()
  79. // query and a limit() query that share the same backend Target ID.
  80. // Since limitToLast() queries are sent to the backend with a modified
  81. // orderBy() clause, they can map to the same target representation as
  82. // limit() query, even if both queries appear separate to the user.
  83. - (void)testListenUnlistenRelistenSequenceOfMirrorQueries {
  84. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  85. @"a" : @{@"k" : @"a", @"sort" : @0},
  86. @"b" : @{@"k" : @"b", @"sort" : @1},
  87. @"c" : @{@"k" : @"c", @"sort" : @1},
  88. @"d" : @{@"k" : @"d", @"sort" : @2},
  89. }];
  90. // Setup a `limit` query.
  91. FIRQuery *limit = [[collRef queryOrderedByField:@"sort" descending:NO] queryLimitedTo:2];
  92. FSTEventAccumulator *limitAccumulator = [FSTEventAccumulator accumulatorForTest:self];
  93. id<FIRListenerRegistration> limitRegistration =
  94. [limit addSnapshotListener:limitAccumulator.valueEventHandler];
  95. // Setup a mirroring `limitToLast` query.
  96. FIRQuery *limitToLast = [[collRef queryOrderedByField:@"sort"
  97. descending:YES] queryLimitedToLast:2];
  98. FSTEventAccumulator *limitToLastAccumulator = [FSTEventAccumulator accumulatorForTest:self];
  99. id<FIRListenerRegistration> limitToLastRegistration =
  100. [limitToLast addSnapshotListener:limitToLastAccumulator.valueEventHandler];
  101. // Verify both queries get expected result.
  102. FIRQuerySnapshot *snapshot = [limitAccumulator awaitEventWithName:@"Snapshot"];
  103. NSArray *expected = @[ @{@"k" : @"a", @"sort" : @0}, @{@"k" : @"b", @"sort" : @1} ];
  104. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  105. snapshot = [limitToLastAccumulator awaitEventWithName:@"Snapshot"];
  106. expected = @[ @{@"k" : @"b", @"sort" : @1}, @{@"k" : @"a", @"sort" : @0} ];
  107. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  108. // Unlisten then re-listen limit query.
  109. [limitRegistration remove];
  110. [limit addSnapshotListener:[limitAccumulator valueEventHandler]];
  111. // Verify limit query still works.
  112. snapshot = [limitAccumulator awaitEventWithName:@"Snapshot"];
  113. expected = @[ @{@"k" : @"a", @"sort" : @0}, @{@"k" : @"b", @"sort" : @1} ];
  114. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  115. // Add a document that would change the result set.
  116. [self addDocumentRef:collRef data:@{@"k" : @"e", @"sort" : @-1}];
  117. // Verify both queries get expected result.
  118. snapshot = [limitAccumulator awaitEventWithName:@"Snapshot"];
  119. expected = @[ @{@"k" : @"e", @"sort" : @-1}, @{@"k" : @"a", @"sort" : @0} ];
  120. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  121. snapshot = [limitToLastAccumulator awaitEventWithName:@"Snapshot"];
  122. expected = @[ @{@"k" : @"a", @"sort" : @0}, @{@"k" : @"e", @"sort" : @-1} ];
  123. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  124. // Unlisten to limitToLast, update a doc, then relisten to limitToLast
  125. [limitToLastRegistration remove];
  126. [self updateDocumentRef:[collRef documentWithPath:@"a"] data:@{@"k" : @"a", @"sort" : @-2}];
  127. [limitToLast addSnapshotListener:[limitToLastAccumulator valueEventHandler]];
  128. // Verify both queries get expected result.
  129. snapshot = [limitAccumulator awaitEventWithName:@"Snapshot"];
  130. expected = @[ @{@"k" : @"a", @"sort" : @-2}, @{@"k" : @"e", @"sort" : @-1} ];
  131. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  132. snapshot = [limitToLastAccumulator awaitEventWithName:@"Snapshot"];
  133. expected = @[ @{@"k" : @"e", @"sort" : @-1}, @{@"k" : @"a", @"sort" : @-2} ];
  134. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), expected);
  135. }
  136. - (void)testLimitToLastQueriesWithCursors {
  137. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  138. @"a" : @{@"k" : @"a", @"sort" : @0},
  139. @"b" : @{@"k" : @"b", @"sort" : @1},
  140. @"c" : @{@"k" : @"c", @"sort" : @1},
  141. @"d" : @{@"k" : @"d", @"sort" : @2},
  142. }];
  143. FIRQuerySnapshot *snapshot =
  144. [self readDocumentSetForRef:[[[collRef queryOrderedByField:@"sort"] queryLimitedToLast:3]
  145. queryEndingBeforeValues:@[ @2 ]]];
  146. XCTAssertEqualObjects(
  147. FIRQuerySnapshotGetData(snapshot), (@[
  148. @{@"k" : @"a", @"sort" : @0}, @{@"k" : @"b", @"sort" : @1}, @{@"k" : @"c", @"sort" : @1}
  149. ]));
  150. snapshot = [self readDocumentSetForRef:[[[collRef queryOrderedByField:@"sort"]
  151. queryLimitedToLast:3] queryEndingAtValues:@[ @1 ]]];
  152. XCTAssertEqualObjects(
  153. FIRQuerySnapshotGetData(snapshot), (@[
  154. @{@"k" : @"a", @"sort" : @0}, @{@"k" : @"b", @"sort" : @1}, @{@"k" : @"c", @"sort" : @1}
  155. ]));
  156. snapshot = [self readDocumentSetForRef:[[[collRef queryOrderedByField:@"sort"]
  157. queryLimitedToLast:3] queryStartingAtValues:@[ @2 ]]];
  158. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ @{@"k" : @"d", @"sort" : @2} ]));
  159. snapshot =
  160. [self readDocumentSetForRef:[[[collRef queryOrderedByField:@"sort"] queryLimitedToLast:3]
  161. queryStartingAfterValues:@[ @0 ]]];
  162. XCTAssertEqualObjects(
  163. FIRQuerySnapshotGetData(snapshot), (@[
  164. @{@"k" : @"b", @"sort" : @1}, @{@"k" : @"c", @"sort" : @1}, @{@"k" : @"d", @"sort" : @2}
  165. ]));
  166. snapshot =
  167. [self readDocumentSetForRef:[[[collRef queryOrderedByField:@"sort"] queryLimitedToLast:3]
  168. queryStartingAfterValues:@[ @-1 ]]];
  169. XCTAssertEqualObjects(
  170. FIRQuerySnapshotGetData(snapshot), (@[
  171. @{@"k" : @"b", @"sort" : @1}, @{@"k" : @"c", @"sort" : @1}, @{@"k" : @"d", @"sort" : @2}
  172. ]));
  173. }
  174. - (void)testKeyOrderIsDescendingForDescendingInequality {
  175. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  176. @"a" : @{@"foo" : @42},
  177. @"b" : @{@"foo" : @42.0},
  178. @"c" : @{@"foo" : @42},
  179. @"d" : @{@"foo" : @21},
  180. @"e" : @{@"foo" : @21.0},
  181. @"f" : @{@"foo" : @66},
  182. @"g" : @{@"foo" : @66.0},
  183. }];
  184. FIRQuerySnapshot *snapshot =
  185. [self readDocumentSetForRef:[[collRef queryWhereField:@"foo"
  186. isGreaterThan:@21] queryOrderedByField:@"foo"
  187. descending:YES]];
  188. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"g", @"f", @"c", @"b", @"a" ]));
  189. }
  190. - (void)testUnaryFilterQueries {
  191. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  192. @"a" : @{@"null" : [NSNull null], @"nan" : @(NAN)},
  193. @"b" : @{@"null" : [NSNull null], @"nan" : @0},
  194. @"c" : @{@"null" : @NO, @"nan" : @(NAN)}
  195. }];
  196. FIRQuerySnapshot *results =
  197. [self readDocumentSetForRef:[[collRef queryWhereField:@"null"
  198. isEqualTo:[NSNull null]] queryWhereField:@"nan"
  199. isEqualTo:@(NAN)]];
  200. XCTAssertEqualObjects(FIRQuerySnapshotGetData(results),
  201. (@[ @{@"null" : [NSNull null], @"nan" : @(NAN)} ]));
  202. }
  203. - (void)testQueryWithFieldPaths {
  204. FIRCollectionReference *collRef = [self
  205. collectionRefWithDocuments:@{@"a" : @{@"a" : @1}, @"b" : @{@"a" : @2}, @"c" : @{@"a" : @3}}];
  206. FIRQuery *query = [collRef queryWhereFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
  207. isLessThan:@3];
  208. query = [query queryOrderedByFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
  209. descending:YES];
  210. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:query];
  211. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"b", @"a" ]));
  212. }
  213. - (void)testQueryWithPredicate {
  214. FIRCollectionReference *collRef = [self
  215. collectionRefWithDocuments:@{@"a" : @{@"a" : @1}, @"b" : @{@"a" : @2}, @"c" : @{@"a" : @3}}];
  216. NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a < 3"];
  217. FIRQuery *query = [collRef queryFilteredUsingPredicate:predicate];
  218. query = [query queryOrderedByFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
  219. descending:YES];
  220. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:query];
  221. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"b", @"a" ]));
  222. }
  223. - (void)testFilterOnInfinity {
  224. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  225. @"a" : @{@"inf" : @(INFINITY)},
  226. @"b" : @{@"inf" : @(-INFINITY)}
  227. }];
  228. FIRQuerySnapshot *results = [self readDocumentSetForRef:[collRef queryWhereField:@"inf"
  229. isEqualTo:@(INFINITY)]];
  230. XCTAssertEqualObjects(FIRQuerySnapshotGetData(results), (@[ @{@"inf" : @(INFINITY)} ]));
  231. }
  232. - (void)testCanExplicitlySortByDocumentID {
  233. NSDictionary *testDocs = @{
  234. @"a" : @{@"key" : @"a"},
  235. @"b" : @{@"key" : @"b"},
  236. @"c" : @{@"key" : @"c"},
  237. };
  238. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  239. // Ideally this would be descending to validate it's different than
  240. // the default, but that requires an extra index
  241. FIRQuerySnapshot *docs =
  242. [self readDocumentSetForRef:[collection queryOrderedByFieldPath:[FIRFieldPath documentID]]];
  243. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs),
  244. (@[ testDocs[@"a"], testDocs[@"b"], testDocs[@"c"] ]));
  245. }
  246. - (void)testCanQueryByDocumentID {
  247. NSDictionary *testDocs = @{
  248. @"aa" : @{@"key" : @"aa"},
  249. @"ab" : @{@"key" : @"ab"},
  250. @"ba" : @{@"key" : @"ba"},
  251. @"bb" : @{@"key" : @"bb"},
  252. };
  253. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  254. FIRQuerySnapshot *docs =
  255. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  256. isEqualTo:@"ab"]];
  257. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"] ]));
  258. }
  259. - (void)testCanQueryByDocumentIDs {
  260. NSDictionary *testDocs = @{
  261. @"aa" : @{@"key" : @"aa"},
  262. @"ab" : @{@"key" : @"ab"},
  263. @"ba" : @{@"key" : @"ba"},
  264. @"bb" : @{@"key" : @"bb"},
  265. };
  266. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  267. FIRQuerySnapshot *docs =
  268. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  269. isEqualTo:@"ab"]];
  270. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"] ]));
  271. docs = [self readDocumentSetForRef:[[collection queryWhereFieldPath:[FIRFieldPath documentID]
  272. isGreaterThan:@"aa"]
  273. queryWhereFieldPath:[FIRFieldPath documentID]
  274. isLessThanOrEqualTo:@"ba"]];
  275. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"], testDocs[@"ba"] ]));
  276. }
  277. - (void)testCanQueryByDocumentIDsUsingRefs {
  278. NSDictionary *testDocs = @{
  279. @"aa" : @{@"key" : @"aa"},
  280. @"ab" : @{@"key" : @"ab"},
  281. @"ba" : @{@"key" : @"ba"},
  282. @"bb" : @{@"key" : @"bb"},
  283. };
  284. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  285. FIRQuerySnapshot *docs = [self
  286. readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  287. isEqualTo:[collection documentWithPath:@"ab"]]];
  288. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"] ]));
  289. docs = [self
  290. readDocumentSetForRef:[[collection queryWhereFieldPath:[FIRFieldPath documentID]
  291. isGreaterThan:[collection documentWithPath:@"aa"]]
  292. queryWhereFieldPath:[FIRFieldPath documentID]
  293. isLessThanOrEqualTo:[collection documentWithPath:@"ba"]]];
  294. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"], testDocs[@"ba"] ]));
  295. }
  296. - (void)testWatchSurvivesNetworkDisconnect {
  297. XCTestExpectation *testExpectiation =
  298. [self expectationWithDescription:@"testWatchSurvivesNetworkDisconnect"];
  299. FIRCollectionReference *collectionRef = [self collectionRef];
  300. FIRDocumentReference *docRef = [collectionRef documentWithAutoID];
  301. FIRFirestore *firestore = collectionRef.firestore;
  302. [collectionRef
  303. addSnapshotListenerWithIncludeMetadataChanges:YES
  304. listener:^(FIRQuerySnapshot *snapshot, NSError *error) {
  305. XCTAssertNil(error);
  306. if (!snapshot.empty && !snapshot.metadata.fromCache) {
  307. [testExpectiation fulfill];
  308. }
  309. }];
  310. [firestore disableNetworkWithCompletion:^(NSError *error) {
  311. XCTAssertNil(error);
  312. [docRef setData:@{@"foo" : @"bar"}];
  313. [firestore enableNetworkWithCompletion:^(NSError *error) {
  314. XCTAssertNil(error);
  315. }];
  316. }];
  317. [self awaitExpectations];
  318. }
  319. - (void)testQueriesFireFromCacheWhenOffline {
  320. NSDictionary *testDocs = @{
  321. @"a" : @{@"foo" : @1},
  322. };
  323. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  324. id<FIRListenerRegistration> registration = [collection
  325. addSnapshotListenerWithIncludeMetadataChanges:YES
  326. listener:self.eventAccumulator.valueEventHandler];
  327. FIRQuerySnapshot *querySnap = [self.eventAccumulator awaitEventWithName:@"initial event"];
  328. XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnap), @[ @{@"foo" : @1} ]);
  329. XCTAssertEqual(querySnap.metadata.isFromCache, NO);
  330. [self disableNetwork];
  331. querySnap = [self.eventAccumulator awaitEventWithName:@"offline event with isFromCache=YES"];
  332. XCTAssertEqual(querySnap.metadata.isFromCache, YES);
  333. [self enableNetwork];
  334. querySnap = [self.eventAccumulator awaitEventWithName:@"back online event with isFromCache=NO"];
  335. XCTAssertEqual(querySnap.metadata.isFromCache, NO);
  336. [registration remove];
  337. }
  338. - (void)testQueriesCanRaiseInitialSnapshotFromCachedEmptyResults {
  339. FIRCollectionReference *collection = [self collectionRefWithDocuments:@{}];
  340. // Populate the cache with empty query result.
  341. FIRQuerySnapshot *querySnapshotA = [self readDocumentSetForRef:collection];
  342. XCTAssertFalse(querySnapshotA.metadata.fromCache);
  343. XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnapshotA), @[]);
  344. // Add a snapshot listener whose first event should be raised from cache.
  345. id<FIRListenerRegistration> registration = [collection
  346. addSnapshotListenerWithIncludeMetadataChanges:YES
  347. listener:self.eventAccumulator.valueEventHandler];
  348. FIRQuerySnapshot *querySnapshotB = [self.eventAccumulator awaitEventWithName:@"initial event"];
  349. XCTAssertTrue(querySnapshotB.metadata.fromCache);
  350. XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnapshotB), @[]);
  351. [registration remove];
  352. }
  353. - (void)testQueriesCanRaiseInitialSnapshotFromEmptyDueToDeleteCachedResults {
  354. NSDictionary *testDocs = @{
  355. @"a" : @{@"foo" : @1},
  356. };
  357. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  358. // Populate the cache with a single document.
  359. FIRQuerySnapshot *querySnapshotA = [self readDocumentSetForRef:collection];
  360. XCTAssertFalse(querySnapshotA.metadata.fromCache);
  361. XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnapshotA), @[ @{@"foo" : @1} ]);
  362. // Delete the document, making the cached query result empty.
  363. FIRDocumentReference *docRef = [collection documentWithPath:@"a"];
  364. [self deleteDocumentRef:docRef];
  365. // Add a snapshot listener whose first event should be raised from cache.
  366. id<FIRListenerRegistration> registration = [collection
  367. addSnapshotListenerWithIncludeMetadataChanges:YES
  368. listener:self.eventAccumulator.valueEventHandler];
  369. FIRQuerySnapshot *querySnapshotB = [self.eventAccumulator awaitEventWithName:@"initial event"];
  370. XCTAssertTrue(querySnapshotB.metadata.fromCache);
  371. XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnapshotB), @[]);
  372. [registration remove];
  373. }
  374. - (void)testDocumentChangesUseNSNotFound {
  375. NSDictionary *testDocs = @{
  376. @"a" : @{@"foo" : @1},
  377. };
  378. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  379. id<FIRListenerRegistration> registration =
  380. [collection addSnapshotListener:self.eventAccumulator.valueEventHandler];
  381. FIRQuerySnapshot *querySnap = [self.eventAccumulator awaitEventWithName:@"initial event"];
  382. XCTAssertEqual(querySnap.documentChanges.count, 1ul);
  383. FIRDocumentChange *change = querySnap.documentChanges[0];
  384. XCTAssertEqual(change.oldIndex, NSNotFound);
  385. XCTAssertEqual(change.newIndex, 0ul);
  386. FIRDocumentReference *doc = change.document.reference;
  387. [self deleteDocumentRef:doc];
  388. querySnap = [self.eventAccumulator awaitEventWithName:@"delete"];
  389. XCTAssertEqual(querySnap.documentChanges.count, 1ul);
  390. change = querySnap.documentChanges[0];
  391. XCTAssertEqual(change.oldIndex, 0ul);
  392. XCTAssertEqual(change.newIndex, NSNotFound);
  393. [registration remove];
  394. }
  395. - (void)testCanHaveMultipleMutationsWhileOffline {
  396. FIRCollectionReference *col = [self collectionRef];
  397. // set a few docs to known values
  398. NSDictionary *initialDocs = @{@"doc1" : @{@"key1" : @"value1"}, @"doc2" : @{@"key2" : @"value2"}};
  399. [self writeAllDocuments:initialDocs toCollection:col];
  400. // go offline for the rest of this test
  401. [self disableNetwork];
  402. // apply *multiple* mutations while offline
  403. [[col documentWithPath:@"doc1"] setData:@{@"key1b" : @"value1b"}];
  404. [[col documentWithPath:@"doc2"] setData:@{@"key2b" : @"value2b"}];
  405. FIRQuerySnapshot *result = [self readDocumentSetForRef:col];
  406. XCTAssertEqualObjects(FIRQuerySnapshotGetData(result), (@[
  407. @{@"key1b" : @"value1b"},
  408. @{@"key2b" : @"value2b"},
  409. ]));
  410. }
  411. - (void)testQueriesCanUseNotEqualFilters {
  412. // These documents are ordered by value in "zip" since notEquals filter is an inequality, which
  413. // results in documents being sorted by value.
  414. NSDictionary *testDocs = @{
  415. @"a" : @{@"zip" : @(NAN)},
  416. @"b" : @{@"zip" : @91102},
  417. @"c" : @{@"zip" : @98101},
  418. @"d" : @{@"zip" : @98103},
  419. @"e" : @{@"zip" : @[ @98101 ]},
  420. @"f" : @{@"zip" : @[ @98101, @98102 ]},
  421. @"g" : @{@"zip" : @[ @"98101", @{@"zip" : @98101} ]},
  422. @"h" : @{@"zip" : @{@"code" : @500}},
  423. @"i" : @{@"zip" : [NSNull null]},
  424. @"j" : @{@"code" : @500},
  425. };
  426. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  427. // Search for zips not matching 98101.
  428. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  429. isNotEqualTo:@98101]];
  430. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  431. testDocs[@"a"], testDocs[@"b"], testDocs[@"d"], testDocs[@"e"],
  432. testDocs[@"f"], testDocs[@"g"], testDocs[@"h"]
  433. ]));
  434. // With objects.
  435. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  436. isNotEqualTo:@{@"code" : @500}]];
  437. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  438. testDocs[@"a"], testDocs[@"b"], testDocs[@"c"], testDocs[@"d"],
  439. testDocs[@"e"], testDocs[@"f"], testDocs[@"g"]
  440. ]));
  441. // With null.
  442. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  443. isNotEqualTo:@[ [NSNull null] ]]];
  444. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  445. testDocs[@"a"], testDocs[@"b"], testDocs[@"c"], testDocs[@"d"],
  446. testDocs[@"e"], testDocs[@"f"], testDocs[@"g"], testDocs[@"h"]
  447. ]));
  448. // With NAN.
  449. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip" isNotEqualTo:@(NAN)]];
  450. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  451. testDocs[@"b"], testDocs[@"c"], testDocs[@"d"], testDocs[@"e"],
  452. testDocs[@"f"], testDocs[@"g"], testDocs[@"h"]
  453. ]));
  454. }
  455. - (void)testQueriesCanUseNotEqualFiltersWithDocIds {
  456. NSDictionary *testDocs = @{
  457. @"aa" : @{@"key" : @"aa"},
  458. @"ab" : @{@"key" : @"ab"},
  459. @"ba" : @{@"key" : @"ba"},
  460. @"bb" : @{@"key" : @"bb"},
  461. };
  462. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  463. FIRQuerySnapshot *snapshot =
  464. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  465. isNotEqualTo:@"aa"]];
  466. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  467. (@[ testDocs[@"ab"], testDocs[@"ba"], testDocs[@"bb"] ]));
  468. }
  469. - (void)testQueriesCanUseArrayContainsFilters {
  470. NSDictionary *testDocs = @{
  471. @"a" : @{@"array" : @[ @42 ]},
  472. @"b" : @{@"array" : @[ @"a", @42, @"c" ]},
  473. @"c" : @{@"array" : @[ @41.999, @"42", @{@"a" : @[ @42 ]} ]},
  474. @"d" : @{@"array" : @[ @42 ], @"array2" : @[ @"bingo" ]},
  475. @"e" : @{@"array" : @[ [NSNull null] ]},
  476. @"f" : @{@"array" : @[ @(NAN) ]},
  477. };
  478. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  479. // Search for 42
  480. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  481. arrayContains:@42]];
  482. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  483. (@[ testDocs[@"a"], testDocs[@"b"], testDocs[@"d"] ]));
  484. // With null.
  485. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  486. arrayContains:[NSNull null]]];
  487. XCTAssertTrue(snapshot.isEmpty);
  488. // With NAN.
  489. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  490. arrayContains:@(NAN)]];
  491. XCTAssertTrue(snapshot.isEmpty);
  492. }
  493. - (void)testQueriesCanUseInFilters {
  494. NSDictionary *testDocs = @{
  495. @"a" : @{@"zip" : @98101},
  496. @"b" : @{@"zip" : @91102},
  497. @"c" : @{@"zip" : @98103},
  498. @"d" : @{@"zip" : @[ @98101 ]},
  499. @"e" : @{@"zip" : @[ @"98101", @{@"zip" : @98101} ]},
  500. @"f" : @{@"zip" : @{@"code" : @500}},
  501. @"g" : @{@"zip" : @[ @98101, @98102 ]},
  502. @"h" : @{@"zip" : [NSNull null]},
  503. @"i" : @{@"zip" : @(NAN)}
  504. };
  505. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  506. // Search for zips matching 98101, 98103, and [98101, 98102].
  507. FIRQuerySnapshot *snapshot = [self
  508. readDocumentSetForRef:[collection queryWhereField:@"zip"
  509. in:@[ @98101, @98103, @[ @98101, @98102 ] ]]];
  510. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  511. (@[ testDocs[@"a"], testDocs[@"c"], testDocs[@"g"] ]));
  512. // With objects
  513. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  514. in:@[ @{@"code" : @500} ]]];
  515. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"f"] ]));
  516. // With null.
  517. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip" in:@[ [NSNull null] ]]];
  518. XCTAssertTrue(snapshot.isEmpty);
  519. // With null and a value.
  520. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  521. in:@[ [NSNull null], @98101 ]]];
  522. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"a"] ]));
  523. // With NAN.
  524. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip" in:@[ @(NAN) ]]];
  525. XCTAssertTrue(snapshot.isEmpty);
  526. // With NAN and a value.
  527. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  528. in:@[ @(NAN), @98101 ]]];
  529. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"a"] ]));
  530. }
  531. - (void)testQueriesCanUseInFiltersWithDocIds {
  532. NSDictionary *testDocs = @{
  533. @"aa" : @{@"key" : @"aa"},
  534. @"ab" : @{@"key" : @"ab"},
  535. @"ba" : @{@"key" : @"ba"},
  536. @"bb" : @{@"key" : @"bb"},
  537. };
  538. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  539. FIRQuerySnapshot *snapshot =
  540. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  541. in:@[ @"aa", @"ab" ]]];
  542. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"aa"], testDocs[@"ab"] ]));
  543. }
  544. - (void)testQueriesCanUseNotInFilters {
  545. // These documents are ordered by value in "zip" since the NOT_IN filter is an inequality, which
  546. // results in documents being sorted by value.
  547. NSDictionary *testDocs = @{
  548. @"a" : @{@"zip" : @(NAN)},
  549. @"b" : @{@"zip" : @91102},
  550. @"c" : @{@"zip" : @98101},
  551. @"d" : @{@"zip" : @98103},
  552. @"e" : @{@"zip" : @[ @98101 ]},
  553. @"f" : @{@"zip" : @[ @98101, @98102 ]},
  554. @"g" : @{@"zip" : @[ @"98101", @{@"zip" : @98101} ]},
  555. @"h" : @{@"zip" : @{@"code" : @500}},
  556. @"i" : @{@"zip" : [NSNull null]},
  557. @"j" : @{@"code" : @500},
  558. };
  559. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  560. // Search for zips not matching 98101, 98103, and [98101, 98102].
  561. FIRQuerySnapshot *snapshot = [self
  562. readDocumentSetForRef:[collection queryWhereField:@"zip"
  563. notIn:@[ @98101, @98103, @[ @98101, @98102 ] ]]];
  564. XCTAssertEqualObjects(
  565. FIRQuerySnapshotGetData(snapshot),
  566. (@[ testDocs[@"a"], testDocs[@"b"], testDocs[@"e"], testDocs[@"g"], testDocs[@"h"] ]));
  567. // With objects.
  568. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  569. notIn:@[ @{@"code" : @500} ]]];
  570. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  571. testDocs[@"a"], testDocs[@"b"], testDocs[@"c"], testDocs[@"d"],
  572. testDocs[@"e"], testDocs[@"f"], testDocs[@"g"]
  573. ]));
  574. // With null.
  575. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  576. notIn:@[ [NSNull null] ]]];
  577. XCTAssertTrue(snapshot.isEmpty);
  578. // With NAN.
  579. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip" notIn:@[ @(NAN) ]]];
  580. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  581. testDocs[@"b"], testDocs[@"c"], testDocs[@"d"], testDocs[@"e"],
  582. testDocs[@"f"], testDocs[@"g"], testDocs[@"h"]
  583. ]));
  584. // With NAN and a number.
  585. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"zip"
  586. notIn:@[ @(NAN), @98101 ]]];
  587. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  588. testDocs[@"b"], testDocs[@"d"], testDocs[@"e"], testDocs[@"f"],
  589. testDocs[@"g"], testDocs[@"h"]
  590. ]));
  591. }
  592. - (void)testQueriesCanUseNotInFiltersWithDocIds {
  593. NSDictionary *testDocs = @{
  594. @"aa" : @{@"key" : @"aa"},
  595. @"ab" : @{@"key" : @"ab"},
  596. @"ba" : @{@"key" : @"ba"},
  597. @"bb" : @{@"key" : @"bb"},
  598. };
  599. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  600. FIRQuerySnapshot *snapshot =
  601. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  602. notIn:@[ @"aa", @"ab" ]]];
  603. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"ba"], testDocs[@"bb"] ]));
  604. }
  605. - (void)testQueriesCanUseArrayContainsAnyFilters {
  606. NSDictionary *testDocs = @{
  607. @"a" : @{@"array" : @[ @42 ]},
  608. @"b" : @{@"array" : @[ @"a", @42, @"c" ]},
  609. @"c" : @{@"array" : @[ @41.999, @"42", @{@"a" : @[ @42 ]} ]},
  610. @"d" : @{@"array" : @[ @42 ], @"array2" : @[ @"bingo" ]},
  611. @"e" : @{@"array" : @[ @43 ]},
  612. @"f" : @{@"array" : @[ @{@"a" : @42} ]},
  613. @"g" : @{@"array" : @42},
  614. @"h" : @{@"array" : @[ [NSNull null] ]},
  615. @"i" : @{@"array" : @[ @(NAN) ]},
  616. };
  617. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  618. // Search for zips matching [42, 43].
  619. FIRQuerySnapshot *snapshot = [self
  620. readDocumentSetForRef:[collection queryWhereField:@"array" arrayContainsAny:@[ @42, @43 ]]];
  621. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  622. (@[ testDocs[@"a"], testDocs[@"b"], testDocs[@"d"], testDocs[@"e"] ]));
  623. // With objects.
  624. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  625. arrayContainsAny:@[ @{@"a" : @42} ]]];
  626. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  627. testDocs[@"f"],
  628. ]));
  629. // With null.
  630. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  631. arrayContainsAny:@[ [NSNull null] ]]];
  632. XCTAssertTrue(snapshot.isEmpty);
  633. // With null and a value.
  634. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  635. arrayContainsAny:@[ [NSNull null], @43 ]]];
  636. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"e"] ]));
  637. // With NAN.
  638. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  639. arrayContainsAny:@[ @(NAN) ]]];
  640. XCTAssertTrue(snapshot.isEmpty);
  641. // With NAN and a value.
  642. snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  643. arrayContainsAny:@[ @(NAN), @43 ]]];
  644. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ testDocs[@"e"] ]));
  645. }
  646. - (void)testCollectionGroupQueries {
  647. // Use .document() to get a random collection group name to use but ensure it starts with 'b'
  648. // for predictable ordering.
  649. NSString *collectionGroup = [NSString
  650. stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID];
  651. NSArray *docPaths = @[
  652. @"abc/123/${collectionGroup}/cg-doc1", @"abc/123/${collectionGroup}/cg-doc2",
  653. @"${collectionGroup}/cg-doc3", @"${collectionGroup}/cg-doc4",
  654. @"def/456/${collectionGroup}/cg-doc5", @"${collectionGroup}/virtual-doc/nested-coll/not-cg-doc",
  655. @"x${collectionGroup}/not-cg-doc", @"${collectionGroup}x/not-cg-doc",
  656. @"abc/123/${collectionGroup}x/not-cg-doc", @"abc/123/x${collectionGroup}/not-cg-doc",
  657. @"abc/${collectionGroup}"
  658. ];
  659. FIRWriteBatch *batch = [self.db batch];
  660. for (NSString *docPath in docPaths) {
  661. NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}"
  662. withString:collectionGroup];
  663. [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]];
  664. }
  665. XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"];
  666. [batch commitWithCompletion:^(NSError *error) {
  667. XCTAssertNil(error);
  668. [expectation fulfill];
  669. }];
  670. [self awaitExpectations];
  671. FIRQuerySnapshot *querySnapshot =
  672. [self readDocumentSetForRef:[self.db collectionGroupWithID:collectionGroup]];
  673. NSArray<NSString *> *ids = FIRQuerySnapshotGetIDs(querySnapshot);
  674. XCTAssertEqualObjects(ids, (@[ @"cg-doc1", @"cg-doc2", @"cg-doc3", @"cg-doc4", @"cg-doc5" ]));
  675. }
  676. - (void)testCollectionGroupQueriesWithStartAtEndAtWithArbitraryDocumentIDs {
  677. // Use .document() to get a random collection group name to use but ensure it starts with 'b'
  678. // for predictable ordering.
  679. NSString *collectionGroup = [NSString
  680. stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID];
  681. NSArray *docPaths = @[
  682. @"a/a/${collectionGroup}/cg-doc1", @"a/b/a/b/${collectionGroup}/cg-doc2",
  683. @"a/b/${collectionGroup}/cg-doc3", @"a/b/c/d/${collectionGroup}/cg-doc4",
  684. @"a/c/${collectionGroup}/cg-doc5", @"${collectionGroup}/cg-doc6", @"a/b/nope/nope"
  685. ];
  686. FIRWriteBatch *batch = [self.db batch];
  687. for (NSString *docPath in docPaths) {
  688. NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}"
  689. withString:collectionGroup];
  690. [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]];
  691. }
  692. XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"];
  693. [batch commitWithCompletion:^(NSError *error) {
  694. XCTAssertNil(error);
  695. [expectation fulfill];
  696. }];
  697. [self awaitExpectations];
  698. FIRQuerySnapshot *querySnapshot = [self
  699. readDocumentSetForRef:[[[[self.db collectionGroupWithID:collectionGroup]
  700. queryOrderedByFieldPath:[FIRFieldPath documentID]]
  701. queryStartingAfterValues:@[ @"a/b" ]]
  702. queryEndingBeforeValues:@[
  703. [NSString stringWithFormat:@"a/b/%@/cg-doc3", collectionGroup]
  704. ]]];
  705. NSArray<NSString *> *ids = FIRQuerySnapshotGetIDs(querySnapshot);
  706. XCTAssertEqualObjects(ids, (@[ @"cg-doc2" ]));
  707. }
  708. - (void)testCollectionGroupQueriesWithWhereFiltersOnArbitraryDocumentIDs {
  709. // Use .document() to get a random collection group name to use but ensure it starts with 'b'
  710. // for predictable ordering.
  711. NSString *collectionGroup = [NSString
  712. stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID];
  713. NSArray *docPaths = @[
  714. @"a/a/${collectionGroup}/cg-doc1", @"a/b/a/b/${collectionGroup}/cg-doc2",
  715. @"a/b/${collectionGroup}/cg-doc3", @"a/b/c/d/${collectionGroup}/cg-doc4",
  716. @"a/c/${collectionGroup}/cg-doc5", @"${collectionGroup}/cg-doc6", @"a/b/nope/nope"
  717. ];
  718. FIRWriteBatch *batch = [self.db batch];
  719. for (NSString *docPath in docPaths) {
  720. NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}"
  721. withString:collectionGroup];
  722. [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]];
  723. }
  724. XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"];
  725. [batch commitWithCompletion:^(NSError *error) {
  726. XCTAssertNil(error);
  727. [expectation fulfill];
  728. }];
  729. [self awaitExpectations];
  730. FIRQuerySnapshot *querySnapshot = [self
  731. readDocumentSetForRef:[[[self.db collectionGroupWithID:collectionGroup]
  732. queryWhereFieldPath:[FIRFieldPath documentID]
  733. isGreaterThanOrEqualTo:@"a/b"]
  734. queryWhereFieldPath:[FIRFieldPath documentID]
  735. isLessThan:[NSString stringWithFormat:@"a/b/%@/cg-doc3",
  736. collectionGroup]]];
  737. NSArray<NSString *> *ids = FIRQuerySnapshotGetIDs(querySnapshot);
  738. XCTAssertEqualObjects(ids, (@[ @"cg-doc2" ]));
  739. }
  740. - (void)testOrQueries {
  741. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  742. @"doc1" : @{@"a" : @1, @"b" : @0},
  743. @"doc2" : @{@"a" : @2, @"b" : @1},
  744. @"doc3" : @{@"a" : @3, @"b" : @2},
  745. @"doc4" : @{@"a" : @1, @"b" : @3},
  746. @"doc5" : @{@"a" : @1, @"b" : @1}
  747. }];
  748. // Two equalities: a==1 || b==1.
  749. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  750. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter filterWhereField:@"b" isEqualTo:@1]
  751. ]];
  752. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  753. matchesResult:@[ @"doc1", @"doc2", @"doc4", @"doc5" ]];
  754. // (a==1 && b==0) || (a==3 && b==2)
  755. FIRFilter *filter2 = [FIRFilter orFilterWithFilters:@[
  756. [FIRFilter andFilterWithFilters:@[
  757. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter filterWhereField:@"b" isEqualTo:@0]
  758. ]],
  759. [FIRFilter andFilterWithFilters:@[
  760. [FIRFilter filterWhereField:@"a" isEqualTo:@3], [FIRFilter filterWhereField:@"b" isEqualTo:@2]
  761. ]]
  762. ]];
  763. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2]
  764. matchesResult:@[ @"doc1", @"doc3" ]];
  765. // a==1 && (b==0 || b==3).
  766. FIRFilter *filter3 = [FIRFilter andFilterWithFilters:@[
  767. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter orFilterWithFilters:@[
  768. [FIRFilter filterWhereField:@"b" isEqualTo:@0], [FIRFilter filterWhereField:@"b" isEqualTo:@3]
  769. ]]
  770. ]];
  771. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter3]
  772. matchesResult:@[ @"doc1", @"doc4" ]];
  773. // (a==2 || b==2) && (a==3 || b==3)
  774. FIRFilter *filter4 = [FIRFilter andFilterWithFilters:@[
  775. [FIRFilter orFilterWithFilters:@[
  776. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" isEqualTo:@2]
  777. ]],
  778. [FIRFilter orFilterWithFilters:@[
  779. [FIRFilter filterWhereField:@"a" isEqualTo:@3], [FIRFilter filterWhereField:@"b" isEqualTo:@3]
  780. ]]
  781. ]];
  782. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter4] matchesResult:@[ @"doc3" ]];
  783. // Test with limits without orderBy (the __name__ ordering is the tie breaker).
  784. FIRFilter *filter5 = [FIRFilter orFilterWithFilters:@[
  785. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" isEqualTo:@1]
  786. ]];
  787. [self checkOnlineAndOfflineQuery:[[collRef queryWhereFilter:filter5] queryLimitedTo:1]
  788. matchesResult:@[ @"doc2" ]];
  789. }
  790. - (void)testOrQueriesWithCompositeIndexes {
  791. // TODO(orquery): Enable this test against production when possible.
  792. XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator],
  793. "Skip this test if running against production because order-by-equality is not "
  794. "supported yet.");
  795. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  796. @"doc1" : @{@"a" : @1, @"b" : @0},
  797. @"doc2" : @{@"a" : @2, @"b" : @1},
  798. @"doc3" : @{@"a" : @3, @"b" : @2},
  799. @"doc4" : @{@"a" : @1, @"b" : @3},
  800. @"doc5" : @{@"a" : @1, @"b" : @1}
  801. }];
  802. // with one inequality: a>2 || b==1.
  803. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  804. [FIRFilter filterWhereField:@"a" isGreaterThan:@2], [FIRFilter filterWhereField:@"b"
  805. isEqualTo:@1]
  806. ]];
  807. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  808. matchesResult:@[ @"doc5", @"doc2", @"doc3" ]];
  809. // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2
  810. FIRFilter *filter2 = [FIRFilter orFilterWithFilters:@[
  811. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter filterWhereField:@"b"
  812. isGreaterThan:@0]
  813. ]];
  814. [self checkOnlineAndOfflineQuery:[[collRef queryWhereFilter:filter2] queryLimitedTo:2]
  815. matchesResult:@[ @"doc1", @"doc2" ]];
  816. // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2
  817. // Note: The public query API does not allow implicit ordering when limitToLast is used.
  818. FIRFilter *filter3 = [FIRFilter orFilterWithFilters:@[
  819. [FIRFilter filterWhereField:@"a" isEqualTo:@1], [FIRFilter filterWhereField:@"b"
  820. isGreaterThan:@0]
  821. ]];
  822. [self checkOnlineAndOfflineQuery:[[[collRef queryWhereFilter:filter3] queryLimitedToLast:2]
  823. queryOrderedByField:@"b"]
  824. matchesResult:@[ @"doc3", @"doc4" ]];
  825. // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1
  826. FIRFilter *filter4 = [FIRFilter orFilterWithFilters:@[
  827. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" isEqualTo:@1]
  828. ]];
  829. [self checkOnlineAndOfflineQuery:[[[collRef queryWhereFilter:filter4] queryLimitedTo:1]
  830. queryOrderedByField:@"a"]
  831. matchesResult:@[ @"doc5" ]];
  832. // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT_TO_LAST 1
  833. FIRFilter *filter5 = [FIRFilter orFilterWithFilters:@[
  834. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" isEqualTo:@1]
  835. ]];
  836. [self checkOnlineAndOfflineQuery:[[[collRef queryWhereFilter:filter5] queryLimitedToLast:1]
  837. queryOrderedByField:@"a"]
  838. matchesResult:@[ @"doc2" ]];
  839. }
  840. - (void)testOrQueriesWithIn {
  841. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  842. @"doc1" : @{@"a" : @1, @"b" : @0},
  843. @"doc2" : @{@"b" : @1},
  844. @"doc3" : @{@"a" : @3, @"b" : @2},
  845. @"doc4" : @{@"a" : @1, @"b" : @3},
  846. @"doc5" : @{@"a" : @1},
  847. @"doc6" : @{@"a" : @2}
  848. }];
  849. // a==2 || b in [2,3]
  850. FIRFilter *filter = [FIRFilter orFilterWithFilters:@[
  851. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b" in:@[ @2, @3 ]]
  852. ]];
  853. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter]
  854. matchesResult:@[ @"doc3", @"doc4", @"doc6" ]];
  855. }
  856. - (void)testOrQueriesWithNotIn {
  857. // TODO(orquery): Enable this test against production when possible.
  858. XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator],
  859. "Skip this test if running against production because it results in a 'missing index' "
  860. "error. The Firestore Emulator, however, does serve these queries");
  861. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  862. @"doc1" : @{@"a" : @1, @"b" : @0},
  863. @"doc2" : @{@"b" : @1},
  864. @"doc3" : @{@"a" : @3, @"b" : @2},
  865. @"doc4" : @{@"a" : @1, @"b" : @3},
  866. @"doc5" : @{@"a" : @1},
  867. @"doc6" : @{@"a" : @2}
  868. }];
  869. // a==2 || b not-in [2,3]
  870. // Has implicit orderBy b.
  871. FIRFilter *filter = [FIRFilter orFilterWithFilters:@[
  872. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b"
  873. notIn:@[ @2, @3 ]]
  874. ]];
  875. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter]
  876. matchesResult:@[ @"doc1", @"doc2" ]];
  877. }
  878. - (void)testOrQueriesWithArrayMembership {
  879. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  880. @"doc1" : @{@"a" : @1, @"b" : @[ @0 ]},
  881. @"doc2" : @{@"b" : @[ @1 ]},
  882. @"doc3" : @{@"a" : @3, @"b" : @[ @2, @7 ]},
  883. @"doc4" : @{@"a" : @1, @"b" : @[ @3, @7 ]},
  884. @"doc5" : @{@"a" : @1},
  885. @"doc6" : @{@"a" : @2}
  886. }];
  887. // a==2 || b array-contains 7
  888. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  889. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b"
  890. arrayContains:@7]
  891. ]];
  892. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  893. matchesResult:@[ @"doc3", @"doc4", @"doc6" ]];
  894. // a==2 || b array-contains-any [0, 3]
  895. FIRFilter *filter2 = [FIRFilter orFilterWithFilters:@[
  896. [FIRFilter filterWhereField:@"a" isEqualTo:@2], [FIRFilter filterWhereField:@"b"
  897. arrayContainsAny:@[ @0, @3 ]]
  898. ]];
  899. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2]
  900. matchesResult:@[ @"doc1", @"doc4", @"doc6" ]];
  901. }
  902. - (void)testMultipleInOps {
  903. // TODO(orquery): Enable this test against production when possible.
  904. XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator],
  905. "Skip this test if running against production because it's not yet supported.");
  906. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  907. @"doc1" : @{@"a" : @1, @"b" : @0},
  908. @"doc2" : @{@"b" : @1},
  909. @"doc3" : @{@"a" : @3, @"b" : @2},
  910. @"doc4" : @{@"a" : @1, @"b" : @3},
  911. @"doc5" : @{@"a" : @1},
  912. @"doc6" : @{@"a" : @2}
  913. }];
  914. // Two IN operations on different fields with disjunction.
  915. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  916. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"b"
  917. in:@[ @0, @2 ]]
  918. ]];
  919. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  920. matchesResult:@[ @"doc1", @"doc3", @"doc6" ]];
  921. // Two IN operations on the same field with disjunction.
  922. // a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]).
  923. FIRFilter *filter2 = [FIRFilter orFilterWithFilters:@[
  924. [FIRFilter filterWhereField:@"a" in:@[ @0, @3 ]], [FIRFilter filterWhereField:@"a"
  925. in:@[ @0, @2 ]]
  926. ]];
  927. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2]
  928. matchesResult:@[ @"doc3", @"doc6" ]];
  929. }
  930. - (void)testUsingInWithArrayContainsAny {
  931. // TODO(orquery): Enable this test against production when possible.
  932. XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator],
  933. "Skip this test if running against production because it's not yet supported.");
  934. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  935. @"doc1" : @{@"a" : @1, @"b" : @[ @0 ]},
  936. @"doc2" : @{@"b" : @[ @1 ]},
  937. @"doc3" : @{@"a" : @3, @"b" : @[ @2, @7 ], @"c" : @10},
  938. @"doc4" : @{@"a" : @1, @"b" : @[ @3, @7 ]},
  939. @"doc5" : @{@"a" : @1},
  940. @"doc6" : @{@"a" : @2, @"c" : @20}
  941. }];
  942. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  943. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"b"
  944. arrayContainsAny:@[ @0, @7 ]]
  945. ]];
  946. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  947. matchesResult:@[ @"doc1", @"doc3", @"doc4", @"doc6" ]];
  948. FIRFilter *filter2 = [FIRFilter orFilterWithFilters:@[
  949. [FIRFilter andFilterWithFilters:@[
  950. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"c"
  951. isEqualTo:@10]
  952. ]],
  953. [FIRFilter filterWhereField:@"b" arrayContainsAny:@[ @0, @7 ]]
  954. ]];
  955. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2]
  956. matchesResult:@[ @"doc1", @"doc3", @"doc4" ]];
  957. }
  958. - (void)testUseInWithArrayContains {
  959. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  960. @"doc1" : @{@"a" : @1, @"b" : @[ @0 ]},
  961. @"doc2" : @{@"b" : @[ @1 ]},
  962. @"doc3" : @{@"a" : @3, @"b" : @[ @2, @7 ]},
  963. @"doc4" : @{@"a" : @1, @"b" : @[ @3, @7 ]},
  964. @"doc5" : @{@"a" : @1},
  965. @"doc6" : @{@"a" : @2}
  966. }];
  967. FIRFilter *filter1 = [FIRFilter orFilterWithFilters:@[
  968. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"b"
  969. arrayContainsAny:@[ @3 ]]
  970. ]];
  971. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter1]
  972. matchesResult:@[ @"doc3", @"doc4", @"doc6" ]];
  973. FIRFilter *filter2 = [FIRFilter andFilterWithFilters:@[
  974. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter filterWhereField:@"b"
  975. arrayContains:@7]
  976. ]];
  977. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter2] matchesResult:@[ @"doc3" ]];
  978. FIRFilter *filter3 = [FIRFilter orFilterWithFilters:@[
  979. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter andFilterWithFilters:@[
  980. [FIRFilter filterWhereField:@"b" arrayContains:@3], [FIRFilter filterWhereField:@"a"
  981. isEqualTo:@1]
  982. ]]
  983. ]];
  984. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter3]
  985. matchesResult:@[ @"doc3", @"doc4", @"doc6" ]];
  986. FIRFilter *filter4 = [FIRFilter andFilterWithFilters:@[
  987. [FIRFilter filterWhereField:@"a" in:@[ @2, @3 ]], [FIRFilter orFilterWithFilters:@[
  988. [FIRFilter filterWhereField:@"b" arrayContains:@7], [FIRFilter filterWhereField:@"a"
  989. isEqualTo:@1]
  990. ]]
  991. ]];
  992. [self checkOnlineAndOfflineQuery:[collRef queryWhereFilter:filter4] matchesResult:@[ @"doc3" ]];
  993. }
  994. - (void)testOrderByEquality {
  995. // TODO(orquery): Enable this test against production when possible.
  996. XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator],
  997. "Skip this test if running against production because order-by-equality is not "
  998. "supported yet.");
  999. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  1000. @"doc1" : @{@"a" : @1, @"b" : @[ @0 ]},
  1001. @"doc2" : @{@"b" : @[ @1 ]},
  1002. @"doc3" : @{@"a" : @3, @"b" : @[ @2, @7 ], @"c" : @10},
  1003. @"doc4" : @{@"a" : @1, @"b" : @[ @3, @7 ]},
  1004. @"doc5" : @{@"a" : @1},
  1005. @"doc6" : @{@"a" : @2, @"c" : @20}
  1006. }];
  1007. [self checkOnlineAndOfflineQuery:[[collRef queryWhereFilter:[FIRFilter filterWhereField:@"a"
  1008. isEqualTo:@1]]
  1009. queryOrderedByField:@"a"]
  1010. matchesResult:@[ @"doc1", @"doc4", @"doc5" ]];
  1011. [self checkOnlineAndOfflineQuery:[[collRef
  1012. queryWhereFilter:[FIRFilter filterWhereField:@"a"
  1013. in:@[ @2, @3 ]]]
  1014. queryOrderedByField:@"a"]
  1015. matchesResult:@[ @"doc6", @"doc3" ]];
  1016. }
  1017. - (void)testResumingAQueryShouldUseExistenceFilterToDetectDeletes {
  1018. // Prepare the names and contents of the 100 documents to create.
  1019. NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *testDocs =
  1020. [[NSMutableDictionary alloc] init];
  1021. for (int i = 0; i < 100; i++) {
  1022. [testDocs setValue:@{@"key" : @42} forKey:[NSString stringWithFormat:@"doc%@", @(1000 + i)]];
  1023. }
  1024. // Create 100 documents in a new collection.
  1025. FIRCollectionReference *collRef = [self collectionRefWithDocuments:testDocs];
  1026. // Run a query to populate the local cache with the 100 documents and a resume token.
  1027. FIRQuerySnapshot *querySnapshot1 = [self readDocumentSetForRef:collRef
  1028. source:FIRFirestoreSourceDefault];
  1029. XCTAssertEqual(querySnapshot1.count, 100, @"querySnapshot1.count has an unexpected value");
  1030. NSArray<FIRDocumentReference *> *createdDocuments =
  1031. FIRDocumentReferenceArrayFromQuerySnapshot(querySnapshot1);
  1032. // Delete 50 of the 100 documents. Do this in a transaction, rather than
  1033. // [FIRDocumentReference deleteDocument], to avoid affecting the local cache.
  1034. NSSet<NSString *> *deletedDocumentIds;
  1035. {
  1036. NSMutableArray<NSString *> *deletedDocumentIdsAccumulator = [[NSMutableArray alloc] init];
  1037. XCTestExpectation *expectation = [self expectationWithDescription:@"DeleteTransaction"];
  1038. [collRef.firestore
  1039. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **) {
  1040. for (decltype(createdDocuments.count) i = 0; i < createdDocuments.count; i += 2) {
  1041. FIRDocumentReference *documentToDelete = createdDocuments[i];
  1042. [transaction deleteDocument:documentToDelete];
  1043. [deletedDocumentIdsAccumulator addObject:documentToDelete.documentID];
  1044. }
  1045. return @"document deletion successful";
  1046. }
  1047. completion:^(id, NSError *) {
  1048. [expectation fulfill];
  1049. }];
  1050. [self awaitExpectation:expectation];
  1051. deletedDocumentIds = [NSSet setWithArray:deletedDocumentIdsAccumulator];
  1052. }
  1053. XCTAssertEqual(deletedDocumentIds.count, 50u, @"deletedDocumentIds has the wrong size");
  1054. // Wait for 10 seconds, during which Watch will stop tracking the query and will send an existence
  1055. // filter rather than "delete" events when the query is resumed.
  1056. [NSThread sleepForTimeInterval:10.0f];
  1057. // Resume the query and save the resulting snapshot for verification.
  1058. FIRQuerySnapshot *querySnapshot2 = [self readDocumentSetForRef:collRef
  1059. source:FIRFirestoreSourceDefault];
  1060. // Verify that the snapshot from the resumed query contains the expected documents; that is,
  1061. // that it contains the 50 documents that were _not_ deleted.
  1062. // TODO(b/270731363): Remove the "if" condition below once the Firestore Emulator is fixed to
  1063. // send an existence filter. At the time of writing, the Firestore emulator fails to send an
  1064. // existence filter, resulting in the client including the deleted documents in the snapshot
  1065. // of the resumed query.
  1066. if (!([FSTIntegrationTestCase isRunningAgainstEmulator] && querySnapshot2.count == 100)) {
  1067. NSSet<NSString *> *actualDocumentIds =
  1068. [NSSet setWithArray:FIRQuerySnapshotGetIDs(querySnapshot2)];
  1069. NSSet<NSString *> *expectedDocumentIds;
  1070. {
  1071. NSMutableArray<NSString *> *expectedDocumentIdsAccumulator = [[NSMutableArray alloc] init];
  1072. for (FIRDocumentReference *documentRef in createdDocuments) {
  1073. if (![deletedDocumentIds containsObject:documentRef.documentID]) {
  1074. [expectedDocumentIdsAccumulator addObject:documentRef.documentID];
  1075. }
  1076. }
  1077. expectedDocumentIds = [NSSet setWithArray:expectedDocumentIdsAccumulator];
  1078. }
  1079. if (![actualDocumentIds isEqualToSet:expectedDocumentIds]) {
  1080. NSArray<NSString *> *unexpectedDocumentIds =
  1081. SortedStringsNotIn(actualDocumentIds, expectedDocumentIds);
  1082. NSArray<NSString *> *missingDocumentIds =
  1083. SortedStringsNotIn(expectedDocumentIds, actualDocumentIds);
  1084. XCTFail(@"querySnapshot2 contained %lu documents (expected %lu): "
  1085. @"%lu unexpected and %lu missing; "
  1086. @"unexpected documents: %@; missing documents: %@",
  1087. (unsigned long)actualDocumentIds.count, (unsigned long)expectedDocumentIds.count,
  1088. (unsigned long)unexpectedDocumentIds.count, (unsigned long)missingDocumentIds.count,
  1089. [unexpectedDocumentIds componentsJoinedByString:@", "],
  1090. [missingDocumentIds componentsJoinedByString:@", "]);
  1091. }
  1092. }
  1093. }
  1094. @end