FSTViewTests.mm 33 KB

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