FSTViewTests.m 28 KB

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