FSTViewTests.mm 27 KB

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