FIRDatabaseTests.mm 63 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685
  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 "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  19. #import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
  20. #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
  21. #import "Firestore/Source/API/FIRFirestore+Internal.h"
  22. #include "Firestore/core/src/api/query_snapshot.h"
  23. #include "Firestore/core/src/core/firestore_client.h"
  24. #include "Firestore/core/test/unit/testutil/app_testing.h"
  25. namespace testutil = firebase::firestore::testutil;
  26. using firebase::firestore::util::TimerId;
  27. @interface FIRDatabaseTests : FSTIntegrationTestCase
  28. @end
  29. @implementation FIRDatabaseTests
  30. - (void)testCanUpdateAnExistingDocument {
  31. FIRDocumentReference *doc = [self.db documentWithPath:@"rooms/eros"];
  32. NSDictionary<NSString *, id> *initialData =
  33. @{@"desc" : @"Description", @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}};
  34. NSDictionary<NSString *, id> *updateData =
  35. @{@"desc" : @"NewDescription", @"owner.email" : @"new@xyz.com"};
  36. NSDictionary<NSString *, id> *finalData =
  37. @{@"desc" : @"NewDescription", @"owner" : @{@"name" : @"Jonny", @"email" : @"new@xyz.com"}};
  38. [self writeDocumentRef:doc data:initialData];
  39. XCTestExpectation *updateCompletion = [self expectationWithDescription:@"updateData"];
  40. [doc updateData:updateData
  41. completion:^(NSError *_Nullable error) {
  42. XCTAssertNil(error);
  43. [updateCompletion fulfill];
  44. }];
  45. [self awaitExpectations];
  46. FIRDocumentSnapshot *result = [self readDocumentForRef:doc];
  47. XCTAssertTrue(result.exists);
  48. XCTAssertEqualObjects(result.data, finalData);
  49. }
  50. - (void)testCanUpdateAnUnknownDocument {
  51. [self readerAndWriterOnDocumentRef:^(FIRDocumentReference *readerRef,
  52. FIRDocumentReference *writerRef) {
  53. [self writeDocumentRef:writerRef data:@{@"a" : @"a"}];
  54. [self updateDocumentRef:readerRef data:@{@"b" : @"b"}];
  55. FIRDocumentSnapshot *writerSnap = [self readDocumentForRef:writerRef
  56. source:FIRFirestoreSourceCache];
  57. XCTAssertTrue(writerSnap.exists);
  58. XCTestExpectation *expectation =
  59. [self expectationWithDescription:@"testCanUpdateAnUnknownDocument"];
  60. [readerRef getDocumentWithSource:FIRFirestoreSourceCache
  61. completion:^(FIRDocumentSnapshot *, NSError *_Nullable error) {
  62. XCTAssertNotNil(error);
  63. [expectation fulfill];
  64. }];
  65. [self awaitExpectations];
  66. writerSnap = [self readDocumentForRef:writerRef];
  67. XCTAssertEqualObjects(writerSnap.data, (@{@"a" : @"a", @"b" : @"b"}));
  68. FIRDocumentSnapshot *readerSnap = [self readDocumentForRef:writerRef];
  69. XCTAssertEqualObjects(readerSnap.data, (@{@"a" : @"a", @"b" : @"b"}));
  70. }];
  71. }
  72. - (void)testCanDeleteAFieldWithAnUpdate {
  73. FIRDocumentReference *doc = [self.db documentWithPath:@"rooms/eros"];
  74. NSDictionary<NSString *, id> *initialData =
  75. @{@"desc" : @"Description", @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}};
  76. NSDictionary<NSString *, id> *updateData =
  77. @{@"owner.email" : [FIRFieldValue fieldValueForDelete]};
  78. NSDictionary<NSString *, id> *finalData =
  79. @{@"desc" : @"Description", @"owner" : @{@"name" : @"Jonny"}};
  80. [self writeDocumentRef:doc data:initialData];
  81. [self updateDocumentRef:doc data:updateData];
  82. FIRDocumentSnapshot *result = [self readDocumentForRef:doc];
  83. XCTAssertTrue(result.exists);
  84. XCTAssertEqualObjects(result.data, finalData);
  85. }
  86. - (void)testDeleteDocument {
  87. FIRDocumentReference *doc = [self.db documentWithPath:@"rooms/eros"];
  88. NSDictionary<NSString *, id> *data = @{@"value" : @"foo"};
  89. [self writeDocumentRef:doc data:data];
  90. FIRDocumentSnapshot *result = [self readDocumentForRef:doc];
  91. XCTAssertEqualObjects(result.data, data);
  92. [self deleteDocumentRef:doc];
  93. result = [self readDocumentForRef:doc];
  94. XCTAssertFalse(result.exists);
  95. }
  96. - (void)testCanRetrieveDocumentThatDoesNotExist {
  97. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  98. FIRDocumentSnapshot *result = [self readDocumentForRef:doc];
  99. XCTAssertNil(result.data);
  100. XCTAssertNil(result[@"foo"]);
  101. }
  102. - (void)testCannotUpdateNonexistentDocument {
  103. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  104. XCTestExpectation *setCompletion = [self expectationWithDescription:@"setData"];
  105. [doc updateData:@{@"owner" : @"abc"}
  106. completion:^(NSError *_Nullable error) {
  107. XCTAssertNotNil(error);
  108. XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
  109. XCTAssertEqual(error.code, FIRFirestoreErrorCodeNotFound);
  110. [setCompletion fulfill];
  111. }];
  112. [self awaitExpectations];
  113. FIRDocumentSnapshot *result = [self readDocumentForRef:doc];
  114. XCTAssertFalse(result.exists);
  115. }
  116. - (void)testCanOverwriteDataAnExistingDocumentUsingSet {
  117. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  118. NSDictionary<NSString *, id> *initialData =
  119. @{@"desc" : @"Description", @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}};
  120. NSDictionary<NSString *, id> *udpateData = @{@"desc" : @"NewDescription"};
  121. [self writeDocumentRef:doc data:initialData];
  122. [self writeDocumentRef:doc data:udpateData];
  123. FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
  124. XCTAssertEqualObjects(document.data, udpateData);
  125. }
  126. - (void)testCanMergeDataWithAnExistingDocumentUsingSet {
  127. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  128. NSDictionary<NSString *, id> *initialData =
  129. @{@"desc" : @"Description", @"owner.data" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}};
  130. NSDictionary<NSString *, id> *mergeData =
  131. @{@"updated" : @YES, @"owner.data" : @{@"name" : @"Sebastian"}};
  132. NSDictionary<NSString *, id> *finalData = @{
  133. @"desc" : @"Description",
  134. @"updated" : @YES,
  135. @"owner.data" : @{@"name" : @"Sebastian", @"email" : @"abc@xyz.com"}
  136. };
  137. [self writeDocumentRef:doc data:initialData];
  138. XCTestExpectation *completed =
  139. [self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"];
  140. [doc setData:mergeData
  141. merge:YES
  142. completion:^(NSError *error) {
  143. XCTAssertNil(error);
  144. [completed fulfill];
  145. }];
  146. [self awaitExpectations];
  147. FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
  148. XCTAssertEqualObjects(document.data, finalData);
  149. }
  150. - (void)testCanMergeEmptyObject {
  151. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  152. FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self];
  153. id<FIRListenerRegistration> listenerRegistration =
  154. [doc addSnapshotListener:[accumulator valueEventHandler]];
  155. [self writeDocumentRef:doc data:@{}];
  156. FIRDocumentSnapshot *snapshot = [accumulator awaitEventWithName:@"Snapshot"];
  157. XCTAssertEqualObjects(snapshot.data, @{});
  158. [self mergeDocumentRef:doc data:@{@"a" : @{}} fields:@[ @"a" ]];
  159. snapshot = [accumulator awaitEventWithName:@"Snapshot"];
  160. XCTAssertEqualObjects(snapshot.data, @{@"a" : @{}});
  161. [self mergeDocumentRef:doc data:@{@"b" : @{}}];
  162. snapshot = [accumulator awaitEventWithName:@"Snapshot"];
  163. XCTAssertEqualObjects(snapshot.data, (@{@"a" : @{}, @"b" : @{}}));
  164. snapshot = [self readDocumentForRef:doc source:FIRFirestoreSourceServer];
  165. XCTAssertEqualObjects(snapshot.data, (@{@"a" : @{}, @"b" : @{}}));
  166. [listenerRegistration remove];
  167. }
  168. - (void)testCanMergeServerTimestamps {
  169. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  170. NSDictionary<NSString *, id> *initialData = @{
  171. @"updated" : @NO,
  172. };
  173. NSDictionary<NSString *, id> *mergeData = @{
  174. @"time" : [FIRFieldValue fieldValueForServerTimestamp],
  175. @"nested" : @{@"time" : [FIRFieldValue fieldValueForServerTimestamp]}
  176. };
  177. [self writeDocumentRef:doc data:initialData];
  178. XCTestExpectation *completed =
  179. [self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"];
  180. [doc setData:mergeData
  181. merge:YES
  182. completion:^(NSError *error) {
  183. XCTAssertNil(error);
  184. [completed fulfill];
  185. }];
  186. [self awaitExpectations];
  187. FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
  188. XCTAssertEqual(document[@"updated"], @NO);
  189. XCTAssertTrue([document[@"time"] isKindOfClass:[FIRTimestamp class]]);
  190. XCTAssertTrue([document[@"nested.time"] isKindOfClass:[FIRTimestamp class]]);
  191. }
  192. - (void)testCanDeleteFieldUsingMerge {
  193. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  194. NSDictionary<NSString *, id> *initialData =
  195. @{@"untouched" : @YES, @"foo" : @"bar", @"nested" : @{@"untouched" : @YES, @"foo" : @"bar"}};
  196. NSDictionary<NSString *, id> *mergeData = @{
  197. @"foo" : [FIRFieldValue fieldValueForDelete],
  198. @"nested" : @{@"foo" : [FIRFieldValue fieldValueForDelete]}
  199. };
  200. [self writeDocumentRef:doc data:initialData];
  201. XCTestExpectation *completed =
  202. [self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"];
  203. [doc setData:mergeData
  204. merge:YES
  205. completion:^(NSError *error) {
  206. XCTAssertNil(error);
  207. [completed fulfill];
  208. }];
  209. [self awaitExpectations];
  210. FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
  211. XCTAssertEqual(document[@"untouched"], @YES);
  212. XCTAssertNil(document[@"foo"]);
  213. XCTAssertEqual(document[@"nested.untouched"], @YES);
  214. XCTAssertNil(document[@"nested.foo"]);
  215. }
  216. - (void)testCanDeleteFieldUsingMergeFields {
  217. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  218. NSDictionary<NSString *, id> *initialData = @{
  219. @"untouched" : @YES,
  220. @"foo" : @"bar",
  221. @"inner" : @{@"removed" : @YES, @"foo" : @"bar"},
  222. @"nested" : @{@"untouched" : @YES, @"foo" : @"bar"}
  223. };
  224. NSDictionary<NSString *, id> *mergeData = @{
  225. @"foo" : [FIRFieldValue fieldValueForDelete],
  226. @"inner" : @{@"foo" : [FIRFieldValue fieldValueForDelete]},
  227. @"nested" : @{
  228. @"untouched" : [FIRFieldValue fieldValueForDelete],
  229. @"foo" : [FIRFieldValue fieldValueForDelete]
  230. }
  231. };
  232. NSDictionary<NSString *, id> *finalData =
  233. @{@"untouched" : @YES, @"inner" : @{}, @"nested" : @{@"untouched" : @YES}};
  234. [self writeDocumentRef:doc data:initialData];
  235. XCTestExpectation *completed =
  236. [self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"];
  237. [doc setData:mergeData
  238. mergeFields:@[ @"foo", @"inner", @"nested.foo" ]
  239. completion:^(NSError *error) {
  240. XCTAssertNil(error);
  241. [completed fulfill];
  242. }];
  243. [self awaitExpectations];
  244. FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
  245. XCTAssertEqualObjects([document data], finalData);
  246. }
  247. - (void)testCanSetServerTimestampsUsingMergeFields {
  248. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  249. NSDictionary<NSString *, id> *initialData =
  250. @{@"untouched" : @YES, @"foo" : @"bar", @"nested" : @{@"untouched" : @YES, @"foo" : @"bar"}};
  251. NSDictionary<NSString *, id> *mergeData = @{
  252. @"foo" : [FIRFieldValue fieldValueForServerTimestamp],
  253. @"inner" : @{@"foo" : [FIRFieldValue fieldValueForServerTimestamp]},
  254. @"nested" : @{@"foo" : [FIRFieldValue fieldValueForServerTimestamp]}
  255. };
  256. [self writeDocumentRef:doc data:initialData];
  257. XCTestExpectation *completed =
  258. [self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"];
  259. [doc setData:mergeData
  260. mergeFields:@[ @"foo", @"inner", @"nested.foo" ]
  261. completion:^(NSError *error) {
  262. XCTAssertNil(error);
  263. [completed fulfill];
  264. }];
  265. [self awaitExpectations];
  266. FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
  267. XCTAssertTrue([document exists]);
  268. XCTAssertTrue([document[@"foo"] isKindOfClass:[FIRTimestamp class]]);
  269. XCTAssertTrue([document[@"inner.foo"] isKindOfClass:[FIRTimestamp class]]);
  270. XCTAssertTrue([document[@"nested.foo"] isKindOfClass:[FIRTimestamp class]]);
  271. }
  272. - (void)testMergeReplacesArrays {
  273. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  274. NSDictionary<NSString *, id> *initialData = @{
  275. @"untouched" : @YES,
  276. @"data" : @"old",
  277. @"topLevel" : @[ @"old", @"old" ],
  278. @"mapInArray" : @[ @{@"data" : @"old"} ]
  279. };
  280. NSDictionary<NSString *, id> *mergeData =
  281. @{@"data" : @"new", @"topLevel" : @[ @"new" ], @"mapInArray" : @[ @{@"data" : @"new"} ]};
  282. NSDictionary<NSString *, id> *finalData = @{
  283. @"untouched" : @YES,
  284. @"data" : @"new",
  285. @"topLevel" : @[ @"new" ],
  286. @"mapInArray" : @[ @{@"data" : @"new"} ]
  287. };
  288. [self writeDocumentRef:doc data:initialData];
  289. XCTestExpectation *completed =
  290. [self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"];
  291. [doc setData:mergeData
  292. merge:YES
  293. completion:^(NSError *error) {
  294. XCTAssertNil(error);
  295. [completed fulfill];
  296. }];
  297. [self awaitExpectations];
  298. FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
  299. XCTAssertEqualObjects(document.data, finalData);
  300. }
  301. - (void)testCannotSpecifyFieldMaskForMissingField {
  302. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  303. XCTAssertThrowsSpecific(
  304. { [doc setData:@{} mergeFields:@[ @"foo" ]]; }, NSException,
  305. @"Field 'foo' is specified in your field mask but missing from your input data.");
  306. }
  307. - (void)testCanSetASubsetOfFieldsUsingMask {
  308. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  309. NSDictionary<NSString *, id> *initialData =
  310. @{@"desc" : @"Description", @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}};
  311. NSDictionary<NSString *, id> *finalData = @{@"desc" : @"Description", @"owner" : @"Sebastian"};
  312. [self writeDocumentRef:doc data:initialData];
  313. XCTestExpectation *completed =
  314. [self expectationWithDescription:@"testCanSetASubsetOfFieldsUsingMask"];
  315. [doc setData:@{@"desc" : @"NewDescription", @"owner" : @"Sebastian"}
  316. mergeFields:@[ @"owner" ]
  317. completion:^(NSError *error) {
  318. XCTAssertNil(error);
  319. [completed fulfill];
  320. }];
  321. [self awaitExpectations];
  322. FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
  323. XCTAssertEqualObjects(document.data, finalData);
  324. }
  325. - (void)testDoesNotApplyFieldDeleteOutsideOfMask {
  326. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  327. NSDictionary<NSString *, id> *initialData =
  328. @{@"desc" : @"Description", @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}};
  329. NSDictionary<NSString *, id> *finalData = @{@"desc" : @"Description", @"owner" : @"Sebastian"};
  330. [self writeDocumentRef:doc data:initialData];
  331. XCTestExpectation *completed =
  332. [self expectationWithDescription:@"testCanSetASubsetOfFieldsUsingMask"];
  333. [doc setData:@{@"desc" : [FIRFieldValue fieldValueForDelete], @"owner" : @"Sebastian"}
  334. mergeFields:@[ @"owner" ]
  335. completion:^(NSError *error) {
  336. XCTAssertNil(error);
  337. [completed fulfill];
  338. }];
  339. [self awaitExpectations];
  340. FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
  341. XCTAssertEqualObjects(document.data, finalData);
  342. }
  343. - (void)testDoesNotApplyFieldTransformOutsideOfMask {
  344. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  345. NSDictionary<NSString *, id> *initialData =
  346. @{@"desc" : @"Description", @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}};
  347. NSDictionary<NSString *, id> *finalData = @{@"desc" : @"Description", @"owner" : @"Sebastian"};
  348. [self writeDocumentRef:doc data:initialData];
  349. XCTestExpectation *completed =
  350. [self expectationWithDescription:@"testCanSetASubsetOfFieldsUsingMask"];
  351. [doc setData:@{@"desc" : [FIRFieldValue fieldValueForServerTimestamp], @"owner" : @"Sebastian"}
  352. mergeFields:@[ @"owner" ]
  353. completion:^(NSError *error) {
  354. XCTAssertNil(error);
  355. [completed fulfill];
  356. }];
  357. [self awaitExpectations];
  358. FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
  359. XCTAssertEqualObjects(document.data, finalData);
  360. }
  361. - (void)testCanSetEmptyFieldMask {
  362. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  363. NSDictionary<NSString *, id> *initialData =
  364. @{@"desc" : @"Description", @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}};
  365. NSDictionary<NSString *, id> *finalData = initialData;
  366. [self writeDocumentRef:doc data:initialData];
  367. XCTestExpectation *completed =
  368. [self expectationWithDescription:@"testCanSetASubsetOfFieldsUsingMask"];
  369. [doc setData:@{@"desc" : [FIRFieldValue fieldValueForServerTimestamp], @"owner" : @"Sebastian"}
  370. mergeFields:@[]
  371. completion:^(NSError *error) {
  372. XCTAssertNil(error);
  373. [completed fulfill];
  374. }];
  375. [self awaitExpectations];
  376. FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
  377. XCTAssertEqualObjects(document.data, finalData);
  378. }
  379. - (void)testCanSpecifyFieldsMultipleTimesInFieldMask {
  380. FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  381. NSDictionary<NSString *, id> *initialData =
  382. @{@"desc" : @"Description", @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}};
  383. NSDictionary<NSString *, id> *finalData =
  384. @{@"desc" : @"Description", @"owner" : @{@"name" : @"Sebastian", @"email" : @"new@xyz.com"}};
  385. [self writeDocumentRef:doc data:initialData];
  386. XCTestExpectation *completed =
  387. [self expectationWithDescription:@"testCanSetASubsetOfFieldsUsingMask"];
  388. [doc setData:@{
  389. @"desc" : @"NewDescription",
  390. @"owner" : @{@"name" : @"Sebastian", @"email" : @"new@xyz.com"}
  391. }
  392. mergeFields:@[ @"owner.name", @"owner", @"owner" ]
  393. completion:^(NSError *error) {
  394. XCTAssertNil(error);
  395. [completed fulfill];
  396. }];
  397. [self awaitExpectations];
  398. FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
  399. XCTAssertEqualObjects(document.data, finalData);
  400. }
  401. - (void)testAddingToACollectionYieldsTheCorrectDocumentReference {
  402. FIRCollectionReference *coll = [self.db collectionWithPath:@"collection"];
  403. FIRDocumentReference *ref = [coll addDocumentWithData:@{@"foo" : @1}];
  404. XCTestExpectation *getCompletion = [self expectationWithDescription:@"getData"];
  405. [ref getDocumentWithCompletion:^(FIRDocumentSnapshot *_Nullable document,
  406. NSError *_Nullable error) {
  407. XCTAssertNil(error);
  408. XCTAssertEqualObjects(document.data, (@{@"foo" : @1}));
  409. [getCompletion fulfill];
  410. }];
  411. [self awaitExpectations];
  412. }
  413. - (void)testSnapshotsInSyncListenerFiresAfterListenersInSync {
  414. FIRCollectionReference *coll = [self.db collectionWithPath:@"collection"];
  415. FIRDocumentReference *ref = [coll addDocumentWithData:@{@"foo" : @1}];
  416. NSMutableArray<NSString *> *events = [NSMutableArray array];
  417. XCTestExpectation *gotInitialSnapshot = [self expectationWithDescription:@"gotInitialSnapshot"];
  418. __block bool setupComplete = false;
  419. [ref addSnapshotListener:^(FIRDocumentSnapshot *, NSError *error) {
  420. XCTAssertNil(error);
  421. [events addObject:@"doc"];
  422. // Wait for the initial event from the backend so that we know we'll get exactly one snapshot
  423. // event for our local write below.
  424. if (!setupComplete) {
  425. setupComplete = true;
  426. [gotInitialSnapshot fulfill];
  427. }
  428. }];
  429. [self awaitExpectations];
  430. [events removeAllObjects];
  431. XCTestExpectation *done = [self expectationWithDescription:@"SnapshotsInSyncListenerDone"];
  432. [ref.firestore addSnapshotsInSyncListener:^() {
  433. [events addObject:@"snapshots-in-sync"];
  434. if ([events count] == 3) {
  435. // We should have an initial snapshots-in-sync event, then a snapshot event
  436. // for set(), then another event to indicate we're in sync again.
  437. NSArray<NSString *> *expected = @[ @"snapshots-in-sync", @"doc", @"snapshots-in-sync" ];
  438. XCTAssertEqualObjects(events, expected);
  439. [done fulfill];
  440. }
  441. }];
  442. [self writeDocumentRef:ref data:@{@"foo" : @3}];
  443. [self awaitExpectation:done];
  444. }
  445. - (void)testSnapshotsInSyncRemoveIsIdempotent {
  446. // This test merely verifies that calling remove multiple times doesn't
  447. // explode.
  448. auto listener = [self.db addSnapshotsInSyncListener:^(){
  449. }];
  450. [listener remove];
  451. [listener remove];
  452. }
  453. - (void)testListenCanBeCalledMultipleTimes {
  454. FIRCollectionReference *coll = [self.db collectionWithPath:@"collection"];
  455. FIRDocumentReference *doc = [coll documentWithAutoID];
  456. XCTestExpectation *completed = [self expectationWithDescription:@"multiple addSnapshotListeners"];
  457. __block NSDictionary<NSString *, id> *resultingData;
  458. // Shut the compiler up about strong references to doc.
  459. FIRDocumentReference *__weak weakDoc = doc;
  460. [doc setData:@{@"foo" : @"bar"}
  461. completion:^(NSError *error1) {
  462. XCTAssertNil(error1);
  463. FIRDocumentReference *strongDoc = weakDoc;
  464. [strongDoc addSnapshotListener:^(FIRDocumentSnapshot *, NSError *error2) {
  465. XCTAssertNil(error2);
  466. FIRDocumentReference *strongDoc2 = weakDoc;
  467. [strongDoc2 addSnapshotListener:^(FIRDocumentSnapshot *snapshot3, NSError *error3) {
  468. XCTAssertNil(error3);
  469. resultingData = snapshot3.data;
  470. [completed fulfill];
  471. }];
  472. }];
  473. }];
  474. [self awaitExpectations];
  475. XCTAssertEqualObjects(resultingData, @{@"foo" : @"bar"});
  476. }
  477. - (void)testDocumentSnapshotEvents_nonExistent {
  478. FIRDocumentReference *docRef = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  479. XCTestExpectation *snapshotCompletion = [self expectationWithDescription:@"snapshot"];
  480. __block int callbacks = 0;
  481. id<FIRListenerRegistration> listenerRegistration =
  482. [docRef addSnapshotListener:^(FIRDocumentSnapshot *_Nullable doc, NSError *) {
  483. callbacks++;
  484. if (callbacks == 1) {
  485. XCTAssertNotNil(doc);
  486. XCTAssertFalse(doc.exists);
  487. [snapshotCompletion fulfill];
  488. } else {
  489. XCTFail("Should not have received this callback");
  490. }
  491. }];
  492. [self awaitExpectations];
  493. [listenerRegistration remove];
  494. }
  495. - (void)testDocumentSnapshotEvents_forAdd {
  496. FIRDocumentReference *docRef = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  497. XCTestExpectation *emptyCompletion = [self expectationWithDescription:@"empty snapshot"];
  498. __block XCTestExpectation *dataCompletion;
  499. __block int callbacks = 0;
  500. id<FIRListenerRegistration> listenerRegistration =
  501. [docRef addSnapshotListener:^(FIRDocumentSnapshot *_Nullable doc, NSError *) {
  502. callbacks++;
  503. if (callbacks == 1) {
  504. XCTAssertNotNil(doc);
  505. XCTAssertFalse(doc.exists);
  506. [emptyCompletion fulfill];
  507. } else if (callbacks == 2) {
  508. XCTAssertEqualObjects(doc.data, (@{@"a" : @1}));
  509. XCTAssertEqual(doc.metadata.hasPendingWrites, YES);
  510. [dataCompletion fulfill];
  511. } else {
  512. XCTFail("Should not have received this callback");
  513. }
  514. }];
  515. [self awaitExpectations];
  516. dataCompletion = [self expectationWithDescription:@"data snapshot"];
  517. [docRef setData:@{@"a" : @1}];
  518. [self awaitExpectations];
  519. [listenerRegistration remove];
  520. }
  521. - (void)testDocumentSnapshotEvents_forAddIncludingMetadata {
  522. FIRDocumentReference *docRef = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  523. XCTestExpectation *emptyCompletion = [self expectationWithDescription:@"empty snapshot"];
  524. __block XCTestExpectation *dataCompletion;
  525. __block int callbacks = 0;
  526. id<FIRListenerRegistration> listenerRegistration = [docRef
  527. addSnapshotListenerWithIncludeMetadataChanges:YES
  528. listener:^(FIRDocumentSnapshot *_Nullable doc,
  529. NSError *) {
  530. callbacks++;
  531. if (callbacks == 1) {
  532. XCTAssertNotNil(doc);
  533. XCTAssertFalse(doc.exists);
  534. [emptyCompletion fulfill];
  535. } else if (callbacks == 2) {
  536. XCTAssertEqualObjects(doc.data, (@{@"a" : @1}));
  537. XCTAssertEqual(doc.metadata.hasPendingWrites, YES);
  538. } else if (callbacks == 3) {
  539. XCTAssertEqualObjects(doc.data, (@{@"a" : @1}));
  540. XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
  541. [dataCompletion fulfill];
  542. } else {
  543. XCTFail("Should not have received this callback");
  544. }
  545. }];
  546. [self awaitExpectations];
  547. dataCompletion = [self expectationWithDescription:@"data snapshot"];
  548. [docRef setData:@{@"a" : @1}];
  549. [self awaitExpectations];
  550. [listenerRegistration remove];
  551. }
  552. - (void)testDocumentSnapshotEvents_forChange {
  553. FIRDocumentReference *docRef = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  554. NSDictionary<NSString *, id> *initialData = @{@"a" : @1};
  555. NSDictionary<NSString *, id> *changedData = @{@"b" : @2};
  556. [self writeDocumentRef:docRef data:initialData];
  557. XCTestExpectation *initialCompletion = [self expectationWithDescription:@"initial data"];
  558. __block XCTestExpectation *changeCompletion;
  559. __block int callbacks = 0;
  560. id<FIRListenerRegistration> listenerRegistration =
  561. [docRef addSnapshotListener:^(FIRDocumentSnapshot *_Nullable doc, NSError *) {
  562. callbacks++;
  563. if (callbacks == 1) {
  564. XCTAssertEqualObjects(doc.data, initialData);
  565. XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
  566. [initialCompletion fulfill];
  567. } else if (callbacks == 2) {
  568. XCTAssertEqualObjects(doc.data, changedData);
  569. XCTAssertEqual(doc.metadata.hasPendingWrites, YES);
  570. [changeCompletion fulfill];
  571. } else {
  572. XCTFail("Should not have received this callback");
  573. }
  574. }];
  575. [self awaitExpectations];
  576. changeCompletion = [self expectationWithDescription:@"listen for changed data"];
  577. [docRef setData:changedData];
  578. [self awaitExpectations];
  579. [listenerRegistration remove];
  580. }
  581. - (void)testDocumentSnapshotEvents_forChangeIncludingMetadata {
  582. FIRDocumentReference *docRef = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  583. NSDictionary<NSString *, id> *initialData = @{@"a" : @1};
  584. NSDictionary<NSString *, id> *changedData = @{@"b" : @2};
  585. [self writeDocumentRef:docRef data:initialData];
  586. XCTestExpectation *initialCompletion = [self expectationWithDescription:@"initial data"];
  587. __block XCTestExpectation *changeCompletion;
  588. __block int callbacks = 0;
  589. id<FIRListenerRegistration> listenerRegistration = [docRef
  590. addSnapshotListenerWithIncludeMetadataChanges:YES
  591. listener:^(FIRDocumentSnapshot *_Nullable doc,
  592. NSError *) {
  593. callbacks++;
  594. if (callbacks == 1) {
  595. XCTAssertEqualObjects(doc.data, initialData);
  596. XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
  597. XCTAssertEqual(doc.metadata.isFromCache, YES);
  598. } else if (callbacks == 2) {
  599. XCTAssertEqualObjects(doc.data, initialData);
  600. XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
  601. XCTAssertEqual(doc.metadata.isFromCache, NO);
  602. [initialCompletion fulfill];
  603. } else if (callbacks == 3) {
  604. XCTAssertEqualObjects(doc.data, changedData);
  605. XCTAssertEqual(doc.metadata.hasPendingWrites, YES);
  606. XCTAssertEqual(doc.metadata.isFromCache, NO);
  607. } else if (callbacks == 4) {
  608. XCTAssertEqualObjects(doc.data, changedData);
  609. XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
  610. XCTAssertEqual(doc.metadata.isFromCache, NO);
  611. [changeCompletion fulfill];
  612. } else {
  613. XCTFail("Should not have received this callback");
  614. }
  615. }];
  616. [self awaitExpectations];
  617. changeCompletion = [self expectationWithDescription:@"listen for changed data"];
  618. [docRef setData:changedData];
  619. [self awaitExpectations];
  620. [listenerRegistration remove];
  621. }
  622. - (void)testDocumentSnapshotEvents_forDelete {
  623. FIRDocumentReference *docRef = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  624. NSDictionary<NSString *, id> *initialData = @{@"a" : @1};
  625. [self writeDocumentRef:docRef data:initialData];
  626. XCTestExpectation *initialCompletion = [self expectationWithDescription:@"initial data"];
  627. __block XCTestExpectation *changeCompletion;
  628. __block int callbacks = 0;
  629. id<FIRListenerRegistration> listenerRegistration =
  630. [docRef addSnapshotListener:^(FIRDocumentSnapshot *_Nullable doc, NSError *) {
  631. callbacks++;
  632. if (callbacks == 1) {
  633. XCTAssertEqualObjects(doc.data, initialData);
  634. XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
  635. XCTAssertEqual(doc.metadata.isFromCache, YES);
  636. [initialCompletion fulfill];
  637. } else if (callbacks == 2) {
  638. XCTAssertFalse(doc.exists);
  639. [changeCompletion fulfill];
  640. } else {
  641. XCTFail("Should not have received this callback");
  642. }
  643. }];
  644. [self awaitExpectations];
  645. changeCompletion = [self expectationWithDescription:@"listen for changed data"];
  646. [docRef deleteDocument];
  647. [self awaitExpectations];
  648. [listenerRegistration remove];
  649. }
  650. - (void)testDocumentSnapshotEvents_forDeleteIncludingMetadata {
  651. FIRDocumentReference *docRef = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
  652. NSDictionary<NSString *, id> *initialData = @{@"a" : @1};
  653. [self writeDocumentRef:docRef data:initialData];
  654. XCTestExpectation *initialCompletion = [self expectationWithDescription:@"initial data"];
  655. __block XCTestExpectation *changeCompletion;
  656. __block int callbacks = 0;
  657. id<FIRListenerRegistration> listenerRegistration = [docRef
  658. addSnapshotListenerWithIncludeMetadataChanges:YES
  659. listener:^(FIRDocumentSnapshot *_Nullable doc,
  660. NSError *) {
  661. callbacks++;
  662. if (callbacks == 1) {
  663. XCTAssertEqualObjects(doc.data, initialData);
  664. XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
  665. XCTAssertEqual(doc.metadata.isFromCache, YES);
  666. } else if (callbacks == 2) {
  667. XCTAssertEqualObjects(doc.data, initialData);
  668. XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
  669. XCTAssertEqual(doc.metadata.isFromCache, NO);
  670. [initialCompletion fulfill];
  671. } else if (callbacks == 3) {
  672. XCTAssertFalse(doc.exists);
  673. XCTAssertEqual(doc.metadata.hasPendingWrites, NO);
  674. XCTAssertEqual(doc.metadata.isFromCache, NO);
  675. [changeCompletion fulfill];
  676. } else {
  677. XCTFail("Should not have received this callback");
  678. }
  679. }];
  680. [self awaitExpectations];
  681. changeCompletion = [self expectationWithDescription:@"listen for changed data"];
  682. [docRef deleteDocument];
  683. [self awaitExpectations];
  684. [listenerRegistration remove];
  685. }
  686. - (void)testQuerySnapshotEvents_forAdd {
  687. FIRCollectionReference *roomsRef = [self collectionRef];
  688. FIRDocumentReference *docRef = [roomsRef documentWithAutoID];
  689. NSDictionary<NSString *, id> *newData = @{@"a" : @1};
  690. XCTestExpectation *emptyCompletion = [self expectationWithDescription:@"empty snapshot"];
  691. __block XCTestExpectation *changeCompletion;
  692. __block int callbacks = 0;
  693. id<FIRListenerRegistration> listenerRegistration =
  694. [roomsRef addSnapshotListener:^(FIRQuerySnapshot *_Nullable docSet, NSError *) {
  695. callbacks++;
  696. if (callbacks == 1) {
  697. XCTAssertEqual(docSet.count, 0);
  698. [emptyCompletion fulfill];
  699. } else if (callbacks == 2) {
  700. XCTAssertEqual(docSet.count, 1);
  701. XCTAssertTrue([docSet.documents[0] isKindOfClass:[FIRQueryDocumentSnapshot class]]);
  702. XCTAssertEqualObjects(docSet.documents[0].data, newData);
  703. XCTAssertEqual(docSet.documents[0].metadata.hasPendingWrites, YES);
  704. [changeCompletion fulfill];
  705. } else {
  706. XCTFail("Should not have received a third callback");
  707. }
  708. }];
  709. [self awaitExpectations];
  710. changeCompletion = [self expectationWithDescription:@"changed snapshot"];
  711. [docRef setData:newData];
  712. [self awaitExpectations];
  713. [listenerRegistration remove];
  714. }
  715. - (void)testQuerySnapshotEvents_forChange {
  716. FIRCollectionReference *roomsRef = [self collectionRef];
  717. FIRDocumentReference *docRef = [roomsRef documentWithAutoID];
  718. NSDictionary<NSString *, id> *initialData = @{@"a" : @1};
  719. NSDictionary<NSString *, id> *changedData = @{@"b" : @2};
  720. [self writeDocumentRef:docRef data:initialData];
  721. XCTestExpectation *initialCompletion = [self expectationWithDescription:@"initial data"];
  722. __block XCTestExpectation *changeCompletion;
  723. __block int callbacks = 0;
  724. id<FIRListenerRegistration> listenerRegistration =
  725. [roomsRef addSnapshotListener:^(FIRQuerySnapshot *_Nullable docSet, NSError *) {
  726. callbacks++;
  727. if (callbacks == 1) {
  728. XCTAssertEqual(docSet.count, 1);
  729. XCTAssertEqualObjects(docSet.documents[0].data, initialData);
  730. XCTAssertEqual(docSet.documents[0].metadata.hasPendingWrites, NO);
  731. [initialCompletion fulfill];
  732. } else if (callbacks == 2) {
  733. XCTAssertEqual(docSet.count, 1);
  734. XCTAssertEqualObjects(docSet.documents[0].data, changedData);
  735. XCTAssertEqual(docSet.documents[0].metadata.hasPendingWrites, YES);
  736. [changeCompletion fulfill];
  737. } else {
  738. XCTFail("Should not have received a third callback");
  739. }
  740. }];
  741. [self awaitExpectations];
  742. changeCompletion = [self expectationWithDescription:@"listen for changed data"];
  743. [docRef setData:changedData];
  744. [self awaitExpectations];
  745. [listenerRegistration remove];
  746. }
  747. - (void)testQuerySnapshotEvents_forDelete {
  748. FIRCollectionReference *roomsRef = [self collectionRef];
  749. FIRDocumentReference *docRef = [roomsRef documentWithAutoID];
  750. NSDictionary<NSString *, id> *initialData = @{@"a" : @1};
  751. [self writeDocumentRef:docRef data:initialData];
  752. XCTestExpectation *initialCompletion = [self expectationWithDescription:@"initial data"];
  753. __block XCTestExpectation *changeCompletion;
  754. __block int callbacks = 0;
  755. id<FIRListenerRegistration> listenerRegistration =
  756. [roomsRef addSnapshotListener:^(FIRQuerySnapshot *_Nullable docSet, NSError *) {
  757. callbacks++;
  758. if (callbacks == 1) {
  759. XCTAssertEqual(docSet.count, 1);
  760. XCTAssertEqualObjects(docSet.documents[0].data, initialData);
  761. XCTAssertEqual(docSet.documents[0].metadata.hasPendingWrites, NO);
  762. [initialCompletion fulfill];
  763. } else if (callbacks == 2) {
  764. XCTAssertEqual(docSet.count, 0);
  765. [changeCompletion fulfill];
  766. } else {
  767. XCTFail("Should not have received a third callback");
  768. }
  769. }];
  770. [self awaitExpectations];
  771. changeCompletion = [self expectationWithDescription:@"listen for changed data"];
  772. [docRef deleteDocument];
  773. [self awaitExpectations];
  774. [listenerRegistration remove];
  775. }
  776. - (void)testExposesFirestoreOnDocumentReferences {
  777. FIRDocumentReference *doc = [self.db documentWithPath:@"foo/bar"];
  778. XCTAssertEqual(doc.firestore, self.db);
  779. }
  780. - (void)testExposesFirestoreOnQueries {
  781. FIRQuery *q = [[self.db collectionWithPath:@"foo"] queryLimitedTo:5];
  782. XCTAssertEqual(q.firestore, self.db);
  783. }
  784. - (void)testDocumentReferenceEquality {
  785. FIRFirestore *firestore = self.db;
  786. FIRDocumentReference *docRef = [firestore documentWithPath:@"foo/bar"];
  787. XCTAssertEqualObjects([firestore documentWithPath:@"foo/bar"], docRef);
  788. XCTAssertEqualObjects([docRef collectionWithPath:@"blah"].parent, docRef);
  789. XCTAssertNotEqualObjects([firestore documentWithPath:@"foo/BAR"], docRef);
  790. FIRFirestore *otherFirestore = [self firestore];
  791. XCTAssertNotEqualObjects([otherFirestore documentWithPath:@"foo/bar"], docRef);
  792. }
  793. - (void)testQueryReferenceEquality {
  794. FIRFirestore *firestore = self.db;
  795. FIRQuery *query =
  796. [[[firestore collectionWithPath:@"foo"] queryOrderedByField:@"bar"] queryWhereField:@"baz"
  797. isEqualTo:@42];
  798. FIRQuery *query2 =
  799. [[[firestore collectionWithPath:@"foo"] queryOrderedByField:@"bar"] queryWhereField:@"baz"
  800. isEqualTo:@42];
  801. XCTAssertEqualObjects(query, query2);
  802. FIRQuery *query3 =
  803. [[[firestore collectionWithPath:@"foo"] queryOrderedByField:@"BAR"] queryWhereField:@"baz"
  804. isEqualTo:@42];
  805. XCTAssertNotEqualObjects(query, query3);
  806. FIRFirestore *otherFirestore = [self firestore];
  807. FIRQuery *query4 = [[[otherFirestore collectionWithPath:@"foo"] queryOrderedByField:@"bar"]
  808. queryWhereField:@"baz"
  809. isEqualTo:@42];
  810. XCTAssertNotEqualObjects(query, query4);
  811. }
  812. - (void)testCanTraverseCollectionsAndDocuments {
  813. NSString *expected = @"a/b/c/d";
  814. // doc path from root Firestore.
  815. XCTAssertEqualObjects([self.db documentWithPath:@"a/b/c/d"].path, expected);
  816. // collection path from root Firestore.
  817. XCTAssertEqualObjects([[self.db collectionWithPath:@"a/b/c"] documentWithPath:@"d"].path,
  818. expected);
  819. // doc path from CollectionReference.
  820. XCTAssertEqualObjects([[self.db collectionWithPath:@"a"] documentWithPath:@"b/c/d"].path,
  821. expected);
  822. // collection path from DocumentReference.
  823. XCTAssertEqualObjects([[self.db documentWithPath:@"a/b"] collectionWithPath:@"c/d/e"].path,
  824. @"a/b/c/d/e");
  825. }
  826. - (void)testCanTraverseCollectionAndDocumentParents {
  827. FIRCollectionReference *collection = [self.db collectionWithPath:@"a/b/c"];
  828. XCTAssertEqualObjects(collection.path, @"a/b/c");
  829. FIRDocumentReference *doc = collection.parent;
  830. XCTAssertEqualObjects(doc.path, @"a/b");
  831. collection = doc.parent;
  832. XCTAssertEqualObjects(collection.path, @"a");
  833. FIRDocumentReference *nilDoc = collection.parent;
  834. XCTAssertNil(nilDoc);
  835. }
  836. - (void)testUpdateFieldsWithDots {
  837. FIRDocumentReference *doc = [self documentRef];
  838. [self writeDocumentRef:doc data:@{@"a.b" : @"old", @"c.d" : @"old"}];
  839. [self updateDocumentRef:doc
  840. data:@{(id)[[FIRFieldPath alloc] initWithFields:@[ @"a.b" ]] : @"new"}];
  841. XCTestExpectation *expectation = [self expectationWithDescription:@"testUpdateFieldsWithDots"];
  842. [doc getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  843. XCTAssertNil(error);
  844. XCTAssertEqualObjects(snapshot.data, (@{@"a.b" : @"new", @"c.d" : @"old"}));
  845. [expectation fulfill];
  846. }];
  847. [self awaitExpectations];
  848. }
  849. - (void)testUpdateNestedFields {
  850. FIRDocumentReference *doc = [self documentRef];
  851. [self writeDocumentRef:doc
  852. data:@{
  853. @"a" : @{@"b" : @"old"},
  854. @"c" : @{@"d" : @"old"},
  855. @"e" : @{@"f" : @"old"}
  856. }];
  857. [self updateDocumentRef:doc
  858. data:@{
  859. (id) @"a.b" : @"new",
  860. (id)[[FIRFieldPath alloc] initWithFields:@[ @"c", @"d" ]] : @"new"
  861. }];
  862. XCTestExpectation *expectation = [self expectationWithDescription:@"testUpdateNestedFields"];
  863. [doc getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  864. XCTAssertNil(error);
  865. XCTAssertEqualObjects(snapshot.data, (@{
  866. @"a" : @{@"b" : @"new"},
  867. @"c" : @{@"d" : @"new"},
  868. @"e" : @{@"f" : @"old"}
  869. }));
  870. [expectation fulfill];
  871. }];
  872. [self awaitExpectations];
  873. }
  874. - (void)testCollectionID {
  875. XCTAssertEqualObjects([self.db collectionWithPath:@"foo"].collectionID, @"foo");
  876. XCTAssertEqualObjects([self.db collectionWithPath:@"foo/bar/baz"].collectionID, @"baz");
  877. }
  878. - (void)testDocumentID {
  879. XCTAssertEqualObjects([self.db documentWithPath:@"foo/bar"].documentID, @"bar");
  880. XCTAssertEqualObjects([self.db documentWithPath:@"foo/bar/baz/qux"].documentID, @"qux");
  881. }
  882. - (void)testCanQueueWritesWhileOffline {
  883. XCTestExpectation *writeEpectation = [self expectationWithDescription:@"successfull write"];
  884. XCTestExpectation *networkExpectation = [self expectationWithDescription:@"enable network"];
  885. FIRDocumentReference *doc = [self documentRef];
  886. FIRFirestore *firestore = doc.firestore;
  887. NSDictionary<NSString *, id> *data = @{@"a" : @"b"};
  888. [firestore disableNetworkWithCompletion:^(NSError *error) {
  889. XCTAssertNil(error);
  890. [doc setData:data
  891. completion:^(NSError *error) {
  892. XCTAssertNil(error);
  893. [writeEpectation fulfill];
  894. }];
  895. [firestore enableNetworkWithCompletion:^(NSError *error) {
  896. XCTAssertNil(error);
  897. [networkExpectation fulfill];
  898. }];
  899. }];
  900. [self awaitExpectations];
  901. XCTestExpectation *getExpectation = [self expectationWithDescription:@"successfull get"];
  902. [doc getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  903. XCTAssertNil(error);
  904. XCTAssertEqualObjects(snapshot.data, data);
  905. XCTAssertFalse(snapshot.metadata.isFromCache);
  906. [getExpectation fulfill];
  907. }];
  908. [self awaitExpectations];
  909. }
  910. - (void)testCanGetDocumentsWhileOffline {
  911. FIRDocumentReference *doc = [self documentRef];
  912. FIRFirestore *firestore = doc.firestore;
  913. NSDictionary<NSString *, id> *data = @{@"a" : @"b"};
  914. XCTestExpectation *failExpectation =
  915. [self expectationWithDescription:@"offline read with no cached data"];
  916. XCTestExpectation *onlineExpectation = [self expectationWithDescription:@"online read"];
  917. XCTestExpectation *networkExpectation = [self expectationWithDescription:@"network online"];
  918. __weak FIRDocumentReference *weakDoc = doc;
  919. [firestore disableNetworkWithCompletion:^(NSError *error) {
  920. XCTAssertNil(error);
  921. [doc getDocumentWithCompletion:^(FIRDocumentSnapshot *, NSError *error) {
  922. XCTAssertNotNil(error);
  923. [failExpectation fulfill];
  924. }];
  925. [doc setData:data
  926. completion:^(NSError *_Nullable error) {
  927. XCTAssertNil(error);
  928. [weakDoc getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  929. XCTAssertNil(error);
  930. // Verify that we are not reading from cache.
  931. XCTAssertFalse(snapshot.metadata.isFromCache);
  932. [onlineExpectation fulfill];
  933. }];
  934. }];
  935. [doc getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  936. XCTAssertNil(error);
  937. // Verify that we are reading from cache.
  938. XCTAssertTrue(snapshot.metadata.fromCache);
  939. XCTAssertEqualObjects(snapshot.data, data);
  940. [firestore enableNetworkWithCompletion:^(NSError *) {
  941. [networkExpectation fulfill];
  942. }];
  943. }];
  944. }];
  945. [self awaitExpectations];
  946. }
  947. - (void)testWriteStreamReconnectsAfterIdle {
  948. FIRDocumentReference *doc = [self documentRef];
  949. FIRFirestore *firestore = doc.firestore;
  950. [self writeDocumentRef:doc data:@{@"foo" : @"bar"}];
  951. [firestore workerQueue]->RunScheduledOperationsUntil(TimerId::WriteStreamIdle);
  952. [self writeDocumentRef:doc data:@{@"foo" : @"bar"}];
  953. }
  954. - (void)testWatchStreamReconnectsAfterIdle {
  955. FIRDocumentReference *doc = [self documentRef];
  956. FIRFirestore *firestore = doc.firestore;
  957. [self readSnapshotForRef:[self documentRef] requireOnline:YES];
  958. [firestore workerQueue]->RunScheduledOperationsUntil(TimerId::ListenStreamIdle);
  959. [self readSnapshotForRef:[self documentRef] requireOnline:YES];
  960. }
  961. - (void)testCanDisableNetwork {
  962. FIRDocumentReference *doc = [self documentRef];
  963. FIRFirestore *firestore = doc.firestore;
  964. [firestore enableNetworkWithCompletion:[self completionForExpectationWithName:@"Enable network"]];
  965. [self awaitExpectations];
  966. [firestore
  967. enableNetworkWithCompletion:[self completionForExpectationWithName:@"Enable network again"]];
  968. [self awaitExpectations];
  969. [firestore
  970. disableNetworkWithCompletion:[self completionForExpectationWithName:@"Disable network"]];
  971. [self awaitExpectations];
  972. [firestore
  973. disableNetworkWithCompletion:[self
  974. completionForExpectationWithName:@"Disable network again"]];
  975. [self awaitExpectations];
  976. [firestore
  977. enableNetworkWithCompletion:[self completionForExpectationWithName:@"Final enable network"]];
  978. [self awaitExpectations];
  979. }
  980. - (void)testClientCallsAfterTerminationFail {
  981. FIRDocumentReference *doc = [self documentRef];
  982. FIRFirestore *firestore = doc.firestore;
  983. [firestore enableNetworkWithCompletion:[self completionForExpectationWithName:@"Enable network"]];
  984. [self awaitExpectations];
  985. [firestore terminateWithCompletion:[self completionForExpectationWithName:@"Terminate"]];
  986. [self awaitExpectations];
  987. XCTAssertThrowsSpecific(
  988. {
  989. [firestore disableNetworkWithCompletion:^(NSError *){
  990. }];
  991. },
  992. NSException, @"The client has already been terminated.");
  993. }
  994. - (void)testMaintainsPersistenceAfterRestarting {
  995. FIRDocumentReference *doc = [self documentRef];
  996. FIRFirestore *firestore = doc.firestore;
  997. FIRApp *app = firestore.app;
  998. NSString *appName = app.name;
  999. FIROptions *options = app.options;
  1000. NSDictionary<NSString *, id> *initialData = @{@"foo" : @"42"};
  1001. [self writeDocumentRef:doc data:initialData];
  1002. // -clearPersistence() requires Firestore to be terminated. Shutdown FIRApp and remove the
  1003. // firestore instance to emulate the way an end user would do this.
  1004. [self terminateFirestore:firestore];
  1005. [self.firestores removeObject:firestore];
  1006. [self deleteApp:app];
  1007. // We restart the app with the same name and options to check that the previous instance's
  1008. // persistent storage persists its data after restarting. Calling [self firestore] here would
  1009. // create a new instance of firestore, which defeats the purpose of this test.
  1010. [FIRApp configureWithName:appName options:options];
  1011. FIRApp *app2 = [FIRApp appNamed:appName];
  1012. FIRFirestore *firestore2 = [self firestoreWithApp:app2];
  1013. FIRDocumentReference *docRef2 = [firestore2 documentWithPath:doc.path];
  1014. FIRDocumentSnapshot *snap = [self readDocumentForRef:docRef2 source:FIRFirestoreSourceCache];
  1015. XCTAssertTrue(snap.exists);
  1016. }
  1017. - (void)testCanClearPersistenceAfterRestarting {
  1018. FIRDocumentReference *doc = [self documentRef];
  1019. FIRFirestore *firestore = doc.firestore;
  1020. FIRApp *app = firestore.app;
  1021. NSString *appName = app.name;
  1022. FIROptions *options = app.options;
  1023. NSDictionary<NSString *, id> *initialData = @{@"foo" : @"42"};
  1024. [self writeDocumentRef:doc data:initialData];
  1025. // -clearPersistence() requires Firestore to be terminated. Shutdown FIRApp and remove the
  1026. // firestore instance to emulate the way an end user would do this.
  1027. [self terminateFirestore:firestore];
  1028. [self.firestores removeObject:firestore];
  1029. [firestore
  1030. clearPersistenceWithCompletion:[self completionForExpectationWithName:@"ClearPersistence"]];
  1031. [self awaitExpectations];
  1032. [self deleteApp:app];
  1033. // We restart the app with the same name and options to check that the previous instance's
  1034. // persistent storage is actually cleared after the restart. Calling [self firestore] here would
  1035. // create a new instance of firestore, which defeats the purpose of this test.
  1036. [FIRApp configureWithName:appName options:options];
  1037. FIRApp *app2 = [FIRApp appNamed:appName];
  1038. FIRFirestore *firestore2 = [self firestoreWithApp:app2];
  1039. FIRDocumentReference *docRef2 = [firestore2 documentWithPath:doc.path];
  1040. XCTestExpectation *expectation2 = [self expectationWithDescription:@"getData"];
  1041. [docRef2 getDocumentWithSource:FIRFirestoreSourceCache
  1042. completion:^(FIRDocumentSnapshot *, NSError *_Nullable error) {
  1043. XCTAssertNotNil(error);
  1044. XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
  1045. XCTAssertEqual(error.code, FIRFirestoreErrorCodeUnavailable);
  1046. [expectation2 fulfill];
  1047. }];
  1048. [self awaitExpectations];
  1049. }
  1050. - (void)testCanClearPersistenceOnANewFirestoreInstance {
  1051. FIRDocumentReference *doc = [self documentRef];
  1052. FIRFirestore *firestore = doc.firestore;
  1053. FIRApp *app = firestore.app;
  1054. NSString *appName = app.name;
  1055. FIROptions *options = app.options;
  1056. NSDictionary<NSString *, id> *initialData = @{@"foo" : @"42"};
  1057. [self writeDocumentRef:doc data:initialData];
  1058. [firestore terminateWithCompletion:[self completionForExpectationWithName:@"Terminate"]];
  1059. [self.firestores removeObject:firestore];
  1060. [self awaitExpectations];
  1061. [self deleteApp:app];
  1062. // We restart the app with the same name and options to check that the previous instance's
  1063. // persistent storage is actually cleared after the restart. Calling [self firestore] here would
  1064. // create a new instance of firestore, which defeats the purpose of this test.
  1065. [FIRApp configureWithName:appName options:options];
  1066. FIRApp *app2 = [FIRApp appNamed:appName];
  1067. FIRFirestore *firestore2 = [self firestoreWithApp:app2];
  1068. [firestore2
  1069. clearPersistenceWithCompletion:[self completionForExpectationWithName:@"ClearPersistence"]];
  1070. [self awaitExpectations];
  1071. FIRDocumentReference *docRef2 = [firestore2 documentWithPath:doc.path];
  1072. XCTestExpectation *expectation2 = [self expectationWithDescription:@"getData"];
  1073. [docRef2 getDocumentWithSource:FIRFirestoreSourceCache
  1074. completion:^(FIRDocumentSnapshot *, NSError *_Nullable error) {
  1075. XCTAssertNotNil(error);
  1076. XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
  1077. XCTAssertEqual(error.code, FIRFirestoreErrorCodeUnavailable);
  1078. [expectation2 fulfill];
  1079. }];
  1080. [self awaitExpectations];
  1081. }
  1082. - (void)testClearPersistenceWhileRunningFails {
  1083. FIRDocumentReference *doc = [self documentRef];
  1084. FIRFirestore *firestore = doc.firestore;
  1085. [self enableNetwork];
  1086. XCTestExpectation *expectation = [self expectationWithDescription:@"clearPersistence"];
  1087. [firestore clearPersistenceWithCompletion:^(NSError *_Nullable error) {
  1088. XCTAssertNotNil(error);
  1089. XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
  1090. XCTAssertEqual(error.code, FIRFirestoreErrorCodeFailedPrecondition);
  1091. [expectation fulfill];
  1092. }];
  1093. [self awaitExpectations];
  1094. }
  1095. - (void)testRestartFirestoreLeadsToNewInstance {
  1096. FIRApp *app = testutil::AppForUnitTesting(util::MakeString([FSTIntegrationTestCase projectID]));
  1097. FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];
  1098. FIRFirestore *sameInstance = [FIRFirestore firestoreForApp:app];
  1099. firestore.settings = [FSTIntegrationTestCase settings];
  1100. XCTAssertEqual(firestore, sameInstance);
  1101. NSDictionary<NSString *, id> *data =
  1102. @{@"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}};
  1103. [self writeDocumentRef:[firestore documentWithPath:@"abc/123"] data:data];
  1104. [self terminateFirestore:firestore];
  1105. // Create a new instance, check it's a different instance.
  1106. FIRFirestore *newInstance = [FIRFirestore firestoreForApp:app];
  1107. newInstance.settings = [FSTIntegrationTestCase settings];
  1108. XCTAssertNotEqual(firestore, newInstance);
  1109. // New instance still functions.
  1110. FIRDocumentSnapshot *snapshot =
  1111. [self readDocumentForRef:[newInstance documentWithPath:@"abc/123"]];
  1112. XCTAssertTrue([data isEqualToDictionary:[snapshot data]]);
  1113. }
  1114. - (void)testAppDeleteLeadsToFirestoreTermination {
  1115. FIRApp *app = testutil::AppForUnitTesting(util::MakeString([FSTIntegrationTestCase projectID]));
  1116. FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];
  1117. firestore.settings = [FSTIntegrationTestCase settings];
  1118. NSDictionary<NSString *, id> *data =
  1119. @{@"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"}};
  1120. [self writeDocumentRef:[firestore documentWithPath:@"abc/123"] data:data];
  1121. [self deleteApp:app];
  1122. XCTAssertTrue(firestore.wrapped->client()->is_terminated());
  1123. }
  1124. // Ensures b/172958106 doesn't regress.
  1125. - (void)testDeleteAppWorksWhenLastReferenceToFirestoreIsInListener {
  1126. FIRApp *app = testutil::AppForUnitTesting(util::MakeString([FSTIntegrationTestCase projectID]));
  1127. FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];
  1128. FIRDocumentReference *doc = [firestore documentWithPath:@"abc/123"];
  1129. // Make sure there is a listener.
  1130. [doc addSnapshotListener:^(FIRDocumentSnapshot *, NSError *){
  1131. }];
  1132. XCTestExpectation *expectation = [self expectationWithDescription:@"App is deleted"];
  1133. [app deleteApp:^(BOOL) {
  1134. [expectation fulfill];
  1135. }];
  1136. // Let go of the last app reference.
  1137. app = nil;
  1138. [self awaitExpectations];
  1139. }
  1140. - (void)testTerminateCanBeCalledMultipleTimes {
  1141. FIRApp *app = testutil::AppForUnitTesting(util::MakeString([FSTIntegrationTestCase projectID]));
  1142. FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];
  1143. [firestore terminateWithCompletion:[self completionForExpectationWithName:@"Terminate1"]];
  1144. [self awaitExpectations];
  1145. XCTAssertThrowsSpecific(
  1146. {
  1147. [firestore disableNetworkWithCompletion:^(NSError *){
  1148. }];
  1149. },
  1150. NSException, @"The client has already been terminated.");
  1151. [firestore terminateWithCompletion:[self completionForExpectationWithName:@"Terminate2"]];
  1152. [self awaitExpectations];
  1153. XCTAssertThrowsSpecific(
  1154. {
  1155. [firestore enableNetworkWithCompletion:^(NSError *){
  1156. }];
  1157. },
  1158. NSException, @"The client has already been terminated.");
  1159. }
  1160. - (void)testCanRemoveListenerAfterTermination {
  1161. FIRApp *app = testutil::AppForUnitTesting(util::MakeString([FSTIntegrationTestCase projectID]));
  1162. FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];
  1163. firestore.settings = [FSTIntegrationTestCase settings];
  1164. FIRDocumentReference *doc = [[firestore collectionWithPath:@"rooms"] documentWithAutoID];
  1165. FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self];
  1166. [self writeDocumentRef:doc data:@{}];
  1167. id<FIRListenerRegistration> listenerRegistration =
  1168. [doc addSnapshotListener:[accumulator valueEventHandler]];
  1169. [accumulator awaitEventWithName:@"Snapshot"];
  1170. [firestore terminateWithCompletion:[self completionForExpectationWithName:@"terminate"]];
  1171. [self awaitExpectations];
  1172. // This should proceed without error.
  1173. [listenerRegistration remove];
  1174. // Multiple calls should proceed as well.
  1175. [listenerRegistration remove];
  1176. }
  1177. - (void)testListenerCallbackBlocksRemove {
  1178. // This tests a guarantee required for C++ that doesn't strictly matter for Objective-C and has no
  1179. // equivalent on other platforms.
  1180. //
  1181. // The problem for C++ is that users can register a listener that refers to some state, then call
  1182. // `ListenerRegistration::Remove()` and expect to be able to immediately delete that state. The
  1183. // trouble is that there may be a callback in progress against that listener so the implementation
  1184. // now blocks the remove call until the callback is complete.
  1185. //
  1186. // To make this work, user callbacks can't be on the main thread because the main thread is
  1187. // blocked waiting for the test to complete (that is, you can't await expectations on the main
  1188. // thread and then have the user callback additionally await expectations).
  1189. dispatch_queue_t userQueue = dispatch_queue_create("firestore.test.user", DISPATCH_QUEUE_SERIAL);
  1190. FIRFirestoreSettings *settings = self.db.settings;
  1191. settings.dispatchQueue = userQueue;
  1192. self.db.settings = settings;
  1193. XCTestExpectation *running = [self expectationWithDescription:@"listener running"];
  1194. XCTestExpectation *allowCompletion =
  1195. [self expectationWithDescription:@"allow listener to complete"];
  1196. XCTestExpectation *removing = [self expectationWithDescription:@"attempting to remove listener"];
  1197. XCTestExpectation *removed = [self expectationWithDescription:@"listener removed"];
  1198. NSMutableString *steps = [NSMutableString string];
  1199. FIRDocumentReference *doc = [self documentRef];
  1200. [self writeDocumentRef:doc data:@{@"foo" : @"bar"}];
  1201. __block bool firstTime = true;
  1202. id<FIRListenerRegistration> listener =
  1203. [doc addSnapshotListener:^(FIRDocumentSnapshot *, NSError *) {
  1204. @synchronized(self) {
  1205. if (!firstTime) {
  1206. return;
  1207. }
  1208. firstTime = false;
  1209. }
  1210. [steps appendString:@"1"];
  1211. [running fulfill];
  1212. [self awaitExpectation:allowCompletion];
  1213. [steps appendString:@"3"];
  1214. }];
  1215. // Call remove asynchronously to avoid blocking the main test thread.
  1216. dispatch_queue_t async = dispatch_queue_create("firestore.async", DISPATCH_QUEUE_SERIAL);
  1217. dispatch_async(async, ^{
  1218. [self awaitExpectation:running];
  1219. [steps appendString:@"2"];
  1220. [removing fulfill];
  1221. [listener remove];
  1222. [steps appendString:@"4"];
  1223. [removed fulfill];
  1224. });
  1225. // Perform a write to `doc` which will trigger the listener callback. Don't wait for completion
  1226. // though because that completion handler is in line behind the listener callback that the test
  1227. // is blocking.
  1228. XCTestExpectation *setData = [self expectationWithDescription:@"setData"];
  1229. [doc setData:@{@"foo" : @"bar"} completion:[self completionForExpectation:setData]];
  1230. [self awaitExpectation:removing];
  1231. [allowCompletion fulfill];
  1232. [self awaitExpectation:removed];
  1233. XCTAssertEqualObjects(steps, @"1234");
  1234. [self awaitExpectation:setData];
  1235. }
  1236. - (void)testListenerCallbackCanCallRemoveWithoutBlocking {
  1237. // This tests a guarantee required for C++ that doesn't strictly matter for Objective-C and has no
  1238. // equivalent on other platforms. See `testListenerCallbackBlocksRemove` for background.
  1239. XCTestExpectation *removed = [self expectationWithDescription:@"listener removed"];
  1240. NSMutableString *steps = [NSMutableString string];
  1241. FIRDocumentReference *doc = [self documentRef];
  1242. [self writeDocumentRef:doc data:@{@"foo" : @"bar"}];
  1243. __block id<FIRListenerRegistration> listener = nil;
  1244. @synchronized(self) {
  1245. listener = [doc addSnapshotListener:^(FIRDocumentSnapshot *, NSError *) {
  1246. [steps appendString:@"1"];
  1247. @synchronized(self) {
  1248. // This test is successful if this method does not block.
  1249. [listener remove];
  1250. }
  1251. [steps appendString:@"2"];
  1252. [removed fulfill];
  1253. }];
  1254. }
  1255. // Perform a write to `doc` which will trigger the listener callback.
  1256. [self writeDocumentRef:doc data:@{@"foo" : @"bar2"}];
  1257. [self awaitExpectation:removed];
  1258. XCTAssertEqualObjects(steps, @"12");
  1259. }
  1260. - (void)testListenerCallbacksHappenOnMainThread {
  1261. // Verify that callbacks occur on the main thread if settings.dispatchQueue is not specified.
  1262. XCTestExpectation *invoked = [self expectationWithDescription:@"listener invoked"];
  1263. invoked.assertForOverFulfill = false;
  1264. FIRDocumentReference *doc = [self documentRef];
  1265. [self writeDocumentRef:doc data:@{@"foo" : @"bar"}];
  1266. __block bool callbackThreadIsMainThread;
  1267. __block NSString *callbackThreadDescription;
  1268. [doc addSnapshotListener:^(FIRDocumentSnapshot *, NSError *) {
  1269. callbackThreadIsMainThread = NSThread.isMainThread;
  1270. callbackThreadDescription = [NSString stringWithFormat:@"%@", NSThread.currentThread];
  1271. [invoked fulfill];
  1272. }];
  1273. [self awaitExpectation:invoked];
  1274. XCTAssertTrue(callbackThreadIsMainThread,
  1275. @"The listener callback was expected to occur on the main thread, but instead it "
  1276. @"occurred on the thread %@",
  1277. callbackThreadDescription);
  1278. }
  1279. - (void)testWaitForPendingWritesCompletes {
  1280. FIRDocumentReference *doc = [self documentRef];
  1281. FIRFirestore *firestore = doc.firestore;
  1282. [self disableNetwork];
  1283. [doc setData:@{@"foo" : @"bar"}];
  1284. [firestore waitForPendingWritesWithCompletion:
  1285. [self completionForExpectationWithName:@"Wait for pending writes"]];
  1286. [firestore enableNetworkWithCompletion:[self completionForExpectationWithName:@"Enable network"]];
  1287. [self awaitExpectations];
  1288. }
  1289. - (void)testWaitForPendingWritesFailsWhenUserChanges {
  1290. FIRFirestore *firestore = self.db;
  1291. [self disableNetwork];
  1292. // Writes to local to prevent immediate call to the completion of waitForPendingWrites.
  1293. NSDictionary<NSString *, id> *data =
  1294. @{@"owner" : @{@"name" : @"Andy", @"email" : @"abc@example.com"}};
  1295. [[self documentRef] setData:data];
  1296. XCTestExpectation *expectation = [self expectationWithDescription:@"waitForPendingWrites"];
  1297. [firestore waitForPendingWritesWithCompletion:^(NSError *_Nullable error) {
  1298. XCTAssertNotNil(error);
  1299. XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
  1300. XCTAssertEqual(error.code, FIRFirestoreErrorCodeCancelled);
  1301. [expectation fulfill];
  1302. }];
  1303. [self triggerUserChangeWithUid:@"user-to-fail-pending-writes"];
  1304. [self awaitExpectations];
  1305. }
  1306. - (void)testWaitForPendingWritesCompletesWhenOfflineIfNoPending {
  1307. FIRFirestore *firestore = self.db;
  1308. [self disableNetwork];
  1309. [firestore waitForPendingWritesWithCompletion:
  1310. [self completionForExpectationWithName:@"Wait for pending writes"]];
  1311. [self awaitExpectations];
  1312. }
  1313. @end