FSTViewTests.mm 27 KB

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