FSTViewTests.mm 29 KB

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