@@ -20,7 +20,7 @@ import { Thread, THREAD_RELATION_TYPE, ThreadEvent } from "../../../src/models/t
2020import { mkThread } from "../../test-utils/thread" ;
2121import { TestClient } from "../../TestClient" ;
2222import { emitPromise , mkMessage , mock } from "../../test-utils/test-utils" ;
23- import { EventStatus , MatrixEvent } from "../../../src" ;
23+ import { Direction , EventStatus , MatrixEvent } from "../../../src" ;
2424import { ReceiptType } from "../../../src/@types/read_receipts" ;
2525import { getMockClientWithEventEmitter , mockClientMethodsUser } from "../../test-utils/client" ;
2626import { ReEmitter } from "../../../src/ReEmitter" ;
@@ -283,4 +283,143 @@ describe("Thread", () => {
283283 expect ( thread2 . getEventReadUpTo ( myUserId ) ) . toBe ( null ) ;
284284 } ) ;
285285 } ) ;
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+ } ) ;
286425} ) ;
0 commit comments