FIRDatabaseTests.mm 62 KB

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