@@ -88,7 +88,7 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
88
88
) ;
89
89
90
90
specTest (
91
- 'Query raises events in secondary client (with recovery)' ,
91
+ 'Query raises events in secondary client (with recovery)' ,
92
92
[ 'multi-client' ] ,
93
93
( ) => {
94
94
const query = Query . atPath ( path ( 'collection' ) ) ;
@@ -144,6 +144,84 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
144
144
}
145
145
) ;
146
146
147
+ specTest (
148
+ 'Query with active view recovers after primary tab failover (with recovery)' ,
149
+ [ 'multi-client' ] ,
150
+ ( ) => {
151
+ const query = Query . atPath ( path ( 'collection' ) ) ;
152
+ const docA = doc ( 'collection/a' , 1000 , { key : 'a' } ) ;
153
+ const docB = doc ( 'collection/b' , 2000 , { key : 'b' } ) ;
154
+
155
+ return (
156
+ client ( 0 , false )
157
+ . expectPrimaryState ( true )
158
+ . client ( 1 )
159
+ // Register a query in the secondary client
160
+ . userListens ( query )
161
+ . client ( 0 )
162
+ . expectListen ( query )
163
+ . watchAcksFull ( query , 1000 , docA )
164
+ // Shutdown the primary client to release its lease
165
+ . shutdown ( )
166
+ . client ( 1 )
167
+ . expectEvents ( query , { added : [ docA ] } )
168
+ // Run the lease refresh to attempt taking over the primary lease. The
169
+ // first lease refresh fails with a simulated transaction failure.
170
+ . failDatabaseTransactions ( 'Allocate target' )
171
+ . runTimer ( TimerId . ClientMetadataRefresh )
172
+ . expectPrimaryState ( false )
173
+ . recoverDatabase ( )
174
+ . runTimer ( TimerId . AsyncQueueRetry )
175
+ . expectPrimaryState ( true )
176
+ . expectListen ( query , 'resume-token-1000' )
177
+ . watchAcksFull ( query , 2000 , docB )
178
+ . expectEvents ( query , { added : [ docB ] } )
179
+ ) ;
180
+ }
181
+ ) ;
182
+
183
+ specTest (
184
+ 'Query without active view recovers after primary tab failover (with recovery)' ,
185
+ [ 'multi-client' ] ,
186
+ ( ) => {
187
+ const query = Query . atPath ( path ( 'collection' ) ) ;
188
+ const docA = doc ( 'collection/a' , 1000 , { key : 'a' } ) ;
189
+ const docB = doc ( 'collection/b' , 2000 , { key : 'b' } ) ;
190
+
191
+ return (
192
+ client ( 0 , false )
193
+ . expectPrimaryState ( true )
194
+ // Initialize a second client that doesn't have any active targets
195
+ . client ( 1 )
196
+ . client ( 2 )
197
+ // Register a query in the third client
198
+ . userListens ( query )
199
+ . client ( 0 )
200
+ . expectListen ( query )
201
+ . watchAcksFull ( query , 1000 , docA )
202
+ . client ( 2 )
203
+ . expectEvents ( query , { added : [ docA ] } )
204
+ . client ( 0 )
205
+ // Shutdown the primary client to release its lease
206
+ . shutdown ( )
207
+ . client ( 1 )
208
+ // Run the lease refresh in the second client, which does not yet have
209
+ // an active view for the third client's query. The lease refresh fails
210
+ // at first, but then recovers and initializes the view.
211
+ . failDatabaseTransactions ( 'Allocate target' )
212
+ . runTimer ( TimerId . ClientMetadataRefresh )
213
+ . expectPrimaryState ( false )
214
+ . recoverDatabase ( )
215
+ . runTimer ( TimerId . AsyncQueueRetry )
216
+ . expectPrimaryState ( true )
217
+ . expectListen ( query , 'resume-token-1000' )
218
+ . watchAcksFull ( query , 2000 , docB )
219
+ . client ( 2 )
220
+ . expectEvents ( query , { added : [ docB ] } )
221
+ ) ;
222
+ }
223
+ ) ;
224
+
147
225
specTest (
148
226
'Ignores intermittent lease refresh failures (with recovery)' ,
149
227
[ 'multi-client' ] ,
0 commit comments