@@ -14,25 +14,34 @@ See the License for the specific language governing permissions and
14
14
limitations under the License.
15
15
*/
16
16
17
- import React from ' react' ;
17
+ import { render , RenderResult } from "@testing-library/ react" ;
18
18
// eslint-disable-next-line deprecate/import
19
19
import { mount , ReactWrapper } from "enzyme" ;
20
- import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline" ;
21
20
import { MessageEvent } from 'matrix-events-sdk' ;
21
+ import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts" ;
22
22
import {
23
23
EventTimelineSet ,
24
24
EventType ,
25
+ MatrixClient ,
25
26
MatrixEvent ,
26
27
PendingEventOrdering ,
27
28
Room ,
28
29
} from 'matrix-js-sdk/src/matrix' ;
29
- import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts" ;
30
- import { render , RenderResult } from "@testing-library/react" ;
30
+ import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline" ;
31
+ import {
32
+ FeatureSupport ,
33
+ Thread ,
34
+ THREAD_RELATION_TYPE ,
35
+ ThreadEvent ,
36
+ ThreadFilterType ,
37
+ } from "matrix-js-sdk/src/models/thread" ;
38
+ import React from 'react' ;
31
39
32
- import { mkRoom , stubClient } from "../../test-utils" ;
33
40
import TimelinePanel from '../../../src/components/structures/TimelinePanel' ;
41
+ import MatrixClientContext from "../../../src/contexts/MatrixClientContext" ;
34
42
import { MatrixClientPeg } from '../../../src/MatrixClientPeg' ;
35
43
import SettingsStore from "../../../src/settings/SettingsStore" ;
44
+ import { mkRoom , stubClient } from "../../test-utils" ;
36
45
37
46
const newReceipt = ( eventId : string , userId : string , readTs : number , fullyReadTs : number ) : MatrixEvent => {
38
47
const receiptContent = {
@@ -52,7 +61,7 @@ const getProps = (room: Room, events: MatrixEvent[]): TimelinePanel["props"] =>
52
61
timelineSet . getLiveTimeline = ( ) => timeline ;
53
62
timelineSet . getTimelineForEvent = ( ) => timeline ;
54
63
timelineSet . getPendingEvents = ( ) => events ;
55
- timelineSet . room . getEventReadUpTo = ( ) => events [ 1 ] . getId ( ) ;
64
+ timelineSet . room ! . getEventReadUpTo = ( ) => events [ 1 ] . getId ( ) ?? null ;
56
65
57
66
return {
58
67
timelineSet,
@@ -67,7 +76,7 @@ const renderPanel = (room: Room, events: MatrixEvent[]): RenderResult => {
67
76
} ;
68
77
69
78
const mockEvents = ( room : Room , count = 2 ) : MatrixEvent [ ] => {
70
- const events = [ ] ;
79
+ const events : MatrixEvent [ ] = [ ] ;
71
80
for ( let index = 0 ; index < count ; index ++ ) {
72
81
events . push ( new MatrixEvent ( {
73
82
room_id : room . roomId ,
@@ -89,7 +98,7 @@ describe('TimelinePanel', () => {
89
98
describe ( 'read receipts and markers' , ( ) => {
90
99
it ( 'should forget the read marker when asked to' , ( ) => {
91
100
const cli = MatrixClientPeg . get ( ) ;
92
- const readMarkersSent = [ ] ;
101
+ const readMarkersSent : string [ ] = [ ] ;
93
102
94
103
// Track calls to setRoomReadMarkers
95
104
cli . setRoomReadMarkers = ( _roomId , rmEventId , _a , _b ) => {
@@ -111,7 +120,7 @@ describe('TimelinePanel', () => {
111
120
} ) ;
112
121
113
122
const roomId = "#room:example.com" ;
114
- const userId = cli . credentials . userId ;
123
+ const userId = cli . credentials . userId ! ;
115
124
const room = new Room (
116
125
roomId ,
117
126
cli ,
@@ -192,4 +201,175 @@ describe('TimelinePanel', () => {
192
201
rerender ( < TimelinePanel { ...props } /> ) ;
193
202
expect ( props . onEventScrolledIntoView ) . toHaveBeenCalledWith ( events [ 1 ] . getId ( ) ) ;
194
203
} ) ;
204
+
205
+ describe ( "when a thread updates" , ( ) => {
206
+ let client : MatrixClient ;
207
+ let room : Room ;
208
+ let allThreads : EventTimelineSet ;
209
+ let root : MatrixEvent ;
210
+ let reply1 : MatrixEvent ;
211
+ let reply2 : MatrixEvent ;
212
+
213
+ beforeEach ( ( ) => {
214
+ client = MatrixClientPeg . get ( ) ;
215
+
216
+ Thread . hasServerSideSupport = FeatureSupport . Stable ;
217
+ client . supportsExperimentalThreads = ( ) => true ;
218
+ const getValueCopy = SettingsStore . getValue ;
219
+ SettingsStore . getValue = jest . fn ( ) . mockImplementation ( ( name : string ) => {
220
+ if ( name === "feature_thread" ) return true ;
221
+ return getValueCopy ( name ) ;
222
+ } ) ;
223
+
224
+ room = new Room ( "roomId" , client , "userId" ) ;
225
+ allThreads = new EventTimelineSet ( room , {
226
+ pendingEvents : false ,
227
+ } , undefined , undefined , ThreadFilterType . All ) ;
228
+ const timeline = new EventTimeline ( allThreads ) ;
229
+ allThreads . getLiveTimeline = ( ) => timeline ;
230
+ allThreads . getTimelineForEvent = ( ) => timeline ;
231
+
232
+ reply1 = new MatrixEvent ( {
233
+ room_id : room . roomId ,
234
+ event_id : 'event_reply_1' ,
235
+ type : EventType . RoomMessage ,
236
+ user_id : "userId" ,
237
+ content : MessageEvent . from ( `ReplyEvent1` ) . serialize ( ) . content ,
238
+ } ) ;
239
+
240
+ reply2 = new MatrixEvent ( {
241
+ room_id : room . roomId ,
242
+ event_id : 'event_reply_2' ,
243
+ type : EventType . RoomMessage ,
244
+ user_id : "userId" ,
245
+ content : MessageEvent . from ( `ReplyEvent2` ) . serialize ( ) . content ,
246
+ } ) ;
247
+
248
+ root = new MatrixEvent ( {
249
+ room_id : room . roomId ,
250
+ event_id : 'event_root_1' ,
251
+ type : EventType . RoomMessage ,
252
+ user_id : "userId" ,
253
+ content : MessageEvent . from ( `RootEvent` ) . serialize ( ) . content ,
254
+ } ) ;
255
+
256
+ const eventMap : { [ key : string ] : MatrixEvent } = {
257
+ [ root . getId ( ) ! ] : root ,
258
+ [ reply1 . getId ( ) ! ] : reply1 ,
259
+ [ reply2 . getId ( ) ! ] : reply2 ,
260
+ } ;
261
+
262
+ room . findEventById = ( eventId : string ) => eventMap [ eventId ] ;
263
+ client . fetchRoomEvent = async ( roomId : string , eventId : string ) =>
264
+ roomId === room . roomId ? eventMap [ eventId ] ?. event : { } ;
265
+ } ) ;
266
+
267
+ it ( 'updates thread previews' , async ( ) => {
268
+ root . setUnsigned ( {
269
+ "m.relations" : {
270
+ [ THREAD_RELATION_TYPE . name ] : {
271
+ "latest_event" : reply1 . event ,
272
+ "count" : 1 ,
273
+ "current_user_participated" : true ,
274
+ } ,
275
+ } ,
276
+ } ) ;
277
+
278
+ const thread = room . createThread ( root . getId ( ) ! , root , [ ] , true ) ;
279
+ // So that we do not have to mock the thread loading
280
+ thread . initialEventsFetched = true ;
281
+ // @ts -ignore
282
+ thread . fetchEditsWhereNeeded = ( ) => Promise . resolve ( ) ;
283
+ await thread . addEvent ( reply1 , true ) ;
284
+ await allThreads . getLiveTimeline ( ) . addEvent ( thread . rootEvent ! , true ) ;
285
+ const replyToEvent = jest . spyOn ( thread , "replyToEvent" , "get" ) ;
286
+
287
+ const dom = render (
288
+ < MatrixClientContext . Provider value = { client } >
289
+ < TimelinePanel
290
+ timelineSet = { allThreads }
291
+ manageReadReceipts
292
+ sendReadReceiptOnLoad
293
+ />
294
+ </ MatrixClientContext . Provider > ,
295
+ ) ;
296
+ await dom . findByText ( "RootEvent" ) ;
297
+ await dom . findByText ( "ReplyEvent1" ) ;
298
+ expect ( replyToEvent ) . toHaveBeenCalled ( ) ;
299
+
300
+ root . setUnsigned ( {
301
+ "m.relations" : {
302
+ [ THREAD_RELATION_TYPE . name ] : {
303
+ "latest_event" : reply2 . event ,
304
+ "count" : 2 ,
305
+ "current_user_participated" : true ,
306
+ } ,
307
+ } ,
308
+ } ) ;
309
+
310
+ replyToEvent . mockClear ( ) ;
311
+ await thread . addEvent ( reply2 , false , true ) ;
312
+ await dom . findByText ( "RootEvent" ) ;
313
+ await dom . findByText ( "ReplyEvent2" ) ;
314
+ expect ( replyToEvent ) . toHaveBeenCalled ( ) ;
315
+ } ) ;
316
+
317
+ it ( 'ignores thread updates for unknown threads' , async ( ) => {
318
+ root . setUnsigned ( {
319
+ "m.relations" : {
320
+ [ THREAD_RELATION_TYPE . name ] : {
321
+ "latest_event" : reply1 . event ,
322
+ "count" : 1 ,
323
+ "current_user_participated" : true ,
324
+ } ,
325
+ } ,
326
+ } ) ;
327
+
328
+ const realThread = room . createThread ( root . getId ( ) ! , root , [ ] , true ) ;
329
+ // So that we do not have to mock the thread loading
330
+ realThread . initialEventsFetched = true ;
331
+ // @ts -ignore
332
+ realThread . fetchEditsWhereNeeded = ( ) => Promise . resolve ( ) ;
333
+ await realThread . addEvent ( reply1 , true ) ;
334
+ await allThreads . getLiveTimeline ( ) . addEvent ( realThread . rootEvent ! , true ) ;
335
+ const replyToEvent = jest . spyOn ( realThread , "replyToEvent" , "get" ) ;
336
+
337
+ // @ts -ignore
338
+ const fakeThread1 : Thread = {
339
+ id : undefined ! ,
340
+ get roomId ( ) : string {
341
+ return room . roomId ;
342
+ } ,
343
+ } ;
344
+
345
+ const fakeRoom = new Room ( "thisroomdoesnotexist" , client , "userId" ) ;
346
+ // @ts -ignore
347
+ const fakeThread2 : Thread = {
348
+ id : root . getId ( ) ! ,
349
+ get roomId ( ) : string {
350
+ return fakeRoom . roomId ;
351
+ } ,
352
+ } ;
353
+
354
+ const dom = render (
355
+ < MatrixClientContext . Provider value = { client } >
356
+ < TimelinePanel
357
+ timelineSet = { allThreads }
358
+ manageReadReceipts
359
+ sendReadReceiptOnLoad
360
+ />
361
+ </ MatrixClientContext . Provider > ,
362
+ ) ;
363
+ await dom . findByText ( "RootEvent" ) ;
364
+ await dom . findByText ( "ReplyEvent1" ) ;
365
+ expect ( replyToEvent ) . toHaveBeenCalled ( ) ;
366
+
367
+ replyToEvent . mockClear ( ) ;
368
+ room . emit ( ThreadEvent . Update , fakeThread1 ) ;
369
+ room . emit ( ThreadEvent . Update , fakeThread2 ) ;
370
+ await dom . findByText ( "ReplyEvent1" ) ;
371
+ expect ( replyToEvent ) . not . toHaveBeenCalled ( ) ;
372
+ replyToEvent . mockClear ( ) ;
373
+ } ) ;
374
+ } ) ;
195
375
} ) ;
0 commit comments