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