FSTViewTests.mm 34 KB

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