diff --git a/firebase-firestore/CHANGELOG.md b/firebase-firestore/CHANGELOG.md index 62e9b05b3d5..209d5794215 100644 --- a/firebase-firestore/CHANGELOG.md +++ b/firebase-firestore/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased +- [fixed] Fixed Firestore failing to raise initial snapshot from empty local cache result. # 24.4.0 * [unchanged] Updated to accommodate the release of the updated diff --git a/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/TestUtil.java b/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/TestUtil.java index 37359d5a080..e11c5af1503 100644 --- a/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/TestUtil.java +++ b/firebase-firestore/ktx/src/test/java/com/google/firebase/firestore/TestUtil.java @@ -105,7 +105,8 @@ public static QuerySnapshot querySnapshot( isFromCache, mutatedKeys, /* didSyncStateChange= */ true, - /* excludesMetadataChanges= */ false); + /* excludesMetadataChanges= */ false, + /* hasCachedResults= */ false); return new QuerySnapshot(query(path), viewSnapshot, FIRESTORE); } } diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/QueryTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/QueryTest.java index f547408410e..c2eef0989c2 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/QueryTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/QueryTest.java @@ -550,6 +550,50 @@ public void testQueriesFireFromCacheWhenOffline() { listener.remove(); } + @Test + public void testQueriesCanRaiseInitialSnapshotFromCachedEmptyResults() { + CollectionReference collectionReference = testCollection(); + + // Populate the cache with empty query result. + QuerySnapshot querySnapshotA = waitFor(collectionReference.get()); + assertFalse(querySnapshotA.getMetadata().isFromCache()); + assertEquals(asList(), querySnapshotToValues(querySnapshotA)); + + // Add a snapshot listener whose first event should be raised from cache. + EventAccumulator accum = new EventAccumulator<>(); + ListenerRegistration listenerRegistration = + collectionReference.addSnapshotListener(accum.listener()); + QuerySnapshot querySnapshotB = accum.await(); + assertTrue(querySnapshotB.getMetadata().isFromCache()); + assertEquals(asList(), querySnapshotToValues(querySnapshotB)); + + listenerRegistration.remove(); + } + + @Test + public void testQueriesCanRaiseInitialSnapshotFromEmptyDueToDeleteCachedResults() { + Map> testDocs = map("a", map("foo", 1L)); + CollectionReference collectionReference = testCollectionWithDocs(testDocs); + // Populate the cache with single document. + QuerySnapshot querySnapshotA = waitFor(collectionReference.get()); + assertFalse(querySnapshotA.getMetadata().isFromCache()); + assertEquals(asList(testDocs.get("a")), querySnapshotToValues(querySnapshotA)); + + // delete the document, make cached result empty. + DocumentReference docRef = collectionReference.document("a"); + waitFor(docRef.delete()); + + // Add a snapshot listener whose first event should be raised from cache. + EventAccumulator accum = new EventAccumulator<>(); + ListenerRegistration listenerRegistration = + collectionReference.addSnapshotListener(accum.listener()); + QuerySnapshot querySnapshotB = accum.await(); + assertTrue(querySnapshotB.getMetadata().isFromCache()); + assertEquals(asList(), querySnapshotToValues(querySnapshotB)); + + listenerRegistration.remove(); + } + @Test public void testQueriesCanUseNotEqualFilters() { // These documents are ordered by value in "zip" since the notEquals filter is an inequality, diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/QueryListener.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/QueryListener.java index b5201cfda43..a041da9191b 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/QueryListener.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/QueryListener.java @@ -87,7 +87,8 @@ public boolean onViewSnapshot(ViewSnapshot newSnapshot) { newSnapshot.isFromCache(), newSnapshot.getMutatedKeys(), newSnapshot.didSyncStateChange(), - /* excludesMetadataChanges= */ true); + /* excludesMetadataChanges= */ true, + newSnapshot.hasCachedResults()); } if (!raisedInitialEvent) { @@ -139,8 +140,11 @@ private boolean shouldRaiseInitialEvent(ViewSnapshot snapshot, OnlineState onlin return false; } - // Raise data from cache if we have any documents or we are offline - return !snapshot.getDocuments().isEmpty() || onlineState.equals(OnlineState.OFFLINE); + // Raise data from cache if we have any documents, have cached results before, + // or we are offline. + return (!snapshot.getDocuments().isEmpty() + || snapshot.hasCachedResults() + || onlineState.equals(OnlineState.OFFLINE)); } private boolean shouldRaiseEvent(ViewSnapshot snapshot) { @@ -171,7 +175,8 @@ private void raiseInitialEvent(ViewSnapshot snapshot) { snapshot.getDocuments(), snapshot.getMutatedKeys(), snapshot.isFromCache(), - snapshot.excludesMetadataChanges()); + snapshot.excludesMetadataChanges(), + snapshot.hasCachedResults()); raisedInitialEvent = true; listener.onEvent(snapshot, null); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java index 7513e2b9f27..c7da9e27e18 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java @@ -54,6 +54,7 @@ import com.google.firebase.firestore.util.Function; import com.google.firebase.firestore.util.Logger; import com.google.firebase.firestore.util.Util; +import com.google.protobuf.ByteString; import io.grpc.Status; import java.io.IOException; import java.util.ArrayList; @@ -204,13 +205,16 @@ public int listen(Query query) { TargetData targetData = localStore.allocateTarget(query.toTarget()); remoteStore.listen(targetData); - ViewSnapshot viewSnapshot = initializeViewAndComputeSnapshot(query, targetData.getTargetId()); + ViewSnapshot viewSnapshot = + initializeViewAndComputeSnapshot( + query, targetData.getTargetId(), targetData.getResumeToken()); syncEngineListener.onViewSnapshots(Collections.singletonList(viewSnapshot)); return targetData.getTargetId(); } - private ViewSnapshot initializeViewAndComputeSnapshot(Query query, int targetId) { + private ViewSnapshot initializeViewAndComputeSnapshot( + Query query, int targetId, ByteString resumeToken) { QueryResult queryResult = localStore.executeQuery(query, /* usePreviousResults= */ true); SyncState currentTargetSyncState = SyncState.NONE; @@ -221,10 +225,10 @@ private ViewSnapshot initializeViewAndComputeSnapshot(Query query, int targetId) if (this.queriesByTarget.get(targetId) != null) { Query mirrorQuery = this.queriesByTarget.get(targetId).get(0); currentTargetSyncState = this.queryViewsByQuery.get(mirrorQuery).getView().getSyncState(); - synthesizedCurrentChange = - TargetChange.createSynthesizedTargetChangeForCurrentChange( - currentTargetSyncState == SyncState.SYNCED); } + synthesizedCurrentChange = + TargetChange.createSynthesizedTargetChangeForCurrentChange( + currentTargetSyncState == SyncState.SYNCED, resumeToken); // TODO(wuandy): Investigate if we can extract the logic of view change computation and // update tracked limbo in one place, and have both emitNewSnapsAndNotifyLocalStore diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/View.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/View.java index 4248998f9b2..7e1cfcf110a 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/View.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/View.java @@ -300,8 +300,11 @@ public ViewChange applyChanges(DocumentChanges docChanges, TargetChange targetCh boolean syncStatedChanged = newSyncState != syncState; syncState = newSyncState; ViewSnapshot snapshot = null; + if (viewChanges.size() != 0 || syncStatedChanged) { boolean fromCache = newSyncState == SyncState.LOCAL; + boolean hasCachedResults = + targetChange == null ? false : !targetChange.getResumeToken().isEmpty(); snapshot = new ViewSnapshot( query, @@ -311,7 +314,8 @@ public ViewChange applyChanges(DocumentChanges docChanges, TargetChange targetCh fromCache, docChanges.mutatedKeys, syncStatedChanged, - /* excludesMetadataChanges= */ false); + /* excludesMetadataChanges= */ false, + hasCachedResults); } return new ViewChange(snapshot, limboDocumentChanges); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/ViewSnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/ViewSnapshot.java index 1e05a3d8e98..2741815c1d4 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/ViewSnapshot.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/ViewSnapshot.java @@ -39,6 +39,7 @@ public enum SyncState { private final ImmutableSortedSet mutatedKeys; private final boolean didSyncStateChange; private boolean excludesMetadataChanges; + private boolean hasCachedResults; public ViewSnapshot( Query query, @@ -48,7 +49,8 @@ public ViewSnapshot( boolean isFromCache, ImmutableSortedSet mutatedKeys, boolean didSyncStateChange, - boolean excludesMetadataChanges) { + boolean excludesMetadataChanges, + boolean hasCachedResults) { this.query = query; this.documents = documents; this.oldDocuments = oldDocuments; @@ -57,6 +59,7 @@ public ViewSnapshot( this.mutatedKeys = mutatedKeys; this.didSyncStateChange = didSyncStateChange; this.excludesMetadataChanges = excludesMetadataChanges; + this.hasCachedResults = hasCachedResults; } /** Returns a view snapshot as if all documents in the snapshot were added. */ @@ -65,7 +68,8 @@ public static ViewSnapshot fromInitialDocuments( DocumentSet documents, ImmutableSortedSet mutatedKeys, boolean fromCache, - boolean excludesMetadataChanges) { + boolean excludesMetadataChanges, + boolean hasCachedResults) { List viewChanges = new ArrayList<>(); for (Document doc : documents) { viewChanges.add(DocumentViewChange.create(DocumentViewChange.Type.ADDED, doc)); @@ -78,7 +82,8 @@ public static ViewSnapshot fromInitialDocuments( fromCache, mutatedKeys, /* didSyncStateChange= */ true, - excludesMetadataChanges); + excludesMetadataChanges, + hasCachedResults); } public Query getQuery() { @@ -117,6 +122,10 @@ public boolean excludesMetadataChanges() { return excludesMetadataChanges; } + public boolean hasCachedResults() { + return hasCachedResults; + } + @Override public final boolean equals(Object o) { if (this == o) { @@ -149,6 +158,9 @@ public final boolean equals(Object o) { if (!oldDocuments.equals(that.oldDocuments)) { return false; } + if (hasCachedResults != that.hasCachedResults) { + return false; + } return changes.equals(that.changes); } @@ -162,6 +174,7 @@ public int hashCode() { result = 31 * result + (isFromCache ? 1 : 0); result = 31 * result + (didSyncStateChange ? 1 : 0); result = 31 * result + (excludesMetadataChanges ? 1 : 0); + result = 31 * result + (hasCachedResults ? 1 : 0); return result; } @@ -183,6 +196,8 @@ public String toString() { + didSyncStateChange + ", excludesMetadataChanges=" + excludesMetadataChanges + + ", hasCachedResults=" + + hasCachedResults + ")"; } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/TargetChange.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/TargetChange.java index 30cd7954cd0..9c8dad77ab5 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/TargetChange.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/TargetChange.java @@ -31,9 +31,10 @@ public final class TargetChange { private final ImmutableSortedSet modifiedDocuments; private final ImmutableSortedSet removedDocuments; - public static TargetChange createSynthesizedTargetChangeForCurrentChange(boolean isCurrent) { + public static TargetChange createSynthesizedTargetChangeForCurrentChange( + boolean isCurrent, ByteString resumeToken) { return new TargetChange( - ByteString.EMPTY, + resumeToken, isCurrent, DocumentKey.emptyKeySet(), DocumentKey.emptyKeySet(), diff --git a/firebase-firestore/src/roboUtil/java/com/google/firebase/firestore/TestUtil.java b/firebase-firestore/src/roboUtil/java/com/google/firebase/firestore/TestUtil.java index a4bf3f7f3dd..d8a33a4f72b 100644 --- a/firebase-firestore/src/roboUtil/java/com/google/firebase/firestore/TestUtil.java +++ b/firebase-firestore/src/roboUtil/java/com/google/firebase/firestore/TestUtil.java @@ -83,7 +83,8 @@ public static QuerySnapshot querySnapshot( Map oldDocs, Map docsToAdd, boolean hasPendingWrites, - boolean isFromCache) { + boolean isFromCache, + boolean hasCachedResults) { DocumentSet oldDocuments = docSet(Document.KEY_COMPARATOR); ImmutableSortedSet mutatedKeys = DocumentKey.emptyKeySet(); for (Map.Entry pair : oldDocs.entrySet()) { @@ -116,7 +117,8 @@ public static QuerySnapshot querySnapshot( isFromCache, mutatedKeys, /* didSyncStateChange= */ true, - /* excludesMetadataChanges= */ false); + /* excludesMetadataChanges= */ false, + hasCachedResults); return new QuerySnapshot(query(path), viewSnapshot, FIRESTORE); } diff --git a/firebase-firestore/src/test/java/com/google/firebase/firestore/QuerySnapshotTest.java b/firebase-firestore/src/test/java/com/google/firebase/firestore/QuerySnapshotTest.java index 4ce018def3e..649fa35bf59 100644 --- a/firebase-firestore/src/test/java/com/google/firebase/firestore/QuerySnapshotTest.java +++ b/firebase-firestore/src/test/java/com/google/firebase/firestore/QuerySnapshotTest.java @@ -56,21 +56,26 @@ public void testEquals() { ObjectValue firstValue = wrapObject("a", 1); ObjectValue secondValue = wrapObject("b", 1); - QuerySnapshot foo = TestUtil.querySnapshot("foo", map(), map("a", firstValue), true, false); - QuerySnapshot fooDup = TestUtil.querySnapshot("foo", map(), map("a", firstValue), true, false); + QuerySnapshot foo = + TestUtil.querySnapshot("foo", map(), map("a", firstValue), true, false, false); + QuerySnapshot fooDup = + TestUtil.querySnapshot("foo", map(), map("a", firstValue), true, false, false); QuerySnapshot differentPath = - TestUtil.querySnapshot("bar", map(), map("a", firstValue), true, false); + TestUtil.querySnapshot("bar", map(), map("a", firstValue), true, false, false); QuerySnapshot differentDoc = - TestUtil.querySnapshot("foo", map(), map("a", secondValue), true, false); + TestUtil.querySnapshot("foo", map(), map("a", secondValue), true, false, false); QuerySnapshot noPendingWrites = - TestUtil.querySnapshot("foo", map(), map("a", firstValue), false, false); + TestUtil.querySnapshot("foo", map(), map("a", firstValue), false, false, false); QuerySnapshot fromCache = - TestUtil.querySnapshot("foo", map(), map("a", firstValue), true, true); + TestUtil.querySnapshot("foo", map(), map("a", firstValue), true, true, false); + QuerySnapshot hasCachedResults = + TestUtil.querySnapshot("foo", map(), map("a", firstValue), true, false, true); assertEquals(foo, fooDup); assertNotEquals(foo, differentPath); assertNotEquals(foo, differentDoc); assertNotEquals(foo, noPendingWrites); assertNotEquals(foo, fromCache); + assertNotEquals(foo, hasCachedResults); // Note: `foo` and `differentDoc` have the same hash code since we no longer take document // contents into account. @@ -88,7 +93,8 @@ public void testToObjects() { ObjectValue objectData = ObjectValue.fromMap(map("timestamp", ServerTimestamps.valueOf(Timestamp.now(), null))); - QuerySnapshot foo = TestUtil.querySnapshot("foo", map(), map("a", objectData), true, false); + QuerySnapshot foo = + TestUtil.querySnapshot("foo", map(), map("a", objectData), true, false, false); List docs = foo.toObjects(POJO.class); assertEquals(1, docs.size()); @@ -126,7 +132,8 @@ public void testIncludeMetadataChanges() { /*isFromCache=*/ false, /*mutatedKeys=*/ keySet(), /*didSyncStateChange=*/ true, - /* excludesMetadataChanges= */ false); + /* excludesMetadataChanges= */ false, + /* hasCachedResults= */ false); QuerySnapshot snapshot = new QuerySnapshot(new Query(fooQuery, firestore), viewSnapshot, firestore); diff --git a/firebase-firestore/src/test/java/com/google/firebase/firestore/core/QueryListenerTest.java b/firebase-firestore/src/test/java/com/google/firebase/firestore/core/QueryListenerTest.java index dbc6e3ddc6a..31c1e9b0712 100644 --- a/firebase-firestore/src/test/java/com/google/firebase/firestore/core/QueryListenerTest.java +++ b/firebase-firestore/src/test/java/com/google/firebase/firestore/core/QueryListenerTest.java @@ -108,7 +108,8 @@ public void testRaisesCollectionEvents() { snap2.isFromCache(), snap2.getMutatedKeys(), /* didSyncStateChange= */ true, - /* excludesMetadataChanges= */ false); + /* excludesMetadataChanges= */ false, + /* hasCachedResults= */ false); assertEquals(asList(snap2Prime), otherAccum); } @@ -262,7 +263,8 @@ public void testRaisesQueryMetadataEventsOnlyWhenHasPendingWritesOnTheQueryChang snap4.isFromCache(), snap4.getMutatedKeys(), snap4.didSyncStateChange(), - /* excludeMetadataChanges= */ true); // This test excludes document metadata changes + /* excludeMetadataChanges= */ true, + /* hasCachedResults= */ false); // This test excludes document metadata changes assertEquals( asList( @@ -302,7 +304,9 @@ public void testMetadataOnlyDocumentChangesAreFilteredOut() { snap2.isFromCache(), snap2.getMutatedKeys(), snap2.didSyncStateChange(), - /* excludesMetadataChanges= */ true); + /* excludesMetadataChanges= */ true, + /* hasCachedResults= */ false); + assertEquals( asList(applyExpectedMetadata(snap1, MetadataChanges.EXCLUDE), expectedSnapshot2), filteredAccum); @@ -344,7 +348,8 @@ public void testWillWaitForSyncIfOnline() { /* isFromCache= */ false, snap3.getMutatedKeys(), /* didSyncStateChange= */ true, - /* excludesMetadataChanges= */ true); + /* excludesMetadataChanges= */ true, + /* hasCachedResults= */ false); assertEquals(asList(expectedSnapshot), events); } @@ -382,7 +387,9 @@ public void testWillRaiseInitialEventWhenGoingOffline() { /* isFromCache= */ true, snap1.getMutatedKeys(), /* didSyncStateChange= */ true, - /* excludesMetadataChanges= */ true); + /* excludesMetadataChanges= */ true, + /* hasCachedResults= */ false); + ViewSnapshot expectedSnapshot2 = new ViewSnapshot( snap2.getQuery(), @@ -392,7 +399,8 @@ public void testWillRaiseInitialEventWhenGoingOffline() { /* isFromCache= */ true, snap2.getMutatedKeys(), /* didSyncStateChange= */ false, - /* excludesMetadataChanges= */ true); + /* excludesMetadataChanges= */ true, + /* hasCachedResults= */ false); assertEquals(asList(expectedSnapshot1, expectedSnapshot2), events); } @@ -419,7 +427,8 @@ public void testWillRaiseInitialEventWhenGoingOfflineAndThereAreNoDocs() { /* isFromCache= */ true, snap1.getMutatedKeys(), /* didSyncStateChange= */ true, - /* excludesMetadataChanges= */ true); + /* excludesMetadataChanges= */ true, + /* hasCachedResults= */ false); assertEquals(asList(expectedSnapshot), events); } @@ -445,7 +454,8 @@ public void testWillRaiseInitialEventWhenStartingOfflineAndThereAreNoDocs() { /* isFromCache= */ true, snap1.getMutatedKeys(), /* didSyncStateChange= */ true, - /* excludesMetadataChanges= */ true); + /* excludesMetadataChanges= */ true, + /* hasCachedResults= */ false); assertEquals(asList(expectedSnapshot), events); } @@ -458,6 +468,7 @@ private ViewSnapshot applyExpectedMetadata(ViewSnapshot snap, MetadataChanges me snap.isFromCache(), snap.getMutatedKeys(), snap.didSyncStateChange(), - MetadataChanges.EXCLUDE.equals(metadata)); + MetadataChanges.EXCLUDE.equals(metadata), + snap.hasCachedResults()); } } diff --git a/firebase-firestore/src/test/java/com/google/firebase/firestore/core/ViewSnapshotTest.java b/firebase-firestore/src/test/java/com/google/firebase/firestore/core/ViewSnapshotTest.java index 647601030ac..ee3920d3658 100644 --- a/firebase-firestore/src/test/java/com/google/firebase/firestore/core/ViewSnapshotTest.java +++ b/firebase-firestore/src/test/java/com/google/firebase/firestore/core/ViewSnapshotTest.java @@ -49,6 +49,7 @@ public void testConstructor() { boolean hasPendingWrites = true; boolean syncStateChanges = true; boolean excludesMetadataChanges = true; + boolean hasCachedResults = true; ViewSnapshot snapshot = new ViewSnapshot( @@ -59,7 +60,8 @@ public void testConstructor() { fromCache, mutatedKeys, syncStateChanges, - excludesMetadataChanges); + excludesMetadataChanges, + hasCachedResults); assertEquals(query, snapshot.getQuery()); assertEquals(docs, snapshot.getDocuments()); @@ -70,5 +72,6 @@ public void testConstructor() { assertEquals(hasPendingWrites, snapshot.hasPendingWrites()); assertEquals(syncStateChanges, snapshot.didSyncStateChange()); assertEquals(excludesMetadataChanges, snapshot.excludesMetadataChanges()); + assertEquals(hasCachedResults, snapshot.hasCachedResults()); } } diff --git a/firebase-firestore/src/test/resources/json/listen_spec_test.json b/firebase-firestore/src/test/resources/json/listen_spec_test.json index 1af1ab6594a..5755019b048 100644 --- a/firebase-firestore/src/test/resources/json/listen_spec_test.json +++ b/firebase-firestore/src/test/resources/json/listen_spec_test.json @@ -2257,19 +2257,16 @@ } ] }, - "Ensure correct query results with latency-compensated deletes": { + "Empty initial snapshot is raised from cache": { "describeName": "Listens:", - "itName": "Ensure correct query results with latency-compensated deletes", + "itName": "Empty initial snapshot is raised from cache", "tags": [ ], "config": { "numClients": 1, - "useGarbageCollection": true + "useGarbageCollection": false }, "steps": [ - { - "userDelete": "collection/b" - }, { "userListen": { "query": { @@ -2306,28 +2303,6 @@ { "watchEntity": { "docs": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "a": true - }, - "version": 1000 - }, - { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "b": true - }, - "version": 1000 - } ], "targets": [ 2 @@ -2350,19 +2325,6 @@ }, "expectedSnapshotEvents": [ { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "a": true - }, - "version": 1000 - } - ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, @@ -2376,42 +2338,48 @@ } ] }, + { + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, { "userListen": { "query": { "filters": [ ], - "limit": 10, - "limitType": "LimitToFirst", "orderBys": [ ], "path": "collection" }, - "targetId": 4 + "targetId": 2 }, "expectedSnapshotEvents": [ { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "a": true - }, - "version": 1000 - } - ], "errorCode": 0, "fromCache": true, "hasPendingWrites": false, "query": { "filters": [ ], - "limit": 10, - "limitType": "LimitToFirst", "orderBys": [ ], "path": "collection" @@ -2430,38 +2398,73 @@ "path": "collection" } ], - "resumeToken": "" - }, - "4": { - "queries": [ - { - "filters": [ - ], - "limit": 10, - "limitType": "LimitToFirst", - "orderBys": [ - ], - "path": "collection" - } - ], - "resumeToken": "" + "resumeToken": "resume-token-1000" } } } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-2000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] } ] }, - "Ignores update from inactive target": { + "Empty initial snapshot is raised from cache in multiple tabs": { "describeName": "Listens:", - "itName": "Ignores update from inactive target", + "itName": "Empty initial snapshot is raised from cache in multiple tabs", "tags": [ + "multi-client" ], "config": { - "numClients": 1, + "numClients": 2, "useGarbageCollection": false }, "steps": [ { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, "userListen": { "query": { "filters": [ @@ -2490,24 +2493,15 @@ } }, { + "clientIndex": 0, "watchAck": [ 2 ] }, { + "clientIndex": 0, "watchEntity": { "docs": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - } ], "targets": [ 2 @@ -2515,6 +2509,7 @@ } }, { + "clientIndex": 0, "watchCurrent": [ [ 2 @@ -2523,6 +2518,7 @@ ] }, { + "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], @@ -2530,19 +2526,6 @@ }, "expectedSnapshotEvents": [ { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - } - ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, @@ -2557,6 +2540,7 @@ ] }, { + "clientIndex": 0, "userUnlisten": [ 2, { @@ -2573,33 +2557,7 @@ } }, { - "watchEntity": { - "docs": [ - { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "b" - }, - "version": 2000 - } - ], - "targets": [ - 2 - ] - } - }, - { - "watchSnapshot": { - "targetIds": [ - ], - "version": 2000 - } - }, - { + "clientIndex": 0, "watchRemove": { "targetIds": [ 2 @@ -2607,6 +2565,11 @@ } }, { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, "userListen": { "query": { "filters": [ @@ -2619,19 +2582,6 @@ }, "expectedSnapshotEvents": [ { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - } - ], "errorCode": 0, "fromCache": true, "hasPendingWrites": false, @@ -2656,36 +2606,102 @@ "path": "collection" } ], - "resumeToken": "resume-token-1000" + "resumeToken": "" } } } - } - ] - }, - "Individual (deleted) documents cannot revert": { - "describeName": "Listens:", - "itName": "Individual (deleted) documents cannot revert", - "tags": [ - ], - "config": { - "numClients": 1, - "useGarbageCollection": false - }, - "steps": [ + }, { - "userListen": { - "query": { - "filters": [ - [ - "visible", - "==", - true - ] - ], - "orderBys": [ - ], - "path": "collection" + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "resume-token-1000" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-2000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + } + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Empty-due-to-delete initial snapshot is raised from cache": { + "describeName": "Listens:", + "itName": "Empty-due-to-delete initial snapshot is raised from cache", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": false + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" }, "targetId": 2 }, @@ -2695,11 +2711,6 @@ "queries": [ { "filters": [ - [ - "visible", - "==", - true - ] ], "orderBys": [ ], @@ -2726,8 +2737,7 @@ "hasLocalMutations": false }, "value": { - "v": "v1000", - "visible": true + "v": 1 }, "version": 1000 } @@ -2761,8 +2771,7 @@ "hasLocalMutations": false }, "value": { - "v": "v1000", - "visible": true + "v": 1 }, "version": 1000 } @@ -2772,11 +2781,6 @@ "hasPendingWrites": false, "query": { "filters": [ - [ - "visible", - "==", - true - ] ], "orderBys": [ ], @@ -2790,11 +2794,6 @@ 2, { "filters": [ - [ - "visible", - "==", - true - ] ], "orderBys": [ ], @@ -2813,6 +2812,9 @@ ] } }, + { + "userDelete": "collection/a" + }, { "userListen": { "query": { @@ -2822,24 +2824,10 @@ ], "path": "collection" }, - "targetId": 4 + "targetId": 2 }, "expectedSnapshotEvents": [ { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "v": "v1000", - "visible": true - }, - "version": 1000 - } - ], "errorCode": 0, "fromCache": true, "hasPendingWrites": false, @@ -2854,7 +2842,53 @@ ], "expectedState": { "activeTargets": { - "4": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "resume-token-1000" + } + } + } + } + ] + }, + "Empty-due-to-delete initial snapshot is raised from cache in multiple tabs": { + "describeName": "Listens:", + "itName": "Empty-due-to-delete initial snapshot is raised from cache in multiple tabs", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 2, + "useGarbageCollection": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { "queries": [ { "filters": [ @@ -2870,51 +2904,51 @@ } }, { + "clientIndex": 0, "watchAck": [ - 4 + 2 ] }, { + "clientIndex": 0, "watchEntity": { "docs": [ { "key": "collection/a", - "value": null, - "version": 3000 + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 } ], - "removedTargets": [ - 4 + "targets": [ + 2 ] } }, { + "clientIndex": 0, "watchCurrent": [ [ - 4 + 2 ], - "resume-token-4000" + "resume-token-1000" ] }, { + "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], - "version": 4000 + "version": 1000 }, "expectedSnapshotEvents": [ { - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "removed": [ + "added": [ { "key": "collection/a", "options": { @@ -2922,18 +2956,28 @@ "hasLocalMutations": false }, "value": { - "v": "v1000", - "visible": true + "v": 1 }, "version": 1000 } - ] + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } } ] }, { + "clientIndex": 0, "userUnlisten": [ - 4, + 2, { "filters": [ ], @@ -2948,21 +2992,26 @@ } }, { + "clientIndex": 0, "watchRemove": { "targetIds": [ - 4 + 2 ] } }, { + "clientIndex": 0, + "userDelete": "collection/a" + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, "userListen": { "query": { "filters": [ - [ - "visible", - "==", - true - ] ], "orderBys": [ ], @@ -2970,130 +3019,43 @@ }, "targetId": 2 }, - "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - [ - "visible", - "==", - true - ] - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "resumeToken": "resume-token-1000" - } - } - } - }, - { - "watchAck": [ - 2 - ] - }, - { - "watchEntity": { - "docs": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "v": "v2000", - "visible": false - }, - "version": 2000 - } - ], - "removedTargets": [ - 2 - ] - } - }, - { - "watchCurrent": [ - [ - 2 - ], - "resume-token-5000" - ] - }, - { - "watchSnapshot": { - "targetIds": [ - ], - "version": 5000 - }, "expectedSnapshotEvents": [ { "errorCode": 0, - "fromCache": false, + "fromCache": true, "hasPendingWrites": false, "query": { "filters": [ - [ - "visible", - "==", - true - ] ], "orderBys": [ ], "path": "collection" } } - ] - }, - { - "userUnlisten": [ - 2, - { - "filters": [ - [ - "visible", - "==", - true - ] - ], - "orderBys": [ - ], - "path": "collection" - } ], "expectedState": { "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } } } }, { - "watchRemove": { - "targetIds": [ - 2 - ] - } - }, - { - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 4 - }, + "clientIndex": 0, + "drainQueue": true, "expectedState": { "activeTargets": { - "4": { + "2": { "queries": [ { "filters": [ @@ -3103,39 +3065,67 @@ "path": "collection" } ], - "resumeToken": "resume-token-4000" + "resumeToken": "resume-token-1000" } } } }, { + "clientIndex": 0, + "writeAck": { + "version": 2000 + }, + "expectedState": { + "userCallbacks": { + "acknowledgedDocs": [ + "collection/a" + ], + "rejectedDocs": [ + ] + } + } + }, + { + "clientIndex": 0, "watchAck": [ - 4 + 2 ] }, { + "clientIndex": 0, "watchEntity": { "docs": [ + { + "key": "collection/a", + "value": null, + "version": 2000 + } ], "targets": [ - 4 + 2 ] } }, { + "clientIndex": 0, "watchCurrent": [ [ - 4 + 2 ], - "resume-token-6000" + "resume-token-2000" ] }, { + "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], - "version": 6000 - }, + "version": 2000 + } + }, + { + "clientIndex": 1, + "drainQueue": true, "expectedSnapshotEvents": [ { "errorCode": 0, @@ -3153,25 +3143,23 @@ } ] }, - "Individual documents cannot revert": { + "Ensure correct query results with latency-compensated deletes": { "describeName": "Listens:", - "itName": "Individual documents cannot revert", + "itName": "Ensure correct query results with latency-compensated deletes", "tags": [ ], "config": { "numClients": 1, - "useGarbageCollection": false + "useGarbageCollection": true }, "steps": [ + { + "userDelete": "collection/b" + }, { "userListen": { "query": { "filters": [ - [ - "visible", - "==", - true - ] ], "orderBys": [ ], @@ -3185,11 +3173,6 @@ "queries": [ { "filters": [ - [ - "visible", - "==", - true - ] ], "orderBys": [ ], @@ -3216,8 +3199,18 @@ "hasLocalMutations": false }, "value": { - "v": "v1000", - "visible": true + "a": true + }, + "version": 1000 + }, + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "b": true }, "version": 1000 } @@ -3251,8 +3244,7 @@ "hasLocalMutations": false }, "value": { - "v": "v1000", - "visible": true + "a": true }, "version": 1000 } @@ -3262,11 +3254,6 @@ "hasPendingWrites": false, "query": { "filters": [ - [ - "visible", - "==", - true - ] ], "orderBys": [ ], @@ -3275,39 +3262,13 @@ } ] }, - { - "userUnlisten": [ - 2, - { - "filters": [ - [ - "visible", - "==", - true - ] - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "expectedState": { - "activeTargets": { - } - } - }, - { - "watchRemove": { - "targetIds": [ - 2 - ] - } - }, { "userListen": { "query": { "filters": [ ], + "limit": 10, + "limitType": "LimitToFirst", "orderBys": [ ], "path": "collection" @@ -3324,8 +3285,7 @@ "hasLocalMutations": false }, "value": { - "v": "v1000", - "visible": true + "a": true }, "version": 1000 } @@ -3336,6 +3296,8 @@ "query": { "filters": [ ], + "limit": 10, + "limitType": "LimitToFirst", "orderBys": [ ], "path": "collection" @@ -3344,11 +3306,25 @@ ], "expectedState": { "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + }, "4": { "queries": [ { "filters": [ ], + "limit": 10, + "limitType": "LimitToFirst", "orderBys": [ ], "path": "collection" @@ -3358,10 +3334,50 @@ } } } - }, + } + ] + }, + "Ignores update from inactive target": { + "describeName": "Listens:", + "itName": "Ignores update from inactive target", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": false + }, + "steps": [ { - "watchAck": [ - 4 + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 ] }, { @@ -3374,37 +3390,33 @@ "hasLocalMutations": false }, "value": { - "v": "v3000", - "visible": false + "key": "a" }, - "version": 3000 + "version": 1000 } ], "targets": [ - 4 + 2 ] } }, { "watchCurrent": [ [ - 4 + 2 ], - "resume-token-4000" + "resume-token-1000" ] }, { "watchSnapshot": { "targetIds": [ ], - "version": 4000 + "version": 1000 }, "expectedSnapshotEvents": [ { - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "modified": [ + "added": [ { "key": "collection/a", "options": { @@ -3412,12 +3424,14 @@ "hasLocalMutations": false }, "value": { - "v": "v3000", - "visible": false + "key": "a" }, - "version": 3000 + "version": 1000 } ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, "query": { "filters": [ ], @@ -3430,7 +3444,7 @@ }, { "userUnlisten": [ - 4, + 2, { "filters": [ ], @@ -3444,13 +3458,107 @@ } } }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b" + }, + "version": 2000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + } + }, { "watchRemove": { "targetIds": [ - 4 + 2 ] } }, + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "resume-token-1000" + } + } + } + } + ] + }, + "Individual (deleted) documents cannot revert": { + "describeName": "Listens:", + "itName": "Individual (deleted) documents cannot revert", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": false + }, + "steps": [ { "userListen": { "query": { @@ -3484,7 +3592,7 @@ "path": "collection" } ], - "resumeToken": "resume-token-1000" + "resumeToken": "" } } } @@ -3504,13 +3612,13 @@ "hasLocalMutations": false }, "value": { - "v": "v2000", - "visible": false + "v": "v1000", + "visible": true }, - "version": 2000 + "version": 1000 } ], - "removedTargets": [ + "targets": [ 2 ] } @@ -3520,17 +3628,31 @@ [ 2 ], - "resume-token-5000" + "resume-token-1000" ] }, { "watchSnapshot": { "targetIds": [ ], - "version": 5000 + "version": 1000 }, "expectedSnapshotEvents": [ { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": "v1000", + "visible": true + }, + "version": 1000 + } + ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, @@ -3598,10 +3720,10 @@ "hasLocalMutations": false }, "value": { - "v": "v3000", - "visible": false + "v": "v1000", + "visible": true }, - "version": 3000 + "version": 1000 } ], "errorCode": 0, @@ -3628,7 +3750,7 @@ "path": "collection" } ], - "resumeToken": "resume-token-4000" + "resumeToken": "" } } } @@ -3641,8 +3763,13 @@ { "watchEntity": { "docs": [ + { + "key": "collection/a", + "value": null, + "version": 3000 + } ], - "targets": [ + "removedTargets": [ 4 ] } @@ -3652,14 +3779,14 @@ [ 4 ], - "resume-token-6000" + "resume-token-4000" ] }, { "watchSnapshot": { "targetIds": [ ], - "version": 6000 + "version": 4000 }, "expectedSnapshotEvents": [ { @@ -3672,67 +3799,110 @@ "orderBys": [ ], "path": "collection" - } + }, + "removed": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": "v1000", + "visible": true + }, + "version": 1000 + } + ] } ] - } - ] - }, - "Listen is established in new primary tab": { - "describeName": "Listens:", - "itName": "Listen is established in new primary tab", - "tags": [ - "multi-client" - ], - "config": { - "numClients": 3, - "useGarbageCollection": false - }, - "steps": [ - { - "clientIndex": 0, - "drainQueue": true, - "expectedState": { - "isPrimary": true - } }, { - "clientIndex": 0, - "userListen": { - "query": { + "userUnlisten": [ + 4, + { "filters": [ ], "orderBys": [ ], "path": "collection" - }, - "targetId": 2 - }, + } + ], "expectedState": { "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" + } + } + }, + { + "watchRemove": { + "targetIds": [ + 4 + ] + } + }, + { + "userListen": { + "query": { + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" } ], - "resumeToken": "" + "resumeToken": "resume-token-1000" } } } }, { - "clientIndex": 0, "watchAck": [ 2 ] }, { - "clientIndex": 0, "watchEntity": { "docs": [ { @@ -3742,52 +3912,43 @@ "hasLocalMutations": false }, "value": { - "key": "a" + "v": "v2000", + "visible": false }, - "version": 1000 + "version": 2000 } ], - "targets": [ + "removedTargets": [ 2 ] } }, { - "clientIndex": 0, "watchCurrent": [ [ 2 ], - "resume-token-1000" + "resume-token-5000" ] }, { - "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], - "version": 1000 + "version": 5000 }, "expectedSnapshotEvents": [ { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - } - ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, "query": { "filters": [ + [ + "visible", + "==", + true + ] ], "orderBys": [ ], @@ -3797,15 +3958,34 @@ ] }, { - "clientIndex": 1, - "drainQueue": true + "userUnlisten": [ + 2, + { + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } }, { - "clientIndex": 2, - "drainQueue": true + "watchRemove": { + "targetIds": [ + 2 + ] + } }, { - "clientIndex": 2, "userListen": { "query": { "filters": [ @@ -3814,25 +3994,12 @@ ], "path": "collection" }, - "targetId": 2 + "targetId": 4 }, "expectedSnapshotEvents": [ { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - } - ], "errorCode": 0, - "fromCache": false, + "fromCache": true, "hasPendingWrites": false, "query": { "filters": [ @@ -3845,7 +4012,7 @@ ], "expectedState": { "activeTargets": { - "2": { + "4": { "queries": [ { "filters": [ @@ -3855,72 +4022,123 @@ "path": "collection" } ], - "resumeToken": "" + "resumeToken": "resume-token-4000" } } } }, { - "clientIndex": 0, - "drainQueue": true + "watchAck": [ + 4 + ] }, { - "clientIndex": 0, - "shutdown": true, - "expectedState": { - "activeLimboDocs": [ + "watchEntity": { + "docs": [ ], - "activeTargets": { - }, - "enqueuedLimboDocs": [ + "targets": [ + 4 ] } }, { - "clientIndex": 1, - "drainQueue": true + "watchCurrent": [ + [ + 4 + ], + "resume-token-6000" + ] }, { - "clientIndex": 1, - "runTimer": "client_metadata_refresh", + "watchSnapshot": { + "targetIds": [ + ], + "version": 6000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Individual documents cannot revert": { + "describeName": "Listens:", + "itName": "Individual documents cannot revert", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": false + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, "expectedState": { "activeTargets": { "2": { "queries": [ { "filters": [ + [ + "visible", + "==", + true + ] ], "orderBys": [ ], "path": "collection" } ], - "resumeToken": "resume-token-1000" + "resumeToken": "" } - }, - "isPrimary": true + } } }, { - "clientIndex": 1, "watchAck": [ 2 ] }, { - "clientIndex": 1, "watchEntity": { "docs": [ { - "key": "collection/b", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "key": "b" + "v": "v1000", + "visible": true }, - "version": 2000 + "version": 1000 } ], "targets": [ @@ -3929,38 +4147,33 @@ } }, { - "clientIndex": 1, "watchCurrent": [ [ 2 ], - "resume-token-2000" + "resume-token-1000" ] }, { - "clientIndex": 1, "watchSnapshot": { "targetIds": [ ], - "version": 2000 - } - }, - { - "clientIndex": 2, - "drainQueue": true, + "version": 1000 + }, "expectedSnapshotEvents": [ { "added": [ { - "key": "collection/b", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "key": "b" + "v": "v1000", + "visible": true }, - "version": 2000 + "version": 1000 } ], "errorCode": 0, @@ -3968,6 +4181,11 @@ "hasPendingWrites": false, "query": { "filters": [ + [ + "visible", + "==", + true + ] ], "orderBys": [ ], @@ -3975,33 +4193,36 @@ } } ] - } - ] - }, - "Listen is established in newly started primary": { - "describeName": "Listens:", - "itName": "Listen is established in newly started primary", - "tags": [ - "multi-client" - ], - "config": { - "numClients": 3, - "useGarbageCollection": false - }, - "steps": [ + }, { - "clientIndex": 0, - "drainQueue": true, + "userUnlisten": [ + 2, + { + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], "expectedState": { - "isPrimary": true + "activeTargets": { + } } }, { - "clientIndex": 1, - "drainQueue": true + "watchRemove": { + "targetIds": [ + 2 + ] + } }, { - "clientIndex": 1, "userListen": { "query": { "filters": [ @@ -4010,31 +4231,39 @@ ], "path": "collection" }, - "targetId": 2 + "targetId": 4 }, - "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": "v1000", + "visible": true + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ ], - "resumeToken": "" + "orderBys": [ + ], + "path": "collection" } } - } - }, - { - "clientIndex": 0, - "drainQueue": true, + ], "expectedState": { "activeTargets": { - "2": { + "4": { "queries": [ { "filters": [ @@ -4050,13 +4279,11 @@ } }, { - "clientIndex": 0, "watchAck": [ - 2 + 4 ] }, { - "clientIndex": 0, "watchEntity": { "docs": [ { @@ -4066,39 +4293,37 @@ "hasLocalMutations": false }, "value": { - "key": "a" + "v": "v3000", + "visible": false }, - "version": 1000 + "version": 3000 } ], "targets": [ - 2 + 4 ] } }, { - "clientIndex": 0, "watchCurrent": [ [ - 2 + 4 ], - "resume-token-1000" + "resume-token-4000" ] }, { - "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], - "version": 1000 - } - }, - { - "clientIndex": 1, - "drainQueue": true, + "version": 4000 + }, "expectedSnapshotEvents": [ { - "added": [ + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "modified": [ { "key": "collection/a", "options": { @@ -4106,14 +4331,12 @@ "hasLocalMutations": false }, "value": { - "key": "a" + "v": "v3000", + "visible": false }, - "version": 1000 + "version": 3000 } ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, "query": { "filters": [ ], @@ -4125,30 +4348,74 @@ ] }, { - "clientIndex": 0, - "drainQueue": true - }, - { - "clientIndex": 0, - "shutdown": true, + "userUnlisten": [ + 4, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], "expectedState": { - "activeLimboDocs": [ - ], "activeTargets": { - }, - "enqueuedLimboDocs": [ + } + } + }, + { + "watchRemove": { + "targetIds": [ + 4 ] } }, { - "clientIndex": 2, - "drainQueue": true, + "userListen": { + "query": { + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], "expectedState": { "activeTargets": { "2": { "queries": [ { "filters": [ + [ + "visible", + "==", + true + ] ], "orderBys": [ ], @@ -4157,77 +4424,61 @@ ], "resumeToken": "resume-token-1000" } - }, - "isPrimary": true + } } }, { - "clientIndex": 2, "watchAck": [ 2 ] }, { - "clientIndex": 2, "watchEntity": { "docs": [ { - "key": "collection/b", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "key": "b" + "v": "v2000", + "visible": false }, "version": 2000 } ], - "targets": [ + "removedTargets": [ 2 ] } }, { - "clientIndex": 2, "watchCurrent": [ [ 2 ], - "resume-token-2000" + "resume-token-5000" ] }, { - "clientIndex": 2, "watchSnapshot": { "targetIds": [ ], - "version": 2000 - } - }, - { - "clientIndex": 1, - "drainQueue": true, + "version": 5000 + }, "expectedSnapshotEvents": [ { - "added": [ - { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "b" - }, - "version": 2000 - } - ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, "query": { "filters": [ + [ + "visible", + "==", + true + ] ], "orderBys": [ ], @@ -4235,33 +4486,36 @@ } } ] - } - ] - }, - "Listen is re-listened to after primary tab failover": { - "describeName": "Listens:", - "itName": "Listen is re-listened to after primary tab failover", - "tags": [ - "multi-client" - ], - "config": { - "numClients": 3, - "useGarbageCollection": false - }, - "steps": [ + }, { - "clientIndex": 0, - "drainQueue": true, + "userUnlisten": [ + 2, + { + "filters": [ + [ + "visible", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], "expectedState": { - "isPrimary": true + "activeTargets": { + } } }, { - "clientIndex": 1, - "drainQueue": true + "watchRemove": { + "targetIds": [ + 2 + ] + } }, { - "clientIndex": 1, "userListen": { "query": { "filters": [ @@ -4270,11 +4524,39 @@ ], "path": "collection" }, - "targetId": 2 + "targetId": 4 }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": "v3000", + "visible": false + }, + "version": 3000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], "expectedState": { "activeTargets": { - "2": { + "4": { "queries": [ { "filters": [ @@ -4284,14 +4566,86 @@ "path": "collection" } ], - "resumeToken": "" + "resumeToken": "resume-token-4000" } } } }, + { + "watchAck": [ + 4 + ] + }, + { + "watchEntity": { + "docs": [ + ], + "targets": [ + 4 + ] + } + }, + { + "watchCurrent": [ + [ + 4 + ], + "resume-token-6000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 6000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Listen is established in new primary tab": { + "describeName": "Listens:", + "itName": "Listen is established in new primary tab", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 3, + "useGarbageCollection": false + }, + "steps": [ { "clientIndex": 0, "drainQueue": true, + "expectedState": { + "isPrimary": true + } + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, "expectedState": { "activeTargets": { "2": { @@ -4351,11 +4705,7 @@ "targetIds": [ ], "version": 1000 - } - }, - { - "clientIndex": 1, - "drainQueue": true, + }, "expectedSnapshotEvents": [ { "added": [ @@ -4384,6 +4734,10 @@ } ] }, + { + "clientIndex": 1, + "drainQueue": true + }, { "clientIndex": 2, "drainQueue": true @@ -4527,34 +4881,7 @@ "targetIds": [ ], "version": 2000 - }, - "expectedSnapshotEvents": [ - { - "added": [ - { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "b" - }, - "version": 2000 - } - ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } - ] + } }, { "clientIndex": 2, @@ -4589,17 +4916,30 @@ } ] }, - "Listens are reestablished after network disconnect": { + "Listen is established in newly started primary": { "describeName": "Listens:", - "itName": "Listens are reestablished after network disconnect", + "itName": "Listen is established in newly started primary", "tags": [ + "multi-client" ], "config": { - "numClients": 1, - "useGarbageCollection": true + "numClients": 3, + "useGarbageCollection": false }, "steps": [ { + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "isPrimary": true + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, "userListen": { "query": { "filters": [ @@ -4624,16 +4964,37 @@ ], "resumeToken": "" } - }, - "watchStreamRequestCount": 1 + } + } + }, + { + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } } }, { + "clientIndex": 0, "watchAck": [ 2 ] }, { + "clientIndex": 0, "watchEntity": { "docs": [ { @@ -4654,6 +5015,7 @@ } }, { + "clientIndex": 0, "watchCurrent": [ [ 2 @@ -4662,11 +5024,16 @@ ] }, { + "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], "version": 1000 - }, + } + }, + { + "clientIndex": 1, + "drainQueue": true, "expectedSnapshotEvents": [ { "added": [ @@ -4696,21 +5063,12 @@ ] }, { - "enableNetwork": false, - "expectedSnapshotEvents": [ - { - "errorCode": 0, - "fromCache": true, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } - ], + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "shutdown": true, "expectedState": { "activeLimboDocs": [ ], @@ -4721,7 +5079,8 @@ } }, { - "enableNetwork": true, + "clientIndex": 2, + "drainQueue": true, "expectedState": { "activeTargets": { "2": { @@ -4737,15 +5096,17 @@ "resumeToken": "resume-token-1000" } }, - "watchStreamRequestCount": 2 + "isPrimary": true } }, { + "clientIndex": 2, "watchAck": [ 2 ] }, { + "clientIndex": 2, "watchEntity": { "docs": [ { @@ -4766,6 +5127,7 @@ } }, { + "clientIndex": 2, "watchCurrent": [ [ 2 @@ -4774,11 +5136,16 @@ ] }, { + "clientIndex": 2, "watchSnapshot": { "targetIds": [ ], "version": 2000 - }, + } + }, + { + "clientIndex": 1, + "drainQueue": true, "expectedSnapshotEvents": [ { "added": [ @@ -4809,9 +5176,9 @@ } ] }, - "Mirror queries from different secondary client": { + "Listen is re-listened to after primary tab failover": { "describeName": "Listens:", - "itName": "Mirror queries from different secondary client", + "itName": "Listen is re-listened to after primary tab failover", "tags": [ "multi-client" ], @@ -4822,76 +5189,22 @@ "steps": [ { "clientIndex": 0, - "drainQueue": true - }, - { - "applyClientState": { - "visibility": "visible" - }, - "clientIndex": 0 - }, - { - "clientIndex": 1, - "drainQueue": true - }, - { - "clientIndex": 1, - "userListen": { - "query": { - "filters": [ - ], - "limit": 2, - "limitType": "LimitToFirst", - "orderBys": [ - [ - "val", - "asc" - ] - ], - "path": "collection" - }, - "targetId": 2 - }, + "drainQueue": true, "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "limit": 2, - "limitType": "LimitToFirst", - "orderBys": [ - [ - "val", - "asc" - ] - ], - "path": "collection" - } - ], - "resumeToken": "" - } - } + "isPrimary": true } }, { - "clientIndex": 2, + "clientIndex": 1, "drainQueue": true }, { - "clientIndex": 2, + "clientIndex": 1, "userListen": { "query": { "filters": [ ], - "limit": 2, - "limitType": "LimitToLast", "orderBys": [ - [ - "val", - "desc" - ] ], "path": "collection" }, @@ -4904,13 +5217,7 @@ { "filters": [ ], - "limit": 2, - "limitType": "LimitToLast", "orderBys": [ - [ - "val", - "desc" - ] ], "path": "collection" } @@ -4930,26 +5237,7 @@ { "filters": [ ], - "limit": 2, - "limitType": "LimitToLast", - "orderBys": [ - [ - "val", - "desc" - ] - ], - "path": "collection" - }, - { - "filters": [ - ], - "limit": 2, - "limitType": "LimitToFirst", "orderBys": [ - [ - "val", - "asc" - ] ], "path": "collection" } @@ -4976,18 +5264,7 @@ "hasLocalMutations": false }, "value": { - "val": 0 - }, - "version": 1000 - }, - { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "val": 1 + "key": "a" }, "version": 1000 } @@ -5027,18 +5304,7 @@ "hasLocalMutations": false }, "value": { - "val": 0 - }, - "version": 1000 - }, - { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "val": 1 + "key": "a" }, "version": 1000 } @@ -5049,13 +5315,7 @@ "query": { "filters": [ ], - "limit": 2, - "limitType": "LimitToFirst", "orderBys": [ - [ - "val", - "asc" - ] ], "path": "collection" } @@ -5064,21 +5324,23 @@ }, { "clientIndex": 2, - "drainQueue": true, + "drainQueue": true + }, + { + "clientIndex": 2, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, "expectedSnapshotEvents": [ { "added": [ - { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "val": 1 - }, - "version": 1000 - }, { "key": "collection/a", "options": { @@ -5086,7 +5348,7 @@ "hasLocalMutations": false }, "value": { - "val": 0 + "key": "a" }, "version": 1000 } @@ -5097,45 +5359,52 @@ "query": { "filters": [ ], - "limit": 2, - "limitType": "LimitToLast", "orderBys": [ - [ - "val", - "desc" - ] ], "path": "collection" } } - ] - }, - { - "clientIndex": 2, - "userUnlisten": [ - 2, - { - "filters": [ - ], - "limit": 2, - "limitType": "LimitToLast", - "orderBys": [ - [ - "val", - "desc" - ] - ], - "path": "collection" - } ], "expectedState": { "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } } } }, { "clientIndex": 0, - "drainQueue": true, + "drainQueue": true + }, + { + "clientIndex": 0, + "shutdown": true, + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + }, + "enqueuedLimboDocs": [ + ] + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "runTimer": "client_metadata_refresh", "expectedState": { "activeTargets": { "2": { @@ -5143,34 +5412,35 @@ { "filters": [ ], - "limit": 2, - "limitType": "LimitToFirst", "orderBys": [ - [ - "val", - "asc" - ] ], "path": "collection" } ], - "resumeToken": "" + "resumeToken": "resume-token-1000" } - } + }, + "isPrimary": true } }, { - "clientIndex": 0, + "clientIndex": 1, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 1, "watchEntity": { "docs": [ { - "key": "collection/c", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "val": 0 + "key": "b" }, "version": 2000 } @@ -5181,18 +5451,686 @@ } }, { - "clientIndex": 0, - "watchSnapshot": { - "targetIds": [ + "clientIndex": 1, + "watchCurrent": [ + [ + 2 ], - "version": 2000 - } + "resume-token-2000" + ] }, { "clientIndex": 1, - "drainQueue": true, - "expectedSnapshotEvents": [ - { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 2, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Listens are reestablished after network disconnect": { + "describeName": "Listens:", + "itName": "Listens are reestablished after network disconnect", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": true + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + }, + "watchStreamRequestCount": 1 + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "enableNetwork": false, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + }, + "enqueuedLimboDocs": [ + ] + } + }, + { + "enableNetwork": true, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "resume-token-1000" + } + }, + "watchStreamRequestCount": 2 + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b" + }, + "version": 2000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-2000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Mirror queries from different secondary client": { + "describeName": "Listens:", + "itName": "Mirror queries from different secondary client", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 3, + "useGarbageCollection": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "val", + "asc" + ] + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "val", + "asc" + ] + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 2, + "drainQueue": true + }, + { + "clientIndex": 2, + "userListen": { + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "val", + "desc" + ] + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "val", + "desc" + ] + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "val", + "desc" + ] + ], + "path": "collection" + }, + { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "val", + "asc" + ] + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "val": 0 + }, + "version": 1000 + }, + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "val": 1 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + } + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "val": 0 + }, + "version": 1000 + }, + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "val": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "val", + "asc" + ] + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 2, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "val": 1 + }, + "version": 1000 + }, + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "val": 0 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "val", + "desc" + ] + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 2, + "userUnlisten": [ + 2, + { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "val", + "desc" + ] + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "val", + "asc" + ] + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "val": 0 + }, + "version": 2000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + } + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { "added": [ { "key": "collection/c", @@ -6414,16 +7352,294 @@ { "clientIndex": 0, "drainQueue": true, - "expectedState": { - "activeTargets": { + "expectedState": { + "activeTargets": { + } + } + } + ] + }, + "New client becomes primary if no client has its network enabled": { + "describeName": "Listens:", + "itName": "New client becomes primary if no client has its network enabled", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 3, + "useGarbageCollection": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "enableNetwork": false, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + }, + "enqueuedLimboDocs": [ + ] + } + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 2, + "drainQueue": true, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "resume-token-1000" + } + }, + "isPrimary": true + } + }, + { + "clientIndex": 2, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 2, + "watchEntity": { + "docs": [ + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 2, + "watchCurrent": [ + [ + 2 + ], + "resume-token-2000" + ] + }, + { + "clientIndex": 2, + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + } + }, + { + "clientIndex": 0, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } } - } + ] + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] } ] }, - "New client becomes primary if no client has its network enabled": { + "New client uses existing online state": { "describeName": "Listens:", - "itName": "New client becomes primary if no client has its network enabled", + "itName": "New client uses existing online state", "tags": [ "multi-client" ], @@ -6516,6 +7732,18 @@ "clientIndex": 1, "drainQueue": true }, + { + "clientIndex": 1, + "enableNetwork": false, + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + }, + "enqueuedLimboDocs": [ + ] + } + }, { "clientIndex": 1, "userListen": { @@ -6590,8 +7818,21 @@ } }, { - "clientIndex": 1, - "drainQueue": true, + "clientIndex": 2, + "drainQueue": true + }, + { + "clientIndex": 2, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, "expectedSnapshotEvents": [ { "errorCode": 0, @@ -6605,11 +7846,7 @@ "path": "collection" } } - ] - }, - { - "clientIndex": 2, - "drainQueue": true, + ], "expectedState": { "activeTargets": { "2": { @@ -6622,52 +7859,27 @@ "path": "collection" } ], - "resumeToken": "resume-token-1000" + "resumeToken": "" } - }, - "isPrimary": true - } - }, - { - "clientIndex": 2, - "watchAck": [ - 2 - ] - }, - { - "clientIndex": 2, - "watchEntity": { - "docs": [ - ], - "targets": [ - 2 - ] + } } }, { "clientIndex": 2, - "watchCurrent": [ - [ - 2 - ], - "resume-token-2000" - ] - }, - { - "clientIndex": 2, - "watchSnapshot": { - "targetIds": [ - ], - "version": 2000 - } - }, - { - "clientIndex": 0, - "drainQueue": true, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, "expectedSnapshotEvents": [ { "errorCode": 0, - "fromCache": false, + "fromCache": true, "hasPendingWrites": false, "query": { "filters": [ @@ -6677,36 +7889,34 @@ "path": "collection" } } - ] - }, - { - "clientIndex": 1, - "drainQueue": true, - "expectedSnapshotEvents": [ - { - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } ], - "path": "collection" + "resumeToken": "" } } - ] + } } ] }, - "New client uses existing online state": { + "Offline state doesn't persist if primary is shut down": { "describeName": "Listens:", - "itName": "New client uses existing online state", + "itName": "Offline state doesn't persist if primary is shut down", "tags": [ "multi-client" ], "config": { - "numClients": 3, + "numClients": 2, "useGarbageCollection": false }, "steps": [ @@ -6745,40 +7955,11 @@ }, { "clientIndex": 0, - "watchAck": [ - 2 - ] - }, - { - "clientIndex": 0, - "watchEntity": { - "docs": [ - ], - "targets": [ - 2 - ] - } - }, - { - "clientIndex": 0, - "watchCurrent": [ - [ - 2 - ], - "resume-token-1000" - ] - }, - { - "clientIndex": 0, - "watchSnapshot": { - "targetIds": [ - ], - "version": 1000 - }, + "enableNetwork": false, "expectedSnapshotEvents": [ { "errorCode": 0, - "fromCache": false, + "fromCache": true, "hasPendingWrites": false, "query": { "filters": [ @@ -6788,15 +7969,19 @@ "path": "collection" } } - ] - }, - { - "clientIndex": 1, - "drainQueue": true + ], + "expectedState": { + "activeLimboDocs": [ + ], + "activeTargets": { + }, + "enqueuedLimboDocs": [ + ] + } }, { - "clientIndex": 1, - "enableNetwork": false, + "clientIndex": 0, + "shutdown": true, "expectedState": { "activeLimboDocs": [ ], @@ -6808,6 +7993,51 @@ }, { "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + } + ] + }, + "Omits global resume tokens for a short while": { + "describeName": "Listens:", + "itName": "Omits global resume tokens for a short while", + "tags": [ + "durable-persistence" + ], + "config": { + "numClients": 1, + "useGarbageCollection": false + }, + "steps": [ + { "userListen": { "query": { "filters": [ @@ -6818,20 +8048,6 @@ }, "targetId": 2 }, - "expectedSnapshotEvents": [ - { - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } - ], "expectedState": { "activeTargets": { "2": { @@ -6850,16 +8066,61 @@ } }, { - "clientIndex": 0, - "drainQueue": true + "watchAck": [ + 2 + ] }, { - "clientIndex": 0, - "enableNetwork": false, + "watchEntity": { + "docs": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, "expectedSnapshotEvents": [ { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], "errorCode": 0, - "fromCache": true, + "fromCache": false, "hasPendingWrites": false, "query": { "filters": [ @@ -6869,7 +8130,18 @@ "path": "collection" } } - ], + ] + }, + { + "watchSnapshot": { + "resumeToken": "resume-token-2000", + "targetIds": [ + ], + "version": 2000 + } + }, + { + "restart": true, "expectedState": { "activeLimboDocs": [ ], @@ -6880,11 +8152,6 @@ } }, { - "clientIndex": 2, - "drainQueue": true - }, - { - "clientIndex": 2, "userListen": { "query": { "filters": [ @@ -6897,6 +8164,19 @@ }, "expectedSnapshotEvents": [ { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], "errorCode": 0, "fromCache": true, "hasPendingWrites": false, @@ -6921,27 +8201,34 @@ "path": "collection" } ], - "resumeToken": "" + "resumeToken": "resume-token-1000" } } } }, { - "clientIndex": 2, - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 2 + "watchAck": [ + 2 + ] + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-3000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 3000 }, "expectedSnapshotEvents": [ { "errorCode": 0, - "fromCache": true, + "fromCache": false, "hasPendingWrites": false, "query": { "filters": [ @@ -6951,43 +8238,22 @@ "path": "collection" } } - ], - "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "resumeToken": "" - } - } - } + ] } ] }, - "Offline state doesn't persist if primary is shut down": { + "Persists global resume tokens if the snapshot is old enough": { "describeName": "Listens:", - "itName": "Offline state doesn't persist if primary is shut down", + "itName": "Persists global resume tokens if the snapshot is old enough", "tags": [ - "multi-client" + "durable-persistence" ], "config": { - "numClients": 2, + "numClients": 1, "useGarbageCollection": false }, "steps": [ { - "clientIndex": 0, - "drainQueue": true - }, - { - "clientIndex": 0, "userListen": { "query": { "filters": [ @@ -7016,12 +8282,61 @@ } }, { - "clientIndex": 0, - "enableNetwork": false, + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, "expectedSnapshotEvents": [ { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], "errorCode": 0, - "fromCache": true, + "fromCache": false, "hasPendingWrites": false, "query": { "filters": [ @@ -7031,19 +8346,18 @@ "path": "collection" } } - ], - "expectedState": { - "activeLimboDocs": [ + ] + }, + { + "watchSnapshot": { + "resumeToken": "resume-token-minutes-later", + "targetIds": [ ], - "activeTargets": { - }, - "enqueuedLimboDocs": [ - ] + "version": 300001000 } }, { - "clientIndex": 0, - "shutdown": true, + "restart": true, "expectedState": { "activeLimboDocs": [ ], @@ -7054,11 +8368,6 @@ } }, { - "clientIndex": 1, - "drainQueue": true - }, - { - "clientIndex": 1, "userListen": { "query": { "filters": [ @@ -7069,6 +8378,33 @@ }, "targetId": 2 }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], "expectedState": { "activeTargets": { "2": { @@ -7081,18 +8417,51 @@ "path": "collection" } ], - "resumeToken": "" + "resumeToken": "resume-token-minutes-later" } } } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-even-later" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 300002000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] } ] }, - "Omits global resume tokens for a short while": { + "Persists global resume tokens on unlisten": { "describeName": "Listens:", - "itName": "Omits global resume tokens for a short while", + "itName": "Persists global resume tokens on unlisten", "tags": [ - "durable-persistence" ], "config": { "numClients": 1, @@ -7203,13 +8572,25 @@ } }, { - "restart": true, + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], "expectedState": { - "activeLimboDocs": [ - ], "activeTargets": { - }, - "enqueuedLimboDocs": [ + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 ] } }, @@ -7263,7 +8644,7 @@ "path": "collection" } ], - "resumeToken": "resume-token-1000" + "resumeToken": "resume-token-2000" } } } @@ -7304,11 +8685,10 @@ } ] }, - "Persists global resume tokens if the snapshot is old enough": { + "Persists resume token sent with target": { "describeName": "Listens:", - "itName": "Persists global resume tokens if the snapshot is old enough", + "itName": "Persists resume token sent with target", "tags": [ - "durable-persistence" ], "config": { "numClients": 1, @@ -7348,6 +8728,44 @@ 2 ] }, + { + "watchEntity": { + "docs": [ + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, { "watchEntity": { "docs": [ @@ -7360,7 +8778,7 @@ "value": { "key": "a" }, - "version": 1000 + "version": 2000 } ], "targets": [ @@ -7369,18 +8787,19 @@ } }, { - "watchCurrent": [ - [ + "watchSnapshot": { + "resumeToken": "resume-token-2000", + "targetIds": [ 2 ], - "resume-token-1000" - ] + "version": 2000 + } }, { "watchSnapshot": { "targetIds": [ ], - "version": 1000 + "version": 2000 }, "expectedSnapshotEvents": [ { @@ -7394,7 +8813,7 @@ "value": { "key": "a" }, - "version": 1000 + "version": 2000 } ], "errorCode": 0, @@ -7411,21 +8830,25 @@ ] }, { - "watchSnapshot": { - "resumeToken": "resume-token-minutes-later", - "targetIds": [ - ], - "version": 300001000 + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } } }, { - "restart": true, - "expectedState": { - "activeLimboDocs": [ - ], - "activeTargets": { - }, - "enqueuedLimboDocs": [ + "watchRemove": { + "targetIds": [ + 2 ] } }, @@ -7452,7 +8875,7 @@ "value": { "key": "a" }, - "version": 1000 + "version": 2000 } ], "errorCode": 0, @@ -7479,7 +8902,7 @@ "path": "collection" } ], - "resumeToken": "resume-token-minutes-later" + "resumeToken": "resume-token-2000" } } } @@ -7489,19 +8912,28 @@ 2 ] }, + { + "watchEntity": { + "docs": [ + ], + "targets": [ + 2 + ] + } + }, { "watchCurrent": [ [ 2 ], - "resume-token-even-later" + "resume-token-3000" ] }, { "watchSnapshot": { "targetIds": [ ], - "version": 300002000 + "version": 3000 }, "expectedSnapshotEvents": [ { @@ -7520,27 +8952,106 @@ } ] }, - "Persists global resume tokens on unlisten": { + "Previous primary immediately regains primary lease": { "describeName": "Listens:", - "itName": "Persists global resume tokens on unlisten", + "itName": "Previous primary immediately regains primary lease", "tags": [ + "multi-client" ], "config": { - "numClients": 1, + "numClients": 2, "useGarbageCollection": false }, "steps": [ { - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 2 + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "applyClientState": { + "primary": true }, + "clientIndex": 1, "expectedState": { "activeTargets": { "2": { @@ -7553,17 +9064,20 @@ "path": "collection" } ], - "resumeToken": "" + "resumeToken": "resume-token-1000" } - } + }, + "isPrimary": true } }, { + "clientIndex": 1, "watchAck": [ 2 ] }, { + "clientIndex": 1, "watchEntity": { "docs": [ { @@ -7575,7 +9089,7 @@ "value": { "key": "a" }, - "version": 1000 + "version": 2000 } ], "targets": [ @@ -7584,89 +9098,44 @@ } }, { + "clientIndex": 1, "watchCurrent": [ [ 2 ], - "resume-token-1000" - ] - }, - { - "watchSnapshot": { - "targetIds": [ - ], - "version": 1000 - }, - "expectedSnapshotEvents": [ - { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - } - ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } + "resume-token-2000" ] }, { + "clientIndex": 1, "watchSnapshot": { - "resumeToken": "resume-token-2000", "targetIds": [ ], "version": 2000 } }, { - "userUnlisten": [ - 2, - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], + "clientIndex": 1, + "shutdown": true, "expectedState": { + "activeLimboDocs": [ + ], "activeTargets": { - } + }, + "enqueuedLimboDocs": [ + ] } }, { - "watchRemove": { - "targetIds": [ - 2 - ] + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "isPrimary": true } }, { - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 2 - }, + "clientIndex": 0, + "runTimer": "client_metadata_refresh", "expectedSnapshotEvents": [ { "added": [ @@ -7679,11 +9148,11 @@ "value": { "key": "a" }, - "version": 1000 + "version": 2000 } ], "errorCode": 0, - "fromCache": true, + "fromCache": false, "hasPendingWrites": false, "query": { "filters": [ @@ -7708,56 +9177,36 @@ ], "resumeToken": "resume-token-2000" } - } + }, + "isPrimary": true } - }, - { - "watchAck": [ - 2 - ] - }, - { - "watchCurrent": [ - [ - 2 - ], - "resume-token-3000" - ] - }, - { - "watchSnapshot": { - "targetIds": [ - ], - "version": 3000 - }, - "expectedSnapshotEvents": [ - { - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } - ] } ] }, - "Persists resume token sent with target": { + "Query bounces between primaries": { "describeName": "Listens:", - "itName": "Persists resume token sent with target", + "itName": "Query bounces between primaries", "tags": [ + "multi-client" ], "config": { - "numClients": 1, + "numClients": 3, "useGarbageCollection": false }, "steps": [ { + "clientIndex": 1, + "drainQueue": true, + "expectedState": { + "isPrimary": true + } + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, "userListen": { "query": { "filters": [ @@ -7786,13 +9235,46 @@ } }, { + "clientIndex": 1, + "drainQueue": true, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 1, "watchAck": [ 2 ] }, { + "clientIndex": 1, "watchEntity": { "docs": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } ], "targets": [ 2 @@ -7800,6 +9282,7 @@ } }, { + "clientIndex": 1, "watchCurrent": [ [ 2 @@ -7808,13 +9291,31 @@ ] }, { + "clientIndex": 1, "watchSnapshot": { "targetIds": [ ], "version": 1000 - }, + } + }, + { + "clientIndex": 0, + "drainQueue": true, "expectedSnapshotEvents": [ { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, @@ -7829,16 +9330,65 @@ ] }, { + "clientIndex": 2, + "drainQueue": true + }, + { + "applyClientState": { + "primary": true + }, + "clientIndex": 2, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "resume-token-1000" + } + }, + "isPrimary": true + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "runTimer": "client_metadata_refresh", + "expectedState": { + "isPrimary": false + } + }, + { + "clientIndex": 2, + "drainQueue": true + }, + { + "clientIndex": 2, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 2, "watchEntity": { "docs": [ { - "key": "collection/a", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "key": "a" + "key": "b" }, "version": 2000 } @@ -7849,31 +9399,36 @@ } }, { - "watchSnapshot": { - "resumeToken": "resume-token-2000", - "targetIds": [ + "clientIndex": 2, + "watchCurrent": [ + [ 2 ], - "version": 2000 - } + "resume-token-2000" + ] }, { + "clientIndex": 2, "watchSnapshot": { "targetIds": [ ], "version": 2000 - }, + } + }, + { + "clientIndex": 0, + "drainQueue": true, "expectedSnapshotEvents": [ { "added": [ { - "key": "collection/a", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "key": "a" + "key": "b" }, "version": 2000 } @@ -7892,66 +9447,14 @@ ] }, { - "userUnlisten": [ - 2, - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "expectedState": { - "activeTargets": { - } - } - }, - { - "watchRemove": { - "targetIds": [ - 2 - ] - } + "clientIndex": 1, + "drainQueue": true }, { - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 2 + "applyClientState": { + "primary": true }, - "expectedSnapshotEvents": [ - { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 2000 - } - ], - "errorCode": 0, - "fromCache": true, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } - ], + "clientIndex": 1, "expectedState": { "activeTargets": { "2": { @@ -7966,17 +9469,31 @@ ], "resumeToken": "resume-token-2000" } - } + }, + "isPrimary": true } }, { + "clientIndex": 1, "watchAck": [ 2 ] }, { + "clientIndex": 1, "watchEntity": { "docs": [ + { + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "c" + }, + "version": 3000 + } ], "targets": [ 2 @@ -7984,6 +9501,7 @@ } }, { + "clientIndex": 1, "watchCurrent": [ [ 2 @@ -7992,13 +9510,31 @@ ] }, { + "clientIndex": 1, "watchSnapshot": { "targetIds": [ ], "version": 3000 - }, + } + }, + { + "clientIndex": 0, + "drainQueue": true, "expectedSnapshotEvents": [ { + "added": [ + { + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "c" + }, + "version": 3000 + } + ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, @@ -8014,9 +9550,9 @@ } ] }, - "Previous primary immediately regains primary lease": { + "Query is executed by primary client": { "describeName": "Listens:", - "itName": "Previous primary immediately regains primary lease", + "itName": "Query is executed by primary client", "tags": [ "multi-client" ], @@ -8030,7 +9566,17 @@ "drainQueue": true }, { - "clientIndex": 0, + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, "userListen": { "query": { "filters": [ @@ -8060,60 +9606,7 @@ }, { "clientIndex": 0, - "watchAck": [ - 2 - ] - }, - { - "clientIndex": 0, - "watchEntity": { - "docs": [ - ], - "targets": [ - 2 - ] - } - }, - { - "clientIndex": 0, - "watchCurrent": [ - [ - 2 - ], - "resume-token-1000" - ] - }, - { - "clientIndex": 0, - "watchSnapshot": { - "targetIds": [ - ], - "version": 1000 - }, - "expectedSnapshotEvents": [ - { - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } - ] - }, - { - "clientIndex": 1, - "drainQueue": true - }, - { - "applyClientState": { - "primary": true - }, - "clientIndex": 1, + "drainQueue": true, "expectedState": { "activeTargets": { "2": { @@ -8126,20 +9619,19 @@ "path": "collection" } ], - "resumeToken": "resume-token-1000" + "resumeToken": "" } - }, - "isPrimary": true + } } }, { - "clientIndex": 1, + "clientIndex": 0, "watchAck": [ 2 ] }, { - "clientIndex": 1, + "clientIndex": 0, "watchEntity": { "docs": [ { @@ -8147,57 +9639,29 @@ "options": { "hasCommittedMutations": false, "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 2000 - } - ], - "targets": [ - 2 - ] - } - }, - { - "clientIndex": 1, - "watchCurrent": [ - [ - 2 - ], - "resume-token-2000" - ] - }, - { - "clientIndex": 1, - "watchSnapshot": { - "targetIds": [ - ], - "version": 2000 - } - }, - { - "clientIndex": 1, - "shutdown": true, - "expectedState": { - "activeLimboDocs": [ + }, + "value": { + "key": "a" + }, + "version": 1000 + } ], - "activeTargets": { - }, - "enqueuedLimboDocs": [ + "targets": [ + 2 ] } }, { "clientIndex": 0, - "drainQueue": true, - "expectedState": { - "isPrimary": true + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 } }, { - "clientIndex": 0, - "runTimer": "client_metadata_refresh", + "clientIndex": 1, + "drainQueue": true, "expectedSnapshotEvents": [ { "added": [ @@ -8210,11 +9674,11 @@ "value": { "key": "a" }, - "version": 2000 + "version": 1000 } ], "errorCode": 0, - "fromCache": false, + "fromCache": true, "hasPendingWrites": false, "query": { "filters": [ @@ -8224,51 +9688,73 @@ "path": "collection" } } - ], - "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } + ] + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-2000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + } + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ ], - "resumeToken": "resume-token-2000" + "orderBys": [ + ], + "path": "collection" } - }, - "isPrimary": true - } + } + ] } ] }, - "Query bounces between primaries": { + "Query is joined by primary client": { "describeName": "Listens:", - "itName": "Query bounces between primaries", + "itName": "Query is joined by primary client", "tags": [ "multi-client" ], "config": { - "numClients": 3, + "numClients": 2, "useGarbageCollection": false }, "steps": [ { - "clientIndex": 1, + "clientIndex": 0, "drainQueue": true, "expectedState": { "isPrimary": true } }, { - "clientIndex": 0, + "clientIndex": 1, "drainQueue": true }, { - "clientIndex": 0, + "clientIndex": 1, "userListen": { "query": { "filters": [ @@ -8297,7 +9783,7 @@ } }, { - "clientIndex": 1, + "clientIndex": 0, "drainQueue": true, "expectedState": { "activeTargets": { @@ -8317,13 +9803,13 @@ } }, { - "clientIndex": 1, + "clientIndex": 0, "watchAck": [ 2 ] }, { - "clientIndex": 1, + "clientIndex": 0, "watchEntity": { "docs": [ { @@ -8344,24 +9830,24 @@ } }, { - "clientIndex": 1, + "clientIndex": 0, "watchCurrent": [ [ 2 ], - "resume-token-1000" + "resume-token-100" ] }, { - "clientIndex": 1, + "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], - "version": 1000 + "version": 100 } }, { - "clientIndex": 0, + "clientIndex": 1, "drainQueue": true, "expectedSnapshotEvents": [ { @@ -8392,14 +9878,88 @@ ] }, { - "clientIndex": 2, + "clientIndex": 0, "drainQueue": true }, { - "applyClientState": { - "primary": true + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b" + }, + "version": 2000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + } + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 }, - "clientIndex": 2, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + }, + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], "expectedState": { "activeTargets": { "2": { @@ -8412,73 +9972,69 @@ "path": "collection" } ], - "resumeToken": "resume-token-1000" + "resumeToken": "" } - }, - "isPrimary": true - } - }, - { - "clientIndex": 1, - "drainQueue": true - }, - { - "clientIndex": 1, - "runTimer": "client_metadata_refresh", - "expectedState": { - "isPrimary": false + } } }, { - "clientIndex": 2, - "drainQueue": true - }, - { - "clientIndex": 2, - "watchAck": [ - 2 - ] - }, - { - "clientIndex": 2, + "clientIndex": 0, "watchEntity": { "docs": [ { - "key": "collection/b", + "key": "collection/c", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "key": "b" + "key": "c" }, - "version": 2000 + "version": 3000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 3000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "c" + }, + "version": 3000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" } - ], - "targets": [ - 2 - ] - } - }, - { - "clientIndex": 2, - "watchCurrent": [ - [ - 2 - ], - "resume-token-2000" + } ] }, { - "clientIndex": 2, - "watchSnapshot": { - "targetIds": [ - ], - "version": 2000 - } - }, - { - "clientIndex": 0, + "clientIndex": 1, "drainQueue": true, "expectedSnapshotEvents": [ { @@ -8505,18 +10061,113 @@ ], "path": "collection" } + }, + { + "added": [ + { + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "c" + }, + "version": 3000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } } ] + } + ] + }, + "Query is rejected and re-listened to": { + "describeName": "Listens:", + "itName": "Query is rejected and re-listened to", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": false + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } }, { - "clientIndex": 1, - "drainQueue": true + "watchRemove": { + "cause": { + "code": 8 + }, + "targetIds": [ + 2 + ] + }, + "expectedSnapshotEvents": [ + { + "errorCode": 8, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } }, { - "applyClientState": { - "primary": true + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 }, - "clientIndex": 1, "expectedState": { "activeTargets": { "2": { @@ -8529,33 +10180,19 @@ "path": "collection" } ], - "resumeToken": "resume-token-2000" + "resumeToken": "" } - }, - "isPrimary": true + } } }, { - "clientIndex": 1, "watchAck": [ 2 ] }, { - "clientIndex": 1, "watchEntity": { "docs": [ - { - "key": "collection/c", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "c" - }, - "version": 3000 - } ], "targets": [ 2 @@ -8563,40 +10200,21 @@ } }, { - "clientIndex": 1, "watchCurrent": [ [ 2 ], - "resume-token-3000" + "resume-token-1000" ] }, { - "clientIndex": 1, "watchSnapshot": { "targetIds": [ ], - "version": 3000 - } - }, - { - "clientIndex": 0, - "drainQueue": true, + "version": 1000 + }, "expectedSnapshotEvents": [ { - "added": [ - { - "key": "collection/c", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "c" - }, - "version": 3000 - } - ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, @@ -8612,9 +10230,9 @@ } ] }, - "Query is executed by primary client": { + "Query is rejected and re-listened to by secondary client": { "describeName": "Listens:", - "itName": "Query is executed by primary client", + "itName": "Query is rejected and re-listened to by secondary client", "tags": [ "multi-client" ], @@ -8688,37 +10306,17 @@ }, { "clientIndex": 0, - "watchAck": [ - 2 - ] - }, - { - "clientIndex": 0, - "watchEntity": { - "docs": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - } - ], - "targets": [ + "watchRemove": { + "cause": { + "code": 8 + }, + "targetIds": [ 2 ] - } - }, - { - "clientIndex": 0, - "watchSnapshot": { - "targetIds": [ - ], - "version": 1000 + }, + "expectedState": { + "activeTargets": { + } } }, { @@ -8726,35 +10324,83 @@ "drainQueue": true, "expectedSnapshotEvents": [ { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - } - ], - "errorCode": 0, - "fromCache": true, + "errorCode": 8, + "fromCache": false, "hasPendingWrites": false, "query": { "filters": [ ], - "orderBys": [ + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 1, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } ], - "path": "collection" + "resumeToken": "" } } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 ] }, { "clientIndex": 0, - "drainQueue": true + "watchEntity": { + "docs": [ + ], + "targets": [ + 2 + ] + } }, { "clientIndex": 0, @@ -8762,7 +10408,7 @@ [ 2 ], - "resume-token-2000" + "resume-token-1000" ] }, { @@ -8770,7 +10416,7 @@ "watchSnapshot": { "targetIds": [ ], - "version": 2000 + "version": 1000 } }, { @@ -8793,9 +10439,9 @@ } ] }, - "Query is joined by primary client": { + "Query is rejected by primary client": { "describeName": "Listens:", - "itName": "Query is joined by primary client", + "itName": "Query is rejected by primary client", "tags": [ "multi-client" ], @@ -8806,10 +10452,13 @@ "steps": [ { "clientIndex": 0, - "drainQueue": true, - "expectedState": { - "isPrimary": true - } + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 }, { "clientIndex": 1, @@ -8866,46 +10515,17 @@ }, { "clientIndex": 0, - "watchAck": [ - 2 - ] - }, - { - "clientIndex": 0, - "watchEntity": { - "docs": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - } - ], - "targets": [ + "watchRemove": { + "cause": { + "code": 8 + }, + "targetIds": [ 2 ] - } - }, - { - "clientIndex": 0, - "watchCurrent": [ - [ - 2 - ], - "resume-token-100" - ] - }, - { - "clientIndex": 0, - "watchSnapshot": { - "targetIds": [ - ], - "version": 100 + }, + "expectedState": { + "activeTargets": { + } } }, { @@ -8913,20 +10533,7 @@ "drainQueue": true, "expectedSnapshotEvents": [ { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - } - ], - "errorCode": 0, + "errorCode": 8, "fromCache": false, "hasPendingWrites": false, "query": { @@ -8938,42 +10545,36 @@ } } ] - }, + } + ] + }, + "Query is resumed by secondary client": { + "describeName": "Listens:", + "itName": "Query is resumed by secondary client", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 2, + "useGarbageCollection": false + }, + "steps": [ { "clientIndex": 0, "drainQueue": true }, { - "clientIndex": 0, - "watchEntity": { - "docs": [ - { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "b" - }, - "version": 2000 - } - ], - "targets": [ - 2 - ] - } + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 }, { - "clientIndex": 0, - "watchSnapshot": { - "targetIds": [ - ], - "version": 2000 - } + "clientIndex": 1, + "drainQueue": true }, { - "clientIndex": 0, + "clientIndex": 1, "userListen": { "query": { "filters": [ @@ -8984,44 +10585,26 @@ }, "targetId": 2 }, - "expectedSnapshotEvents": [ - { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - }, - { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "b" - }, - "version": 2000 - } - ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } ], - "path": "collection" + "resumeToken": "" } } - ], + } + }, + { + "clientIndex": 0, + "drainQueue": true, "expectedState": { "activeTargets": { "2": { @@ -9039,20 +10622,26 @@ } } }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, { "clientIndex": 0, "watchEntity": { "docs": [ { - "key": "collection/c", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "key": "c" + "key": "a" }, - "version": 3000 + "version": 1000 } ], "targets": [ @@ -9060,26 +10649,39 @@ ] } }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, { "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], - "version": 3000 - }, + "version": 1000 + } + }, + { + "clientIndex": 1, + "drainQueue": true, "expectedSnapshotEvents": [ { "added": [ { - "key": "collection/c", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "key": "c" + "key": "a" }, - "version": 3000 + "version": 1000 } ], "errorCode": 0, @@ -9097,49 +10699,70 @@ }, { "clientIndex": 1, - "drainQueue": true, - "expectedSnapshotEvents": [ + "userUnlisten": [ + 2, { - "added": [ - { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "b" - }, - "version": 2000 - } + "filters": [ ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ { "added": [ { - "key": "collection/c", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "key": "c" + "key": "a" }, - "version": 3000 + "version": 1000 } ], "errorCode": 0, - "fromCache": false, + "fromCache": true, "hasPendingWrites": false, "query": { "filters": [ @@ -9149,31 +10772,7 @@ "path": "collection" } } - ] - } - ] - }, - "Query is rejected and re-listened to": { - "describeName": "Listens:", - "itName": "Query is rejected and re-listened to", - "tags": [ - ], - "config": { - "numClients": 1, - "useGarbageCollection": false - }, - "steps": [ - { - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 2 - }, + ], "expectedState": { "activeTargets": { "2": { @@ -9192,44 +10791,8 @@ } }, { - "watchRemove": { - "cause": { - "code": 8 - }, - "targetIds": [ - 2 - ] - }, - "expectedSnapshotEvents": [ - { - "errorCode": 8, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } - ], - "expectedState": { - "activeTargets": { - } - } - }, - { - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 2 - }, + "clientIndex": 0, + "drainQueue": true, "expectedState": { "activeTargets": { "2": { @@ -9242,19 +10805,32 @@ "path": "collection" } ], - "resumeToken": "" + "resumeToken": "resume-token-1000" } } } }, { + "clientIndex": 0, "watchAck": [ 2 ] }, { + "clientIndex": 0, "watchEntity": { "docs": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } ], "targets": [ 2 @@ -9262,21 +10838,40 @@ } }, { + "clientIndex": 0, "watchCurrent": [ [ 2 ], - "resume-token-1000" + "resume-token-2000" ] }, { + "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], - "version": 1000 - }, + "version": 2000 + } + }, + { + "clientIndex": 1, + "drainQueue": true, "expectedSnapshotEvents": [ { + "added": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } + ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, @@ -9292,14 +10887,14 @@ } ] }, - "Query is rejected and re-listened to by secondary client": { + "Query is shared between primary and secondary client": { "describeName": "Listens:", - "itName": "Query is rejected and re-listened to by secondary client", + "itName": "Query is shared between primary and secondary client", "tags": [ "multi-client" ], "config": { - "numClients": 2, + "numClients": 3, "useGarbageCollection": false }, "steps": [ @@ -9314,11 +10909,7 @@ "clientIndex": 0 }, { - "clientIndex": 1, - "drainQueue": true - }, - { - "clientIndex": 1, + "clientIndex": 0, "userListen": { "query": { "filters": [ @@ -9348,45 +10939,63 @@ }, { "clientIndex": 0, - "drainQueue": true, - "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "resumeToken": "" - } - } - } + "watchAck": [ + 2 + ] }, { "clientIndex": 0, - "watchRemove": { - "cause": { - "code": 8 - }, - "targetIds": [ + "watchEntity": { + "docs": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ 2 ] - }, - "expectedState": { - "activeTargets": { - } } }, { - "clientIndex": 1, - "drainQueue": true, + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, "expectedSnapshotEvents": [ { - "errorCode": 8, + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, "fromCache": false, "hasPendingWrites": false, "query": { @@ -9399,6 +11008,10 @@ } ] }, + { + "clientIndex": 1, + "drainQueue": true + }, { "clientIndex": 1, "userListen": { @@ -9411,6 +11024,33 @@ }, "targetId": 2 }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], "expectedState": { "activeTargets": { "2": { @@ -9429,8 +11069,48 @@ } }, { - "clientIndex": 0, - "drainQueue": true, + "clientIndex": 2, + "drainQueue": true + }, + { + "clientIndex": 2, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], "expectedState": { "activeTargets": { "2": { @@ -9450,42 +11130,113 @@ }, { "clientIndex": 0, - "watchAck": [ - 2 - ] + "drainQueue": true }, { "clientIndex": 0, "watchEntity": { "docs": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } ], "targets": [ 2 ] } }, - { - "clientIndex": 0, - "watchCurrent": [ - [ - 2 - ], - "resume-token-1000" - ] - }, { "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], - "version": 1000 - } + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] }, { "clientIndex": 1, "drainQueue": true, "expectedSnapshotEvents": [ { + "added": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 2, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } + ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, @@ -9501,9 +11252,9 @@ } ] }, - "Query is rejected by primary client": { + "Query is unlistened to by primary client": { "describeName": "Listens:", - "itName": "Query is rejected by primary client", + "itName": "Query is unlistened to by primary client", "tags": [ "multi-client" ], @@ -9523,11 +11274,7 @@ "clientIndex": 0 }, { - "clientIndex": 1, - "drainQueue": true - }, - { - "clientIndex": 1, + "clientIndex": 0, "userListen": { "query": { "filters": [ @@ -9557,45 +11304,63 @@ }, { "clientIndex": 0, - "drainQueue": true, - "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "resumeToken": "" - } - } - } + "watchAck": [ + 2 + ] }, { "clientIndex": 0, - "watchRemove": { - "cause": { - "code": 8 - }, - "targetIds": [ + "watchEntity": { + "docs": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ 2 ] - }, - "expectedState": { - "activeTargets": { - } } }, { - "clientIndex": 1, - "drainQueue": true, + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, "expectedSnapshotEvents": [ { - "errorCode": 8, + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, "fromCache": false, "hasPendingWrites": false, "query": { @@ -9607,29 +11372,6 @@ } } ] - } - ] - }, - "Query is resumed by secondary client": { - "describeName": "Listens:", - "itName": "Query is resumed by secondary client", - "tags": [ - "multi-client" - ], - "config": { - "numClients": 2, - "useGarbageCollection": false - }, - "steps": [ - { - "clientIndex": 0, - "drainQueue": true - }, - { - "applyClientState": { - "visibility": "visible" - }, - "clientIndex": 0 }, { "clientIndex": 1, @@ -9647,6 +11389,33 @@ }, "targetId": 2 }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], "expectedState": { "activeTargets": { "2": { @@ -9666,7 +11435,20 @@ }, { "clientIndex": 0, - "drainQueue": true, + "drainQueue": true + }, + { + "clientIndex": 0, + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], "expectedState": { "activeTargets": { "2": { @@ -9684,18 +11466,12 @@ } } }, - { - "clientIndex": 0, - "watchAck": [ - 2 - ] - }, { "clientIndex": 0, "watchEntity": { "docs": [ { - "key": "collection/a", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -9703,7 +11479,7 @@ "value": { "key": "a" }, - "version": 1000 + "version": 2000 } ], "targets": [ @@ -9711,21 +11487,12 @@ ] } }, - { - "clientIndex": 0, - "watchCurrent": [ - [ - 2 - ], - "resume-token-1000" - ] - }, { "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], - "version": 1000 + "version": 2000 } }, { @@ -9735,7 +11502,7 @@ { "added": [ { - "key": "collection/a", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -9743,7 +11510,7 @@ "value": { "key": "a" }, - "version": 1000 + "version": 2000 } ], "errorCode": 0, @@ -9783,21 +11550,40 @@ "activeTargets": { } } - }, + } + ] + }, + "Query only raises events in participating clients": { + "describeName": "Listens:", + "itName": "Query only raises events in participating clients", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 4, + "useGarbageCollection": false + }, + "steps": [ { "clientIndex": 0, - "watchRemove": { - "targetIds": [ - 2 - ] - } + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 }, { "clientIndex": 1, "drainQueue": true }, { - "clientIndex": 1, + "clientIndex": 2, + "drainQueue": true + }, + { + "clientIndex": 2, "userListen": { "query": { "filters": [ @@ -9808,33 +11594,39 @@ }, "targetId": 2 }, - "expectedSnapshotEvents": [ - { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - } - ], - "errorCode": 0, - "fromCache": true, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } ], - "path": "collection" + "resumeToken": "" } } - ], + } + }, + { + "clientIndex": 3, + "drainQueue": true + }, + { + "clientIndex": 3, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, "expectedState": { "activeTargets": { "2": { @@ -9867,7 +11659,7 @@ "path": "collection" } ], - "resumeToken": "resume-token-1000" + "resumeToken": "" } } } @@ -9883,7 +11675,7 @@ "watchEntity": { "docs": [ { - "key": "collection/b", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -9891,39 +11683,74 @@ "value": { "key": "a" }, - "version": 2000 + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 2, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" } - ], - "targets": [ - 2 - ] - } - }, - { - "clientIndex": 0, - "watchCurrent": [ - [ - 2 - ], - "resume-token-2000" + } ] }, { - "clientIndex": 0, - "watchSnapshot": { - "targetIds": [ - ], - "version": 2000 - } - }, - { - "clientIndex": 1, + "clientIndex": 3, "drainQueue": true, "expectedSnapshotEvents": [ { "added": [ { - "key": "collection/b", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -9931,7 +11758,7 @@ "value": { "key": "a" }, - "version": 2000 + "version": 1000 } ], "errorCode": 0, @@ -9949,26 +11776,23 @@ } ] }, - "Query is shared between primary and secondary client": { + "Query recovers after primary takeover": { "describeName": "Listens:", - "itName": "Query is shared between primary and secondary client", + "itName": "Query recovers after primary takeover", "tags": [ "multi-client" ], "config": { - "numClients": 3, + "numClients": 2, "useGarbageCollection": false }, "steps": [ { "clientIndex": 0, - "drainQueue": true - }, - { - "applyClientState": { - "visibility": "visible" - }, - "clientIndex": 0 + "drainQueue": true, + "expectedState": { + "isPrimary": true + } }, { "clientIndex": 0, @@ -10131,48 +11955,10 @@ } }, { - "clientIndex": 2, - "drainQueue": true - }, - { - "clientIndex": 2, - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 2 + "applyClientState": { + "primary": true }, - "expectedSnapshotEvents": [ - { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - } - ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } - ], + "clientIndex": 1, "expectedState": { "activeTargets": { "2": { @@ -10185,17 +11971,20 @@ "path": "collection" } ], - "resumeToken": "" + "resumeToken": "resume-token-1000" } - } + }, + "isPrimary": true } }, { - "clientIndex": 0, - "drainQueue": true + "clientIndex": 1, + "watchAck": [ + 2 + ] }, { - "clientIndex": 0, + "clientIndex": 1, "watchEntity": { "docs": [ { @@ -10205,7 +11994,7 @@ "hasLocalMutations": false }, "value": { - "key": "a" + "key": "b" }, "version": 2000 } @@ -10216,174 +12005,71 @@ } }, { - "clientIndex": 0, - "watchSnapshot": { - "targetIds": [ + "clientIndex": 1, + "watchCurrent": [ + [ + 2 ], - "version": 2000 - }, - "expectedSnapshotEvents": [ - { - "added": [ - { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 2000 - } - ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } + "resume-token-2000" ] }, { "clientIndex": 1, - "drainQueue": true, - "expectedSnapshotEvents": [ - { - "added": [ - { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 2000 - } - ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } - ] - }, - { - "clientIndex": 2, - "drainQueue": true, + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, "expectedSnapshotEvents": [ { "added": [ { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 2000 - } - ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } - ] - } - ] - }, - "Query is unlistened to by primary client": { - "describeName": "Listens:", - "itName": "Query is unlistened to by primary client", - "tags": [ - "multi-client" - ], - "config": { - "numClients": 2, - "useGarbageCollection": false - }, - "steps": [ - { - "clientIndex": 0, - "drainQueue": true - }, - { - "applyClientState": { - "visibility": "visible" - }, - "clientIndex": 0 - }, - { - "clientIndex": 0, - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 2 - }, - "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "b" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ ], - "resumeToken": "" + "orderBys": [ + ], + "path": "collection" } } - } + ] }, { "clientIndex": 0, - "watchAck": [ - 2 - ] + "drainQueue": true }, { - "clientIndex": 0, + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, "watchEntity": { "docs": [ { - "key": "collection/a", + "key": "collection/c", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "key": "a" + "key": "c" }, - "version": 1000 + "version": 3000 } ], "targets": [ @@ -10392,34 +12078,25 @@ } }, { - "clientIndex": 0, - "watchCurrent": [ - [ - 2 - ], - "resume-token-1000" - ] - }, - { - "clientIndex": 0, + "clientIndex": 1, "watchSnapshot": { "targetIds": [ ], - "version": 1000 + "version": 3000 }, "expectedSnapshotEvents": [ { "added": [ { - "key": "collection/a", + "key": "collection/c", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "key": "a" + "key": "c" }, - "version": 1000 + "version": 3000 } ], "errorCode": 0, @@ -10436,34 +12113,36 @@ ] }, { - "clientIndex": 1, + "clientIndex": 0, "drainQueue": true }, { - "clientIndex": 1, - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 2 - }, + "clientIndex": 0, + "runTimer": "client_metadata_refresh", "expectedSnapshotEvents": [ { "added": [ { - "key": "collection/a", + "key": "collection/b", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "key": "a" + "key": "b" }, - "version": 1000 + "version": 2000 + }, + { + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "c" + }, + "version": 3000 } ], "errorCode": 0, @@ -10479,38 +12158,32 @@ } ], "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "resumeToken": "" - } - } + "isPrimary": false } - }, - { - "clientIndex": 0, - "drainQueue": true - }, + } + ] + }, + "Re-opens target without existence filter": { + "describeName": "Listens:", + "itName": "Re-opens target without existence filter", + "tags": [ + ], + "config": { + "numClients": 1, + "useGarbageCollection": false + }, + "steps": [ { - "clientIndex": 0, - "userUnlisten": [ - 2, - { + "userListen": { + "query": { "filters": [ ], "orderBys": [ ], "path": "collection" - } - ], + }, + "targetId": 2 + }, "expectedState": { "activeTargets": { "2": { @@ -10529,11 +12202,15 @@ } }, { - "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { "watchEntity": { "docs": [ { - "key": "collection/b", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -10541,7 +12218,7 @@ "value": { "key": "a" }, - "version": 2000 + "version": 1000 } ], "targets": [ @@ -10550,21 +12227,24 @@ } }, { - "clientIndex": 0, - "watchSnapshot": { - "targetIds": [ + "watchCurrent": [ + [ + 2 ], - "version": 2000 - } + "resume-token-1000" + ] }, { - "clientIndex": 1, - "drainQueue": true, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, "expectedSnapshotEvents": [ { "added": [ { - "key": "collection/b", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false @@ -10572,7 +12252,7 @@ "value": { "key": "a" }, - "version": 2000 + "version": 1000 } ], "errorCode": 0, @@ -10589,7 +12269,6 @@ ] }, { - "clientIndex": 1, "userUnlisten": [ 2, { @@ -10606,79 +12285,13 @@ } }, { - "clientIndex": 0, - "drainQueue": true, - "expectedState": { - "activeTargets": { - } - } - } - ] - }, - "Query only raises events in participating clients": { - "describeName": "Listens:", - "itName": "Query only raises events in participating clients", - "tags": [ - "multi-client" - ], - "config": { - "numClients": 4, - "useGarbageCollection": false - }, - "steps": [ - { - "clientIndex": 0, - "drainQueue": true - }, - { - "applyClientState": { - "visibility": "visible" - }, - "clientIndex": 0 - }, - { - "clientIndex": 1, - "drainQueue": true - }, - { - "clientIndex": 2, - "drainQueue": true - }, - { - "clientIndex": 2, - "userListen": { - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - }, - "targetId": 2 - }, - "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - ], - "resumeToken": "" - } - } + "watchRemove": { + "targetIds": [ + 2 + ] } }, { - "clientIndex": 3, - "drainQueue": true - }, - { - "clientIndex": 3, "userListen": { "query": { "filters": [ @@ -10689,26 +12302,33 @@ }, "targetId": 2 }, - "expectedState": { - "activeTargets": { - "2": { - "queries": [ - { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } + "expectedSnapshotEvents": [ + { + "added": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ ], - "resumeToken": "" + "orderBys": [ + ], + "path": "collection" } } - } - }, - { - "clientIndex": 0, - "drainQueue": true, + ], "expectedState": { "activeTargets": { "2": { @@ -10721,77 +12341,46 @@ "path": "collection" } ], - "resumeToken": "" + "resumeToken": "resume-token-1000" } } } }, { - "clientIndex": 0, "watchAck": [ 2 ] }, { - "clientIndex": 0, "watchEntity": { "docs": [ { "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 + "value": null, + "version": 2000 } ], - "targets": [ + "removedTargets": [ 2 ] } }, { - "clientIndex": 0, "watchCurrent": [ [ 2 ], - "resume-token-1000" + "resume-token-2000" ] }, { - "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], - "version": 1000 - } - }, - { - "clientIndex": 1, - "drainQueue": true - }, - { - "clientIndex": 2, - "drainQueue": true, + "version": 2000 + }, "expectedSnapshotEvents": [ { - "added": [ - { - "key": "collection/a", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "a" - }, - "version": 1000 - } - ], "errorCode": 0, "fromCache": false, "hasPendingWrites": false, @@ -10801,16 +12390,8 @@ "orderBys": [ ], "path": "collection" - } - } - ] - }, - { - "clientIndex": 3, - "drainQueue": true, - "expectedSnapshotEvents": [ - { - "added": [ + }, + "removed": [ { "key": "collection/a", "options": { @@ -10822,25 +12403,15 @@ }, "version": 1000 } - ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } + ] } ] } ] }, - "Query recovers after primary takeover": { + "Secondary client advances query state with global snapshot from primary": { "describeName": "Listens:", - "itName": "Query recovers after primary takeover", + "itName": "Secondary client advances query state with global snapshot from primary", "tags": [ "multi-client" ], @@ -10851,7 +12422,13 @@ "steps": [ { "clientIndex": 0, - "drainQueue": true, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0, "expectedState": { "isPrimary": true } @@ -10902,7 +12479,7 @@ "hasLocalMutations": false }, "value": { - "key": "a" + "key": "1" }, "version": 1000 } @@ -10938,7 +12515,7 @@ "hasLocalMutations": false }, "value": { - "key": "a" + "key": "1" }, "version": 1000 } @@ -10956,6 +12533,31 @@ } ] }, + { + "clientIndex": 0, + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, { "clientIndex": 1, "drainQueue": true @@ -10982,13 +12584,13 @@ "hasLocalMutations": false }, "value": { - "key": "a" + "key": "1" }, "version": 1000 } ], "errorCode": 0, - "fromCache": false, + "fromCache": true, "hasPendingWrites": false, "query": { "filters": [ @@ -11017,10 +12619,8 @@ } }, { - "applyClientState": { - "primary": true - }, - "clientIndex": 1, + "clientIndex": 0, + "drainQueue": true, "expectedState": { "activeTargets": { "2": { @@ -11035,29 +12635,142 @@ ], "resumeToken": "resume-token-1000" } - }, - "isPrimary": true + } } }, { - "clientIndex": 1, + "clientIndex": 0, "watchAck": [ 2 ] }, { - "clientIndex": 1, + "clientIndex": 0, "watchEntity": { "docs": [ { - "key": "collection/b", + "key": "collection/a", "options": { "hasCommittedMutations": false, "hasLocalMutations": false }, "value": { - "key": "b" + "key": "1" }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1500" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1500 + } + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "userDelete": "collection/a" + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ + { + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "1" + }, + "version": 1000 + } + ] + } + ] + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "writeAck": { + "version": 2000 + }, + "expectedState": { + "userCallbacks": { + "acknowledgedDocs": [ + "collection/a" + ], + "rejectedDocs": [ + ] + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "key": "collection/a", + "value": null, "version": 2000 } ], @@ -11067,7 +12780,7 @@ } }, { - "clientIndex": 1, + "clientIndex": 0, "watchCurrent": [ [ 2 @@ -11076,140 +12789,51 @@ ] }, { - "clientIndex": 1, + "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], "version": 2000 - }, - "expectedSnapshotEvents": [ - { - "added": [ - { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "b" - }, - "version": 2000 - } - ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } - } - ] - }, - { - "clientIndex": 0, - "drainQueue": true + } }, { "clientIndex": 1, "drainQueue": true }, { - "clientIndex": 1, - "watchEntity": { - "docs": [ - { - "key": "collection/c", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "c" - }, - "version": 3000 - } - ], - "targets": [ - 2 - ] - } + "clientIndex": 0, + "drainQueue": true }, { - "clientIndex": 1, - "watchSnapshot": { - "targetIds": [ - ], - "version": 3000 - }, - "expectedSnapshotEvents": [ + "clientIndex": 0, + "userSet": [ + "collection/a", { - "added": [ - { - "key": "collection/c", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "c" - }, - "version": 3000 - } - ], - "errorCode": 0, - "fromCache": false, - "hasPendingWrites": false, - "query": { - "filters": [ - ], - "orderBys": [ - ], - "path": "collection" - } + "key": "2" } ] }, { - "clientIndex": 0, - "drainQueue": true - }, - { - "clientIndex": 0, - "runTimer": "client_metadata_refresh", + "clientIndex": 1, + "drainQueue": true, "expectedSnapshotEvents": [ { "added": [ { - "key": "collection/b", - "options": { - "hasCommittedMutations": false, - "hasLocalMutations": false - }, - "value": { - "key": "b" - }, - "version": 2000 - }, - { - "key": "collection/c", + "key": "collection/a", "options": { "hasCommittedMutations": false, - "hasLocalMutations": false + "hasLocalMutations": true }, "value": { - "key": "c" + "key": "2" }, - "version": 3000 + "version": 0 } ], "errorCode": 0, "fromCache": false, - "hasPendingWrites": false, + "hasPendingWrites": true, "query": { "filters": [ ], @@ -11218,24 +12842,36 @@ "path": "collection" } } - ], - "expectedState": { - "isPrimary": false - } + ] } ] }, - "Re-opens target without existence filter": { + "Secondary client raises latency compensated snapshot from primary mutation": { "describeName": "Listens:", - "itName": "Re-opens target without existence filter", + "itName": "Secondary client raises latency compensated snapshot from primary mutation", "tags": [ + "multi-client" ], "config": { - "numClients": 1, + "numClients": 2, "useGarbageCollection": false }, "steps": [ { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0, + "expectedState": { + "isPrimary": true + } + }, + { + "clientIndex": 0, "userListen": { "query": { "filters": [ @@ -11264,11 +12900,13 @@ } }, { + "clientIndex": 0, "watchAck": [ 2 ] }, { + "clientIndex": 0, "watchEntity": { "docs": [ { @@ -11278,7 +12916,7 @@ "hasLocalMutations": false }, "value": { - "key": "a" + "key": "1" }, "version": 1000 } @@ -11289,6 +12927,7 @@ } }, { + "clientIndex": 0, "watchCurrent": [ [ 2 @@ -11297,6 +12936,7 @@ ] }, { + "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], @@ -11312,7 +12952,7 @@ "hasLocalMutations": false }, "value": { - "key": "a" + "key": "1" }, "version": 1000 } @@ -11331,6 +12971,7 @@ ] }, { + "clientIndex": 0, "userUnlisten": [ 2, { @@ -11347,6 +12988,7 @@ } }, { + "clientIndex": 0, "watchRemove": { "targetIds": [ 2 @@ -11354,6 +12996,11 @@ } }, { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, "userListen": { "query": { "filters": [ @@ -11374,7 +13021,7 @@ "hasLocalMutations": false }, "value": { - "key": "a" + "key": "1" }, "version": 1000 } @@ -11391,6 +13038,26 @@ } } ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "drainQueue": true, "expectedState": { "activeTargets": { "2": { @@ -11409,38 +13076,52 @@ } }, { + "clientIndex": 0, "watchAck": [ 2 ] }, { + "clientIndex": 0, "watchEntity": { "docs": [ { "key": "collection/a", - "value": null, - "version": 2000 + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "1" + }, + "version": 1000 } ], - "removedTargets": [ + "targets": [ 2 ] } }, { + "clientIndex": 0, "watchCurrent": [ [ 2 ], - "resume-token-2000" + "resume-token-1500" ] }, { + "clientIndex": 0, "watchSnapshot": { "targetIds": [ ], - "version": 2000 - }, + "version": 1500 + } + }, + { + "clientIndex": 1, + "drainQueue": true, "expectedSnapshotEvents": [ { "errorCode": 0, @@ -11452,20 +13133,51 @@ "orderBys": [ ], "path": "collection" - }, - "removed": [ + } + } + ] + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "userSet": [ + "collection/a", + { + "key": "2" + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": true, + "modified": [ { "key": "collection/a", "options": { "hasCommittedMutations": false, - "hasLocalMutations": false + "hasLocalMutations": true }, "value": { - "key": "a" + "key": "2" }, - "version": 1000 + "version": 0 } - ] + ], + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } } ] } @@ -14035,7 +15747,7 @@ "value": { "v": 2 }, - "version": 1000 + "version": 0 } ], "query": { @@ -14251,7 +15963,7 @@ "value": { "v": 2 }, - "version": 1000 + "version": 0 } ], "query": { @@ -14295,7 +16007,7 @@ "value": { "v": 3 }, - "version": 1000 + "version": 0 } ], "query": { @@ -14334,7 +16046,7 @@ "value": { "v": 4 }, - "version": 1000 + "version": 0 } ], "query": { @@ -14602,7 +16314,7 @@ "value": { "v": 2 }, - "version": 1000 + "version": 0 } ], "query": { @@ -14627,7 +16339,7 @@ "value": { "v": 2 }, - "version": 1000 + "version": 0 } ], "query": {