FSTViewTests.mm 34 KB

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