@@ -27,11 +27,13 @@ import {
27
27
MatrixEvent ,
28
28
PendingEventOrdering ,
29
29
Room ,
30
+ RoomEvent ,
30
31
} from "../../src/matrix" ;
31
32
import { logger } from "../../src/logger" ;
32
33
import { encodeUri } from "../../src/utils" ;
33
34
import { TestClient } from "../TestClient" ;
34
35
import { FeatureSupport , Thread , THREAD_RELATION_TYPE } from "../../src/models/thread" ;
36
+ import { emitPromise } from "../test-utils/test-utils" ;
35
37
36
38
const userId = "@alice:localhost" ;
37
39
const userName = "Alice" ;
@@ -1093,21 +1095,46 @@ describe("MatrixClient event timelines", function() {
1093
1095
return request ;
1094
1096
}
1095
1097
1096
- function respondToContext ( ) : ExpectedHttpRequest {
1098
+ function respondToThread (
1099
+ root : Partial < IEvent > ,
1100
+ replies : Partial < IEvent > [ ] ,
1101
+ ) : ExpectedHttpRequest {
1102
+ const request = httpBackend . when ( "GET" , "/_matrix/client/v1/rooms/!foo%3Abar/relations/" +
1103
+ encodeURIComponent ( root . event_id ! ) + "/" +
1104
+ encodeURIComponent ( THREAD_RELATION_TYPE . name ) + "?dir=b&limit=1" ) ;
1105
+ request . respond ( 200 , function ( ) {
1106
+ return {
1107
+ original_event : root ,
1108
+ chunk : [ replies ] ,
1109
+ // no next batch as this is the oldest end of the timeline
1110
+ } ;
1111
+ } ) ;
1112
+ return request ;
1113
+ }
1114
+
1115
+ function respondToContext ( event : Partial < IEvent > = THREAD_ROOT ) : ExpectedHttpRequest {
1097
1116
const request = httpBackend . when ( "GET" , encodeUri ( "/_matrix/client/r0/rooms/$roomId/context/$eventId" , {
1098
1117
$roomId : roomId ,
1099
- $eventId : THREAD_ROOT . event_id ! ,
1118
+ $eventId : event . event_id ! ,
1100
1119
} ) ) ;
1101
1120
request . respond ( 200 , {
1102
1121
end : `${ Direction . Forward } ${ RANDOM_TOKEN } 1` ,
1103
1122
start : `${ Direction . Backward } ${ RANDOM_TOKEN } 1` ,
1104
1123
state : [ ] ,
1105
1124
events_before : [ ] ,
1106
1125
events_after : [ ] ,
1107
- event : THREAD_ROOT ,
1126
+ event : event ,
1108
1127
} ) ;
1109
1128
return request ;
1110
1129
}
1130
+ function respondToEvent ( event : Partial < IEvent > = THREAD_ROOT ) : ExpectedHttpRequest {
1131
+ const request = httpBackend . when ( "GET" , encodeUri ( "/_matrix/client/r0/rooms/$roomId/event/$eventId" , {
1132
+ $roomId : roomId ,
1133
+ $eventId : event . event_id ! ,
1134
+ } ) ) ;
1135
+ request . respond ( 200 , event ) ;
1136
+ return request ;
1137
+ }
1111
1138
function respondToMessagesRequest ( ) : ExpectedHttpRequest {
1112
1139
const request = httpBackend . when ( "GET" , encodeUri ( "/_matrix/client/r0/rooms/$roomId/messages" , {
1113
1140
$roomId : roomId ,
@@ -1193,6 +1220,127 @@ describe("MatrixClient event timelines", function() {
1193
1220
expect ( myThreads . getPendingEvents ( ) ) . toHaveLength ( 0 ) ;
1194
1221
expect ( room . getPendingEvents ( ) ) . toHaveLength ( 1 ) ;
1195
1222
} ) ;
1223
+
1224
+ it ( "should handle thread updates by reordering the thread list" , async ( ) => {
1225
+ // Test data for a second thread
1226
+ const THREAD2_ROOT = utils . mkEvent ( {
1227
+ room : roomId ,
1228
+ user : userId ,
1229
+ type : "m.room.message" ,
1230
+ content : {
1231
+ "body" : "thread root" ,
1232
+ "msgtype" : "m.text" ,
1233
+ } ,
1234
+ unsigned : {
1235
+ "m.relations" : {
1236
+ "io.element.thread" : {
1237
+ //"latest_event": undefined,
1238
+ "count" : 1 ,
1239
+ "current_user_participated" : true ,
1240
+ } ,
1241
+ } ,
1242
+ } ,
1243
+ event : false ,
1244
+ } ) ;
1245
+
1246
+ const THREAD2_REPLY = utils . mkEvent ( {
1247
+ room : roomId ,
1248
+ user : userId ,
1249
+ type : "m.room.message" ,
1250
+ content : {
1251
+ "body" : "thread reply" ,
1252
+ "msgtype" : "m.text" ,
1253
+ "m.relates_to" : {
1254
+ // We can't use the const here because we change server support mode for test
1255
+ rel_type : "io.element.thread" ,
1256
+ event_id : THREAD_ROOT . event_id ,
1257
+ } ,
1258
+ } ,
1259
+ event : false ,
1260
+ } ) ;
1261
+
1262
+ // @ts -ignore we know this is a defined path for THREAD ROOT
1263
+ THREAD2_ROOT . unsigned [ "m.relations" ] [ "io.element.thread" ] . latest_event = THREAD2_REPLY ;
1264
+
1265
+ // Test data for a second reply to the first thread
1266
+ const THREAD_REPLY2 = utils . mkEvent ( {
1267
+ room : roomId ,
1268
+ user : userId ,
1269
+ type : "m.room.message" ,
1270
+ content : {
1271
+ "body" : "thread reply" ,
1272
+ "msgtype" : "m.text" ,
1273
+ "m.relates_to" : {
1274
+ // We can't use the const here because we change server support mode for test
1275
+ rel_type : "io.element.thread" ,
1276
+ event_id : THREAD_ROOT . event_id ,
1277
+ } ,
1278
+ } ,
1279
+ event : false ,
1280
+ } ) ;
1281
+
1282
+ // Test data for the first thread, with the second reply
1283
+ const THREAD_ROOT_UPDATED = {
1284
+ ...THREAD_ROOT ,
1285
+ unsigned : {
1286
+ ...THREAD_ROOT . unsigned ,
1287
+ "m.relations" : {
1288
+ ...THREAD_ROOT . unsigned ! [ "m.relations" ] ,
1289
+ "io.element.thread" : {
1290
+ ...THREAD_ROOT . unsigned ! [ "m.relations" ] ! [ "io.element.thread" ] ,
1291
+ count : 2 ,
1292
+ latest_event : THREAD_REPLY2 ,
1293
+ } ,
1294
+ } ,
1295
+ } ,
1296
+ } ;
1297
+
1298
+ // Response with test data for the thread list request
1299
+ const threadsResponse = {
1300
+ chunk : [ THREAD2_ROOT , THREAD_ROOT ] ,
1301
+ state : [ ] ,
1302
+ next_batch : RANDOM_TOKEN as string | null ,
1303
+ } ;
1304
+
1305
+ // @ts -ignore
1306
+ client . clientOpts . experimentalThreadSupport = true ;
1307
+ Thread . setServerSideSupport ( FeatureSupport . Stable ) ;
1308
+ Thread . setServerSideListSupport ( FeatureSupport . Stable ) ;
1309
+ Thread . setServerSideFwdPaginationSupport ( FeatureSupport . Stable ) ;
1310
+
1311
+ await client . stopClient ( ) ; // we don't need the client to be syncing at this time
1312
+ const room = client . getRoom ( roomId ) ! ;
1313
+
1314
+ // Setup room threads
1315
+ const timelineSets = await room ! . createThreadsTimelineSets ( ) ;
1316
+ expect ( timelineSets ) . not . toBeNull ( ) ;
1317
+ respondToThreads ( threadsResponse ) ;
1318
+ respondToThreads ( threadsResponse ) ;
1319
+ respondToEvent ( THREAD_ROOT ) ;
1320
+ respondToEvent ( THREAD_ROOT ) ;
1321
+ respondToEvent ( THREAD2_ROOT ) ;
1322
+ respondToEvent ( THREAD2_ROOT ) ;
1323
+ respondToThread ( THREAD_ROOT , [ THREAD_REPLY ] ) ;
1324
+ respondToThread ( THREAD2_ROOT , [ THREAD2_REPLY ] ) ;
1325
+ await flushHttp ( room . fetchRoomThreads ( ) ) ;
1326
+ const [ allThreads ] = timelineSets ! ;
1327
+ const timeline = allThreads . getLiveTimeline ( ) ! ;
1328
+ // Test threads are in chronological order
1329
+ expect ( timeline . getEvents ( ) . map ( it => it . event . event_id ) )
1330
+ . toEqual ( [ THREAD_ROOT . event_id , THREAD2_ROOT . event_id ] ) ;
1331
+
1332
+ // Test adding a second event to the first thread
1333
+ const thread = room . getThread ( THREAD_ROOT . event_id ! ) ! ;
1334
+ const prom = emitPromise ( allThreads ! , RoomEvent . Timeline ) ;
1335
+ await thread . addEvent ( client . getEventMapper ( ) ( THREAD_REPLY2 ) , false ) ;
1336
+ respondToEvent ( THREAD_ROOT_UPDATED ) ;
1337
+ respondToEvent ( THREAD_ROOT_UPDATED ) ;
1338
+ await httpBackend . flushAllExpected ( ) ;
1339
+ await prom ;
1340
+ // Test threads are in chronological order
1341
+ expect ( timeline ! . getEvents ( ) . map ( it => it . event . event_id ) )
1342
+ . toEqual ( [ THREAD2_ROOT . event_id , THREAD_ROOT . event_id ] ) ;
1343
+ } ) ;
1196
1344
} ) ;
1197
1345
1198
1346
describe ( "without server compatibility" , function ( ) {
0 commit comments