FSTViewTests.mm 34 KB

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