FSTViewTests.mm 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  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 "Firestore/Source/Core/FSTView.h"
  17. #import <XCTest/XCTest.h>
  18. #import "Firestore/Source/API/FIRFirestore+Internal.h"
  19. #import "Firestore/Source/Core/FSTQuery.h"
  20. #import "Firestore/Source/Core/FSTViewSnapshot.h"
  21. #import "Firestore/Source/Model/FSTDocument.h"
  22. #import "Firestore/Source/Model/FSTDocumentSet.h"
  23. #import "Firestore/Source/Model/FSTFieldValue.h"
  24. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  25. #include "Firestore/core/src/firebase/firestore/model/resource_path.h"
  26. #include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
  27. #include "absl/types/optional.h"
  28. namespace testutil = firebase::firestore::testutil;
  29. using firebase::firestore::core::DocumentViewChangeType;
  30. using firebase::firestore::model::ResourcePath;
  31. using firebase::firestore::model::DocumentKeySet;
  32. NS_ASSUME_NONNULL_BEGIN
  33. @interface FSTViewTests : XCTestCase
  34. @end
  35. @implementation FSTViewTests
  36. /** Returns a new empty query to use for testing. */
  37. - (FSTQuery *)queryForMessages {
  38. return [FSTQuery queryWithPath:ResourcePath{"rooms", "eros", "messages"}];
  39. }
  40. - (void)testAddsDocumentsBasedOnQuery {
  41. FSTQuery *query = [self queryForMessages];
  42. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  43. FSTDocument *doc1 =
  44. FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced);
  45. FSTDocument *doc2 =
  46. FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced);
  47. FSTDocument *doc3 =
  48. FSTTestDoc("rooms/other/messages/1", 0, @{@"text" : @"msg3"}, FSTDocumentStateSynced);
  49. FSTViewSnapshot *_Nullable snapshot = FSTTestApplyChanges(
  50. view, @[ doc1, doc2, doc3 ], FSTTestTargetChangeAckDocuments({doc1.key, doc2.key, doc3.key}));
  51. XCTAssertEqual(snapshot.query, query);
  52. XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc2 ]));
  53. XCTAssertEqualObjects(
  54. snapshot.documentChanges, (@[
  55. [FSTDocumentViewChange changeWithDocument:doc1 type:DocumentViewChangeType::kAdded],
  56. [FSTDocumentViewChange changeWithDocument:doc2 type:DocumentViewChangeType::kAdded]
  57. ]));
  58. XCTAssertFalse(snapshot.isFromCache);
  59. XCTAssertFalse(snapshot.hasPendingWrites);
  60. XCTAssertTrue(snapshot.syncStateChanged);
  61. }
  62. - (void)testRemovesDocuments {
  63. FSTQuery *query = [self queryForMessages];
  64. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  65. FSTDocument *doc1 =
  66. FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced);
  67. FSTDocument *doc2 =
  68. FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced);
  69. FSTDocument *doc3 =
  70. FSTTestDoc("rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, FSTDocumentStateSynced);
  71. // initial state
  72. FSTTestApplyChanges(view, @[ doc1, doc2 ], absl::nullopt);
  73. // delete doc2, add doc3
  74. FSTViewSnapshot *snapshot =
  75. FSTTestApplyChanges(view, @[ FSTTestDeletedDoc("rooms/eros/messages/2", 0, NO), doc3 ],
  76. FSTTestTargetChangeAckDocuments({doc1.key, doc3.key}));
  77. XCTAssertEqual(snapshot.query, query);
  78. XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc3 ]));
  79. XCTAssertEqualObjects(
  80. snapshot.documentChanges, (@[
  81. [FSTDocumentViewChange changeWithDocument:doc2 type:DocumentViewChangeType::kRemoved],
  82. [FSTDocumentViewChange changeWithDocument:doc3 type:DocumentViewChangeType::kAdded]
  83. ]));
  84. XCTAssertFalse(snapshot.isFromCache);
  85. XCTAssertTrue(snapshot.syncStateChanged);
  86. }
  87. - (void)testReturnsNilIfThereAreNoChanges {
  88. FSTQuery *query = [self queryForMessages];
  89. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  90. FSTDocument *doc1 =
  91. FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced);
  92. FSTDocument *doc2 =
  93. FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced);
  94. // initial state
  95. FSTTestApplyChanges(view, @[ doc1, doc2 ], absl::nullopt);
  96. // reapply same docs, no changes
  97. FSTViewSnapshot *snapshot = FSTTestApplyChanges(view, @[ doc1, doc2 ], absl::nullopt);
  98. XCTAssertNil(snapshot);
  99. }
  100. - (void)testDoesNotReturnNilForFirstChanges {
  101. FSTQuery *query = [self queryForMessages];
  102. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  103. FSTViewSnapshot *snapshot = FSTTestApplyChanges(view, @[], absl::nullopt);
  104. XCTAssertNotNil(snapshot);
  105. }
  106. - (void)testFiltersDocumentsBasedOnQueryWithFilter {
  107. FSTQuery *query = [self queryForMessages];
  108. FSTRelationFilter *filter =
  109. [FSTRelationFilter filterWithField:testutil::Field("sort")
  110. filterOperator:FSTRelationFilterOperatorLessThanOrEqual
  111. value:[FSTDoubleValue doubleValue:2]];
  112. query = [query queryByAddingFilter:filter];
  113. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  114. FSTDocument *doc1 =
  115. FSTTestDoc("rooms/eros/messages/1", 0, @{@"sort" : @1}, FSTDocumentStateSynced);
  116. FSTDocument *doc2 =
  117. FSTTestDoc("rooms/eros/messages/2", 0, @{@"sort" : @2}, FSTDocumentStateSynced);
  118. FSTDocument *doc3 =
  119. FSTTestDoc("rooms/eros/messages/3", 0, @{@"sort" : @3}, FSTDocumentStateSynced);
  120. FSTDocument *doc4 =
  121. FSTTestDoc("rooms/eros/messages/4", 0, @{}, FSTDocumentStateSynced); // no sort, no match
  122. FSTDocument *doc5 =
  123. FSTTestDoc("rooms/eros/messages/5", 0, @{@"sort" : @1}, FSTDocumentStateSynced);
  124. FSTViewSnapshot *snapshot =
  125. FSTTestApplyChanges(view, @[ doc1, doc2, doc3, doc4, doc5 ], absl::nullopt);
  126. XCTAssertEqual(snapshot.query, query);
  127. XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc5, doc2 ]));
  128. XCTAssertEqualObjects(
  129. snapshot.documentChanges, (@[
  130. [FSTDocumentViewChange changeWithDocument:doc1 type:DocumentViewChangeType::kAdded],
  131. [FSTDocumentViewChange changeWithDocument:doc5 type:DocumentViewChangeType::kAdded],
  132. [FSTDocumentViewChange changeWithDocument:doc2 type:DocumentViewChangeType::kAdded]
  133. ]));
  134. XCTAssertTrue(snapshot.isFromCache);
  135. XCTAssertTrue(snapshot.syncStateChanged);
  136. }
  137. - (void)testUpdatesDocumentsBasedOnQueryWithFilter {
  138. FSTQuery *query = [self queryForMessages];
  139. FSTRelationFilter *filter =
  140. [FSTRelationFilter filterWithField:testutil::Field("sort")
  141. filterOperator:FSTRelationFilterOperatorLessThanOrEqual
  142. value:[FSTDoubleValue doubleValue:2]];
  143. query = [query queryByAddingFilter:filter];
  144. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  145. FSTDocument *doc1 =
  146. FSTTestDoc("rooms/eros/messages/1", 0, @{@"sort" : @1}, FSTDocumentStateSynced);
  147. FSTDocument *doc2 =
  148. FSTTestDoc("rooms/eros/messages/2", 0, @{@"sort" : @3}, FSTDocumentStateSynced);
  149. FSTDocument *doc3 =
  150. FSTTestDoc("rooms/eros/messages/3", 0, @{@"sort" : @2}, FSTDocumentStateSynced);
  151. FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/4", 0, @{}, FSTDocumentStateSynced);
  152. FSTViewSnapshot *snapshot = FSTTestApplyChanges(view, @[ doc1, doc2, doc3, doc4 ], absl::nullopt);
  153. XCTAssertEqual(snapshot.query, query);
  154. XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc3 ]));
  155. FSTDocument *newDoc2 =
  156. FSTTestDoc("rooms/eros/messages/2", 1, @{@"sort" : @2}, FSTDocumentStateSynced);
  157. FSTDocument *newDoc3 =
  158. FSTTestDoc("rooms/eros/messages/3", 1, @{@"sort" : @3}, FSTDocumentStateSynced);
  159. FSTDocument *newDoc4 =
  160. FSTTestDoc("rooms/eros/messages/4", 1, @{@"sort" : @0}, FSTDocumentStateSynced);
  161. snapshot = FSTTestApplyChanges(view, @[ newDoc2, newDoc3, newDoc4 ], absl::nullopt);
  162. XCTAssertEqual(snapshot.query, query);
  163. XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ newDoc4, doc1, newDoc2 ]));
  164. XCTAssertEqualObjects(
  165. snapshot.documentChanges, (@[
  166. [FSTDocumentViewChange changeWithDocument:doc3 type:DocumentViewChangeType::kRemoved],
  167. [FSTDocumentViewChange changeWithDocument:newDoc4 type:DocumentViewChangeType::kAdded],
  168. [FSTDocumentViewChange changeWithDocument:newDoc2 type:DocumentViewChangeType::kAdded]
  169. ]));
  170. XCTAssertTrue(snapshot.isFromCache);
  171. XCTAssertFalse(snapshot.syncStateChanged);
  172. }
  173. - (void)testRemovesDocumentsForQueryWithLimit {
  174. FSTQuery *query = [self queryForMessages];
  175. query = [query queryBySettingLimit:2];
  176. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  177. FSTDocument *doc1 =
  178. FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced);
  179. FSTDocument *doc2 =
  180. FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced);
  181. FSTDocument *doc3 =
  182. FSTTestDoc("rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, FSTDocumentStateSynced);
  183. // initial state
  184. FSTTestApplyChanges(view, @[ doc1, doc3 ], absl::nullopt);
  185. // add doc2, which should push out doc3
  186. FSTViewSnapshot *snapshot = FSTTestApplyChanges(
  187. view, @[ doc2 ], FSTTestTargetChangeAckDocuments({doc1.key, doc2.key, doc3.key}));
  188. XCTAssertEqual(snapshot.query, query);
  189. XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc2 ]));
  190. XCTAssertEqualObjects(
  191. snapshot.documentChanges, (@[
  192. [FSTDocumentViewChange changeWithDocument:doc3 type:DocumentViewChangeType::kRemoved],
  193. [FSTDocumentViewChange changeWithDocument:doc2 type:DocumentViewChangeType::kAdded]
  194. ]));
  195. XCTAssertFalse(snapshot.isFromCache);
  196. XCTAssertTrue(snapshot.syncStateChanged);
  197. }
  198. - (void)testDoesntReportChangesForDocumentBeyondLimitOfQuery {
  199. FSTQuery *query = [self queryForMessages];
  200. query = [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("num")
  201. ascending:YES]];
  202. query = [query queryBySettingLimit:2];
  203. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  204. FSTDocument *doc1 =
  205. FSTTestDoc("rooms/eros/messages/1", 0, @{@"num" : @1}, FSTDocumentStateSynced);
  206. FSTDocument *doc2 =
  207. FSTTestDoc("rooms/eros/messages/2", 0, @{@"num" : @2}, FSTDocumentStateSynced);
  208. FSTDocument *doc3 =
  209. FSTTestDoc("rooms/eros/messages/3", 0, @{@"num" : @3}, FSTDocumentStateSynced);
  210. FSTDocument *doc4 =
  211. FSTTestDoc("rooms/eros/messages/4", 0, @{@"num" : @4}, FSTDocumentStateSynced);
  212. // initial state
  213. FSTTestApplyChanges(view, @[ doc1, doc2 ], absl::nullopt);
  214. // change doc2 to 5, and add doc3 and doc4.
  215. // doc2 will be modified + removed = removed
  216. // doc3 will be added
  217. // doc4 will be added + removed = nothing
  218. doc2 = FSTTestDoc("rooms/eros/messages/2", 1, @{@"num" : @5}, FSTDocumentStateSynced);
  219. FSTViewDocumentChanges *viewDocChanges =
  220. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2, doc3, doc4 ])];
  221. XCTAssertTrue(viewDocChanges.needsRefill);
  222. // Verify that all the docs still match.
  223. viewDocChanges = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3, doc4 ])
  224. previousChanges:viewDocChanges];
  225. FSTViewSnapshot *snapshot =
  226. [view applyChangesToDocuments:viewDocChanges
  227. targetChange:FSTTestTargetChangeAckDocuments(
  228. {doc1.key, doc2.key, doc3.key, doc4.key})]
  229. .snapshot;
  230. XCTAssertEqual(snapshot.query, query);
  231. XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc3 ]));
  232. XCTAssertEqualObjects(
  233. snapshot.documentChanges, (@[
  234. [FSTDocumentViewChange changeWithDocument:doc2 type:DocumentViewChangeType::kRemoved],
  235. [FSTDocumentViewChange changeWithDocument:doc3 type:DocumentViewChangeType::kAdded]
  236. ]));
  237. XCTAssertFalse(snapshot.isFromCache);
  238. XCTAssertTrue(snapshot.syncStateChanged);
  239. }
  240. - (void)testKeepsTrackOfLimboDocuments {
  241. FSTQuery *query = [self queryForMessages];
  242. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  243. FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
  244. FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
  245. FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, FSTDocumentStateSynced);
  246. FSTViewChange *change = [view
  247. applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])]];
  248. XCTAssertEqualObjects(change.limboChanges, @[]);
  249. change = [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[])]
  250. targetChange:FSTTestTargetChangeMarkCurrent()];
  251. XCTAssertEqualObjects(change.limboChanges,
  252. @[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeAdded
  253. key:doc1.key] ]);
  254. change = [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[])]
  255. targetChange:FSTTestTargetChangeAckDocuments({doc1.key})];
  256. XCTAssertEqualObjects(change.limboChanges,
  257. @[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeRemoved
  258. key:doc1.key] ]);
  259. change =
  260. [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2 ])]
  261. targetChange:FSTTestTargetChangeAckDocuments({doc2.key})];
  262. XCTAssertEqualObjects(change.limboChanges, @[]);
  263. change = [view
  264. applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])]];
  265. XCTAssertEqualObjects(change.limboChanges,
  266. @[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeAdded
  267. key:doc3.key] ]);
  268. change = [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[
  269. FSTTestDeletedDoc("rooms/eros/messages/2", 1, NO)
  270. ])]]; // remove
  271. XCTAssertEqualObjects(change.limboChanges,
  272. @[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeRemoved
  273. key:doc3.key] ]);
  274. }
  275. - (void)testResumingQueryCreatesNoLimbos {
  276. FSTQuery *query = [self queryForMessages];
  277. FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
  278. FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
  279. // Unlike other cases, here the view is initialized with a set of previously synced documents
  280. // which happens when listening to a previously listened-to query.
  281. FSTView *view = [[FSTView alloc] initWithQuery:query
  282. remoteDocuments:DocumentKeySet{doc1.key, doc2.key}];
  283. FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[])];
  284. FSTViewChange *change = [view applyChangesToDocuments:changes
  285. targetChange:FSTTestTargetChangeMarkCurrent()];
  286. XCTAssertEqualObjects(change.limboChanges, @[]);
  287. }
  288. - (void)assertDocSet:(FSTDocumentSet *)docSet containsDocs:(NSArray<FSTDocument *> *)docs {
  289. XCTAssertEqual(docs.count, docSet.count);
  290. for (FSTDocument *doc in docs) {
  291. XCTAssertTrue([docSet containsKey:doc.key]);
  292. }
  293. }
  294. - (void)testReturnsNeedsRefillOnDeleteInLimitQuery {
  295. FSTQuery *query = [[self queryForMessages] queryBySettingLimit:2];
  296. FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
  297. FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
  298. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  299. // Start with a full view.
  300. FSTViewDocumentChanges *changes =
  301. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
  302. [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
  303. XCTAssertFalse(changes.needsRefill);
  304. XCTAssertEqual(2, [changes.changeSet changes].count);
  305. [view applyChangesToDocuments:changes];
  306. // Remove one of the docs.
  307. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ FSTTestDeletedDoc(
  308. "rooms/eros/messages/0", 0, NO) ])];
  309. [self assertDocSet:changes.documentSet containsDocs:@[ doc2 ]];
  310. XCTAssertTrue(changes.needsRefill);
  311. XCTAssertEqual(1, [changes.changeSet changes].count);
  312. // Refill it with just the one doc remaining.
  313. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2 ]) previousChanges:changes];
  314. [self assertDocSet:changes.documentSet containsDocs:@[ doc2 ]];
  315. XCTAssertFalse(changes.needsRefill);
  316. XCTAssertEqual(1, [changes.changeSet changes].count);
  317. [view applyChangesToDocuments:changes];
  318. }
  319. - (void)testReturnsNeedsRefillOnReorderInLimitQuery {
  320. FSTQuery *query = [self queryForMessages];
  321. query =
  322. [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("order")
  323. ascending:YES]];
  324. query = [query queryBySettingLimit:2];
  325. FSTDocument *doc1 =
  326. FSTTestDoc("rooms/eros/messages/0", 0, @{@"order" : @1}, FSTDocumentStateSynced);
  327. FSTDocument *doc2 =
  328. FSTTestDoc("rooms/eros/messages/1", 0, @{@"order" : @2}, FSTDocumentStateSynced);
  329. FSTDocument *doc3 =
  330. FSTTestDoc("rooms/eros/messages/2", 0, @{@"order" : @3}, FSTDocumentStateSynced);
  331. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  332. // Start with a full view.
  333. FSTViewDocumentChanges *changes =
  334. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3 ])];
  335. [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
  336. XCTAssertFalse(changes.needsRefill);
  337. XCTAssertEqual(2, [changes.changeSet changes].count);
  338. [view applyChangesToDocuments:changes];
  339. // Move one of the docs.
  340. doc2 = FSTTestDoc("rooms/eros/messages/1", 1, @{@"order" : @2000}, FSTDocumentStateSynced);
  341. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2 ])];
  342. [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
  343. XCTAssertTrue(changes.needsRefill);
  344. XCTAssertEqual(1, [changes.changeSet changes].count);
  345. // Refill it with all three current docs.
  346. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3 ])
  347. previousChanges:changes];
  348. [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc3 ]];
  349. XCTAssertFalse(changes.needsRefill);
  350. XCTAssertEqual(2, [changes.changeSet changes].count);
  351. [view applyChangesToDocuments:changes];
  352. }
  353. - (void)testDoesntNeedRefillOnReorderWithinLimit {
  354. FSTQuery *query = [self queryForMessages];
  355. query =
  356. [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("order")
  357. ascending:YES]];
  358. query = [query queryBySettingLimit:3];
  359. FSTDocument *doc1 =
  360. FSTTestDoc("rooms/eros/messages/0", 0, @{@"order" : @1}, FSTDocumentStateSynced);
  361. FSTDocument *doc2 =
  362. FSTTestDoc("rooms/eros/messages/1", 0, @{@"order" : @2}, FSTDocumentStateSynced);
  363. FSTDocument *doc3 =
  364. FSTTestDoc("rooms/eros/messages/2", 0, @{@"order" : @3}, FSTDocumentStateSynced);
  365. FSTDocument *doc4 =
  366. FSTTestDoc("rooms/eros/messages/3", 0, @{@"order" : @4}, FSTDocumentStateSynced);
  367. FSTDocument *doc5 =
  368. FSTTestDoc("rooms/eros/messages/4", 0, @{@"order" : @5}, FSTDocumentStateSynced);
  369. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  370. // Start with a full view.
  371. FSTViewDocumentChanges *changes =
  372. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3, doc4, doc5 ])];
  373. [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2, doc3 ]];
  374. XCTAssertFalse(changes.needsRefill);
  375. XCTAssertEqual(3, [changes.changeSet changes].count);
  376. [view applyChangesToDocuments:changes];
  377. // Move one of the docs.
  378. doc1 = FSTTestDoc("rooms/eros/messages/0", 1, @{@"order" : @3}, FSTDocumentStateSynced);
  379. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])];
  380. [self assertDocSet:changes.documentSet containsDocs:@[ doc2, doc3, doc1 ]];
  381. XCTAssertFalse(changes.needsRefill);
  382. XCTAssertEqual(1, [changes.changeSet changes].count);
  383. [view applyChangesToDocuments:changes];
  384. }
  385. - (void)testDoesntNeedRefillOnReorderAfterLimitQuery {
  386. FSTQuery *query = [self queryForMessages];
  387. query =
  388. [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("order")
  389. ascending:YES]];
  390. query = [query queryBySettingLimit:3];
  391. FSTDocument *doc1 =
  392. FSTTestDoc("rooms/eros/messages/0", 0, @{@"order" : @1}, FSTDocumentStateSynced);
  393. FSTDocument *doc2 =
  394. FSTTestDoc("rooms/eros/messages/1", 0, @{@"order" : @2}, FSTDocumentStateSynced);
  395. FSTDocument *doc3 =
  396. FSTTestDoc("rooms/eros/messages/2", 0, @{@"order" : @3}, FSTDocumentStateSynced);
  397. FSTDocument *doc4 =
  398. FSTTestDoc("rooms/eros/messages/3", 0, @{@"order" : @4}, FSTDocumentStateSynced);
  399. FSTDocument *doc5 =
  400. FSTTestDoc("rooms/eros/messages/4", 0, @{@"order" : @5}, FSTDocumentStateSynced);
  401. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  402. // Start with a full view.
  403. FSTViewDocumentChanges *changes =
  404. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3, doc4, doc5 ])];
  405. [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2, doc3 ]];
  406. XCTAssertFalse(changes.needsRefill);
  407. XCTAssertEqual(3, [changes.changeSet changes].count);
  408. [view applyChangesToDocuments:changes];
  409. // Move one of the docs.
  410. doc4 = FSTTestDoc("rooms/eros/messages/3", 1, @{@"order" : @6}, FSTDocumentStateSynced);
  411. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc4 ])];
  412. [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2, doc3 ]];
  413. XCTAssertFalse(changes.needsRefill);
  414. XCTAssertEqual(0, [changes.changeSet changes].count);
  415. [view applyChangesToDocuments:changes];
  416. }
  417. - (void)testDoesntNeedRefillForAdditionAfterTheLimit {
  418. FSTQuery *query = [[self queryForMessages] queryBySettingLimit:2];
  419. FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
  420. FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
  421. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  422. // Start with a full view.
  423. FSTViewDocumentChanges *changes =
  424. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
  425. [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
  426. XCTAssertFalse(changes.needsRefill);
  427. XCTAssertEqual(2, [changes.changeSet changes].count);
  428. [view applyChangesToDocuments:changes];
  429. // Add a doc that is past the limit.
  430. FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 1, @{}, FSTDocumentStateSynced);
  431. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])];
  432. [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
  433. XCTAssertFalse(changes.needsRefill);
  434. XCTAssertEqual(0, [changes.changeSet changes].count);
  435. [view applyChangesToDocuments:changes];
  436. }
  437. - (void)testDoesntNeedRefillForDeletionsWhenNotNearTheLimit {
  438. FSTQuery *query = [[self queryForMessages] queryBySettingLimit:20];
  439. FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
  440. FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
  441. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  442. FSTViewDocumentChanges *changes =
  443. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
  444. [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
  445. XCTAssertFalse(changes.needsRefill);
  446. XCTAssertEqual(2, [changes.changeSet changes].count);
  447. [view applyChangesToDocuments:changes];
  448. // Remove one of the docs.
  449. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ FSTTestDeletedDoc(
  450. "rooms/eros/messages/1", 0, NO) ])];
  451. [self assertDocSet:changes.documentSet containsDocs:@[ doc1 ]];
  452. XCTAssertFalse(changes.needsRefill);
  453. XCTAssertEqual(1, [changes.changeSet changes].count);
  454. [view applyChangesToDocuments:changes];
  455. }
  456. - (void)testHandlesApplyingIrrelevantDocs {
  457. FSTQuery *query = [[self queryForMessages] queryBySettingLimit:2];
  458. FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
  459. FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
  460. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  461. // Start with a full view.
  462. FSTViewDocumentChanges *changes =
  463. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
  464. [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
  465. XCTAssertFalse(changes.needsRefill);
  466. XCTAssertEqual(2, [changes.changeSet changes].count);
  467. [view applyChangesToDocuments:changes];
  468. // Remove a doc that isn't even in the results.
  469. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ FSTTestDeletedDoc(
  470. "rooms/eros/messages/2", 0, NO) ])];
  471. [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
  472. XCTAssertFalse(changes.needsRefill);
  473. XCTAssertEqual(0, [changes.changeSet changes].count);
  474. [view applyChangesToDocuments:changes];
  475. }
  476. - (void)testComputesMutatedKeys {
  477. FSTQuery *query = [self queryForMessages];
  478. FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
  479. FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
  480. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  481. // Start with a full view.
  482. FSTViewDocumentChanges *changes =
  483. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
  484. [view applyChangesToDocuments:changes];
  485. XCTAssertEqual(changes.mutatedKeys, DocumentKeySet{});
  486. FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, FSTDocumentStateLocalMutations);
  487. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])];
  488. XCTAssertEqual(changes.mutatedKeys, DocumentKeySet{doc3.key});
  489. }
  490. - (void)testRemovesKeysFromMutatedKeysWhenNewDocHasNoLocalChanges {
  491. FSTQuery *query = [self queryForMessages];
  492. FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
  493. FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateLocalMutations);
  494. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  495. // Start with a full view.
  496. FSTViewDocumentChanges *changes =
  497. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
  498. [view applyChangesToDocuments:changes];
  499. XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
  500. FSTDocument *doc2Prime = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
  501. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2Prime ])];
  502. [view applyChangesToDocuments:changes];
  503. XCTAssertEqual(changes.mutatedKeys, DocumentKeySet{});
  504. }
  505. - (void)testRemembersLocalMutationsFromPreviousSnapshot {
  506. FSTQuery *query = [self queryForMessages];
  507. FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
  508. FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateLocalMutations);
  509. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  510. // Start with a full view.
  511. FSTViewDocumentChanges *changes =
  512. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
  513. [view applyChangesToDocuments:changes];
  514. XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
  515. FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, FSTDocumentStateSynced);
  516. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])];
  517. [view applyChangesToDocuments:changes];
  518. XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
  519. }
  520. - (void)testRemembersLocalMutationsFromPreviousCallToComputeChangesWithDocuments {
  521. FSTQuery *query = [self queryForMessages];
  522. FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
  523. FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateLocalMutations);
  524. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  525. // Start with a full view.
  526. FSTViewDocumentChanges *changes =
  527. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
  528. XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
  529. FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, FSTDocumentStateSynced);
  530. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ]) previousChanges:changes];
  531. XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
  532. }
  533. - (void)testRaisesHasPendingWritesForPendingMutationsInInitialSnapshot {
  534. FSTQuery *query = [self queryForMessages];
  535. FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateLocalMutations);
  536. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  537. FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])];
  538. FSTViewChange *viewChange = [view applyChangesToDocuments:changes];
  539. XCTAssertTrue(viewChange.snapshot.hasPendingWrites);
  540. }
  541. - (void)testDoesntRaiseHasPendingWritesForCommittedMutationsInInitialSnapshot {
  542. FSTQuery *query = [self queryForMessages];
  543. FSTDocument *doc1 =
  544. FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateCommittedMutations);
  545. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  546. FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])];
  547. FSTViewChange *viewChange = [view applyChangesToDocuments:changes];
  548. XCTAssertFalse(viewChange.snapshot.hasPendingWrites);
  549. }
  550. - (void)testSuppressesWriteAcknowledgementIfWatchHasNotCaughtUp {
  551. // This test verifies that we don't get three events for an FSTServerTimestamp mutation. We
  552. // suppress the event generated by the write acknowledgement and instead wait for Watch to catch
  553. // up.
  554. FSTQuery *query = [self queryForMessages];
  555. FSTDocument *doc1 =
  556. FSTTestDoc("rooms/eros/messages/1", 1, @{@"time" : @1}, FSTDocumentStateLocalMutations);
  557. FSTDocument *doc1Committed =
  558. FSTTestDoc("rooms/eros/messages/1", 2, @{@"time" : @2}, FSTDocumentStateCommittedMutations);
  559. FSTDocument *doc1Acknowledged =
  560. FSTTestDoc("rooms/eros/messages/1", 2, @{@"time" : @2}, FSTDocumentStateSynced);
  561. FSTDocument *doc2 =
  562. FSTTestDoc("rooms/eros/messages/2", 1, @{@"time" : @1}, FSTDocumentStateLocalMutations);
  563. FSTDocument *doc2Modified =
  564. FSTTestDoc("rooms/eros/messages/2", 2, @{@"time" : @3}, FSTDocumentStateLocalMutations);
  565. FSTDocument *doc2Acknowledged =
  566. FSTTestDoc("rooms/eros/messages/2", 2, @{@"time" : @3}, FSTDocumentStateSynced);
  567. FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
  568. FSTViewDocumentChanges *changes =
  569. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
  570. FSTViewChange *viewChange = [view applyChangesToDocuments:changes];
  571. XCTAssertEqualObjects(
  572. (@[
  573. [FSTDocumentViewChange changeWithDocument:doc1 type:DocumentViewChangeType::kAdded],
  574. [FSTDocumentViewChange changeWithDocument:doc2 type:DocumentViewChangeType::kAdded]
  575. ]),
  576. viewChange.snapshot.documentChanges);
  577. changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1Committed, doc2Modified ])];
  578. viewChange = [view applyChangesToDocuments:changes];
  579. // The 'doc1Committed' update is suppressed
  580. XCTAssertEqualObjects(
  581. (@[ [FSTDocumentViewChange changeWithDocument:doc2Modified
  582. type:DocumentViewChangeType::kModified] ]),
  583. viewChange.snapshot.documentChanges);
  584. changes =
  585. [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1Acknowledged, doc2Acknowledged ])];
  586. viewChange = [view applyChangesToDocuments:changes];
  587. XCTAssertEqualObjects(
  588. (@[
  589. [FSTDocumentViewChange changeWithDocument:doc1Acknowledged
  590. type:DocumentViewChangeType::kModified],
  591. [FSTDocumentViewChange changeWithDocument:doc2Acknowledged
  592. type:DocumentViewChangeType::kMetadata]
  593. ]),
  594. viewChange.snapshot.documentChanges);
  595. }
  596. @end
  597. NS_ASSUME_NONNULL_END