1717 *
1818 */
1919
20+ import { CONVERSATION_TYPE } from '@wireapp/api-client/lib/conversation' ;
21+
2022import { Conversation } from 'Repositories/entity/Conversation' ;
21- import { getTabConversations } from 'src/script/page/LeftSidebar/panels/Conversations/helpers' ;
23+ import {
24+ conversationFilters ,
25+ conversationSearchFilter ,
26+ getConversationsWithHeadings ,
27+ getTabConversations ,
28+ scrollToConversation ,
29+ } from 'src/script/page/LeftSidebar/panels/Conversations/helpers' ;
2230import { generateConversation } from 'test/helper/ConversationGenerator' ;
31+ import { generateUser } from 'test/helper/UserGenerator' ;
2332
2433import { SidebarTabs } from './useSidebarStore' ;
2534
35+ describe ( 'conversationFilters' , ( ) => {
36+ it ( 'detects mentions, replies, pings, and archived state' , ( ) => {
37+ const mentionsConversation = generateConversation ( { name : 'Mentions' } ) ;
38+ jest
39+ . spyOn ( mentionsConversation , 'unreadState' )
40+ . mockReturnValue ( { selfMentions : [ { } ] , selfReplies : [ ] , pings : [ ] } as any ) ;
41+ jest . spyOn ( mentionsConversation , 'is_archived' ) . mockReturnValue ( false as any ) ;
42+
43+ const repliesConversation = generateConversation ( { name : 'Replies' } ) ;
44+ jest
45+ . spyOn ( repliesConversation , 'unreadState' )
46+ . mockReturnValue ( { selfMentions : [ ] , selfReplies : [ { } ] , pings : [ ] } as any ) ;
47+ jest . spyOn ( repliesConversation , 'is_archived' ) . mockReturnValue ( true as any ) ;
48+
49+ const pingsConversation = generateConversation ( { name : 'Pings' } ) ;
50+ jest
51+ . spyOn ( pingsConversation , 'unreadState' )
52+ . mockReturnValue ( { selfMentions : [ ] , selfReplies : [ ] , pings : [ { } ] } as any ) ;
53+ jest . spyOn ( pingsConversation , 'is_archived' ) . mockReturnValue ( false as any ) ;
54+
55+ expect ( conversationFilters . hasMentions ( mentionsConversation ) ) . toBe ( true ) ;
56+ expect ( conversationFilters . hasReplies ( repliesConversation ) ) . toBe ( true ) ;
57+ expect ( conversationFilters . hasPings ( pingsConversation ) ) . toBe ( true ) ;
58+ expect ( conversationFilters . notArchived ( repliesConversation ) ) . toBe ( false ) ;
59+ } ) ;
60+ } ) ;
61+
62+ describe ( 'conversationSearchFilter' , ( ) => {
63+ it ( 'matches conversations by normalized display name' , ( ) => {
64+ const conversation = generateConversation ( { name : 'Wêb Têam' } ) ;
65+ const matches = conversationSearchFilter ( 'web team' ) ;
66+
67+ expect ( matches ( conversation ) ) . toBe ( true ) ;
68+ } ) ;
69+ } ) ;
70+
71+ describe ( 'scrollToConversation' , ( ) => {
72+ const originalInnerHeight = window . innerHeight ;
73+ const originalInnerWidth = window . innerWidth ;
74+
75+ beforeEach ( ( ) => {
76+ Object . defineProperty ( window , 'innerHeight' , { value : 600 , configurable : true } ) ;
77+ Object . defineProperty ( window , 'innerWidth' , { value : 800 , configurable : true } ) ;
78+ } ) ;
79+
80+ afterEach ( ( ) => {
81+ Object . defineProperty ( window , 'innerHeight' , { value : originalInnerHeight , configurable : true } ) ;
82+ Object . defineProperty ( window , 'innerWidth' , { value : originalInnerWidth , configurable : true } ) ;
83+ document . body . innerHTML = '' ;
84+ } ) ;
85+
86+ it ( 'scrolls when the conversation is out of view' , ( ) => {
87+ const conversationId = 'conv-1' ;
88+ const element = document . createElement ( 'div' ) ;
89+ element . className = 'conversation-list-cell' ;
90+ element . dataset . uieUid = conversationId ;
91+ element . getBoundingClientRect = jest . fn ( ( ) => ( {
92+ top : - 10 ,
93+ left : 0 ,
94+ bottom : 10 ,
95+ right : 10 ,
96+ } ) ) as any ;
97+ element . scrollIntoView = jest . fn ( ) ;
98+ document . body . appendChild ( element ) ;
99+
100+ scrollToConversation ( conversationId ) ;
101+
102+ expect ( element . scrollIntoView ) . toHaveBeenCalledWith ( { behavior : 'instant' , block : 'center' , inline : 'nearest' } ) ;
103+ } ) ;
104+
105+ it ( 'does not scroll when the conversation is already visible' , ( ) => {
106+ const conversationId = 'conv-2' ;
107+ const element = document . createElement ( 'div' ) ;
108+ element . className = 'conversation-list-cell' ;
109+ element . dataset . uieUid = conversationId ;
110+ element . getBoundingClientRect = jest . fn ( ( ) => ( {
111+ top : 10 ,
112+ left : 10 ,
113+ bottom : 100 ,
114+ right : 100 ,
115+ } ) ) as any ;
116+ element . scrollIntoView = jest . fn ( ) ;
117+ document . body . appendChild ( element ) ;
118+
119+ scrollToConversation ( conversationId ) ;
120+
121+ expect ( element . scrollIntoView ) . not . toHaveBeenCalled ( ) ;
122+ } ) ;
123+ } ) ;
124+
125+ describe ( 'getConversationsWithHeadings' , ( ) => {
126+ it ( 'adds people and group headings when filtering recent conversations' , ( ) => {
127+ const directConversation = generateConversation ( { name : 'Direct' } ) ;
128+ jest . spyOn ( directConversation , 'isGroup' ) . mockReturnValue ( false as any ) ;
129+
130+ const groupConversation = generateConversation ( { name : 'Group' } ) ;
131+ jest . spyOn ( groupConversation , 'isGroup' ) . mockReturnValue ( true as any ) ;
132+
133+ const result = getConversationsWithHeadings ( [ directConversation , groupConversation ] , 'group' , SidebarTabs . RECENT ) ;
134+
135+ expect ( result [ 0 ] ) . toEqual ( { isHeader : true , heading : 'searchConversationNames' } ) ;
136+ expect ( result [ 1 ] ) . toBe ( directConversation ) ;
137+ expect ( result [ 2 ] ) . toEqual ( { isHeader : true , heading : 'searchGroupParticipants' } ) ;
138+ expect ( result [ 3 ] ) . toBe ( groupConversation ) ;
139+ } ) ;
140+
141+ it ( 'returns the list unchanged when not filtering recent conversations' , ( ) => {
142+ const conversation = generateConversation ( { name : 'Direct' } ) ;
143+ jest . spyOn ( conversation , 'isGroup' ) . mockReturnValue ( false as any ) ;
144+
145+ const result = getConversationsWithHeadings ( [ conversation ] , '' , SidebarTabs . RECENT ) ;
146+
147+ expect ( result ) . toEqual ( [ conversation ] ) ;
148+ } ) ;
149+ } ) ;
150+
26151describe ( 'getTabConversations' , ( ) => {
27152 let conversations : Conversation [ ] ;
28153 let groupConversations : Conversation [ ] ;
@@ -31,11 +156,31 @@ describe('getTabConversations', () => {
31156 let archivedConversations : Conversation [ ] ;
32157
33158 beforeEach ( ( ) => {
34- const conversation1 = generateConversation ( { name : 'Virgile' } ) ;
35- const conversation2 = generateConversation ( { name : 'Tim' } ) ;
36- const conversation3 = generateConversation ( { name : 'Bardia' } ) ;
37- const conversation4 = generateConversation ( { name : 'Tom' } ) ;
38- const conversation5 = generateConversation ( { name : 'Wêb Têam' } ) ;
159+ const conversation1 = generateConversation ( {
160+ name : 'Virgile' ,
161+ type : CONVERSATION_TYPE . ONE_TO_ONE ,
162+ users : [ generateUser ( undefined , { name : 'Virgile' } ) ] ,
163+ } ) ;
164+ const conversation2 = generateConversation ( {
165+ name : 'Tim' ,
166+ type : CONVERSATION_TYPE . ONE_TO_ONE ,
167+ users : [ generateUser ( undefined , { name : 'Tim' } ) ] ,
168+ } ) ;
169+ const conversation3 = generateConversation ( {
170+ name : 'Bardia' ,
171+ type : CONVERSATION_TYPE . ONE_TO_ONE ,
172+ users : [ generateUser ( undefined , { name : 'Bardia' } ) ] ,
173+ } ) ;
174+ const conversation4 = generateConversation ( {
175+ name : 'Tom' ,
176+ type : CONVERSATION_TYPE . ONE_TO_ONE ,
177+ users : [ generateUser ( undefined , { name : 'Tom' } ) ] ,
178+ } ) ;
179+ const conversation5 = generateConversation ( {
180+ name : 'Wêb Têam' ,
181+ type : CONVERSATION_TYPE . REGULAR ,
182+ users : [ generateUser ( undefined , { name : 'Wêb Têam' } ) ] ,
183+ } ) ;
39184
40185 conversations = [ conversation1 , conversation2 , conversation3 , conversation4 , conversation5 ] ;
41186 groupConversations = [ conversation5 ] ;
@@ -74,6 +219,28 @@ describe('getTabConversations', () => {
74219 expect ( searchInputPlaceholder ) . toBe ( 'searchGroupConversations' ) ;
75220 } ) ;
76221
222+ it ( 'should use group conversations when channels are enabled' , ( ) => {
223+ const extraConversation = generateConversation ( { name : 'Channel Group' } ) ;
224+ const channelAndGroupConversations = [ groupConversations [ 0 ] , extraConversation ] ;
225+
226+ const { conversations : filteredConversations , searchInputPlaceholder} = getTabConversations ( {
227+ currentTab : SidebarTabs . GROUPS ,
228+ conversations,
229+ groupConversations,
230+ directConversations,
231+ favoriteConversations,
232+ archivedConversations,
233+ conversationsFilter : '' ,
234+ channelAndGroupConversations,
235+ channelConversations : [ ] ,
236+ isChannelsEnabled : true ,
237+ draftConversations : [ ] ,
238+ } ) ;
239+
240+ expect ( filteredConversations ) . toEqual ( groupConversations ) ;
241+ expect ( searchInputPlaceholder ) . toBe ( 'searchGroupConversations' ) ;
242+ } ) ;
243+
77244 it ( 'should return direct conversations if the current tab is DIRECTS' , ( ) => {
78245 const { conversations : filteredConversations , searchInputPlaceholder} = runTest ( SidebarTabs . DIRECTS , '' ) ;
79246
@@ -152,4 +319,163 @@ describe('getTabConversations', () => {
152319 expect ( filteredConversations ) . toEqual ( [ ] ) ;
153320 expect ( searchInputPlaceholder ) . toBe ( '' ) ;
154321 } ) ;
322+
323+ it ( 'should return unread conversations when current tab is UNREAD' , ( ) => {
324+ const unreadConversation = generateConversation ( { name : 'Unread' } ) ;
325+ jest . spyOn ( unreadConversation , 'hasUnread' ) . mockReturnValue ( true as any ) ;
326+
327+ const readConversation = generateConversation ( { name : 'Read' } ) ;
328+ jest . spyOn ( readConversation , 'hasUnread' ) . mockReturnValue ( false as any ) ;
329+
330+ const { conversations : filteredConversations , searchInputPlaceholder} = getTabConversations ( {
331+ currentTab : SidebarTabs . UNREAD ,
332+ conversations : [ unreadConversation , readConversation ] ,
333+ groupConversations,
334+ directConversations,
335+ favoriteConversations,
336+ archivedConversations : [ ] ,
337+ conversationsFilter : '' ,
338+ channelAndGroupConversations : groupConversations ,
339+ channelConversations : [ ] ,
340+ isChannelsEnabled : false ,
341+ draftConversations : [ ] ,
342+ } ) ;
343+
344+ expect ( filteredConversations ) . toEqual ( [ unreadConversation ] ) ;
345+ expect ( searchInputPlaceholder ) . toBe ( 'searchUnreadConversations' ) ;
346+ } ) ;
347+
348+ it ( 'should return draft conversations when current tab is DRAFTS' , ( ) => {
349+ const draftConversation = generateConversation ( { name : 'Draft' } ) ;
350+ const nonDraftConversation = generateConversation ( { name : 'NoDraft' } ) ;
351+
352+ const { conversations : filteredConversations , searchInputPlaceholder} = getTabConversations ( {
353+ currentTab : SidebarTabs . DRAFTS ,
354+ conversations : [ draftConversation , nonDraftConversation ] ,
355+ groupConversations,
356+ directConversations,
357+ favoriteConversations,
358+ archivedConversations : [ ] ,
359+ conversationsFilter : '' ,
360+ channelAndGroupConversations : groupConversations ,
361+ channelConversations : [ ] ,
362+ isChannelsEnabled : false ,
363+ draftConversations : [ draftConversation ] ,
364+ } ) ;
365+
366+ expect ( filteredConversations ) . toEqual ( [ draftConversation ] ) ;
367+ expect ( searchInputPlaceholder ) . toBe ( 'searchDraftsConversations' ) ;
368+ } ) ;
369+
370+ it ( 'should return channels that are not archived when current tab is CHANNELS' , ( ) => {
371+ const activeChannel = generateConversation ( { name : 'Active Channel' } ) ;
372+ jest . spyOn ( activeChannel , 'is_archived' ) . mockReturnValue ( false as any ) ;
373+
374+ const archivedChannel = generateConversation ( { name : 'Archived Channel' } ) ;
375+ jest . spyOn ( archivedChannel , 'is_archived' ) . mockReturnValue ( true as any ) ;
376+
377+ const { conversations : filteredConversations , searchInputPlaceholder} = getTabConversations ( {
378+ currentTab : SidebarTabs . CHANNELS ,
379+ conversations : [ activeChannel , archivedChannel ] ,
380+ groupConversations,
381+ directConversations,
382+ favoriteConversations,
383+ archivedConversations : [ archivedChannel ] ,
384+ conversationsFilter : '' ,
385+ channelAndGroupConversations : groupConversations ,
386+ channelConversations : [ activeChannel , archivedChannel ] ,
387+ isChannelsEnabled : true ,
388+ draftConversations : [ ] ,
389+ } ) ;
390+
391+ expect ( filteredConversations ) . toEqual ( [ activeChannel ] ) ;
392+ expect ( searchInputPlaceholder ) . toBe ( 'searchChannelConversations' ) ;
393+ } ) ;
394+
395+ it ( 'should return conversations with mentions when current tab is MENTIONS' , ( ) => {
396+ const mentionsConversation = generateConversation ( { name : 'Mentions' } ) ;
397+ jest
398+ . spyOn ( mentionsConversation , 'unreadState' )
399+ . mockReturnValue ( { selfMentions : [ { } ] , selfReplies : [ ] , pings : [ ] } as any ) ;
400+
401+ const noMentionsConversation = generateConversation ( { name : 'NoMentions' } ) ;
402+ jest
403+ . spyOn ( noMentionsConversation , 'unreadState' )
404+ . mockReturnValue ( { selfMentions : [ ] , selfReplies : [ ] , pings : [ ] } as any ) ;
405+
406+ const { conversations : filteredConversations , searchInputPlaceholder} = getTabConversations ( {
407+ currentTab : SidebarTabs . MENTIONS ,
408+ conversations : [ mentionsConversation , noMentionsConversation ] ,
409+ groupConversations,
410+ directConversations,
411+ favoriteConversations,
412+ archivedConversations : [ ] ,
413+ conversationsFilter : '' ,
414+ channelAndGroupConversations : groupConversations ,
415+ channelConversations : [ ] ,
416+ isChannelsEnabled : false ,
417+ draftConversations : [ ] ,
418+ } ) ;
419+
420+ expect ( filteredConversations ) . toEqual ( [ mentionsConversation ] ) ;
421+ expect ( searchInputPlaceholder ) . toBe ( 'searchMentionsConversations' ) ;
422+ } ) ;
423+
424+ it ( 'should return conversations with replies when current tab is REPLIES' , ( ) => {
425+ const repliesConversation = generateConversation ( { name : 'Replies' } ) ;
426+ jest
427+ . spyOn ( repliesConversation , 'unreadState' )
428+ . mockReturnValue ( { selfMentions : [ ] , selfReplies : [ { } ] , pings : [ ] } as any ) ;
429+
430+ const noRepliesConversation = generateConversation ( { name : 'NoReplies' } ) ;
431+ jest
432+ . spyOn ( noRepliesConversation , 'unreadState' )
433+ . mockReturnValue ( { selfMentions : [ ] , selfReplies : [ ] , pings : [ ] } as any ) ;
434+
435+ const { conversations : filteredConversations , searchInputPlaceholder} = getTabConversations ( {
436+ currentTab : SidebarTabs . REPLIES ,
437+ conversations : [ repliesConversation , noRepliesConversation ] ,
438+ groupConversations,
439+ directConversations,
440+ favoriteConversations,
441+ archivedConversations : [ ] ,
442+ conversationsFilter : '' ,
443+ channelAndGroupConversations : groupConversations ,
444+ channelConversations : [ ] ,
445+ isChannelsEnabled : false ,
446+ draftConversations : [ ] ,
447+ } ) ;
448+
449+ expect ( filteredConversations ) . toEqual ( [ repliesConversation ] ) ;
450+ expect ( searchInputPlaceholder ) . toBe ( 'searchRepliesConversations' ) ;
451+ } ) ;
452+
453+ it ( 'should return conversations with pings when current tab is PINGS' , ( ) => {
454+ const pingsConversation = generateConversation ( { name : 'Pings' } ) ;
455+ jest
456+ . spyOn ( pingsConversation , 'unreadState' )
457+ . mockReturnValue ( { selfMentions : [ ] , selfReplies : [ ] , pings : [ { } ] } as any ) ;
458+
459+ const noPingsConversation = generateConversation ( { name : 'NoPings' } ) ;
460+ jest
461+ . spyOn ( noPingsConversation , 'unreadState' )
462+ . mockReturnValue ( { selfMentions : [ ] , selfReplies : [ ] , pings : [ ] } as any ) ;
463+
464+ const { conversations : filteredConversations , searchInputPlaceholder} = getTabConversations ( {
465+ currentTab : SidebarTabs . PINGS ,
466+ conversations : [ pingsConversation , noPingsConversation ] ,
467+ groupConversations,
468+ directConversations,
469+ favoriteConversations,
470+ archivedConversations : [ ] ,
471+ conversationsFilter : '' ,
472+ channelAndGroupConversations : groupConversations ,
473+ channelConversations : [ ] ,
474+ isChannelsEnabled : false ,
475+ draftConversations : [ ] ,
476+ } ) ;
477+
478+ expect ( filteredConversations ) . toEqual ( [ pingsConversation ] ) ;
479+ expect ( searchInputPlaceholder ) . toBe ( 'searchPingsConversations' ) ;
480+ } ) ;
155481} ) ;
0 commit comments