FIRDatabaseTests.mm 64 KB

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