@@ -19,7 +19,6 @@ use eyeball_im::VectorDiff;
19
19
use futures_util:: StreamExt as _;
20
20
use matrix_sdk:: {
21
21
assert_let_timeout,
22
- room:: reply:: { EnforceThread , Reply } ,
23
22
test_utils:: mocks:: { MatrixMockServer , RoomRelationsResponseTemplate } ,
24
23
} ;
25
24
use matrix_sdk_test:: {
@@ -32,8 +31,9 @@ use ruma::{
32
31
api:: client:: receipt:: create_receipt:: v3:: ReceiptType as SendReceiptType ,
33
32
event_id,
34
33
events:: {
34
+ AnySyncTimelineEvent ,
35
35
receipt:: { ReceiptThread , ReceiptType } ,
36
- room:: message:: { ReplyWithinThread , RoomMessageEventContentWithoutRelation } ,
36
+ room:: message:: { Relation , ReplacementMetadata , RoomMessageEventContent } ,
37
37
} ,
38
38
owned_event_id, room_id, user_id,
39
39
} ;
@@ -732,17 +732,12 @@ async fn test_thread_timeline_gets_local_echoes() {
732
732
// update.
733
733
let sent_event_id = event_id ! ( "$sent_msg" ) ;
734
734
server. mock_room_state_encryption ( ) . plain ( ) . mount ( ) . await ;
735
- server. mock_room_send ( ) . ok ( sent_event_id) . mock_once ( ) . mount ( ) . await ;
736
- timeline
737
- . send_reply (
738
- RoomMessageEventContentWithoutRelation :: text_plain ( "hello to you too!" ) ,
739
- Reply {
740
- event_id : threaded_event_id. to_owned ( ) ,
741
- enforce_thread : EnforceThread :: Threaded ( ReplyWithinThread :: No ) ,
742
- } ,
743
- )
744
- . await
745
- . unwrap ( ) ;
735
+
736
+ let ( event_receiver, mock_builder) =
737
+ server. mock_room_send ( ) . ok_with_capture ( sent_event_id, * ALICE ) ;
738
+ mock_builder. mock_once ( ) . mount ( ) . await ;
739
+
740
+ timeline. send ( RoomMessageEventContent :: text_plain ( "hello to you too!" ) . into ( ) ) . await . unwrap ( ) ;
746
741
747
742
// I get the local echo for the in-thread event.
748
743
assert_let_timeout ! ( Some ( timeline_updates) = stream. next( ) ) ;
@@ -753,19 +748,41 @@ async fn test_thread_timeline_gets_local_echoes() {
753
748
assert ! ( event_item. is_local_echo( ) ) ;
754
749
assert ! ( event_item. event_id( ) . is_none( ) ) ;
755
750
751
+ // The thread information is properly filled.
752
+ let msglike = event_item. content ( ) . as_msglike ( ) . unwrap ( ) ;
753
+ assert_eq ! ( msglike. thread_root. as_ref( ) , Some ( & thread_root_event_id) ) ;
754
+ assert ! ( msglike. in_reply_to. is_none( ) ) ;
755
+
756
756
// Then the local echo morphs into a sent local echo.
757
757
assert_let_timeout ! ( Some ( timeline_updates) = stream. next( ) ) ;
758
758
assert_eq ! ( timeline_updates. len( ) , 1 ) ;
759
759
760
760
assert_let ! ( VectorDiff :: Set { index: 2 , value } = & timeline_updates[ 0 ] ) ;
761
761
let event_item = value. as_event ( ) . unwrap ( ) ;
762
762
assert_eq ! ( event_item. event_id( ) , Some ( sent_event_id) ) ;
763
- assert_eq ! ( event_item. event_id( ) , Some ( sent_event_id) ) ;
764
763
assert ! ( event_item. content( ) . reactions( ) . unwrap( ) . is_empty( ) ) ;
765
764
766
765
// Then nothing else.
767
766
assert_pending ! ( stream) ;
768
767
768
+ // The raw event includes the correctly reply-to fallback.
769
+ {
770
+ let raw_event = event_receiver. await . unwrap ( ) ;
771
+ let event = raw_event. deserialize ( ) . unwrap ( ) ;
772
+ assert_let ! (
773
+ AnySyncTimelineEvent :: MessageLike (
774
+ ruma:: events:: AnySyncMessageLikeEvent :: RoomMessage ( event)
775
+ ) = event
776
+ ) ;
777
+ let event = event. as_original ( ) . unwrap ( ) ;
778
+ assert_let ! ( Some ( Relation :: Thread ( thread) ) = event. content. relates_to. clone( ) ) ;
779
+ assert_eq ! ( thread. event_id, thread_root_event_id) ;
780
+ assert ! ( thread. is_falling_back) ;
781
+ // The reply-to fallback is set to the latest in-thread event, not the thread
782
+ // root.
783
+ assert_eq ! ( thread. in_reply_to. unwrap( ) . event_id, threaded_event_id) ;
784
+ }
785
+
769
786
// If I send a reaction for the in-thread event, the timeline gets updated, even
770
787
// though the reaction doesn't mention the thread directly.
771
788
server. mock_room_send ( ) . ok ( event_id ! ( "$reaction_id" ) ) . mock_once ( ) . mount ( ) . await ;
@@ -791,6 +808,94 @@ async fn test_thread_timeline_gets_local_echoes() {
791
808
assert_pending ! ( stream) ;
792
809
}
793
810
811
+ #[ async_test]
812
+ async fn test_thread_timeline_can_send_edit ( ) {
813
+ // If I send an edit to a threaded timeline, it just works (aka the system to
814
+ // set the threaded relationship doesn't kick in, since there's already a
815
+ // relationship).
816
+
817
+ let server = MatrixMockServer :: new ( ) . await ;
818
+ let client = server. client_builder ( ) . build ( ) . await ;
819
+
820
+ let room_id = room_id ! ( "!a:b.c" ) ;
821
+ let thread_root_event_id = owned_event_id ! ( "$root" ) ;
822
+ let threaded_event_id = event_id ! ( "$threaded_event" ) ;
823
+
824
+ let room = server. sync_joined_room ( & client, room_id) . await ;
825
+
826
+ let timeline = room
827
+ . timeline_builder ( )
828
+ . with_focus ( TimelineFocus :: Thread { root_event_id : thread_root_event_id. clone ( ) } )
829
+ . build ( )
830
+ . await
831
+ . unwrap ( ) ;
832
+
833
+ let ( initial_items, mut stream) = timeline. subscribe ( ) . await ;
834
+
835
+ // At first, the timeline is empty.
836
+ assert ! ( initial_items. is_empty( ) ) ;
837
+ assert_pending ! ( stream) ;
838
+
839
+ // Start the timeline with an in-thread event.
840
+ let f = EventFactory :: new ( ) ;
841
+ server
842
+ . sync_room (
843
+ & client,
844
+ JoinedRoomBuilder :: new ( room_id) . add_timeline_event (
845
+ f. text_msg ( "hello world" )
846
+ . sender ( * ALICE )
847
+ . event_id ( threaded_event_id)
848
+ . in_thread ( & thread_root_event_id, threaded_event_id)
849
+ . server_ts ( MilliSecondsSinceUnixEpoch :: now ( ) ) ,
850
+ ) ,
851
+ )
852
+ . await ;
853
+
854
+ // Sanity check: I receive the event and the date divider.
855
+ assert_let_timeout ! ( Some ( timeline_updates) = stream. next( ) ) ;
856
+ assert_eq ! ( timeline_updates. len( ) , 2 ) ;
857
+
858
+ // If I send an edit to an in-thread event, the timeline receives an update.
859
+ // Note: it's a bit far fetched to send an edit without using
860
+ // `Timeline::edit()`, but since it's possible…
861
+ let sent_event_id = event_id ! ( "$sent_msg" ) ;
862
+ server. mock_room_state_encryption ( ) . plain ( ) . mount ( ) . await ;
863
+
864
+ // No mock_once here: this endpoint may or may not be called, depending on
865
+ // timing of the end of the test.
866
+ server. mock_room_send ( ) . ok ( sent_event_id) . mount ( ) . await ;
867
+
868
+ timeline
869
+ . send (
870
+ RoomMessageEventContent :: text_plain ( "bonjour monde" )
871
+ . make_replacement (
872
+ ReplacementMetadata :: new ( threaded_event_id. to_owned ( ) , None ) ,
873
+ None ,
874
+ )
875
+ . into ( ) ,
876
+ )
877
+ . await
878
+ . unwrap ( ) ;
879
+
880
+ // I get the local echo for the in-thread event.
881
+ assert_let_timeout ! ( Some ( timeline_updates) = stream. next( ) ) ;
882
+ assert_eq ! ( timeline_updates. len( ) , 1 ) ;
883
+
884
+ assert_let ! ( VectorDiff :: Set { index: 1 , value } = & timeline_updates[ 0 ] ) ;
885
+ let event_item = value. as_event ( ) . unwrap ( ) ;
886
+
887
+ // The thread information is (still) present.
888
+ let msglike = event_item. content ( ) . as_msglike ( ) . unwrap ( ) ;
889
+ assert_eq ! ( msglike. thread_root. as_ref( ) , Some ( & thread_root_event_id) ) ;
890
+ assert ! ( msglike. in_reply_to. is_none( ) ) ;
891
+
892
+ // Text is eagerly updated.
893
+ assert_eq ! ( msglike. as_message( ) . unwrap( ) . body( ) , "bonjour monde" ) ;
894
+
895
+ // Then we're done.
896
+ assert_pending ! ( stream) ;
897
+ }
898
+
794
899
#[ async_test]
795
900
async fn test_sending_read_receipt_with_no_events_doesnt_unset_read_flag ( ) {
796
901
// If a thread timeline has no events, then marking it as read doesn't unset the
0 commit comments