FSTViewTests.mm 28 KB

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