From 92b6a50c343948b48415fe81de0b1f601aa02979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?= <60678893+juliajforesti@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:49:38 -0300 Subject: [PATCH] feat: add unread badge to sidebar collapser (#33897) --- .changeset/friendly-ravens-teach.md | 5 ++ .../client/sidebarv2/RoomList/RoomList.tsx | 12 +-- .../sidebarv2/RoomList/RoomListCollapser.tsx | 37 ++++++++ .../sidebarv2/hooks/useRoomList.spec.tsx | 78 +++++++++++------ .../client/sidebarv2/hooks/useRoomList.ts | 57 +++++++++---- apps/meteor/tests/e2e/feature-preview.spec.ts | 34 ++++++-- .../page-objects/fragments/home-sidenav.ts | 26 ------ .../tests/e2e/page-objects/fragments/index.ts | 1 + .../e2e/page-objects/fragments/sidebar.ts | 85 +++++++++++++++++++ .../tests/e2e/page-objects/home-channel.ts | 5 +- 10 files changed, 260 insertions(+), 80 deletions(-) create mode 100644 .changeset/friendly-ravens-teach.md create mode 100644 apps/meteor/client/sidebarv2/RoomList/RoomListCollapser.tsx create mode 100644 apps/meteor/tests/e2e/page-objects/fragments/sidebar.ts diff --git a/.changeset/friendly-ravens-teach.md b/.changeset/friendly-ravens-teach.md new file mode 100644 index 000000000000..1c464a8679b6 --- /dev/null +++ b/.changeset/friendly-ravens-teach.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +--- + +adds unread badge to sidebar collapser diff --git a/apps/meteor/client/sidebarv2/RoomList/RoomList.tsx b/apps/meteor/client/sidebarv2/RoomList/RoomList.tsx index 6afa27992fb2..226a32b2aebc 100644 --- a/apps/meteor/client/sidebarv2/RoomList/RoomList.tsx +++ b/apps/meteor/client/sidebarv2/RoomList/RoomList.tsx @@ -1,4 +1,4 @@ -import { Box, SidebarV2CollapseGroup } from '@rocket.chat/fuselage'; +import { Box } from '@rocket.chat/fuselage'; import { useResizeObserver } from '@rocket.chat/fuselage-hooks'; import { useUserPreference, useUserId } from '@rocket.chat/ui-contexts'; import React, { useMemo } from 'react'; @@ -13,6 +13,7 @@ import { usePreventDefault } from '../hooks/usePreventDefault'; import { useRoomList } from '../hooks/useRoomList'; import { useShortcutOpenMenu } from '../hooks/useShortcutOpenMenu'; import { useTemplateByViewMode } from '../hooks/useTemplateByViewMode'; +import RoomListCollapser from './RoomListCollapser'; import RoomListRow from './RoomListRow'; import RoomListRowWrapper from './RoomListRowWrapper'; import RoomListWrapper from './RoomListWrapper'; @@ -22,7 +23,7 @@ const RoomList = () => { const isAnonymous = !useUserId(); const { collapsedGroups, handleClick, handleKeyDown } = useCollapsedGroups(); - const { groupsCount, groupsList, roomList } = useRoomList({ collapsedGroups }); + const { groupsCount, groupsList, roomList, groupedUnreadInfo } = useRoomList({ collapsedGroups }); const avatarTemplate = useAvatarTemplate(); const sideBarItemTemplate = useTemplateByViewMode(); const { ref } = useResizeObserver({ debounceDelay: 100 }); @@ -51,11 +52,12 @@ const RoomList = () => { ( - handleClick(groupsList[index])} onKeyDown={(e) => handleKeyDown(e, groupsList[index])} - expanded={!collapsedGroups.includes(groupsList[index])} + groupTitle={groupsList[index]} + unreadCount={groupedUnreadInfo[index]} /> )} {...(roomList.length > 0 && { diff --git a/apps/meteor/client/sidebarv2/RoomList/RoomListCollapser.tsx b/apps/meteor/client/sidebarv2/RoomList/RoomListCollapser.tsx new file mode 100644 index 000000000000..3dfd17cb74dd --- /dev/null +++ b/apps/meteor/client/sidebarv2/RoomList/RoomListCollapser.tsx @@ -0,0 +1,37 @@ +import type { ISubscription } from '@rocket.chat/core-typings'; +import { Badge, SidebarV2CollapseGroup } from '@rocket.chat/fuselage'; +import type { HTMLAttributes, KeyboardEvent, MouseEventHandler } from 'react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useUnreadDisplay } from '../hooks/useUnreadDisplay'; + +type RoomListCollapserProps = { + groupTitle: string; + collapsedGroups: string[]; + onClick: MouseEventHandler; + onKeyDown: (e: KeyboardEvent) => void; + unreadCount: Pick; +} & Omit, 'onClick' | 'onKeyDown'>; +const RoomListCollapser = ({ groupTitle, unreadCount: unreadGroupCount, collapsedGroups, ...props }: RoomListCollapserProps) => { + const { t } = useTranslation(); + + const { unreadTitle, unreadVariant, showUnread, unreadCount } = useUnreadDisplay(unreadGroupCount); + + return ( + + {unreadCount.total} + + ) : undefined + } + {...props} + /> + ); +}; + +export default RoomListCollapser; diff --git a/apps/meteor/client/sidebarv2/hooks/useRoomList.spec.tsx b/apps/meteor/client/sidebarv2/hooks/useRoomList.spec.tsx index fc1c10c70cf7..27247d0cd8b4 100644 --- a/apps/meteor/client/sidebarv2/hooks/useRoomList.spec.tsx +++ b/apps/meteor/client/sidebarv2/hooks/useRoomList.spec.tsx @@ -13,43 +13,53 @@ const user = createFakeUser({ type: 'user', }); -const unreadRooms = [ - { ...createFakeSubscription({ t: 'c', unread: 1 }), ...createFakeRoom({ t: 'c' }) }, - { ...createFakeSubscription({ t: 'c', unread: 1 }), ...createFakeRoom({ t: 'c' }) }, - { ...createFakeSubscription({ t: 'c', unread: 1 }), ...createFakeRoom({ t: 'c' }) }, - { ...createFakeSubscription({ t: 'c', unread: 1 }), ...createFakeRoom({ t: 'c' }) }, +const emptyUnread = { + userMentions: 0, + groupMentions: 0, + unread: 0, + tunread: undefined, + tunreadUser: undefined, + tunreadGroup: undefined, + alert: false, +}; + +const unreadChannels = [ + { ...createFakeSubscription({ t: 'c', tunread: ['1'] }), ...createFakeRoom({ t: 'c' }) }, + { ...createFakeSubscription({ t: 'c', tunread: ['1'] }), ...createFakeRoom({ t: 'c' }) }, + { ...createFakeSubscription({ t: 'c', tunreadUser: ['1'] }), ...createFakeRoom({ t: 'c' }) }, + { ...createFakeSubscription({ t: 'c', tunreadUser: ['1'] }), ...createFakeRoom({ t: 'c' }) }, ]; const favoriteRooms = [ - { ...createFakeSubscription({ t: 'c', f: true, unread: undefined }), ...createFakeRoom({ t: 'c' }) }, - { ...createFakeSubscription({ t: 'c', f: true, unread: undefined }), ...createFakeRoom({ t: 'c' }) }, - { ...createFakeSubscription({ t: 'c', f: true, unread: undefined }), ...createFakeRoom({ t: 'c' }) }, + { ...createFakeSubscription({ t: 'c', f: true, ...emptyUnread }), ...createFakeRoom({ t: 'c' }) }, + { ...createFakeSubscription({ t: 'c', f: true, ...emptyUnread }), ...createFakeRoom({ t: 'c' }) }, + { ...createFakeSubscription({ t: 'c', f: true, ...emptyUnread }), ...createFakeRoom({ t: 'c' }) }, ]; const teams = [ - { ...createFakeSubscription({ unread: undefined }), ...createFakeRoom({ teamMain: true }) }, - { ...createFakeSubscription({ unread: undefined }), ...createFakeRoom({ teamMain: true }) }, - { ...createFakeSubscription({ unread: undefined }), ...createFakeRoom({ teamMain: true }) }, - { ...createFakeSubscription({ unread: undefined }), ...createFakeRoom({ teamMain: true }) }, - { ...createFakeSubscription({ unread: undefined }), ...createFakeRoom({ teamMain: true }) }, + { ...createFakeSubscription({ ...emptyUnread }), ...createFakeRoom({ teamMain: true }) }, + { ...createFakeSubscription({ ...emptyUnread }), ...createFakeRoom({ teamMain: true }) }, + { ...createFakeSubscription({ ...emptyUnread }), ...createFakeRoom({ teamMain: true }) }, + { ...createFakeSubscription({ ...emptyUnread }), ...createFakeRoom({ teamMain: true }) }, + { ...createFakeSubscription({ ...emptyUnread }), ...createFakeRoom({ teamMain: true }) }, ]; const discussionRooms = [ - { ...createFakeSubscription({ unread: undefined }), ...createFakeRoom({ prid: '123' }) }, - { ...createFakeSubscription({ unread: undefined }), ...createFakeRoom({ prid: '124' }) }, - { ...createFakeSubscription({ unread: undefined }), ...createFakeRoom({ prid: '125' }) }, - { ...createFakeSubscription({ unread: undefined }), ...createFakeRoom({ prid: '126' }) }, - { ...createFakeSubscription({ unread: undefined }), ...createFakeRoom({ prid: '127' }) }, + { ...createFakeSubscription({ ...emptyUnread }), ...createFakeRoom({ prid: '123' }) }, + { ...createFakeSubscription({ ...emptyUnread }), ...createFakeRoom({ prid: '124' }) }, + { ...createFakeSubscription({ ...emptyUnread }), ...createFakeRoom({ prid: '125' }) }, + { ...createFakeSubscription({ ...emptyUnread }), ...createFakeRoom({ prid: '126' }) }, + { ...createFakeSubscription({ ...emptyUnread }), ...createFakeRoom({ prid: '127' }) }, ]; const directRooms = [ - { ...createFakeSubscription({ t: 'd', unread: undefined }), ...createFakeRoom({ t: 'd' }) }, - { ...createFakeSubscription({ t: 'd', unread: undefined }), ...createFakeRoom({ t: 'd' }) }, - { ...createFakeSubscription({ t: 'd', unread: undefined }), ...createFakeRoom({ t: 'd' }) }, - { ...createFakeSubscription({ t: 'd', unread: undefined }), ...createFakeRoom({ t: 'd' }) }, + { ...createFakeSubscription({ t: 'd', ...emptyUnread }), ...createFakeRoom({ t: 'd' }) }, + { ...createFakeSubscription({ t: 'd', ...emptyUnread }), ...createFakeRoom({ t: 'd' }) }, + { ...createFakeSubscription({ t: 'd', ...emptyUnread }), ...createFakeRoom({ t: 'd' }) }, + { ...createFakeSubscription({ t: 'd', ...emptyUnread }), ...createFakeRoom({ t: 'd' }) }, ]; -const fakeRooms = [...unreadRooms, ...favoriteRooms, ...teams, ...discussionRooms, ...directRooms]; +const fakeRooms = [...unreadChannels, ...favoriteRooms, ...teams, ...discussionRooms, ...directRooms]; const emptyArr: any[] = []; @@ -228,7 +238,7 @@ it('should return "Unread" group with the correct items if sidebarShowUnread is }); const unreadIndex = result.current.groupsList.indexOf('Unread'); expect(result.current.groupsList).toContain('Unread'); - expect(result.current.groupsCount[unreadIndex]).toEqual(unreadRooms.length); + expect(result.current.groupsCount[unreadIndex]).toEqual(unreadChannels.length); }); it('should not include unread room in unread group if hideUnreadStatus is enabled', async () => { @@ -246,6 +256,22 @@ it('should not include unread room in unread group if hideUnreadStatus is enable const unreadIndex = result.current.groupsList.indexOf('Unread'); const roomListUnread = result.current.roomList.filter((room) => room.unread); - expect(result.current.groupsCount[unreadIndex]).toEqual(unreadRooms.length); - expect(roomListUnread.length).not.toEqual(unreadRooms.length); + expect(result.current.groupsCount[unreadIndex]).toEqual(unreadChannels.length); + expect(roomListUnread.length).not.toEqual(unreadChannels.length); +}); + +it('should accumulate unread data into `groupedUnreadInfo` when group is collapsed', async () => { + const { result } = renderHook(() => useRoomList({ collapsedGroups: ['Channels'] }), { + legacyRoot: true, + wrapper: getWrapperSettings({ sidebarGroupByType: true }).build(), + }); + + const channelsIndex = result.current.groupsList.indexOf('Channels'); + const { groupMentions, unread, userMentions, tunread, tunreadUser } = result.current.groupedUnreadInfo[channelsIndex]; + + expect(groupMentions).toEqual(fakeRooms.reduce((acc, cv) => acc + cv.groupMentions, 0)); + expect(unread).toEqual(fakeRooms.reduce((acc, cv) => acc + cv.unread, 0)); + expect(userMentions).toEqual(fakeRooms.reduce((acc, cv) => acc + cv.userMentions, 0)); + expect(tunread).toEqual(fakeRooms.reduce((acc, cv) => [...acc, ...(cv.tunread || [])], [] as string[])); + expect(tunreadUser).toEqual(fakeRooms.reduce((acc, cv) => [...acc, ...(cv.tunreadUser || [])], [] as string[])); }); diff --git a/apps/meteor/client/sidebarv2/hooks/useRoomList.ts b/apps/meteor/client/sidebarv2/hooks/useRoomList.ts index 5f9a1234c269..d80d32e1e6cd 100644 --- a/apps/meteor/client/sidebarv2/hooks/useRoomList.ts +++ b/apps/meteor/client/sidebarv2/hooks/useRoomList.ts @@ -1,6 +1,6 @@ import type { ILivechatInquiryRecord, IRoom, ISubscription } from '@rocket.chat/core-typings'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import type { TranslationKey } from '@rocket.chat/ui-contexts'; +import type { SubscriptionWithRoom, TranslationKey } from '@rocket.chat/ui-contexts'; import { useUserPreference, useUserSubscriptions, useSetting } from '@rocket.chat/ui-contexts'; import { useMemo } from 'react'; @@ -27,15 +27,16 @@ const order = [ 'Conversations', ] as const; -export const useRoomList = ({ - collapsedGroups, -}: { - collapsedGroups?: string[]; -}): { +type useRoomListReturnType = { roomList: Array; groupsCount: number[]; groupsList: TranslationKey[]; -} => { + groupedUnreadInfo: Pick< + SubscriptionWithRoom, + 'userMentions' | 'groupMentions' | 'unread' | 'tunread' | 'tunreadUser' | 'tunreadGroup' | 'alert' | 'hideUnreadStatus' + >[]; +}; +export const useRoomList = ({ collapsedGroups }: { collapsedGroups?: string[] }): useRoomListReturnType => { const showOmnichannel = useOmnichannelEnabled(); const sidebarGroupByType = useUserPreference('sidebarGroupByType'); const favoritesEnabled = useUserPreference('sidebarShowFavorites'); @@ -53,7 +54,7 @@ export const useRoomList = ({ const queue = inquiries.enabled ? inquiries.queue : emptyQueue; - const { groupsCount, groupsList, roomList } = useDebouncedValue( + const { groupsCount, groupsList, roomList, groupedUnreadInfo } = useDebouncedValue( useMemo(() => { const isCollapsed = (groupTitle: string) => collapsedGroups?.includes(groupTitle); @@ -133,7 +134,7 @@ export const useRoomList = ({ !sidebarGroupByType && groups.set('Conversations', conversation); - const { groupsCount, groupsList, roomList } = sidebarOrder.reduce( + const { groupsCount, groupsList, roomList, groupedUnreadInfo } = sidebarOrder.reduce( (acc, key) => { const value = groups.get(key); @@ -142,11 +143,39 @@ export const useRoomList = ({ } acc.groupsList.push(key as TranslationKey); + + const groupedUnreadInfoAcc = { + userMentions: 0, + groupMentions: 0, + tunread: [], + tunreadUser: [], + unread: 0, + }; + if (isCollapsed(key)) { + const groupedUnreadInfo = [...value].reduce( + (counter, { userMentions, groupMentions, tunread, tunreadUser, unread, alert, hideUnreadStatus }) => { + if (hideUnreadStatus) { + return counter; + } + + counter.userMentions += userMentions || 0; + counter.groupMentions += groupMentions || 0; + counter.tunread = [...counter.tunread, ...(tunread || [])]; + counter.tunreadUser = [...counter.tunreadUser, ...(tunreadUser || [])]; + counter.unread += unread || 0; + !unread && !tunread?.length && alert && (counter.unread += 1); + return counter; + }, + groupedUnreadInfoAcc, + ); + + acc.groupedUnreadInfo.push(groupedUnreadInfo); acc.groupsCount.push(0); return acc; } + acc.groupedUnreadInfo.push(groupedUnreadInfoAcc); acc.groupsCount.push(value.size); acc.roomList.push(...value); return acc; @@ -155,14 +184,11 @@ export const useRoomList = ({ groupsCount: [], groupsList: [], roomList: [], - } as { - groupsCount: number[]; - groupsList: TranslationKey[]; - roomList: Array; - }, + groupedUnreadInfo: [], + } as useRoomListReturnType, ); - return { groupsCount, groupsList, roomList }; + return { groupsCount, groupsList, roomList, groupedUnreadInfo }; }, [ rooms, showOmnichannel, @@ -183,5 +209,6 @@ export const useRoomList = ({ roomList, groupsCount, groupsList, + groupedUnreadInfo, }; }; diff --git a/apps/meteor/tests/e2e/feature-preview.spec.ts b/apps/meteor/tests/e2e/feature-preview.spec.ts index 67f8133d8a2a..c19e894e3811 100644 --- a/apps/meteor/tests/e2e/feature-preview.spec.ts +++ b/apps/meteor/tests/e2e/feature-preview.spec.ts @@ -1,6 +1,6 @@ import { Users } from './fixtures/userStates'; import { AccountProfile, HomeChannel } from './page-objects'; -import { setSettingValueById } from './utils'; +import { createTargetChannel, setSettingValueById } from './utils'; import { setUserPreferences } from './utils/setUserPreferences'; import { test, expect } from './utils/test'; @@ -9,13 +9,16 @@ test.use({ storageState: Users.admin.state }); test.describe.serial('feature preview', () => { let poHomeChannel: HomeChannel; let poAccountProfile: AccountProfile; + let targetChannel: string; test.beforeAll(async ({ api }) => { await setSettingValueById(api, 'Accounts_AllowFeaturePreview', true); + targetChannel = await createTargetChannel(api); }); test.afterAll(async ({ api }) => { await setSettingValueById(api, 'Accounts_AllowFeaturePreview', false); + await api.post('/channels.delete', { roomName: targetChannel }); }); test.beforeEach(async ({ page }) => { @@ -73,13 +76,13 @@ test.describe.serial('feature preview', () => { test('should display "Recent" button on sidebar search section, and display recent chats when clicked', async ({ page }) => { await page.goto('/home'); - await poHomeChannel.sidenav.btnRecent.click(); - await expect(poHomeChannel.sidenav.sidebar.getByRole('heading', { name: 'Recent' })).toBeVisible(); + await poHomeChannel.sidebar.btnRecent.click(); + await expect(poHomeChannel.sidebar.sidebar.getByRole('heading', { name: 'Recent' })).toBeVisible(); }); test('should expand/collapse sidebar groups', async ({ page }) => { await page.goto('/home'); - const collapser = poHomeChannel.sidenav.firstCollapser; + const collapser = poHomeChannel.sidebar.firstCollapser; let isExpanded: boolean; await collapser.click(); @@ -94,7 +97,7 @@ test.describe.serial('feature preview', () => { test('should expand/collapse sidebar groups with keyboard', async ({ page }) => { await page.goto('/home'); - const collapser = poHomeChannel.sidenav.firstCollapser; + const collapser = poHomeChannel.sidebar.firstCollapser; await expect(async () => { await collapser.focus(); @@ -115,7 +118,7 @@ test.describe.serial('feature preview', () => { test('should be able to use keyboard to navigate through sidebar items', async ({ page }) => { await page.goto('/home'); - const collapser = poHomeChannel.sidenav.firstCollapser; + const collapser = poHomeChannel.sidebar.firstCollapser; const dataIndex = await collapser.locator('../..').getAttribute('data-index'); const nextItem = page.locator(`[data-index="${Number(dataIndex) + 1}"]`).getByRole('link'); @@ -129,7 +132,7 @@ test.describe.serial('feature preview', () => { test('should persist collapsed/expanded groups after page reload', async ({ page }) => { await page.goto('/home'); - const collapser = poHomeChannel.sidenav.firstCollapser; + const collapser = poHomeChannel.sidebar.firstCollapser; await collapser.click(); const isExpanded = await collapser.getAttribute('aria-expanded'); @@ -138,5 +141,22 @@ test.describe.serial('feature preview', () => { const isExpandedAfterReload = await collapser.getAttribute('aria-expanded'); expect(isExpanded).toEqual(isExpandedAfterReload); }); + + test('should show unread badge on collapser when group is collapsed and has unread items', async ({ page }) => { + await page.goto('/home'); + + await poHomeChannel.sidebar.openChat(targetChannel); + await poHomeChannel.content.sendMessage('hello world'); + + await poHomeChannel.sidebar.typeSearch(targetChannel); + const item = poHomeChannel.sidebar.getSearchRoomByName(targetChannel); + await poHomeChannel.sidebar.markItemAsUnread(item); + await poHomeChannel.sidebar.escSearch(); + + const collapser = poHomeChannel.sidebar.firstCollapser; + await collapser.click(); + + await expect(poHomeChannel.sidebar.getItemUnreadBadge(collapser)).toBeVisible(); + }); }); }); diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts index c5454cf14999..46f806104420 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts @@ -188,30 +188,4 @@ export class HomeSidenav { getSearchChannelBadge(name: string): Locator { return this.page.locator(`[data-qa="sidebar-item"][aria-label="${name}"]`).first().getByRole('status', { exact: true }); } - - // New navigation selectors - - get sidebar(): Locator { - return this.page.getByRole('navigation', { name: 'sidebar' }); - } - - get sidebarSearchSection(): Locator { - return this.sidebar.getByRole('search'); - } - - get btnRecent(): Locator { - return this.sidebarSearchSection.getByRole('button', { name: 'Recent' }); - } - - get channelsList(): Locator { - return this.sidebar.getByRole('list', { name: 'Channels' }); - } - - getCollapseGroupByName(name: string): Locator { - return this.channelsList.getByRole('button', { name, exact: true }); - } - - get firstCollapser(): Locator { - return this.channelsList.getByRole('button').first(); - } } diff --git a/apps/meteor/tests/e2e/page-objects/fragments/index.ts b/apps/meteor/tests/e2e/page-objects/fragments/index.ts index 02f1769ba7ea..fc5ab5e62385 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/index.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/index.ts @@ -5,3 +5,4 @@ export * from './home-sidenav'; export * from './omnichannel-sidenav'; export * from './omnichannel-close-chat-modal'; export * from './navbar'; +export * from './sidebar'; diff --git a/apps/meteor/tests/e2e/page-objects/fragments/sidebar.ts b/apps/meteor/tests/e2e/page-objects/fragments/sidebar.ts new file mode 100644 index 000000000000..b19fccd141a7 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/fragments/sidebar.ts @@ -0,0 +1,85 @@ +import type { Locator, Page } from '@playwright/test'; + +import { expect } from '../../utils/test'; + +export class Sidebar { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + // New navigation locators + get sidebar(): Locator { + return this.page.getByRole('navigation', { name: 'sidebar' }); + } + + get sidebarSearchSection(): Locator { + return this.sidebar.getByRole('search'); + } + + get btnRecent(): Locator { + return this.sidebarSearchSection.getByRole('button', { name: 'Recent' }); + } + + get channelsList(): Locator { + return this.sidebar.getByRole('list', { name: 'Channels' }); + } + + get searchList(): Locator { + return this.sidebar.getByRole('search').getByRole('list', { name: 'Channels' }); + } + + get firstCollapser(): Locator { + return this.channelsList.getByRole('button').first(); + } + + get firstChannelFromList(): Locator { + return this.channelsList.getByRole('listitem').first(); + } + + get searchInput(): Locator { + return this.sidebarSearchSection.getByRole('searchbox'); + } + + async escSearch(): Promise { + await this.page.keyboard.press('Escape'); + } + + async waitForChannel(): Promise { + await this.page.locator('role=main').waitFor(); + await this.page.locator('role=main >> role=heading[level=1]').waitFor(); + await this.page.locator('role=main >> role=list').waitFor(); + + await expect(this.page.locator('role=main >> role=list')).not.toHaveAttribute('aria-busy', 'true'); + } + + async typeSearch(name: string): Promise { + return this.searchInput.fill(name); + } + + getSearchRoomByName(name: string): Locator { + return this.searchList.getByRole('link', { name }); + } + + async openChat(name: string): Promise { + await this.typeSearch(name); + await this.getSearchRoomByName(name).click(); + await this.waitForChannel(); + } + + async markItemAsUnread(item: Locator): Promise { + await item.hover(); + await item.focus(); + await item.locator('.rcx-sidebar-item__menu').click(); + await this.page.getByRole('option', { name: 'Mark Unread' }).click(); + } + + getCollapseGroupByName(name: string): Locator { + return this.channelsList.getByRole('button', { name, exact: true }); + } + + getItemUnreadBadge(item: Locator): Locator { + return item.getByRole('status', { name: 'unread' }); + } +} diff --git a/apps/meteor/tests/e2e/page-objects/home-channel.ts b/apps/meteor/tests/e2e/page-objects/home-channel.ts index 15b471e15b4c..76929b6c2dcf 100644 --- a/apps/meteor/tests/e2e/page-objects/home-channel.ts +++ b/apps/meteor/tests/e2e/page-objects/home-channel.ts @@ -1,6 +1,6 @@ import type { Locator, Page } from '@playwright/test'; -import { HomeContent, HomeSidenav, HomeFlextab, Navbar } from './fragments'; +import { HomeContent, HomeSidenav, HomeFlextab, Navbar, Sidebar } from './fragments'; export class HomeChannel { public readonly page: Page; @@ -9,6 +9,8 @@ export class HomeChannel { readonly sidenav: HomeSidenav; + readonly sidebar: Sidebar; + readonly navbar: Navbar; readonly tabs: HomeFlextab; @@ -17,6 +19,7 @@ export class HomeChannel { this.page = page; this.content = new HomeContent(page); this.sidenav = new HomeSidenav(page); + this.sidebar = new Sidebar(page); this.navbar = new Navbar(page); this.tabs = new HomeFlextab(page); }