FSTViewTests.mm 33 KB

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