@@ -47,6 +47,7 @@ import {
47
47
Query ,
48
48
query ,
49
49
QuerySnapshot ,
50
+ runTransaction ,
50
51
setDoc ,
51
52
startAfter ,
52
53
startAt ,
@@ -2059,6 +2060,62 @@ apiDescribe('Queries', (persistence: boolean) => {
2059
2060
} ) ;
2060
2061
} ) ;
2061
2062
} ) ;
2063
+
2064
+ it ( 'resuming a query should use existence filter to detect deletes' , async ( ) => {
2065
+ // Prepare the names and contents of the 100 documents to create.
2066
+ const testDocs : { [ key : string ] : object } = { } ;
2067
+ for ( let i = 0 ; i < 100 ; i ++ ) {
2068
+ testDocs [ 'doc' + ( 1000 + i ) ] = { key : 42 } ;
2069
+ }
2070
+
2071
+ return withTestCollection ( persistence , testDocs , async ( coll , db ) => {
2072
+ // Run a query to populate the local cache with the 100 documents and a
2073
+ // resume token.
2074
+ const snapshot1 = await getDocs ( coll ) ;
2075
+ expect ( snapshot1 . size , 'snapshot1.size' ) . to . equal ( 100 ) ;
2076
+ const createdDocuments = snapshot1 . docs . map ( snapshot => snapshot . ref ) ;
2077
+
2078
+ // Delete 50 of the 100 documents. Do this in a transaction, rather than
2079
+ // deleteDoc(), to avoid affecting the local cache.
2080
+ const deletedDocumentIds = new Set < string > ( ) ;
2081
+ await runTransaction ( db , async txn => {
2082
+ for ( let i = 0 ; i < createdDocuments . length ; i += 2 ) {
2083
+ const documentToDelete = createdDocuments [ i ] ;
2084
+ txn . delete ( documentToDelete ) ;
2085
+ deletedDocumentIds . add ( documentToDelete . id ) ;
2086
+ }
2087
+ } ) ;
2088
+
2089
+ // Wait for 10 seconds, during which Watch will stop tracking the query
2090
+ // and will send an existence filter rather than "delete" events when the
2091
+ // query is resumed.
2092
+ await new Promise ( resolve => setTimeout ( resolve , 10000 ) ) ;
2093
+
2094
+ // Resume the query and save the resulting snapshot for verification.
2095
+ const snapshot2 = await getDocs ( coll ) ;
2096
+
2097
+ // Verify that the snapshot from the resumed query contains the expected
2098
+ // documents; that is, that it contains the 50 documents that were _not_
2099
+ // deleted.
2100
+ // TODO(b/270731363): Remove the "if" condition below once the
2101
+ // Firestore Emulator is fixed to send an existence filter. At the time of
2102
+ // writing, the Firestore emulator fails to send an existence filter,
2103
+ // resulting in the client including the deleted documents in the snapshot
2104
+ // of the resumed query.
2105
+ if ( ! ( USE_EMULATOR && snapshot2 . size === 100 ) ) {
2106
+ const actualDocumentIds = snapshot2 . docs
2107
+ . map ( documentSnapshot => documentSnapshot . ref . id )
2108
+ . sort ( ) ;
2109
+ const expectedDocumentIds = createdDocuments
2110
+ . filter ( documentRef => ! deletedDocumentIds . has ( documentRef . id ) )
2111
+ . map ( documentRef => documentRef . id )
2112
+ . sort ( ) ;
2113
+ expect ( actualDocumentIds , 'snapshot2.docs' ) . to . deep . equal (
2114
+ expectedDocumentIds
2115
+ ) ;
2116
+ }
2117
+ } ) ;
2118
+ } ) . timeout ( '90s' ) ;
2062
2119
} ) ;
2063
2120
2064
2121
function verifyDocumentChange < T > (
0 commit comments