Skip to content

Commit d5b0b4c

Browse files
committed
test(tabs): expand filters test coverage
test(tabs): cover helper utilities test(tabs): cover groups channels flag test(tabs): clarify conversation types test(tabs): expand store and helpers coverage
1 parent 1ed830f commit d5b0b4c

File tree

5 files changed

+639
-6
lines changed

5 files changed

+639
-6
lines changed

apps/webapp/src/script/page/LeftSidebar/panels/Conversations/Helpers.test.tsx

Lines changed: 332 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,137 @@
1717
*
1818
*/
1919

20+
import {CONVERSATION_TYPE} from '@wireapp/api-client/lib/conversation';
21+
2022
import {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';
2230
import {generateConversation} from 'test/helper/ConversationGenerator';
31+
import {generateUser} from 'test/helper/UserGenerator';
2332

2433
import {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+
26151
describe('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

Comments
 (0)