FSTViewTests.mm 33 KB

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