@@ -14,18 +14,23 @@ See the License for the specific language governing permissions and
1414limitations under the License.
1515*/
1616
17- import React from "react" ;
18- import { render , screen , fireEvent } from "@testing-library/react" ;
19- import { mocked } from "jest-mock" ;
17+ import { fireEvent , render , screen , waitFor } from "@testing-library/react" ;
2018import "focus-visible" ; // to fix context menus
19+ import { mocked } from "jest-mock" ;
20+ import React from "react" ;
21+ import { MatrixClient , MatrixEvent , PendingEventOrdering , Room } from "matrix-js-sdk/src/matrix" ;
22+ import { FeatureSupport , Thread } from "matrix-js-sdk/src/models/thread" ;
2123
2224import ThreadPanel , { ThreadFilterType , ThreadPanelHeader } from "../../../src/components/structures/ThreadPanel" ;
25+ import MatrixClientContext from "../../../src/contexts/MatrixClientContext" ;
26+ import RoomContext from "../../../src/contexts/RoomContext" ;
2327import { _t } from "../../../src/languageHandler" ;
24- import ResizeNotifier from "../../../src/utils/ResizeNotifier" ;
25- import { RoomPermalinkCreator } from "../../../src/utils/permalinks/Permalinks" ;
26- import { createTestClient , mkStubRoom } from "../../test-utils" ;
28+ import { MatrixClientPeg } from "../../../src/MatrixClientPeg" ;
2729import { shouldShowFeedback } from "../../../src/utils/Feedback" ;
28- import MatrixClientContext from "../../../src/contexts/MatrixClientContext" ;
30+ import { RoomPermalinkCreator } from "../../../src/utils/permalinks/Permalinks" ;
31+ import ResizeNotifier from "../../../src/utils/ResizeNotifier" ;
32+ import { createTestClient , getRoomContext , mkStubRoom , mockPlatformPeg , stubClient } from "../../test-utils" ;
33+ import { mkThread } from "../../test-utils/threads" ;
2934
3035jest . mock ( "../../../src/utils/Feedback" ) ;
3136
@@ -122,4 +127,184 @@ describe("ThreadPanel", () => {
122127 expect ( foundButton ) . toMatchSnapshot ( ) ;
123128 } ) ;
124129 } ) ;
130+
131+ describe ( "Filtering" , ( ) => {
132+ const ROOM_ID = "!roomId:example.org" ;
133+ const SENDER = "@alice:example.org" ;
134+
135+ let mockClient : MatrixClient ;
136+ let room : Room ;
137+
138+ const TestThreadPanel = ( ) => (
139+ < MatrixClientContext . Provider value = { mockClient } >
140+ < RoomContext . Provider
141+ value = { getRoomContext ( room , {
142+ canSendMessages : true ,
143+ } ) }
144+ >
145+ < ThreadPanel
146+ roomId = { ROOM_ID }
147+ onClose = { jest . fn ( ) }
148+ resizeNotifier = { new ResizeNotifier ( ) }
149+ permalinkCreator = { new RoomPermalinkCreator ( room ) }
150+ />
151+ </ RoomContext . Provider >
152+ </ MatrixClientContext . Provider >
153+ ) ;
154+
155+ beforeEach ( async ( ) => {
156+ jest . clearAllMocks ( ) ;
157+
158+ stubClient ( ) ;
159+ mockPlatformPeg ( ) ;
160+ mockClient = mocked ( MatrixClientPeg . get ( ) ) ;
161+ Thread . setServerSideSupport ( FeatureSupport . Stable ) ;
162+ Thread . setServerSideListSupport ( FeatureSupport . Stable ) ;
163+ Thread . setServerSideFwdPaginationSupport ( FeatureSupport . Stable ) ;
164+ jest . spyOn ( mockClient , "supportsExperimentalThreads" ) . mockReturnValue ( true ) ;
165+
166+ room = new Room ( ROOM_ID , mockClient , mockClient . getUserId ( ) ?? "" , {
167+ pendingEventOrdering : PendingEventOrdering . Detached ,
168+ } ) ;
169+ jest . spyOn ( room , "fetchRoomThreads" ) . mockReturnValue ( Promise . resolve ( ) ) ;
170+ jest . spyOn ( mockClient , "getRoom" ) . mockReturnValue ( room ) ;
171+ await room . createThreadsTimelineSets ( ) ;
172+ const [ allThreads , myThreads ] = room . threadsTimelineSets ;
173+ jest . spyOn ( room , "createThreadsTimelineSets" )
174+ . mockReturnValue ( Promise . resolve ( [ allThreads , myThreads ] ) ) ;
175+ } ) ;
176+
177+ function toggleThreadFilter ( container : HTMLElement , newFilter : ThreadFilterType ) {
178+ fireEvent . click ( container . querySelector ( ".mx_ThreadPanel_dropdown" ) ) ;
179+ const found = screen . queryAllByRole ( "menuitemradio" ) ;
180+ expect ( found ) . toHaveLength ( 2 ) ;
181+
182+ const allThreadsContent = `${ _t ( "All threads" ) } ${ _t ( "Shows all threads from current room" ) } ` ;
183+ const myThreadsContent = `${ _t ( "My threads" ) } ${ _t ( "Shows all threads you've participated in" ) } ` ;
184+
185+ const allThreadsOption = found . find ( it => it . textContent === allThreadsContent ) ;
186+ const myThreadsOption = found . find ( it => it . textContent === myThreadsContent ) ;
187+ expect ( allThreadsOption ) . toBeTruthy ( ) ;
188+ expect ( myThreadsOption ) . toBeTruthy ( ) ;
189+
190+ const toSelect = newFilter === ThreadFilterType . My ? myThreadsOption : allThreadsOption ;
191+ fireEvent . click ( toSelect ) ;
192+ }
193+
194+ type EventData = { sender : string , content : string } ;
195+
196+ function findEvents ( container : HTMLElement ) : EventData [ ] {
197+ return Array . from ( container . querySelectorAll ( ".mx_EventTile" ) ) . map ( el => {
198+ const sender = el . querySelector ( ".mx_DisambiguatedProfile_displayName" ) . textContent ;
199+ const content = el . querySelector ( ".mx_EventTile_body" ) . textContent ;
200+ return { sender, content } ;
201+ } ) ;
202+ }
203+
204+ function toEventData ( event : MatrixEvent ) : EventData {
205+ return { sender : event . event . sender , content : event . event . content . body } ;
206+ }
207+
208+ it ( "correctly filters Thread List with multiple threads" , async ( ) => {
209+ const otherThread = mkThread ( {
210+ room,
211+ client : mockClient ,
212+ authorId : SENDER ,
213+ participantUserIds : [ mockClient . getUserId ( ) ] ,
214+ } ) ;
215+
216+ const mixedThread = mkThread ( {
217+ room,
218+ client : mockClient ,
219+ authorId : SENDER ,
220+ participantUserIds : [ SENDER , mockClient . getUserId ( ) ] ,
221+ } ) ;
222+
223+ const ownThread = mkThread ( {
224+ room,
225+ client : mockClient ,
226+ authorId : mockClient . getUserId ( ) ,
227+ participantUserIds : [ mockClient . getUserId ( ) ] ,
228+ } ) ;
229+
230+ const threadRoots = [ otherThread . rootEvent , mixedThread . rootEvent , ownThread . rootEvent ] ;
231+ jest . spyOn ( mockClient , "fetchRoomEvent" ) . mockImplementation (
232+ ( _ , eventId ) => Promise . resolve ( threadRoots . find ( it => it . getId ( ) === eventId ) . event ) ,
233+ ) ;
234+ const [ allThreads , myThreads ] = room . threadsTimelineSets ;
235+ allThreads . addLiveEvent ( otherThread . rootEvent ) ;
236+ allThreads . addLiveEvent ( mixedThread . rootEvent ) ;
237+ allThreads . addLiveEvent ( ownThread . rootEvent ) ;
238+ myThreads . addLiveEvent ( mixedThread . rootEvent ) ;
239+ myThreads . addLiveEvent ( ownThread . rootEvent ) ;
240+
241+ let events : EventData [ ] = [ ] ;
242+ const renderResult = render ( < TestThreadPanel /> ) ;
243+ await waitFor ( ( ) => expect ( renderResult . container . querySelector ( ".mx_AutoHideScrollbar" ) ) . toBeFalsy ( ) ) ;
244+ await waitFor ( ( ) => {
245+ events = findEvents ( renderResult . container ) ;
246+ expect ( findEvents ( renderResult . container ) ) . toHaveLength ( 3 ) ;
247+ } ) ;
248+ expect ( events [ 0 ] ) . toEqual ( toEventData ( otherThread . rootEvent ) ) ;
249+ expect ( events [ 1 ] ) . toEqual ( toEventData ( mixedThread . rootEvent ) ) ;
250+ expect ( events [ 2 ] ) . toEqual ( toEventData ( ownThread . rootEvent ) ) ;
251+ await waitFor ( ( ) => expect ( renderResult . container . querySelector ( ".mx_ThreadPanel_dropdown" ) ) . toBeTruthy ( ) ) ;
252+ toggleThreadFilter ( renderResult . container , ThreadFilterType . My ) ;
253+ await waitFor ( ( ) => expect ( renderResult . container . querySelector ( ".mx_AutoHideScrollbar" ) ) . toBeFalsy ( ) ) ;
254+ await waitFor ( ( ) => {
255+ events = findEvents ( renderResult . container ) ;
256+ expect ( findEvents ( renderResult . container ) ) . toHaveLength ( 2 ) ;
257+ } ) ;
258+ expect ( events [ 0 ] ) . toEqual ( toEventData ( mixedThread . rootEvent ) ) ;
259+ expect ( events [ 1 ] ) . toEqual ( toEventData ( ownThread . rootEvent ) ) ;
260+ toggleThreadFilter ( renderResult . container , ThreadFilterType . All ) ;
261+ await waitFor ( ( ) => expect ( renderResult . container . querySelector ( ".mx_AutoHideScrollbar" ) ) . toBeFalsy ( ) ) ;
262+ await waitFor ( ( ) => {
263+ events = findEvents ( renderResult . container ) ;
264+ expect ( findEvents ( renderResult . container ) ) . toHaveLength ( 3 ) ;
265+ } ) ;
266+ expect ( events [ 0 ] ) . toEqual ( toEventData ( otherThread . rootEvent ) ) ;
267+ expect ( events [ 1 ] ) . toEqual ( toEventData ( mixedThread . rootEvent ) ) ;
268+ expect ( events [ 2 ] ) . toEqual ( toEventData ( ownThread . rootEvent ) ) ;
269+ } ) ;
270+
271+ it ( "correctly filters Thread List with a single, unparticipated thread" , async ( ) => {
272+ const otherThread = mkThread ( {
273+ room,
274+ client : mockClient ,
275+ authorId : SENDER ,
276+ participantUserIds : [ mockClient . getUserId ( ) ] ,
277+ } ) ;
278+
279+ const threadRoots = [ otherThread . rootEvent ] ;
280+ jest . spyOn ( mockClient , "fetchRoomEvent" ) . mockImplementation (
281+ ( _ , eventId ) => Promise . resolve ( threadRoots . find ( it => it . getId ( ) === eventId ) . event ) ,
282+ ) ;
283+ const [ allThreads ] = room . threadsTimelineSets ;
284+ allThreads . addLiveEvent ( otherThread . rootEvent ) ;
285+
286+ let events : EventData [ ] = [ ] ;
287+ const renderResult = render ( < TestThreadPanel /> ) ;
288+ await waitFor ( ( ) => expect ( renderResult . container . querySelector ( ".mx_AutoHideScrollbar" ) ) . toBeFalsy ( ) ) ;
289+ await waitFor ( ( ) => {
290+ events = findEvents ( renderResult . container ) ;
291+ expect ( findEvents ( renderResult . container ) ) . toHaveLength ( 1 ) ;
292+ } ) ;
293+ expect ( events [ 0 ] ) . toEqual ( toEventData ( otherThread . rootEvent ) ) ;
294+ await waitFor ( ( ) => expect ( renderResult . container . querySelector ( ".mx_ThreadPanel_dropdown" ) ) . toBeTruthy ( ) ) ;
295+ toggleThreadFilter ( renderResult . container , ThreadFilterType . My ) ;
296+ await waitFor ( ( ) => expect ( renderResult . container . querySelector ( ".mx_AutoHideScrollbar" ) ) . toBeFalsy ( ) ) ;
297+ await waitFor ( ( ) => {
298+ events = findEvents ( renderResult . container ) ;
299+ expect ( findEvents ( renderResult . container ) ) . toHaveLength ( 0 ) ;
300+ } ) ;
301+ toggleThreadFilter ( renderResult . container , ThreadFilterType . All ) ;
302+ await waitFor ( ( ) => expect ( renderResult . container . querySelector ( ".mx_AutoHideScrollbar" ) ) . toBeFalsy ( ) ) ;
303+ await waitFor ( ( ) => {
304+ events = findEvents ( renderResult . container ) ;
305+ expect ( findEvents ( renderResult . container ) ) . toHaveLength ( 1 ) ;
306+ } ) ;
307+ expect ( events [ 0 ] ) . toEqual ( toEventData ( otherThread . rootEvent ) ) ;
308+ } ) ;
309+ } )
125310} ) ;
0 commit comments