aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/Example/Tests/Core/FSTViewTests.mm
diff options
context:
space:
mode:
Diffstat (limited to 'Firestore/Example/Tests/Core/FSTViewTests.mm')
-rw-r--r--Firestore/Example/Tests/Core/FSTViewTests.mm618
1 files changed, 618 insertions, 0 deletions
diff --git a/Firestore/Example/Tests/Core/FSTViewTests.mm b/Firestore/Example/Tests/Core/FSTViewTests.mm
new file mode 100644
index 0000000..e6c4510
--- /dev/null
+++ b/Firestore/Example/Tests/Core/FSTViewTests.mm
@@ -0,0 +1,618 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "Firestore/Source/Core/FSTView.h"
+
+#import <XCTest/XCTest.h>
+
+#import "Firestore/Source/API/FIRFirestore+Internal.h"
+#import "Firestore/Source/Core/FSTQuery.h"
+#import "Firestore/Source/Core/FSTViewSnapshot.h"
+#import "Firestore/Source/Model/FSTDocument.h"
+#import "Firestore/Source/Model/FSTDocumentKey.h"
+#import "Firestore/Source/Model/FSTDocumentSet.h"
+#import "Firestore/Source/Model/FSTFieldValue.h"
+#import "Firestore/Source/Model/FSTPath.h"
+#import "Firestore/Source/Remote/FSTRemoteEvent.h"
+
+#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FSTViewTests : XCTestCase
+@end
+
+@implementation FSTViewTests
+
+/** Returns a new empty query to use for testing. */
+- (FSTQuery *)queryForMessages {
+ return [FSTQuery
+ queryWithPath:[FSTResourcePath pathWithSegments:@[ @"rooms", @"eros", @"messages" ]]];
+}
+
+- (void)testAddsDocumentsBasedOnQuery {
+ FSTQuery *query = [self queryForMessages];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/other/messages/1", 0, @{@"text" : @"msg3"}, NO);
+
+ FSTViewSnapshot *_Nullable snapshot =
+ FSTTestApplyChanges(view, @[ doc1, doc2, doc3 ],
+ [FSTTargetChange changeWithDocuments:@[ doc1, doc2, doc3 ]
+ currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]);
+
+ XCTAssertEqual(snapshot.query, query);
+
+ XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc2 ]));
+
+ XCTAssertEqualObjects(
+ snapshot.documentChanges, (@[
+ [FSTDocumentViewChange changeWithDocument:doc1 type:FSTDocumentViewChangeTypeAdded],
+ [FSTDocumentViewChange changeWithDocument:doc2 type:FSTDocumentViewChangeTypeAdded]
+ ]));
+
+ XCTAssertFalse(snapshot.isFromCache);
+ XCTAssertFalse(snapshot.hasPendingWrites);
+ XCTAssertTrue(snapshot.syncStateChanged);
+}
+
+- (void)testRemovesDocuments {
+ FSTQuery *query = [self queryForMessages];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, NO);
+
+ // initial state
+ FSTTestApplyChanges(view, @[ doc1, doc2 ], nil);
+
+ // delete doc2, add doc3
+ FSTViewSnapshot *snapshot =
+ FSTTestApplyChanges(view, @[ FSTTestDeletedDoc(@"rooms/eros/messages/2", 0), doc3 ],
+ [FSTTargetChange changeWithDocuments:@[ doc1, doc3 ]
+ currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]);
+
+ XCTAssertEqual(snapshot.query, query);
+
+ XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc3 ]));
+
+ XCTAssertEqualObjects(
+ snapshot.documentChanges, (@[
+ [FSTDocumentViewChange changeWithDocument:doc2 type:FSTDocumentViewChangeTypeRemoved],
+ [FSTDocumentViewChange changeWithDocument:doc3 type:FSTDocumentViewChangeTypeAdded]
+ ]));
+
+ XCTAssertFalse(snapshot.isFromCache);
+ XCTAssertTrue(snapshot.syncStateChanged);
+}
+
+- (void)testReturnsNilIfThereAreNoChanges {
+ FSTQuery *query = [self queryForMessages];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
+
+ // initial state
+ FSTTestApplyChanges(view, @[ doc1, doc2 ], nil);
+
+ // reapply same docs, no changes
+ FSTViewSnapshot *snapshot = FSTTestApplyChanges(view, @[ doc1, doc2 ], nil);
+ XCTAssertNil(snapshot);
+}
+
+- (void)testDoesNotReturnNilForFirstChanges {
+ FSTQuery *query = [self queryForMessages];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ FSTViewSnapshot *snapshot = FSTTestApplyChanges(view, @[], nil);
+ XCTAssertNotNil(snapshot);
+}
+
+- (void)testFiltersDocumentsBasedOnQueryWithFilter {
+ FSTQuery *query = [self queryForMessages];
+ FSTRelationFilter *filter =
+ [FSTRelationFilter filterWithField:FSTTestFieldPath(@"sort")
+ filterOperator:FSTRelationFilterOperatorLessThanOrEqual
+ value:[FSTDoubleValue doubleValue:2]];
+ query = [query queryByAddingFilter:filter];
+
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{ @"sort" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{ @"sort" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{ @"sort" : @3 }, NO);
+ FSTDocument *doc4 = FSTTestDoc(@"rooms/eros/messages/4", 0, @{}, NO); // no sort, no match
+ FSTDocument *doc5 = FSTTestDoc(@"rooms/eros/messages/5", 0, @{ @"sort" : @1 }, NO);
+
+ FSTViewSnapshot *snapshot = FSTTestApplyChanges(view, @[ doc1, doc2, doc3, doc4, doc5 ], nil);
+
+ XCTAssertEqual(snapshot.query, query);
+
+ XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc5, doc2 ]));
+
+ XCTAssertEqualObjects(
+ snapshot.documentChanges, (@[
+ [FSTDocumentViewChange changeWithDocument:doc1 type:FSTDocumentViewChangeTypeAdded],
+ [FSTDocumentViewChange changeWithDocument:doc5 type:FSTDocumentViewChangeTypeAdded],
+ [FSTDocumentViewChange changeWithDocument:doc2 type:FSTDocumentViewChangeTypeAdded]
+ ]));
+
+ XCTAssertTrue(snapshot.isFromCache);
+ XCTAssertTrue(snapshot.syncStateChanged);
+}
+
+- (void)testUpdatesDocumentsBasedOnQueryWithFilter {
+ FSTQuery *query = [self queryForMessages];
+ FSTRelationFilter *filter =
+ [FSTRelationFilter filterWithField:FSTTestFieldPath(@"sort")
+ filterOperator:FSTRelationFilterOperatorLessThanOrEqual
+ value:[FSTDoubleValue doubleValue:2]];
+ query = [query queryByAddingFilter:filter];
+
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{ @"sort" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{ @"sort" : @3 }, NO);
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{ @"sort" : @2 }, NO);
+ FSTDocument *doc4 = FSTTestDoc(@"rooms/eros/messages/4", 0, @{}, NO);
+
+ FSTViewSnapshot *snapshot = FSTTestApplyChanges(view, @[ doc1, doc2, doc3, doc4 ], nil);
+
+ XCTAssertEqual(snapshot.query, query);
+
+ XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc3 ]));
+
+ FSTDocument *newDoc2 = FSTTestDoc(@"rooms/eros/messages/2", 1, @{ @"sort" : @2 }, NO);
+ FSTDocument *newDoc3 = FSTTestDoc(@"rooms/eros/messages/3", 1, @{ @"sort" : @3 }, NO);
+ FSTDocument *newDoc4 = FSTTestDoc(@"rooms/eros/messages/4", 1, @{ @"sort" : @0 }, NO);
+
+ snapshot = FSTTestApplyChanges(view, @[ newDoc2, newDoc3, newDoc4 ], nil);
+
+ XCTAssertEqual(snapshot.query, query);
+
+ XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ newDoc4, doc1, newDoc2 ]));
+
+ XCTAssertEqualObjects(
+ snapshot.documentChanges, (@[
+ [FSTDocumentViewChange changeWithDocument:doc3 type:FSTDocumentViewChangeTypeRemoved],
+ [FSTDocumentViewChange changeWithDocument:newDoc4 type:FSTDocumentViewChangeTypeAdded],
+ [FSTDocumentViewChange changeWithDocument:newDoc2 type:FSTDocumentViewChangeTypeAdded]
+ ]));
+
+ XCTAssertTrue(snapshot.isFromCache);
+ XCTAssertFalse(snapshot.syncStateChanged);
+}
+
+- (void)testRemovesDocumentsForQueryWithLimit {
+ FSTQuery *query = [self queryForMessages];
+ query = [query queryBySettingLimit:2];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, NO);
+
+ // initial state
+ FSTTestApplyChanges(view, @[ doc1, doc3 ], nil);
+
+ // add doc2, which should push out doc3
+ FSTViewSnapshot *snapshot =
+ FSTTestApplyChanges(view, @[ doc2 ],
+ [FSTTargetChange changeWithDocuments:@[ doc1, doc2, doc3 ]
+ currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]);
+
+ XCTAssertEqual(snapshot.query, query);
+
+ XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc2 ]));
+
+ XCTAssertEqualObjects(
+ snapshot.documentChanges, (@[
+ [FSTDocumentViewChange changeWithDocument:doc3 type:FSTDocumentViewChangeTypeRemoved],
+ [FSTDocumentViewChange changeWithDocument:doc2 type:FSTDocumentViewChangeTypeAdded]
+ ]));
+
+ XCTAssertFalse(snapshot.isFromCache);
+ XCTAssertTrue(snapshot.syncStateChanged);
+}
+
+- (void)testDoesntReportChangesForDocumentBeyondLimitOfQuery {
+ FSTQuery *query = [self queryForMessages];
+ query =
+ [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"num")
+ ascending:YES]];
+ query = [query queryBySettingLimit:2];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{ @"num" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{ @"num" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{ @"num" : @3 }, NO);
+ FSTDocument *doc4 = FSTTestDoc(@"rooms/eros/messages/4", 0, @{ @"num" : @4 }, NO);
+
+ // initial state
+ FSTTestApplyChanges(view, @[ doc1, doc2 ], nil);
+
+ // change doc2 to 5, and add doc3 and doc4.
+ // doc2 will be modified + removed = removed
+ // doc3 will be added
+ // doc4 will be added + removed = nothing
+ doc2 = FSTTestDoc(@"rooms/eros/messages/2", 1, @{ @"num" : @5 }, NO);
+ FSTViewDocumentChanges *viewDocChanges =
+ [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2, doc3, doc4 ])];
+ XCTAssertTrue(viewDocChanges.needsRefill);
+ // Verify that all the docs still match.
+ viewDocChanges = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3, doc4 ])
+ previousChanges:viewDocChanges];
+ FSTViewSnapshot *snapshot =
+ [view applyChangesToDocuments:viewDocChanges
+ targetChange:[FSTTargetChange
+ changeWithDocuments:@[ doc1, doc2, doc3, doc4 ]
+ currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]]
+ .snapshot;
+
+ XCTAssertEqual(snapshot.query, query);
+
+ XCTAssertEqualObjects(snapshot.documents.arrayValue, (@[ doc1, doc3 ]));
+
+ XCTAssertEqualObjects(
+ snapshot.documentChanges, (@[
+ [FSTDocumentViewChange changeWithDocument:doc2 type:FSTDocumentViewChangeTypeRemoved],
+ [FSTDocumentViewChange changeWithDocument:doc3 type:FSTDocumentViewChangeTypeAdded]
+ ]));
+
+ XCTAssertFalse(snapshot.isFromCache);
+ XCTAssertTrue(snapshot.syncStateChanged);
+}
+
+- (void)testKeepsTrackOfLimboDocuments {
+ FSTQuery *query = [self queryForMessages];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{}, NO);
+
+ FSTViewChange *change = [view
+ applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])]];
+ XCTAssertEqualObjects(change.limboChanges, @[]);
+
+ change =
+ [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[])]
+ targetChange:[FSTTargetChange
+ changeWithDocuments:@[]
+ currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]];
+ XCTAssertEqualObjects(
+ change.limboChanges,
+ @[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeAdded key:doc1.key] ]);
+
+ change = [view
+ applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[])]
+ targetChange:[FSTTargetChange changeWithDocuments:@[ doc1 ]
+ currentStatusUpdate:FSTCurrentStatusUpdateNone]];
+ XCTAssertEqualObjects(
+ change.limboChanges,
+ @[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeRemoved key:doc1.key] ]);
+
+ change = [view
+ applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2 ])]
+ targetChange:[FSTTargetChange changeWithDocuments:@[ doc2 ]
+ currentStatusUpdate:FSTCurrentStatusUpdateNone]];
+ XCTAssertEqualObjects(change.limboChanges, @[]);
+
+ change = [view
+ applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])]];
+ XCTAssertEqualObjects(
+ change.limboChanges,
+ @[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeAdded key:doc3.key] ]);
+
+ change = [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[
+ FSTTestDeletedDoc(@"rooms/eros/messages/2",
+ 1)
+ ])]]; // remove
+ XCTAssertEqualObjects(
+ change.limboChanges,
+ @[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeRemoved key:doc3.key] ]);
+}
+
+- (void)testResumingQueryCreatesNoLimbos {
+ FSTQuery *query = [self queryForMessages];
+
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+
+ // Unlike other cases, here the view is initialized with a set of previously synced documents
+ // which happens when listening to a previously listened-to query.
+ FSTView *view = [[FSTView alloc] initWithQuery:query
+ remoteDocuments:FSTTestDocKeySet(@[ doc1.key, doc2.key ])];
+
+ FSTTargetChange *markCurrent =
+ [FSTTargetChange changeWithDocuments:@[]
+ currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent];
+ FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[])];
+ FSTViewChange *change = [view applyChangesToDocuments:changes targetChange:markCurrent];
+ XCTAssertEqualObjects(change.limboChanges, @[]);
+}
+
+- (void)assertDocSet:(FSTDocumentSet *)docSet containsDocs:(NSArray<FSTDocument *> *)docs {
+ XCTAssertEqual(docs.count, docSet.count);
+ for (FSTDocument *doc in docs) {
+ XCTAssertTrue([docSet containsKey:doc.key]);
+ }
+}
+
+- (void)testReturnsNeedsRefillOnDeleteInLimitQuery {
+ FSTQuery *query = [[self queryForMessages] queryBySettingLimit:2];
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ // Start with a full view.
+ FSTViewDocumentChanges *changes =
+ [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(2, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+
+ // Remove one of the docs.
+ changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ FSTTestDeletedDoc(
+ @"rooms/eros/messages/0", 0) ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc2 ]];
+ XCTAssertTrue(changes.needsRefill);
+ XCTAssertEqual(1, [changes.changeSet changes].count);
+ // Refill it with just the one doc remaining.
+ changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2 ]) previousChanges:changes];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc2 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(1, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+}
+
+- (void)testReturnsNeedsRefillOnReorderInLimitQuery {
+ FSTQuery *query = [self queryForMessages];
+ query =
+ [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"order")
+ ascending:YES]];
+ query = [query queryBySettingLimit:2];
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{ @"order" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{ @"order" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{ @"order" : @3 }, NO);
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ // Start with a full view.
+ FSTViewDocumentChanges *changes =
+ [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3 ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(2, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+
+ // Move one of the docs.
+ doc2 = FSTTestDoc(@"rooms/eros/messages/1", 1, @{ @"order" : @2000 }, NO);
+ changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2 ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
+ XCTAssertTrue(changes.needsRefill);
+ XCTAssertEqual(1, [changes.changeSet changes].count);
+ // Refill it with all three current docs.
+ changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3 ])
+ previousChanges:changes];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc3 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(2, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+}
+
+- (void)testDoesntNeedRefillOnReorderWithinLimit {
+ FSTQuery *query = [self queryForMessages];
+ query =
+ [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"order")
+ ascending:YES]];
+ query = [query queryBySettingLimit:3];
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{ @"order" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{ @"order" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{ @"order" : @3 }, NO);
+ FSTDocument *doc4 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{ @"order" : @4 }, NO);
+ FSTDocument *doc5 = FSTTestDoc(@"rooms/eros/messages/4", 0, @{ @"order" : @5 }, NO);
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ // Start with a full view.
+ FSTViewDocumentChanges *changes =
+ [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3, doc4, doc5 ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2, doc3 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(3, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+
+ // Move one of the docs.
+ doc1 = FSTTestDoc(@"rooms/eros/messages/0", 1, @{ @"order" : @3 }, NO);
+ changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc2, doc3, doc1 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(1, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+}
+
+- (void)testDoesntNeedRefillOnReorderAfterLimitQuery {
+ FSTQuery *query = [self queryForMessages];
+ query =
+ [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"order")
+ ascending:YES]];
+ query = [query queryBySettingLimit:3];
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{ @"order" : @1 }, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{ @"order" : @2 }, NO);
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{ @"order" : @3 }, NO);
+ FSTDocument *doc4 = FSTTestDoc(@"rooms/eros/messages/3", 0, @{ @"order" : @4 }, NO);
+ FSTDocument *doc5 = FSTTestDoc(@"rooms/eros/messages/4", 0, @{ @"order" : @5 }, NO);
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ // Start with a full view.
+ FSTViewDocumentChanges *changes =
+ [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2, doc3, doc4, doc5 ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2, doc3 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(3, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+
+ // Move one of the docs.
+ doc4 = FSTTestDoc(@"rooms/eros/messages/3", 1, @{ @"order" : @6 }, NO);
+ changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc4 ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2, doc3 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(0, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+}
+
+- (void)testDoesntNeedRefillForAdditionAfterTheLimit {
+ FSTQuery *query = [[self queryForMessages] queryBySettingLimit:2];
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ // Start with a full view.
+ FSTViewDocumentChanges *changes =
+ [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(2, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+
+ // Add a doc that is past the limit.
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 1, @{}, NO);
+ changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(0, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+}
+
+- (void)testDoesntNeedRefillForDeletionsWhenNotNearTheLimit {
+ FSTQuery *query = [[self queryForMessages] queryBySettingLimit:20];
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ FSTViewDocumentChanges *changes =
+ [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(2, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+
+ // Remove one of the docs.
+ changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ FSTTestDeletedDoc(
+ @"rooms/eros/messages/1", 0) ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc1 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(1, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+}
+
+- (void)testHandlesApplyingIrrelevantDocs {
+ FSTQuery *query = [[self queryForMessages] queryBySettingLimit:2];
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ // Start with a full view.
+ FSTViewDocumentChanges *changes =
+ [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(2, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+
+ // Remove a doc that isn't even in the results.
+ changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ FSTTestDeletedDoc(
+ @"rooms/eros/messages/2", 0) ])];
+ [self assertDocSet:changes.documentSet containsDocs:@[ doc1, doc2 ]];
+ XCTAssertFalse(changes.needsRefill);
+ XCTAssertEqual(0, [changes.changeSet changes].count);
+ [view applyChangesToDocuments:changes];
+}
+
+- (void)testComputesMutatedKeys {
+ FSTQuery *query = [self queryForMessages];
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ // Start with a full view.
+ FSTViewDocumentChanges *changes =
+ [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
+ [view applyChangesToDocuments:changes];
+ XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[]));
+
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{}, YES);
+ changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])];
+ XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc3.key ]));
+}
+
+- (void)testRemovesKeysFromMutatedKeysWhenNewDocHasNoLocalChanges {
+ FSTQuery *query = [self queryForMessages];
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, YES);
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ // Start with a full view.
+ FSTViewDocumentChanges *changes =
+ [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
+ [view applyChangesToDocuments:changes];
+ XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
+
+ FSTDocument *doc2Prime = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, NO);
+ changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2Prime ])];
+ [view applyChangesToDocuments:changes];
+ XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[]));
+}
+
+- (void)testRemembersLocalMutationsFromPreviousSnapshot {
+ FSTQuery *query = [self queryForMessages];
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, YES);
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ // Start with a full view.
+ FSTViewDocumentChanges *changes =
+ [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
+ [view applyChangesToDocuments:changes];
+ XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
+
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{}, NO);
+ changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])];
+ [view applyChangesToDocuments:changes];
+ XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
+}
+
+- (void)testRemembersLocalMutationsFromPreviousCallToComputeChangesWithDocuments {
+ FSTQuery *query = [self queryForMessages];
+ FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/0", 0, @{}, NO);
+ FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{}, YES);
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+
+ // Start with a full view.
+ FSTViewDocumentChanges *changes =
+ [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
+ XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
+
+ FSTDocument *doc3 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{}, NO);
+ changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ]) previousChanges:changes];
+ XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
+}
+
+@end
+
+NS_ASSUME_NONNULL_END