Files
firebase-ios-sdk/Firestore/Example/Tests/Core/FSTViewTests.mm
T
Sebastian Schmidt 5bc4cc55a6 Remove Held Write Acks (#2029)
* Drop acknowledged mutations in schema migration (#1818)

Drop acknowledged mutations in schema migration as part of removing held write acks.

Port of firebase/firebase-js-sdk#1149

* Change removeMutationBatch to remove a single batch (#1955)

* Update Firestore Generated Protos

*  Adding UnknownDocument and hasCommittedMutations (#1971)

* Removing held write batch handling from LocalStore (#1997)

* View changes for held write acks (#2004)

* Held Write Acks Unit Test Fixes (#2008)

* Held Write Ack Removal Spec Tests (#2018)

* Fix integration test
2018-11-01 09:44:30 -07:00

717 lines
33 KiB
Plaintext

/*
* 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/Remote/FSTRemoteEvent.h"
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
namespace testutil = firebase::firestore::testutil;
using firebase::firestore::model::ResourcePath;
using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
@interface FSTViewTests : XCTestCase
@end
@implementation FSTViewTests
/** Returns a new empty query to use for testing. */
- (FSTQuery *)queryForMessages {
return [FSTQuery queryWithPath:ResourcePath{"rooms", "eros", "messages"}];
}
- (void)testAddsDocumentsBasedOnQuery {
FSTQuery *query = [self queryForMessages];
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTDocument *doc1 =
FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced);
FSTDocument *doc2 =
FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced);
FSTDocument *doc3 =
FSTTestDoc("rooms/other/messages/1", 0, @{@"text" : @"msg3"}, FSTDocumentStateSynced);
FSTViewSnapshot *_Nullable snapshot = FSTTestApplyChanges(
view, @[ doc1, doc2, doc3 ], FSTTestTargetChangeAckDocuments({doc1.key, doc2.key, doc3.key}));
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:DocumentKeySet{}];
FSTDocument *doc1 =
FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced);
FSTDocument *doc2 =
FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced);
FSTDocument *doc3 =
FSTTestDoc("rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, FSTDocumentStateSynced);
// initial state
FSTTestApplyChanges(view, @[ doc1, doc2 ], nil);
// delete doc2, add doc3
FSTViewSnapshot *snapshot =
FSTTestApplyChanges(view, @[ FSTTestDeletedDoc("rooms/eros/messages/2", 0, NO), doc3 ],
FSTTestTargetChangeAckDocuments({doc1.key, doc3.key}));
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:DocumentKeySet{}];
FSTDocument *doc1 =
FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced);
FSTDocument *doc2 =
FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced);
// 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:DocumentKeySet{}];
FSTViewSnapshot *snapshot = FSTTestApplyChanges(view, @[], nil);
XCTAssertNotNil(snapshot);
}
- (void)testFiltersDocumentsBasedOnQueryWithFilter {
FSTQuery *query = [self queryForMessages];
FSTRelationFilter *filter =
[FSTRelationFilter filterWithField:testutil::Field("sort")
filterOperator:FSTRelationFilterOperatorLessThanOrEqual
value:[FSTDoubleValue doubleValue:2]];
query = [query queryByAddingFilter:filter];
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0,
@{@"sort" : @1}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0,
@{@"sort" : @2}, FSTDocumentStateSynced);
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0,
@{@"sort" : @3}, FSTDocumentStateSynced);
FSTDocument *doc4 =
FSTTestDoc("rooms/eros/messages/4", 0, @{}, FSTDocumentStateSynced); // no sort, no match
FSTDocument *doc5 = FSTTestDoc("rooms/eros/messages/5", 0,
@{@"sort" : @1}, FSTDocumentStateSynced);
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:testutil::Field("sort")
filterOperator:FSTRelationFilterOperatorLessThanOrEqual
value:[FSTDoubleValue doubleValue:2]];
query = [query queryByAddingFilter:filter];
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0,
@{@"sort" : @1}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0,
@{@"sort" : @3}, FSTDocumentStateSynced);
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0,
@{@"sort" : @2}, FSTDocumentStateSynced);
FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/4", 0, @{}, FSTDocumentStateSynced);
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}, FSTDocumentStateSynced);
FSTDocument *newDoc3 = FSTTestDoc("rooms/eros/messages/3", 1,
@{@"sort" : @3}, FSTDocumentStateSynced);
FSTDocument *newDoc4 = FSTTestDoc("rooms/eros/messages/4", 1,
@{@"sort" : @0}, FSTDocumentStateSynced);
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:DocumentKeySet{}];
FSTDocument *doc1 =
FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, FSTDocumentStateSynced);
FSTDocument *doc2 =
FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, FSTDocumentStateSynced);
FSTDocument *doc3 =
FSTTestDoc("rooms/eros/messages/3", 0, @{@"text" : @"msg3"}, FSTDocumentStateSynced);
// initial state
FSTTestApplyChanges(view, @[ doc1, doc3 ], nil);
// add doc2, which should push out doc3
FSTViewSnapshot *snapshot = FSTTestApplyChanges(
view, @[ doc2 ], FSTTestTargetChangeAckDocuments({doc1.key, doc2.key, doc3.key}));
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:testutil::Field("num")
ascending:YES]];
query = [query queryBySettingLimit:2];
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0,
@{@"num" : @1}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0,
@{@"num" : @2}, FSTDocumentStateSynced);
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0,
@{@"num" : @3}, FSTDocumentStateSynced);
FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/4", 0,
@{@"num" : @4}, FSTDocumentStateSynced);
// 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}, FSTDocumentStateSynced);
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:FSTTestTargetChangeAckDocuments(
{doc1.key, doc2.key, doc3.key, doc4.key})]
.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:DocumentKeySet{}];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, FSTDocumentStateSynced);
FSTViewChange *change = [view
applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])]];
XCTAssertEqualObjects(change.limboChanges, @[]);
change = [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[])]
targetChange:FSTTestTargetChangeMarkCurrent()];
XCTAssertEqualObjects(
change.limboChanges,
@[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeAdded key:doc1.key] ]);
change = [view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[])]
targetChange:FSTTestTargetChangeAckDocuments({doc1.key})];
XCTAssertEqualObjects(
change.limboChanges,
@[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeRemoved key:doc1.key] ]);
change =
[view applyChangesToDocuments:[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2 ])]
targetChange:FSTTestTargetChangeAckDocuments({doc2.key})];
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, NO)
])]]; // remove
XCTAssertEqualObjects(
change.limboChanges,
@[ [FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeRemoved key:doc3.key] ]);
}
- (void)testResumingQueryCreatesNoLimbos {
FSTQuery *query = [self queryForMessages];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
// 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:DocumentKeySet{doc1.key, doc2.key}];
FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[])];
FSTViewChange *change =
[view applyChangesToDocuments:changes targetChange:FSTTestTargetChangeMarkCurrent()];
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, @{}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// 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, NO) ])];
[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:testutil::Field("order")
ascending:YES]];
query = [query queryBySettingLimit:2];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0,
@{@"order" : @1}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0,
@{@"order" : @2}, FSTDocumentStateSynced);
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0,
@{@"order" : @3}, FSTDocumentStateSynced);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// 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}, FSTDocumentStateSynced);
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:testutil::Field("order")
ascending:YES]];
query = [query queryBySettingLimit:3];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0,
@{@"order" : @1}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0,
@{@"order" : @2}, FSTDocumentStateSynced);
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0,
@{@"order" : @3}, FSTDocumentStateSynced);
FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/3", 0,
@{@"order" : @4}, FSTDocumentStateSynced);
FSTDocument *doc5 = FSTTestDoc("rooms/eros/messages/4", 0,
@{@"order" : @5}, FSTDocumentStateSynced);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// 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}, FSTDocumentStateSynced);
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:testutil::Field("order")
ascending:YES]];
query = [query queryBySettingLimit:3];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0,
@{@"order" : @1}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0,
@{@"order" : @2}, FSTDocumentStateSynced);
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0,
@{@"order" : @3}, FSTDocumentStateSynced);
FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/3", 0,
@{@"order" : @4}, FSTDocumentStateSynced);
FSTDocument *doc5 = FSTTestDoc("rooms/eros/messages/4", 0,
@{@"order" : @5}, FSTDocumentStateSynced);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// 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}, FSTDocumentStateSynced);
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, @{}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// 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, @{}, FSTDocumentStateSynced);
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, @{}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
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, NO) ])];
[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, @{}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// 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, NO) ])];
[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, @{}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
[view applyChangesToDocuments:changes];
XCTAssertEqual(changes.mutatedKeys, DocumentKeySet{});
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, FSTDocumentStateLocalMutations);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])];
XCTAssertEqual(changes.mutatedKeys, DocumentKeySet{doc3.key});
}
- (void)testRemovesKeysFromMutatedKeysWhenNewDocHasNoLocalChanges {
FSTQuery *query = [self queryForMessages];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateLocalMutations);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
[view applyChangesToDocuments:changes];
XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
FSTDocument *doc2Prime = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateSynced);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2Prime ])];
[view applyChangesToDocuments:changes];
XCTAssertEqual(changes.mutatedKeys, DocumentKeySet{});
}
- (void)testRemembersLocalMutationsFromPreviousSnapshot {
FSTQuery *query = [self queryForMessages];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateLocalMutations);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
[view applyChangesToDocuments:changes];
XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, FSTDocumentStateSynced);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])];
[view applyChangesToDocuments:changes];
XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
}
- (void)testRemembersLocalMutationsFromPreviousCallToComputeChangesWithDocuments {
FSTQuery *query = [self queryForMessages];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateLocalMutations);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, FSTDocumentStateSynced);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ]) previousChanges:changes];
XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
}
- (void)testRaisesHasPendingWritesForPendingMutationsInInitialSnapshot {
FSTQuery *query = [self queryForMessages];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateLocalMutations);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])];
FSTViewChange *viewChange = [view applyChangesToDocuments:changes];
XCTAssertTrue(viewChange.snapshot.hasPendingWrites);
}
- (void)testDoesntRaiseHasPendingWritesForCommittedMutationsInInitialSnapshot {
FSTQuery *query = [self queryForMessages];
FSTDocument *doc1 =
FSTTestDoc("rooms/eros/messages/1", 0, @{}, FSTDocumentStateCommittedMutations);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewDocumentChanges *changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1 ])];
FSTViewChange *viewChange = [view applyChangesToDocuments:changes];
XCTAssertFalse(viewChange.snapshot.hasPendingWrites);
}
- (void)testSuppressesWriteAcknowledgementIfWatchHasNotCaughtUp {
// This test verifies that we don't get three events for an FSTServerTimestamp mutation. We
// suppress the event generated by the write acknowledgement and instead wait for Watch to catch
// up.
FSTQuery *query = [self queryForMessages];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 1,
@{@"time" : @1}, FSTDocumentStateLocalMutations);
FSTDocument *doc1Committed = FSTTestDoc("rooms/eros/messages/1", 2,
@{@"time" : @2}, FSTDocumentStateCommittedMutations);
FSTDocument *doc1Acknowledged = FSTTestDoc("rooms/eros/messages/1", 2,
@{@"time" : @2}, FSTDocumentStateSynced);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 1,
@{@"time" : @1}, FSTDocumentStateLocalMutations);
FSTDocument *doc2Modified = FSTTestDoc("rooms/eros/messages/2", 2,
@{@"time" : @3}, FSTDocumentStateLocalMutations);
FSTDocument *doc2Acknowledged = FSTTestDoc("rooms/eros/messages/2", 2,
@{@"time" : @3}, FSTDocumentStateSynced);
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewDocumentChanges *changes =
[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
FSTViewChange *viewChange = [view applyChangesToDocuments:changes];
XCTAssertEqualObjects(
(@[
[FSTDocumentViewChange changeWithDocument:doc1 type:FSTDocumentViewChangeTypeAdded],
[FSTDocumentViewChange changeWithDocument:doc2 type:FSTDocumentViewChangeTypeAdded]
]),
viewChange.snapshot.documentChanges);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1Committed, doc2Modified ])];
viewChange = [view applyChangesToDocuments:changes];
// The 'doc1Committed' update is suppressed
XCTAssertEqualObjects(
(@[ [FSTDocumentViewChange changeWithDocument:doc2Modified
type:FSTDocumentViewChangeTypeModified] ]),
viewChange.snapshot.documentChanges);
changes =
[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1Acknowledged, doc2Acknowledged ])];
viewChange = [view applyChangesToDocuments:changes];
XCTAssertEqualObjects(
(@[
[FSTDocumentViewChange changeWithDocument:doc1Acknowledged
type:FSTDocumentViewChangeTypeModified],
[FSTDocumentViewChange changeWithDocument:doc2Acknowledged
type:FSTDocumentViewChangeTypeMetadata]
]),
viewChange.snapshot.documentChanges);
}
@end
NS_ASSUME_NONNULL_END