FSTViewTests.mm 33 KB

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