@@ -235,7 +235,7 @@ function sendMarkerEventAndEnsureHistoryDetectedStatusBar(asMatrixClient) {
235235 } ) ;
236236
237237 // Ensure the "History import detected" notice is shown
238- cy . get ( `[data-cy ="historical-import-detected-status-bar"]` ) . should ( "exist" ) ;
238+ cy . get ( `[data-test-id ="historical-import-detected-status-bar"]` ) . should ( "exist" ) ;
239239}
240240
241241/**
@@ -361,6 +361,9 @@ describe("MSC2716: Historical Import", () => {
361361 const AS_TOKEN = 'as_token123' ;
362362
363363 beforeEach ( ( ) => {
364+ // Default threads to ON for this spec
365+ cy . enableLabsFeature ( "feature_thread" ) ;
366+
364367 cy . window ( ) . then ( win => {
365368 // Collapse left panel for these tests (get more space in the area we care about)
366369 win . localStorage . setItem ( "mx_lhs_size" , "0" ) ;
@@ -409,7 +412,7 @@ describe("MSC2716: Historical Import", () => {
409412 } ) ;
410413
411414 // Press "Refresh timeline"
412- cy . get ( `[data-cy ="refresh-timeline-button"]` ) . click ( ) ;
415+ cy . get ( `[data-test-id ="refresh-timeline-button"]` ) . click ( ) ;
413416
414417 // Ensure historical messages are now shown
415418 cy . all ( [
@@ -434,7 +437,7 @@ describe("MSC2716: Historical Import", () => {
434437 } ) ;
435438
436439 // Press "Refresh timeline"
437- cy . get ( `[data-cy ="refresh-timeline-button"]` ) . click ( ) ;
440+ cy . get ( `[data-test-id ="refresh-timeline-button"]` ) . click ( ) ;
438441
439442 // Ensure historical messages are now shown
440443 cy . all ( [
@@ -456,7 +459,7 @@ describe("MSC2716: Historical Import", () => {
456459 sendMarkerEventAndEnsureHistoryDetectedStatusBar ( asMatrixClient ) ;
457460
458461 // Press "Refresh timeline"
459- cy . get ( `[data-cy ="refresh-timeline-button"]` ) . click ( ) ;
462+ cy . get ( `[data-test-id ="refresh-timeline-button"]` ) . click ( ) ;
460463
461464 // Ensure all of the messages still show afterwards
462465 cy . all ( [
@@ -508,11 +511,11 @@ describe("MSC2716: Historical Import", () => {
508511 } ) ;
509512
510513 // Press "Refresh timeline"
511- cy . get ( `[data-cy ="refresh-timeline-button"]` ) . click ( ) ;
514+ cy . get ( `[data-test-id ="refresh-timeline-button"]` ) . click ( ) ;
512515
513516 // Wait for the timeline to go blank (meaning it was reset)
514517 // and in the middle of the refrsehing timeline function.
515- cy . get ( '[data-cy ="message-list"] [data-event-id]' )
518+ cy . get ( '[data-test-id ="message-list"] [data-event-id]' )
516519 . should ( 'not.exist' ) ;
517520
518521 // Then make a `/sync` happen by sending a message and seeing that it
@@ -608,13 +611,13 @@ describe("MSC2716: Historical Import", () => {
608611 } ) ;
609612
610613 // Press "Refresh timeline"
611- cy . get ( `[data-cy ="refresh-timeline-button"]` ) . click ( ) ;
614+ cy . get ( `[data-test-id ="refresh-timeline-button"]` ) . click ( ) ;
612615
613616 // Make sure the request was intercepted and thew an error
614617 cy . wait ( '@contextRequestThatWillTryToMakeNewTimeline' ) . its ( 'response.statusCode' ) . should ( 'eq' , 500 ) ;
615618
616619 // Make sure we tell the user that an error happened
617- cy . get ( `[data-cy ="historical-import-detected-error-content"]` ) . should ( "exist" ) ;
620+ cy . get ( `[data-test-id ="historical-import-detected-error-content"]` ) . should ( "exist" ) ;
618621
619622 // Allow the requests to succeed now
620623 cy . all ( [
@@ -633,7 +636,7 @@ describe("MSC2716: Historical Import", () => {
633636 } ) ;
634637
635638 // Press "Refresh timeline" again, this time the network request should succeed
636- cy . get ( `[data-cy ="refresh-timeline-button"]` ) . click ( ) ;
639+ cy . get ( `[data-test-id ="refresh-timeline-button"]` ) . click ( ) ;
637640
638641 // Make sure the request was intercepted and succeeded
639642 cy . wait ( '@contextRequestThatWillMakeNewTimeline' ) . its ( 'response.statusCode' ) . should ( 'eq' , 200 ) ;
@@ -674,4 +677,122 @@ describe("MSC2716: Historical Import", () => {
674677 ] ) ;
675678 } ) ;
676679 } ) ;
680+
681+ it . only ( "Perfectly resolves timelines when refresh fails and then another refresh causes `getLatestTimeline()` " +
682+ "finds a threaded event" , ( ) => {
683+ setupRoomWithHistoricalMessagesAndMarker ( {
684+ synapse,
685+ asMatrixClient,
686+ virtualUserIDs,
687+ } ) ;
688+
689+ // Send a threaded message so it's the latest message in the room
690+ cy . get < string > ( "@roomId" ) . then ( async ( roomId ) => {
691+ const { event_id : eventIdToThreadFrom } = await asMatrixClient . sendMessage ( roomId , null , {
692+ body : `event to thread from (root)` ,
693+ msgtype : "m.text" ,
694+ } ) ;
695+ const { event_id : eventIdThreadedMessage } = await asMatrixClient . sendMessage ( roomId , null , {
696+ "body" : `threaded message1` ,
697+ "msgtype" : "m.text" ,
698+ "m.relates_to" : {
699+ "rel_type" : "m.thread" ,
700+ "event_id" : eventIdToThreadFrom ,
701+ "is_falling_back" : true ,
702+ "m.in_reply_to" : {
703+ "event_id" : eventIdToThreadFrom ,
704+ } ,
705+ } ,
706+ } ) ;
707+
708+ // Wait for the message to show up for the logged in user
709+ waitForEventIdsInClient ( [ eventIdToThreadFrom ] ) ;
710+ cy . wrap ( eventIdToThreadFrom ) . as ( 'eventIdToThreadFrom' ) ;
711+ // We don't wait for this event in the client because it will be
712+ // hidden away in a thread.
713+ cy . wrap ( eventIdThreadedMessage ) . as ( 'eventIdThreadedMessage' ) ;
714+
715+ // Wait for the thread summary to appear which indicates that
716+ // `eventIdThreadedMessage` made it to the client
717+ cy . get ( `[data-event-id="${ eventIdToThreadFrom } "] [data-test-id="thread-summary"]` ) ;
718+ } ) ;
719+
720+ // Make the `/context` fail when we try to refresh the timeline. We want
721+ // to make sure that we are resilient to this type of failure and can
722+ // retry and recover.
723+ cy . all ( [
724+ cy . get < string > ( "@roomId" ) ,
725+ cy . get < string > ( "@eventIdToThreadFrom" ) ,
726+ ] ) . then ( async ( [ roomId , eventIdToThreadFrom ] ) => {
727+ // We're using `eventIdToThreadFrom` here because it's the latest
728+ // event in the rooms main timeline which the refresh timeline logic
729+ // will use if available.
730+ const prefix = '/_matrix/client/r0' ;
731+ const path = `/rooms/${ encodeURIComponent ( roomId ) } /context/${ encodeURIComponent ( eventIdToThreadFrom ) } ` ;
732+ const contextUrl = `${ synapse . baseUrl } ${ prefix } ${ path } *` ;
733+ cy . intercept ( contextUrl , {
734+ statusCode : 500 ,
735+ body : {
736+ errcode : 'CYPRESS_FAKE_ERROR' ,
737+ error : 'We purposely intercepted this /context request to make it fail ' +
738+ 'in order to test whether the refresh timeline code is resilient' ,
739+ } ,
740+ } ) . as ( 'contextRequestThatWillTryToMakeNewTimeline' ) ;
741+ } ) ;
742+
743+ // Press "Refresh timeline"
744+ cy . get ( `[data-test-id="refresh-timeline-button"]` ) . click ( ) ;
745+
746+ // Make sure the request was intercepted and thew an error
747+ cy . wait ( '@contextRequestThatWillTryToMakeNewTimeline' ) . its ( 'response.statusCode' ) . should ( 'eq' , 500 ) ;
748+
749+ // Wait for the timeline to go blank (meaning it was reset)
750+ // and refreshing the timeline failed.
751+ cy . get ( '[data-test-id="message-list"] [data-event-id]' )
752+ . should ( 'not.exist' ) ;
753+
754+ // Press "Refresh timeline" again, this time the network request should succeed.
755+ //
756+ // Since the timeline is now blank, we have no most recent event to
757+ // draw from locally. So `MatrixClient::getLatestTimeline` will
758+ // fetch the latest from `/messages` which will return
759+ // `eventIdThreadedMessage` as the latest event in the room.
760+ cy . get ( `[data-test-id="refresh-timeline-button"]` ) . click ( ) ;
761+
762+ // Make sure sync pagination still works by seeing a new message show up
763+ cy . get < string > ( "@roomId" ) . then ( async ( roomId ) => {
764+ const { event_id : eventIdAfterRefresh } = await asMatrixClient . sendMessage ( roomId , null , {
765+ body : `live_event after refresh` ,
766+ msgtype : "m.text" ,
767+ } ) ;
768+
769+ // Wait for the message to show up for the logged in user
770+ waitForEventIdsInClient ( [ eventIdAfterRefresh ] ) ;
771+
772+ cy . wrap ( eventIdAfterRefresh ) . as ( 'eventIdAfterRefresh' ) ;
773+ } ) ;
774+
775+ // Ensure historical messages are now shown
776+ cy . all ( [
777+ cy . get < string [ ] > ( "@liveMessageEventIds" ) ,
778+ cy . get < string [ ] > ( "@historicalEventIds" ) ,
779+ cy . get < string > ( "@eventIdToThreadFrom" ) ,
780+ cy . get < string > ( "@eventIdAfterRefresh" ) ,
781+ ] ) . then ( async ( [
782+ liveMessageEventIds ,
783+ historicalEventIds ,
784+ eventIdToThreadFrom ,
785+ eventIdAfterRefresh ,
786+ ] ) => {
787+ // FIXME: Assert that they appear in the correct order
788+ waitForEventIdsInClient ( [
789+ liveMessageEventIds [ 0 ] ,
790+ liveMessageEventIds [ 1 ] ,
791+ ...historicalEventIds ,
792+ liveMessageEventIds [ 2 ] ,
793+ eventIdToThreadFrom ,
794+ eventIdAfterRefresh ,
795+ ] ) ;
796+ } ) ;
797+ } ) ;
677798} ) ;
0 commit comments