@@ -20,7 +20,7 @@ import { Thread, THREAD_RELATION_TYPE, ThreadEvent } from "../../../src/models/t
20
20
import { mkThread } from "../../test-utils/thread" ;
21
21
import { TestClient } from "../../TestClient" ;
22
22
import { emitPromise , mkMessage , mock } from "../../test-utils/test-utils" ;
23
- import { EventStatus , MatrixEvent } from "../../../src" ;
23
+ import { Direction , EventStatus , MatrixEvent } from "../../../src" ;
24
24
import { ReceiptType } from "../../../src/@types/read_receipts" ;
25
25
import { getMockClientWithEventEmitter , mockClientMethodsUser } from "../../test-utils/client" ;
26
26
import { ReEmitter } from "../../../src/ReEmitter" ;
@@ -283,4 +283,143 @@ describe("Thread", () => {
283
283
expect ( thread2 . getEventReadUpTo ( myUserId ) ) . toBe ( null ) ;
284
284
} ) ;
285
285
} ) ;
286
+
287
+ describe ( "resetLiveTimeline" , ( ) => {
288
+ // ResetLiveTimeline is used when we have missing messages between the current live timeline's end and newly
289
+ // received messages. In that case, we want to replace the existing live timeline. To ensure pagination
290
+ // continues working correctly, new pagination tokens need to be set on both the old live timeline (which is
291
+ // now a regular timeline) and the new live timeline.
292
+ it ( "replaces the live timeline and correctly sets pagination tokens" , async ( ) => {
293
+ const myUserId = "@bob:example.org" ;
294
+ const testClient = new TestClient ( myUserId , "DEVICE" , "ACCESS_TOKEN" , undefined , {
295
+ timelineSupport : false ,
296
+ } ) ;
297
+ const client = testClient . client ;
298
+ const room = new Room ( "123" , client , myUserId , {
299
+ pendingEventOrdering : PendingEventOrdering . Detached ,
300
+ } ) ;
301
+
302
+ jest . spyOn ( client , "getRoom" ) . mockReturnValue ( room ) ;
303
+
304
+ const { thread } = mkThread ( {
305
+ room,
306
+ client,
307
+ authorId : myUserId ,
308
+ participantUserIds : [ "@alice:example.org" ] ,
309
+ length : 3 ,
310
+ } ) ;
311
+ await emitPromise ( thread , ThreadEvent . Update ) ;
312
+ expect ( thread . length ) . toBe ( 2 ) ;
313
+
314
+ jest . spyOn ( client , "createMessagesRequest" ) . mockImplementation ( ( _ , token ) =>
315
+ Promise . resolve ( {
316
+ chunk : [ ] ,
317
+ start : `${ token } -new` ,
318
+ end : `${ token } -new` ,
319
+ } ) ,
320
+ ) ;
321
+
322
+ function timelines ( ) : [ string | null , string | null ] [ ] {
323
+ return thread . timelineSet
324
+ . getTimelines ( )
325
+ . map ( ( it ) => [ it . getPaginationToken ( Direction . Backward ) , it . getPaginationToken ( Direction . Forward ) ] ) ;
326
+ }
327
+
328
+ expect ( timelines ( ) ) . toEqual ( [ [ null , null ] ] ) ;
329
+ const promise = thread . resetLiveTimeline ( "b1" , "f1" ) ;
330
+ expect ( timelines ( ) ) . toEqual ( [
331
+ [ null , "f1" ] ,
332
+ [ "b1" , null ] ,
333
+ ] ) ;
334
+ await promise ;
335
+ expect ( timelines ( ) ) . toEqual ( [
336
+ [ null , "f1-new" ] ,
337
+ [ "b1-new" , null ] ,
338
+ ] ) ;
339
+ } ) ;
340
+
341
+ // As the pagination tokens cannot be used right now, resetLiveTimeline needs to replace them before they can
342
+ // be used. But if in the future the bug in synapse is fixed, and they can actually be used, we can get into a
343
+ // state where the client has paginated (and changed the tokens) while resetLiveTimeline tries to set the
344
+ // corrected tokens. To prevent such a race condition, we make sure that resetLiveTimeline respects any
345
+ // changes done to the pagination tokens.
346
+ it ( "replaces the live timeline but does not replace changed pagination tokens" , async ( ) => {
347
+ const myUserId = "@bob:example.org" ;
348
+ const testClient = new TestClient ( myUserId , "DEVICE" , "ACCESS_TOKEN" , undefined , {
349
+ timelineSupport : false ,
350
+ } ) ;
351
+ const client = testClient . client ;
352
+ const room = new Room ( "123" , client , myUserId , {
353
+ pendingEventOrdering : PendingEventOrdering . Detached ,
354
+ } ) ;
355
+
356
+ jest . spyOn ( client , "getRoom" ) . mockReturnValue ( room ) ;
357
+
358
+ const { thread } = mkThread ( {
359
+ room,
360
+ client,
361
+ authorId : myUserId ,
362
+ participantUserIds : [ "@alice:example.org" ] ,
363
+ length : 3 ,
364
+ } ) ;
365
+ await emitPromise ( thread , ThreadEvent . Update ) ;
366
+ expect ( thread . length ) . toBe ( 2 ) ;
367
+
368
+ jest . spyOn ( client , "createMessagesRequest" ) . mockImplementation ( ( _ , token ) =>
369
+ Promise . resolve ( {
370
+ chunk : [ ] ,
371
+ start : `${ token } -new` ,
372
+ end : `${ token } -new` ,
373
+ } ) ,
374
+ ) ;
375
+
376
+ function timelines ( ) : [ string | null , string | null ] [ ] {
377
+ return thread . timelineSet
378
+ . getTimelines ( )
379
+ . map ( ( it ) => [ it . getPaginationToken ( Direction . Backward ) , it . getPaginationToken ( Direction . Forward ) ] ) ;
380
+ }
381
+
382
+ expect ( timelines ( ) ) . toEqual ( [ [ null , null ] ] ) ;
383
+ const promise = thread . resetLiveTimeline ( "b1" , "f1" ) ;
384
+ expect ( timelines ( ) ) . toEqual ( [
385
+ [ null , "f1" ] ,
386
+ [ "b1" , null ] ,
387
+ ] ) ;
388
+ thread . timelineSet . getTimelines ( ) [ 0 ] . setPaginationToken ( "f2" , Direction . Forward ) ;
389
+ thread . timelineSet . getTimelines ( ) [ 1 ] . setPaginationToken ( "b2" , Direction . Backward ) ;
390
+ await promise ;
391
+ expect ( timelines ( ) ) . toEqual ( [
392
+ [ null , "f2" ] ,
393
+ [ "b2" , null ] ,
394
+ ] ) ;
395
+ } ) ;
396
+
397
+ it ( "is correctly called by the room" , async ( ) => {
398
+ const myUserId = "@bob:example.org" ;
399
+ const testClient = new TestClient ( myUserId , "DEVICE" , "ACCESS_TOKEN" , undefined , {
400
+ timelineSupport : false ,
401
+ } ) ;
402
+ const client = testClient . client ;
403
+ const room = new Room ( "123" , client , myUserId , {
404
+ pendingEventOrdering : PendingEventOrdering . Detached ,
405
+ } ) ;
406
+
407
+ jest . spyOn ( client , "getRoom" ) . mockReturnValue ( room ) ;
408
+
409
+ const { thread } = mkThread ( {
410
+ room,
411
+ client,
412
+ authorId : myUserId ,
413
+ participantUserIds : [ "@alice:example.org" ] ,
414
+ length : 3 ,
415
+ } ) ;
416
+ await emitPromise ( thread , ThreadEvent . Update ) ;
417
+ expect ( thread . length ) . toBe ( 2 ) ;
418
+ const mock = jest . spyOn ( thread , "resetLiveTimeline" ) ;
419
+ mock . mockReturnValue ( Promise . resolve ( ) ) ;
420
+
421
+ room . resetLiveTimeline ( "b1" , "f1" ) ;
422
+ expect ( mock ) . toHaveBeenCalledWith ( "b1" , "f1" ) ;
423
+ } ) ;
424
+ } ) ;
286
425
} ) ;
0 commit comments