FSTViewTests.mm 34 KB

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