@@ -556,34 +556,36 @@ describe("MatrixClient", function() {
556556 } ) ;
557557
558558 describe ( "partitionThreadedEvents" , function ( ) {
559+ const room = new Room ( "!STrMRsukXHtqQdSeHa:matrix.org" , client , userId ) ;
560+
559561 it ( "returns empty arrays when given an empty arrays" , function ( ) {
560562 const events = [ ] ;
561- const [ timeline , threaded ] = client . partitionThreadedEvents ( events ) ;
563+ const [ timeline , threaded ] = client . partitionThreadedEvents ( room , events ) ;
562564 expect ( timeline ) . toEqual ( [ ] ) ;
563565 expect ( threaded ) . toEqual ( [ ] ) ;
564566 } ) ;
565567
566568 it ( "copies pre-thread in-timeline vote events onto both timelines" , function ( ) {
567569 client . clientOpts = { experimentalThreadSupport : true } ;
568570
569- const eventMessageInThread = buildEventMessageInThread ( ) ;
570571 const eventPollResponseReference = buildEventPollResponseReference ( ) ;
571572 const eventPollStartThreadRoot = buildEventPollStartThreadRoot ( ) ;
573+ const eventMessageInThread = buildEventMessageInThread ( eventPollStartThreadRoot ) ;
572574
573575 const events = [
576+ eventPollStartThreadRoot ,
574577 eventMessageInThread ,
575578 eventPollResponseReference ,
576- eventPollStartThreadRoot ,
577579 ] ;
578580 // Vote has no threadId yet
579581 expect ( eventPollResponseReference . threadId ) . toBeFalsy ( ) ;
580582
581- const [ timeline , threaded ] = client . partitionThreadedEvents ( events ) ;
583+ const [ timeline , threaded ] = client . partitionThreadedEvents ( room , events ) ;
582584
583585 expect ( timeline ) . toEqual ( [
584586 // The message that was sent in a thread is missing
585- eventPollResponseReference ,
586587 eventPollStartThreadRoot ,
588+ eventPollResponseReference ,
587589 ] ) ;
588590
589591 // The vote event has been copied into the thread
@@ -592,33 +594,34 @@ describe("MatrixClient", function() {
592594 expect ( eventRefWithThreadId . threadId ) . toBeTruthy ( ) ;
593595
594596 expect ( threaded ) . toEqual ( [
597+ eventPollStartThreadRoot ,
595598 eventMessageInThread ,
596599 eventRefWithThreadId ,
597- // Thread does not see thread root
598600 ] ) ;
599601 } ) ;
600602
601603 it ( "copies pre-thread in-timeline reactions onto both timelines" , function ( ) {
602604 client . clientOpts = { experimentalThreadSupport : true } ;
603605
604- const eventMessageInThread = buildEventMessageInThread ( ) ;
605- const eventReaction = buildEventReaction ( ) ;
606606 const eventPollStartThreadRoot = buildEventPollStartThreadRoot ( ) ;
607+ const eventMessageInThread = buildEventMessageInThread ( eventPollStartThreadRoot ) ;
608+ const eventReaction = buildEventReaction ( eventPollStartThreadRoot ) ;
607609
608610 const events = [
611+ eventPollStartThreadRoot ,
609612 eventMessageInThread ,
610613 eventReaction ,
611- eventPollStartThreadRoot ,
612614 ] ;
613615
614- const [ timeline , threaded ] = client . partitionThreadedEvents ( events ) ;
616+ const [ timeline , threaded ] = client . partitionThreadedEvents ( room , events ) ;
615617
616618 expect ( timeline ) . toEqual ( [
617- eventReaction ,
618619 eventPollStartThreadRoot ,
620+ eventReaction ,
619621 ] ) ;
620622
621623 expect ( threaded ) . toEqual ( [
624+ eventPollStartThreadRoot ,
622625 eventMessageInThread ,
623626 withThreadId ( eventReaction , eventPollStartThreadRoot . getId ( ) ) ,
624627 ] ) ;
@@ -628,23 +631,24 @@ describe("MatrixClient", function() {
628631 client . clientOpts = { experimentalThreadSupport : true } ;
629632
630633 const eventPollResponseReference = buildEventPollResponseReference ( ) ;
631- const eventMessageInThread = buildEventMessageInThread ( ) ;
632634 const eventPollStartThreadRoot = buildEventPollStartThreadRoot ( ) ;
635+ const eventMessageInThread = buildEventMessageInThread ( eventPollStartThreadRoot ) ;
633636
634637 const events = [
638+ eventPollStartThreadRoot ,
635639 eventPollResponseReference ,
636640 eventMessageInThread ,
637- eventPollStartThreadRoot ,
638641 ] ;
639642
640- const [ timeline , threaded ] = client . partitionThreadedEvents ( events ) ;
643+ const [ timeline , threaded ] = client . partitionThreadedEvents ( room , events ) ;
641644
642645 expect ( timeline ) . toEqual ( [
643- eventPollResponseReference ,
644646 eventPollStartThreadRoot ,
647+ eventPollResponseReference ,
645648 ] ) ;
646649
647650 expect ( threaded ) . toEqual ( [
651+ eventPollStartThreadRoot ,
648652 withThreadId ( eventPollResponseReference , eventPollStartThreadRoot . getId ( ) ) ,
649653 eventMessageInThread ,
650654 ] ) ;
@@ -653,36 +657,37 @@ describe("MatrixClient", function() {
653657 it ( "copies post-thread in-timeline reactions onto both timelines" , function ( ) {
654658 client . clientOpts = { experimentalThreadSupport : true } ;
655659
656- const eventReaction = buildEventReaction ( ) ;
657- const eventMessageInThread = buildEventMessageInThread ( ) ;
658660 const eventPollStartThreadRoot = buildEventPollStartThreadRoot ( ) ;
661+ const eventMessageInThread = buildEventMessageInThread ( eventPollStartThreadRoot ) ;
662+ const eventReaction = buildEventReaction ( eventPollStartThreadRoot ) ;
659663
660664 const events = [
661- eventReaction ,
662- eventMessageInThread ,
663665 eventPollStartThreadRoot ,
666+ eventMessageInThread ,
667+ eventReaction ,
664668 ] ;
665669
666- const [ timeline , threaded ] = client . partitionThreadedEvents ( events ) ;
670+ const [ timeline , threaded ] = client . partitionThreadedEvents ( room , events ) ;
667671
668672 expect ( timeline ) . toEqual ( [
669- eventReaction ,
670673 eventPollStartThreadRoot ,
674+ eventReaction ,
671675 ] ) ;
672676
673677 expect ( threaded ) . toEqual ( [
674- withThreadId ( eventReaction , eventPollStartThreadRoot . getId ( ) ) ,
678+ eventPollStartThreadRoot ,
675679 eventMessageInThread ,
680+ withThreadId ( eventReaction , eventPollStartThreadRoot . getId ( ) ) ,
676681 ] ) ;
677682 } ) ;
678683
679684 it ( "sends room state events to the main timeline only" , function ( ) {
680685 client . clientOpts = { experimentalThreadSupport : true } ;
681686 // This is based on recording the events in a real room:
682687
683- const eventMessageInThread = buildEventMessageInThread ( ) ;
684- const eventPollResponseReference = buildEventPollResponseReference ( ) ;
685688 const eventPollStartThreadRoot = buildEventPollStartThreadRoot ( ) ;
689+ const eventPollResponseReference = buildEventPollResponseReference ( ) ;
690+ const eventMessageInThread = buildEventMessageInThread ( eventPollStartThreadRoot ) ;
686691 const eventRoomName = buildEventRoomName ( ) ;
687692 const eventEncryption = buildEventEncryption ( ) ;
688693 const eventGuestAccess = buildEventGuestAccess ( ) ;
@@ -693,9 +698,9 @@ describe("MatrixClient", function() {
693698 const eventCreate = buildEventCreate ( ) ;
694699
695700 const events = [
696- eventMessageInThread ,
697- eventPollResponseReference ,
698701 eventPollStartThreadRoot ,
702+ eventPollResponseReference ,
703+ eventMessageInThread ,
699704 eventRoomName ,
700705 eventEncryption ,
701706 eventGuestAccess ,
@@ -705,12 +710,12 @@ describe("MatrixClient", function() {
705710 eventMember ,
706711 eventCreate ,
707712 ] ;
708- const [ timeline , threaded ] = client . partitionThreadedEvents ( events ) ;
713+ const [ timeline , threaded ] = client . partitionThreadedEvents ( room , events ) ;
709714
710715 expect ( timeline ) . toEqual ( [
711716 // The message that was sent in a thread is missing
712- eventPollResponseReference ,
713717 eventPollStartThreadRoot ,
718+ eventPollResponseReference ,
714719 eventRoomName ,
715720 eventEncryption ,
716721 eventGuestAccess ,
@@ -721,11 +726,95 @@ describe("MatrixClient", function() {
721726 eventCreate ,
722727 ] ) ;
723728
724- // Thread should contain only stuff that happened in the thread -
725- // no thread root, and no room state events
729+ // Thread should contain only stuff that happened in the thread - no room state events
726730 expect ( threaded ) . toEqual ( [
727- eventMessageInThread ,
731+ eventPollStartThreadRoot ,
728732 withThreadId ( eventPollResponseReference , eventPollStartThreadRoot . getId ( ) ) ,
733+ eventMessageInThread ,
734+ ] ) ;
735+ } ) ;
736+
737+ it ( "sends redactions of reactions to thread responses to thread timeline only" , ( ) => {
738+ client . clientOpts = { experimentalThreadSupport : true } ;
739+
740+ const threadRootEvent = buildEventPollStartThreadRoot ( ) ;
741+ const eventMessageInThread = buildEventMessageInThread ( threadRootEvent ) ;
742+ const threadedReaction = buildEventReaction ( eventMessageInThread ) ;
743+ const threadedReactionRedaction = buildEventRedaction ( threadedReaction ) ;
744+
745+ const events = [
746+ threadRootEvent ,
747+ eventMessageInThread ,
748+ threadedReaction ,
749+ threadedReactionRedaction ,
750+ ] ;
751+
752+ const [ timeline , threaded ] = client . partitionThreadedEvents ( room , events ) ;
753+
754+ expect ( timeline ) . toEqual ( [
755+ threadRootEvent ,
756+ ] ) ;
757+
758+ expect ( threaded ) . toEqual ( [
759+ threadRootEvent ,
760+ eventMessageInThread ,
761+ threadedReaction ,
762+ threadedReactionRedaction ,
763+ ] ) ;
764+ } ) ;
765+
766+ it ( "sends reply to reply to thread root outside of thread to main timeline only" , ( ) => {
767+ client . clientOpts = { experimentalThreadSupport : true } ;
768+
769+ const threadRootEvent = buildEventPollStartThreadRoot ( ) ;
770+ const eventMessageInThread = buildEventMessageInThread ( threadRootEvent ) ;
771+ const directReplyToThreadRoot = buildEventReply ( threadRootEvent ) ;
772+ const replyToReply = buildEventReply ( directReplyToThreadRoot ) ;
773+
774+ const events = [
775+ threadRootEvent ,
776+ eventMessageInThread ,
777+ directReplyToThreadRoot ,
778+ replyToReply ,
779+ ] ;
780+
781+ const [ timeline , threaded ] = client . partitionThreadedEvents ( room , events ) ;
782+
783+ expect ( timeline ) . toEqual ( [
784+ threadRootEvent ,
785+ directReplyToThreadRoot ,
786+ replyToReply ,
787+ ] ) ;
788+
789+ expect ( threaded ) . toEqual ( [
790+ threadRootEvent ,
791+ eventMessageInThread ,
792+ ] ) ;
793+ } ) ;
794+
795+ it ( "sends reply to thread responses to thread timeline only" , ( ) => {
796+ client . clientOpts = { experimentalThreadSupport : true } ;
797+
798+ const threadRootEvent = buildEventPollStartThreadRoot ( ) ;
799+ const eventMessageInThread = buildEventMessageInThread ( threadRootEvent ) ;
800+ const replyToThreadResponse = buildEventReply ( eventMessageInThread ) ;
801+
802+ const events = [
803+ threadRootEvent ,
804+ eventMessageInThread ,
805+ replyToThreadResponse ,
806+ ] ;
807+
808+ const [ timeline , threaded ] = client . partitionThreadedEvents ( room , events ) ;
809+
810+ expect ( timeline ) . toEqual ( [
811+ threadRootEvent ,
812+ ] ) ;
813+
814+ expect ( threaded ) . toEqual ( [
815+ threadRootEvent ,
816+ eventMessageInThread ,
817+ replyToThreadResponse ,
729818 ] ) ;
730819 } ) ;
731820 } ) ;
@@ -737,16 +826,16 @@ function withThreadId(event, newThreadId) {
737826 return ret ;
738827}
739828
740- const buildEventMessageInThread = ( ) => new MatrixEvent ( {
829+ const buildEventMessageInThread = ( root ) => new MatrixEvent ( {
741830 "age" : 80098509 ,
742831 "content" : {
743832 "algorithm" : "m.megolm.v1.aes-sha2" ,
744833 "ciphertext" : "ENCRYPTEDSTUFF" ,
745834 "device_id" : "XISFUZSKHH" ,
746835 "m.relates_to" : {
747- "event_id" : "$VLS2ojbPmxb6x8ECetn45hmND6cRDcjgv-j-to9m7Vo" ,
836+ "event_id" : root . getId ( ) ,
748837 "m.in_reply_to" : {
749- "event_id" : "$VLS2ojbPmxb6x8ECetn45hmND6cRDcjgv-j-to9m7Vo" ,
838+ "event_id" : root . getId ( ) ,
750839 } ,
751840 "rel_type" : "m.thread" ,
752841 } ,
@@ -784,10 +873,10 @@ const buildEventPollResponseReference = () => new MatrixEvent({
784873 "user_id" : "@andybalaam-test1:matrix.org" ,
785874} ) ;
786875
787- const buildEventReaction = ( ) => new MatrixEvent ( {
876+ const buildEventReaction = ( event ) => new MatrixEvent ( {
788877 "content" : {
789878 "m.relates_to" : {
790- "event_id" : "$VLS2ojbPmxb6x8ECetn45hmND6cRDcjgv-j-to9m7Vo" ,
879+ "event_id" : event . getId ( ) ,
791880 "key" : "🤗" ,
792881 "rel_type" : "m.annotation" ,
793882 } ,
@@ -803,6 +892,22 @@ const buildEventReaction = () => new MatrixEvent({
803892 "room_id" : "!STrMRsukXHtqQdSeHa:matrix.org" ,
804893} ) ;
805894
895+ const buildEventRedaction = ( event ) => new MatrixEvent ( {
896+ "content" : {
897+
898+ } ,
899+ "origin_server_ts" : 1643977249239 ,
900+ "sender" : "@andybalaam-test1:matrix.org" ,
901+ "redacts" : event . getId ( ) ,
902+ "type" : "m.room.redaction" ,
903+ "unsigned" : {
904+ "age" : 22597 ,
905+ "transaction_id" : "m1643977249073.17" ,
906+ } ,
907+ "event_id" : "$86B2b-x3LgE4DlV4y24b7UHnt72LIA3rzjvMysTtAfB" ,
908+ "room_id" : "!STrMRsukXHtqQdSeHa:matrix.org" ,
909+ } ) ;
910+
806911const buildEventPollStartThreadRoot = ( ) => new MatrixEvent ( {
807912 "age" : 80108647 ,
808913 "content" : {
@@ -821,6 +926,29 @@ const buildEventPollStartThreadRoot = () => new MatrixEvent({
821926 "user_id" : "@andybalaam-test1:matrix.org" ,
822927} ) ;
823928
929+ const buildEventReply = ( target ) => new MatrixEvent ( {
930+ "age" : 80098509 ,
931+ "content" : {
932+ "algorithm" : "m.megolm.v1.aes-sha2" ,
933+ "ciphertext" : "ENCRYPTEDSTUFF" ,
934+ "device_id" : "XISFUZSKHH" ,
935+ "m.relates_to" : {
936+ "m.in_reply_to" : {
937+ "event_id" : target . getId ( ) ,
938+ } ,
939+ } ,
940+ "sender_key" : "i3N3CtG/CD2bGB8rA9fW6adLYSDvlUhf2iuU73L65Vg" ,
941+ "session_id" : "Ja11R/KG6ua0wdk8zAzognrxjio1Gm/RK2Gn6lFL804" ,
942+ } ,
943+ "event_id" : target . getId ( ) + Math . random ( ) ,
944+ "origin_server_ts" : 1643815466378 ,
945+ "room_id" : "!STrMRsukXHtqQdSeHa:matrix.org" ,
946+ "sender" : "@andybalaam-test1:matrix.org" ,
947+ "type" : "m.room.encrypted" ,
948+ "unsigned" : { "age" : 80098509 } ,
949+ "user_id" : "@andybalaam-test1:matrix.org" ,
950+ } ) ;
951+
824952const buildEventRoomName = ( ) => new MatrixEvent ( {
825953 "age" : 80123249 ,
826954 "content" : {
0 commit comments