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