From 16a73eea7c5d61493e10fad49bf06d72e36af9f5 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Mon, 8 Apr 2024 14:43:14 -0300 Subject: [PATCH 01/27] feat: persisting the room filter between navigation on the admin section --- .changeset/brown-lobsters-join.md | 7 ++ .../client/contexts/SearchFilterContext.ts | 18 +++ .../client/providers/MeteorProvider.tsx | 5 +- .../SearchFilterProvider.spec.tsx | 87 +++++++++++++ .../SearchFilterProvider.tsx | 16 +++ .../client/views/admin/rooms/RoomsTable.tsx | 35 +++--- .../views/admin/rooms/RoomsTableFilters.tsx | 28 ++--- apps/meteor/tests/e2e/admin-room.spec.ts | 117 ++++++++++++++++++ .../tests/e2e/app-modal-interaction.spec.ts | 74 +++++++++++ 9 files changed, 354 insertions(+), 33 deletions(-) create mode 100644 .changeset/brown-lobsters-join.md create mode 100644 apps/meteor/client/contexts/SearchFilterContext.ts create mode 100644 apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.spec.tsx create mode 100644 apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.tsx create mode 100644 apps/meteor/tests/e2e/admin-room.spec.ts create mode 100644 apps/meteor/tests/e2e/app-modal-interaction.spec.ts diff --git a/.changeset/brown-lobsters-join.md b/.changeset/brown-lobsters-join.md new file mode 100644 index 000000000000..b6fecbf01958 --- /dev/null +++ b/.changeset/brown-lobsters-join.md @@ -0,0 +1,7 @@ +--- +'@rocket.chat/meteor': patch +--- + +Update: Fixed Room Filter Persistence in Admin Navigation + +Resolved an issue causing room filter settings to reset on admin page navigation. Now, filter selections remain intact across different admin sections, streamlining task management and reducing redundant inputs. diff --git a/apps/meteor/client/contexts/SearchFilterContext.ts b/apps/meteor/client/contexts/SearchFilterContext.ts new file mode 100644 index 000000000000..de8f4fb7a552 --- /dev/null +++ b/apps/meteor/client/contexts/SearchFilterContext.ts @@ -0,0 +1,18 @@ +import type { OptionProp } from '@rocket.chat/ui-client'; +import type { Dispatch, SetStateAction } from 'react'; +import { createContext } from 'react'; + +export type SearchFilters = { + searchText: string; + types: OptionProp[]; +}; + +type SearchFilterContextValue = { + searchFilters: SearchFilters; + setSearchFilters: Dispatch>; +}; + +export const SearchFilterContext = createContext({ + searchFilters: { searchText: '', types: [] }, + setSearchFilters: () => undefined, +}); diff --git a/apps/meteor/client/providers/MeteorProvider.tsx b/apps/meteor/client/providers/MeteorProvider.tsx index cf6b14e7946b..7afc251a71f4 100644 --- a/apps/meteor/client/providers/MeteorProvider.tsx +++ b/apps/meteor/client/providers/MeteorProvider.tsx @@ -15,6 +15,7 @@ import LayoutProvider from './LayoutProvider'; import ModalProvider from './ModalProvider/ModalProvider'; import OmnichannelProvider from './OmnichannelProvider'; import RouterProvider from './RouterProvider'; +import { SearchFilterProvider } from './SearchFiltersProvider/SearchFilterProvider'; import ServerProvider from './ServerProvider'; import SessionProvider from './SessionProvider'; import SettingsProvider from './SettingsProvider'; @@ -48,7 +49,9 @@ const MeteorProvider: FC = ({ children }) => ( - {children} + + {children} + diff --git a/apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.spec.tsx b/apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.spec.tsx new file mode 100644 index 000000000000..84e71832779e --- /dev/null +++ b/apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.spec.tsx @@ -0,0 +1,87 @@ +/* eslint-disable no-await-in-loop */ +import '@testing-library/jest-dom'; +import type { OptionProp } from '@rocket.chat/ui-client'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React, { useContext } from 'react'; + +import type { SearchFilters } from '../../contexts/SearchFilterContext'; +import { SearchFilterContext } from '../../contexts/SearchFilterContext'; +import { SearchFilterProvider } from './SearchFilterProvider'; + +const DropdownTestComponent = () => { + const { searchFilters, setSearchFilters } = useContext(SearchFilterContext); + + const handleCheckboxChange = (id: string) => { + setSearchFilters({ + ...searchFilters, + types: searchFilters.types.map((type) => (type.id === id ? { ...type, checked: !type.checked } : type)), + } as SearchFilters); + }; + + const handleSearchTextChange = (e: React.ChangeEvent) => { + setSearchFilters({ ...searchFilters, searchText: e.target.value }); + }; + + return ( +
+ + {searchFilters.types.map((option) => + option.isGroupTitle ? ( +
{option.text}
+ ) : ( + + ), + )} +
+ ); +}; + +describe('SearchFilterProvider', () => { + test('handles types in context correctly', async () => { + const initialTypes = [ + { id: '1', text: 'Type 1', isGroupTitle: false, checked: false }, + { id: '2', text: 'Type 2', isGroupTitle: false, checked: false }, + ] as OptionProp[]; + + render( + + + , + ); + + for (const type of initialTypes) { + const checkbox = await screen.findByLabelText(type.text); + expect(checkbox).not.toBeChecked(); + } + + const firstCheckbox = await screen.findByLabelText(initialTypes[0].text); + userEvent.click(firstCheckbox); + + const updatedFirstCheckbox = await screen.findByLabelText(initialTypes[0].text); + expect(updatedFirstCheckbox).toBeChecked(); + }); + + test('handles searchText and types in context correctly', async () => { + const initialTypes: OptionProp[] = [ + { id: '1', text: 'Type 1', isGroupTitle: false, checked: false }, + { id: '2', text: 'Type 2', isGroupTitle: false, checked: false }, + ] as OptionProp[]; + + render( + + + , + ); + + const searchInput = screen.getByLabelText('Search'); + expect(searchInput).toHaveValue(''); + + userEvent.type(searchInput, 'New Search Text'); + + expect(searchInput).toHaveValue('New Search Text'); + }); +}); diff --git a/apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.tsx b/apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.tsx new file mode 100644 index 000000000000..2f0efc2bd797 --- /dev/null +++ b/apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.tsx @@ -0,0 +1,16 @@ +import type { FC, ReactNode } from 'react'; +import React, { useState } from 'react'; + +import type { SearchFilters } from '../../contexts/SearchFilterContext'; +import { SearchFilterContext } from '../../contexts/SearchFilterContext'; + +type SearchFilterProviderProps = { + children: ReactNode; + initialState?: SearchFilters; +}; + +export const SearchFilterProvider: FC = ({ children, initialState }) => { + const [searchFilters, setSearchFilters] = useState(initialState || { searchText: '', types: [] }); + + return {children}; +}; diff --git a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx index 094ccb95857a..b8474fd369b8 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx @@ -1,10 +1,9 @@ import { Pagination, States, StatesIcon, StatesTitle, StatesActions, StatesAction } from '@rocket.chat/fuselage'; import { useMediaQuery, useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import type { OptionProp } from '@rocket.chat/ui-client'; import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; import type { ReactElement, MutableRefObject } from 'react'; -import React, { useRef, useState, useEffect, useMemo } from 'react'; +import React, { useRef, useEffect, useMemo, useContext } from 'react'; import GenericNoResults from '../../../components/GenericNoResults'; import { @@ -16,41 +15,43 @@ import { } from '../../../components/GenericTable'; import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; import { useSort } from '../../../components/GenericTable/hooks/useSort'; +import type { SearchFilters } from '../../../contexts/SearchFilterContext'; +import { SearchFilterContext } from '../../../contexts/SearchFilterContext'; import RoomRow from './RoomRow'; import RoomsTableFilters from './RoomsTableFilters'; -type RoomFilters = { - searchText: string; - types: OptionProp[]; -}; - const DEFAULT_TYPES = ['d', 'p', 'c', 'l', 'discussions', 'teams']; const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): ReactElement => { const t = useTranslation(); const mediaQuery = useMediaQuery('(min-width: 1024px)'); - const [roomFilters, setRoomFilters] = useState({ searchText: '', types: [] }); + const { searchFilters } = useContext(SearchFilterContext); - const prevRoomFilterText = useRef(roomFilters.searchText); + const prevRoomFilters = useRef(searchFilters); const { sortBy, sortDirection, setSort } = useSort<'name' | 't' | 'usersCount' | 'msgs' | 'default' | 'featured'>('name'); const { current, itemsPerPage, setItemsPerPage, setCurrent, ...paginationProps } = usePagination(); - const searchText = useDebouncedValue(roomFilters.searchText, 500); + const searchText = useDebouncedValue(searchFilters.searchText, 500); const query = useDebouncedValue( useMemo(() => { - if (searchText !== prevRoomFilterText.current) { + const filtersChanged = + searchText !== prevRoomFilters.current.searchText || + JSON.stringify(searchFilters.types) !== JSON.stringify(prevRoomFilters.current.types); + + if (filtersChanged) { setCurrent(0); } + return { filter: searchText || '', sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`, count: itemsPerPage, - offset: searchText === prevRoomFilterText.current ? current : 0, - types: roomFilters.types.length ? [...roomFilters.types.map((roomType) => roomType.id)] : DEFAULT_TYPES, + offset: filtersChanged ? 0 : current, + types: searchFilters.types.length ? [...searchFilters.types.map((roomType) => roomType.id)] : DEFAULT_TYPES, }; - }, [searchText, sortBy, sortDirection, itemsPerPage, current, roomFilters.types, setCurrent]), + }, [searchText, sortBy, sortDirection, itemsPerPage, current, searchFilters.types, setCurrent]), 500, ); @@ -63,8 +64,8 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React }, [reload, refetch]); useEffect(() => { - prevRoomFilterText.current = searchText; - }, [searchText]); + prevRoomFilters.current = searchFilters; + }, [searchFilters]); const headers = ( <> @@ -116,7 +117,7 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React return ( <> - + {isLoading && ( {headers} diff --git a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx index fcdaa29c9dff..4b4338dba402 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx @@ -2,9 +2,11 @@ import { Box, Icon, TextInput } from '@rocket.chat/fuselage'; import type { OptionProp } from '@rocket.chat/ui-client'; import { MultiSelectCustom } from '@rocket.chat/ui-client'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useContext } from 'react'; import type { Dispatch, ReactElement, SetStateAction } from 'react'; +import { SearchFilterContext } from '../../../contexts/SearchFilterContext'; + const roomTypeFilterStructure = [ { id: 'filter_by_room', @@ -43,27 +45,23 @@ const roomTypeFilterStructure = [ }, ] as OptionProp[]; -const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch> }): ReactElement => { +const RoomsTableFilters = (): ReactElement => { + const { searchFilters, setSearchFilters } = useContext(SearchFilterContext); const t = useTranslation(); - const [text, setText] = useState(''); - - const [roomTypeSelectedOptions, setRoomTypeSelectedOptions] = useState([]); const handleSearchTextChange = useCallback( - (event) => { - const text = event.currentTarget.value; - setFilters({ searchText: text, types: roomTypeSelectedOptions }); - setText(text); + (event: React.ChangeEvent) => { + const newText = event.currentTarget.value; + setSearchFilters((filters) => ({ ...filters, searchText: newText, types: searchFilters.types })); }, - [roomTypeSelectedOptions, setFilters], + [searchFilters.types, setSearchFilters], ); const handleRoomTypeChange = useCallback( (options: OptionProp[]) => { - setFilters({ searchText: text, types: options }); - setRoomTypeSelectedOptions(options); + setSearchFilters((filters) => ({ ...filters, types: options, searchText: searchFilters.searchText })); }, - [text, setFilters], + [searchFilters.searchText, setSearchFilters], ) as Dispatch>; return ( @@ -83,7 +81,7 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch} onChange={handleSearchTextChange} - value={text} + value={searchFilters.searchText} /> @@ -92,7 +90,7 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch diff --git a/apps/meteor/tests/e2e/admin-room.spec.ts b/apps/meteor/tests/e2e/admin-room.spec.ts new file mode 100644 index 000000000000..76c0ad1df444 --- /dev/null +++ b/apps/meteor/tests/e2e/admin-room.spec.ts @@ -0,0 +1,117 @@ +import { Page } from '@playwright/test'; + +import { Users } from './fixtures/userStates'; +import { createTargetChannel, createTargetPrivateChannel } from './utils'; +import { expect, test } from './utils/test'; + +test.use({ storageState: Users.admin.state }); + +test.describe.serial('admin-rooms', () => { + let adminPage: Page; + let channel: string; + let privateRoom: string; + + test.beforeEach(async ({ page }) => { + await page.goto('/admin/rooms'); + }); + + test.beforeAll(async ({ browser, api }) => { + adminPage = await browser.newPage({ storageState: Users.admin.state }); + await adminPage.goto('/home'); + await adminPage.waitForSelector('[data-qa-id="home-header"]'); + + channel = await createTargetChannel(api); + privateRoom = await createTargetPrivateChannel(api); + }); + test('should display the Rooms Table', async ({ page }) => { + await expect(page.locator('[data-qa-type="PageHeader-title"]')).toContainText('Rooms'); + await expect(page.locator(`[qa-room-name="${privateRoom}"]`)).toBeVisible(); + await expect(page.locator(`[qa-room-name="${channel}"]`)).toBeVisible(); + }); + + test('should filter room by name', async ({ page }) => { + const input = page.locator('input[type="text"]'); + console.log('🚀 ~ test ~ channel:', channel); + + await input.click(); + await input.fill(channel); + + await expect(page.locator(`[qa-room-name="${channel}"]`)).toBeVisible(); + }); + + test('should filter rooms by type', async ({ page }) => { + const dropdown = page.locator('text=All rooms'); + await dropdown.click(); + + const privateOption = page.locator('text=Private channels'); + + await privateOption.waitFor(); + await privateOption.click(); + + await expect(page.locator(`[qa-room-name="${privateRoom}"]`)).toBeVisible(); + }); + + test('should filter rooms by type and name', async ({ page }) => { + const input = page.locator('input[type="text"]'); + + await input.click(); + await input.fill(privateRoom); + + const dropdown = page.locator('text=All rooms'); + await dropdown.click(); + + const privateOption = page.locator('text=Private channels'); + + await privateOption.waitFor(); + await privateOption.click(); + + await expect(page.locator(`[qa-room-name="${privateRoom}"]`)).toBeVisible(); + }); + + test('should be empty in case of the search does not find any room', async ({ page }) => { + const input = page.locator('input[type="text"]'); + + await input.click(); + await input.fill('Wrong-channel'); + + const dropdown = page.locator('text=All rooms'); + await dropdown.click(); + + const privateOption = page.locator('text=Private channels'); + + await privateOption.waitFor(); + await privateOption.click(); + + await expect(page.locator('text=No results found')).toBeVisible(); + }); + + test.only('should filter rooms by type and name and keep the filter after changing section', async ({ page }) => { + const input = page.locator('input[type="text"]'); + + await input.click(); + await input.fill(privateRoom); + + const dropdown = page.locator('text=All rooms'); + await dropdown.click(); + + const privateOption = page.locator('text=Private channels'); + + await privateOption.waitFor(); + await privateOption.click(); + + await page.locator('text=Workspace').click(); + + await page.locator('text=Rooms').click(); + + const selectDropdown = page.locator('text=Rooms (1)'); + await expect(selectDropdown).toBeVisible(); + + await selectDropdown.click(); + + const isChecked = await privateOption.isChecked(); + + expect(isChecked).toBe(true); + + await expect(input).toHaveValue(privateRoom); + }); +}); diff --git a/apps/meteor/tests/e2e/app-modal-interaction.spec.ts b/apps/meteor/tests/e2e/app-modal-interaction.spec.ts new file mode 100644 index 000000000000..2124a9607e13 --- /dev/null +++ b/apps/meteor/tests/e2e/app-modal-interaction.spec.ts @@ -0,0 +1,74 @@ +import { Users } from './fixtures/userStates'; +import { HomeChannel } from './page-objects'; +import { createTargetChannel } from './utils/create-target-channel'; +import { test, expect } from './utils/test'; + +test.use({ storageState: Users.admin.state }); + +test.describe.serial('app-surfaces-interaction', () => { + let poHomeChannel: HomeChannel; + let targetChannel: string; + + test.beforeAll(async ({ api }) => { + targetChannel = await createTargetChannel(api); + }); + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + + await page.goto('/home'); + }); + + test('expect to submit an success modal', async ({ page }) => { + await poHomeChannel.sidenav.openChat(targetChannel); + await page.locator('role=button[name="Options"]').click(); + await page.locator('[data-key="success"]').click(); + await page.locator('role=button[name="success"]').click(); + + const updatedButton = page.locator('role=button[name="success"]'); + await expect(updatedButton).not.toBeVisible(); + }); + + test('expect to not close the modal and there is an error in the modal', async ({ page }) => { + await poHomeChannel.sidenav.openChat(targetChannel); + await page.locator('role=button[name="Options"]').click(); + await page.locator('[data-key="error"]').click(); + await page.locator('role=button[name="error"]').click(); + + const updatedTitle = page.locator('role=button[name="error"]'); + expect(updatedTitle).toBeDefined(); + + const input = page.locator('input[type="text"]'); + + await input.click(); + await input.fill('fixed'); + + await page.locator('role=button[name="error"]').click(); + await expect(input).not.toBeVisible(); + }); + + test('expect to show the toaster error for modal that timeout the execution', async ({ page }) => { + await poHomeChannel.sidenav.openChat(targetChannel); + await page.locator('role=button[name="Options"]').click(); + await page.locator('[data-key="timeout"]').click(); + await page.locator('role=button[name="timeout"]').click(); + await page.locator('role=alert').waitFor(); + + const toaster = page.locator('role=alert'); + + await expect(toaster).toBeVisible(); + }); + + test('expect change the modal and then submit the updated modal', async ({ page }) => { + await poHomeChannel.sidenav.openChat(targetChannel); + await page.locator('role=button[name="Options"]').click(); + await page.locator('[data-key="update"]').click(); + await page.locator('role=button[name="update"]').click(); + + const updatedTitle = page.locator('role=button[name="title updated"]'); + expect(updatedTitle).toBeDefined(); + + const updatedButton = page.locator('role=button[name="updated"]'); + expect(updatedButton).toBeDefined(); + }); +}); From 45cffd969ca7f426bfd549025cb6617edeeab745 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Fri, 12 Apr 2024 17:00:08 -0300 Subject: [PATCH 02/27] feat: changing the logic from context/provider to local storage --- .../client/contexts/SearchFilterContext.ts | 18 ---- .../client/providers/MeteorProvider.tsx | 5 +- .../SearchFilterProvider.spec.tsx | 87 ------------------- .../SearchFilterProvider.tsx | 16 ---- .../client/views/admin/rooms/RoomsTable.tsx | 37 +++++--- .../views/admin/rooms/RoomsTableFilters.tsx | 45 +++++++--- apps/meteor/tests/e2e/admin-room.spec.ts | 27 +++--- 7 files changed, 68 insertions(+), 167 deletions(-) delete mode 100644 apps/meteor/client/contexts/SearchFilterContext.ts delete mode 100644 apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.spec.tsx delete mode 100644 apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.tsx diff --git a/apps/meteor/client/contexts/SearchFilterContext.ts b/apps/meteor/client/contexts/SearchFilterContext.ts deleted file mode 100644 index de8f4fb7a552..000000000000 --- a/apps/meteor/client/contexts/SearchFilterContext.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { OptionProp } from '@rocket.chat/ui-client'; -import type { Dispatch, SetStateAction } from 'react'; -import { createContext } from 'react'; - -export type SearchFilters = { - searchText: string; - types: OptionProp[]; -}; - -type SearchFilterContextValue = { - searchFilters: SearchFilters; - setSearchFilters: Dispatch>; -}; - -export const SearchFilterContext = createContext({ - searchFilters: { searchText: '', types: [] }, - setSearchFilters: () => undefined, -}); diff --git a/apps/meteor/client/providers/MeteorProvider.tsx b/apps/meteor/client/providers/MeteorProvider.tsx index 7afc251a71f4..cf6b14e7946b 100644 --- a/apps/meteor/client/providers/MeteorProvider.tsx +++ b/apps/meteor/client/providers/MeteorProvider.tsx @@ -15,7 +15,6 @@ import LayoutProvider from './LayoutProvider'; import ModalProvider from './ModalProvider/ModalProvider'; import OmnichannelProvider from './OmnichannelProvider'; import RouterProvider from './RouterProvider'; -import { SearchFilterProvider } from './SearchFiltersProvider/SearchFilterProvider'; import ServerProvider from './ServerProvider'; import SessionProvider from './SessionProvider'; import SettingsProvider from './SettingsProvider'; @@ -49,9 +48,7 @@ const MeteorProvider: FC = ({ children }) => ( - - {children} - + {children} diff --git a/apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.spec.tsx b/apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.spec.tsx deleted file mode 100644 index 84e71832779e..000000000000 --- a/apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.spec.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable no-await-in-loop */ -import '@testing-library/jest-dom'; -import type { OptionProp } from '@rocket.chat/ui-client'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React, { useContext } from 'react'; - -import type { SearchFilters } from '../../contexts/SearchFilterContext'; -import { SearchFilterContext } from '../../contexts/SearchFilterContext'; -import { SearchFilterProvider } from './SearchFilterProvider'; - -const DropdownTestComponent = () => { - const { searchFilters, setSearchFilters } = useContext(SearchFilterContext); - - const handleCheckboxChange = (id: string) => { - setSearchFilters({ - ...searchFilters, - types: searchFilters.types.map((type) => (type.id === id ? { ...type, checked: !type.checked } : type)), - } as SearchFilters); - }; - - const handleSearchTextChange = (e: React.ChangeEvent) => { - setSearchFilters({ ...searchFilters, searchText: e.target.value }); - }; - - return ( -
- - {searchFilters.types.map((option) => - option.isGroupTitle ? ( -
{option.text}
- ) : ( - - ), - )} -
- ); -}; - -describe('SearchFilterProvider', () => { - test('handles types in context correctly', async () => { - const initialTypes = [ - { id: '1', text: 'Type 1', isGroupTitle: false, checked: false }, - { id: '2', text: 'Type 2', isGroupTitle: false, checked: false }, - ] as OptionProp[]; - - render( - - - , - ); - - for (const type of initialTypes) { - const checkbox = await screen.findByLabelText(type.text); - expect(checkbox).not.toBeChecked(); - } - - const firstCheckbox = await screen.findByLabelText(initialTypes[0].text); - userEvent.click(firstCheckbox); - - const updatedFirstCheckbox = await screen.findByLabelText(initialTypes[0].text); - expect(updatedFirstCheckbox).toBeChecked(); - }); - - test('handles searchText and types in context correctly', async () => { - const initialTypes: OptionProp[] = [ - { id: '1', text: 'Type 1', isGroupTitle: false, checked: false }, - { id: '2', text: 'Type 2', isGroupTitle: false, checked: false }, - ] as OptionProp[]; - - render( - - - , - ); - - const searchInput = screen.getByLabelText('Search'); - expect(searchInput).toHaveValue(''); - - userEvent.type(searchInput, 'New Search Text'); - - expect(searchInput).toHaveValue('New Search Text'); - }); -}); diff --git a/apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.tsx b/apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.tsx deleted file mode 100644 index 2f0efc2bd797..000000000000 --- a/apps/meteor/client/providers/SearchFiltersProvider/SearchFilterProvider.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { FC, ReactNode } from 'react'; -import React, { useState } from 'react'; - -import type { SearchFilters } from '../../contexts/SearchFilterContext'; -import { SearchFilterContext } from '../../contexts/SearchFilterContext'; - -type SearchFilterProviderProps = { - children: ReactNode; - initialState?: SearchFilters; -}; - -export const SearchFilterProvider: FC = ({ children, initialState }) => { - const [searchFilters, setSearchFilters] = useState(initialState || { searchText: '', types: [] }); - - return {children}; -}; diff --git a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx index b8474fd369b8..ed229c518aad 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx @@ -1,9 +1,10 @@ import { Pagination, States, StatesIcon, StatesTitle, StatesActions, StatesAction } from '@rocket.chat/fuselage'; -import { useMediaQuery, useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import { useMediaQuery, useDebouncedValue, useLocalStorage } from '@rocket.chat/fuselage-hooks'; +import type { OptionProp } from '@rocket.chat/ui-client'; import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; import type { ReactElement, MutableRefObject } from 'react'; -import React, { useRef, useEffect, useMemo, useContext } from 'react'; +import React, { useRef, useEffect, useMemo } from 'react'; import GenericNoResults from '../../../components/GenericNoResults'; import { @@ -15,30 +16,37 @@ import { } from '../../../components/GenericTable'; import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; import { useSort } from '../../../components/GenericTable/hooks/useSort'; -import type { SearchFilters } from '../../../contexts/SearchFilterContext'; -import { SearchFilterContext } from '../../../contexts/SearchFilterContext'; import RoomRow from './RoomRow'; import RoomsTableFilters from './RoomsTableFilters'; const DEFAULT_TYPES = ['d', 'p', 'c', 'l', 'discussions', 'teams']; +export type SearchFilters = { + searchText: string; + types: OptionProp[]; +}; + const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): ReactElement => { const t = useTranslation(); const mediaQuery = useMediaQuery('(min-width: 1024px)'); - const { searchFilters } = useContext(SearchFilterContext); + const [roomSearchText, setRoomSearchText] = useLocalStorage('roomSearchText', ''); + const [roomSearchTypes, setRoomSearchTypes] = useLocalStorage('roomSearchTypes', []); - const prevRoomFilters = useRef(searchFilters); + const prevRoomFilters = useRef({ + searchText: roomSearchText, + types: roomSearchTypes, + }); const { sortBy, sortDirection, setSort } = useSort<'name' | 't' | 'usersCount' | 'msgs' | 'default' | 'featured'>('name'); const { current, itemsPerPage, setItemsPerPage, setCurrent, ...paginationProps } = usePagination(); - const searchText = useDebouncedValue(searchFilters.searchText, 500); + const searchText = useDebouncedValue(roomSearchText, 500); const query = useDebouncedValue( useMemo(() => { const filtersChanged = searchText !== prevRoomFilters.current.searchText || - JSON.stringify(searchFilters.types) !== JSON.stringify(prevRoomFilters.current.types); + JSON.stringify(roomSearchTypes) !== JSON.stringify(prevRoomFilters.current.types); if (filtersChanged) { setCurrent(0); @@ -49,9 +57,9 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`, count: itemsPerPage, offset: filtersChanged ? 0 : current, - types: searchFilters.types.length ? [...searchFilters.types.map((roomType) => roomType.id)] : DEFAULT_TYPES, + types: roomSearchTypes.length ? [...roomSearchTypes.map((roomType: OptionProp) => roomType.id)] : DEFAULT_TYPES, }; - }, [searchText, sortBy, sortDirection, itemsPerPage, current, searchFilters.types, setCurrent]), + }, [searchText, roomSearchTypes, sortBy, sortDirection, itemsPerPage, current, setCurrent]), 500, ); @@ -64,8 +72,11 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React }, [reload, refetch]); useEffect(() => { - prevRoomFilters.current = searchFilters; - }, [searchFilters]); + prevRoomFilters.current = { + searchText: roomSearchText, + types: roomSearchTypes, + }; + }, [roomSearchText, roomSearchTypes]); const headers = ( <> @@ -117,7 +128,7 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React return ( <> - + {isLoading && ( {headers} diff --git a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx index 4b4338dba402..caee521a40e6 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx @@ -2,10 +2,9 @@ import { Box, Icon, TextInput } from '@rocket.chat/fuselage'; import type { OptionProp } from '@rocket.chat/ui-client'; import { MultiSelectCustom } from '@rocket.chat/ui-client'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useCallback, useContext } from 'react'; -import type { Dispatch, ReactElement, SetStateAction } from 'react'; +import React, { useCallback, useEffect, type Dispatch, type MutableRefObject, type ReactElement, type SetStateAction } from 'react'; -import { SearchFilterContext } from '../../../contexts/SearchFilterContext'; +import type { SearchFilters } from './RoomsTable'; const roomTypeFilterStructure = [ { @@ -45,25 +44,42 @@ const roomTypeFilterStructure = [ }, ] as OptionProp[]; -const RoomsTableFilters = (): ReactElement => { - const { searchFilters, setSearchFilters } = useContext(SearchFilterContext); +const RoomsTableFilters = ({ + setRoomSearchText, + setRoomSearchTypes, + prevRoomFilters, +}: { + setRoomSearchText: Dispatch>; + setRoomSearchTypes: Dispatch>; + prevRoomFilters: MutableRefObject; +}): ReactElement => { const t = useTranslation(); const handleSearchTextChange = useCallback( - (event: React.ChangeEvent) => { - const newText = event.currentTarget.value; - setSearchFilters((filters) => ({ ...filters, searchText: newText, types: searchFilters.types })); + (event) => { + const text = event.currentTarget.value; + setRoomSearchText(text); }, - [searchFilters.types, setSearchFilters], + [setRoomSearchText], ); const handleRoomTypeChange = useCallback( (options: OptionProp[]) => { - setSearchFilters((filters) => ({ ...filters, types: options, searchText: searchFilters.searchText })); + setRoomSearchTypes(options); }, - [searchFilters.searchText, setSearchFilters], + [setRoomSearchTypes], ) as Dispatch>; + useEffect(() => { + roomTypeFilterStructure.forEach((type, index) => { + prevRoomFilters.current.types.forEach((selectedType) => { + if (type.id === selectedType.id) { + roomTypeFilterStructure[index] = selectedType; + } + }); + }); + }, [prevRoomFilters]); + return ( { > } onChange={handleSearchTextChange} - value={searchFilters.searchText} + value={prevRoomFilters.current.searchText} /> - + diff --git a/apps/meteor/tests/e2e/admin-room.spec.ts b/apps/meteor/tests/e2e/admin-room.spec.ts index 76c0ad1df444..283433cc9806 100644 --- a/apps/meteor/tests/e2e/admin-room.spec.ts +++ b/apps/meteor/tests/e2e/admin-room.spec.ts @@ -25,13 +25,10 @@ test.describe.serial('admin-rooms', () => { }); test('should display the Rooms Table', async ({ page }) => { await expect(page.locator('[data-qa-type="PageHeader-title"]')).toContainText('Rooms'); - await expect(page.locator(`[qa-room-name="${privateRoom}"]`)).toBeVisible(); - await expect(page.locator(`[qa-room-name="${channel}"]`)).toBeVisible(); }); test('should filter room by name', async ({ page }) => { - const input = page.locator('input[type="text"]'); - console.log('🚀 ~ test ~ channel:', channel); + const input = page.locator('[data-qa-id="AdminRoomSearchInput"]'); await input.click(); await input.fill(channel); @@ -40,7 +37,7 @@ test.describe.serial('admin-rooms', () => { }); test('should filter rooms by type', async ({ page }) => { - const dropdown = page.locator('text=All rooms'); + const dropdown = page.locator('[data-qa-id="AdminRoomDropdownInput"]'); await dropdown.click(); const privateOption = page.locator('text=Private channels'); @@ -48,16 +45,16 @@ test.describe.serial('admin-rooms', () => { await privateOption.waitFor(); await privateOption.click(); - await expect(page.locator(`[qa-room-name="${privateRoom}"]`)).toBeVisible(); + await expect(page.locator('text=Private Channel').first()).toBeVisible(); }); test('should filter rooms by type and name', async ({ page }) => { - const input = page.locator('input[type="text"]'); + const input = page.locator('[data-qa-id="AdminRoomSearchInput"]'); await input.click(); await input.fill(privateRoom); - const dropdown = page.locator('text=All rooms'); + const dropdown = page.locator('[data-qa-id="AdminRoomDropdownInput"]'); await dropdown.click(); const privateOption = page.locator('text=Private channels'); @@ -69,12 +66,12 @@ test.describe.serial('admin-rooms', () => { }); test('should be empty in case of the search does not find any room', async ({ page }) => { - const input = page.locator('input[type="text"]'); + const input = page.locator('[data-qa-id="AdminRoomSearchInput"]'); await input.click(); await input.fill('Wrong-channel'); - const dropdown = page.locator('text=All rooms'); + const dropdown = page.locator('[data-qa-id="AdminRoomDropdownInput"]'); await dropdown.click(); const privateOption = page.locator('text=Private channels'); @@ -85,13 +82,13 @@ test.describe.serial('admin-rooms', () => { await expect(page.locator('text=No results found')).toBeVisible(); }); - test.only('should filter rooms by type and name and keep the filter after changing section', async ({ page }) => { - const input = page.locator('input[type="text"]'); + test('should filter rooms by type and name and keep the filter after changing section', async ({ page }) => { + const input = page.locator('[data-qa-id="AdminRoomSearchInput"]'); await input.click(); await input.fill(privateRoom); - const dropdown = page.locator('text=All rooms'); + const dropdown = page.locator('[data-qa-id="AdminRoomDropdownInput"]'); await dropdown.click(); const privateOption = page.locator('text=Private channels'); @@ -99,9 +96,9 @@ test.describe.serial('admin-rooms', () => { await privateOption.waitFor(); await privateOption.click(); - await page.locator('text=Workspace').click(); + await page.goto('/admin/info'); - await page.locator('text=Rooms').click(); + await page.goto('/admin/rooms'); const selectDropdown = page.locator('text=Rooms (1)'); await expect(selectDropdown).toBeVisible(); From 5e44741a1625a2bc10f3d552e291a18b8a24d36b Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Mon, 15 Apr 2024 10:54:26 -0300 Subject: [PATCH 03/27] fix: fixing channel name on search --- apps/meteor/tests/e2e/admin-room.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/meteor/tests/e2e/admin-room.spec.ts b/apps/meteor/tests/e2e/admin-room.spec.ts index 283433cc9806..d7c851acb703 100644 --- a/apps/meteor/tests/e2e/admin-room.spec.ts +++ b/apps/meteor/tests/e2e/admin-room.spec.ts @@ -1,3 +1,4 @@ +import { faker } from '@faker-js/faker'; import { Page } from '@playwright/test'; import { Users } from './fixtures/userStates'; @@ -69,7 +70,8 @@ test.describe.serial('admin-rooms', () => { const input = page.locator('[data-qa-id="AdminRoomSearchInput"]'); await input.click(); - await input.fill('Wrong-channel'); + const wrongChannel = faker.string.alpha(10); + await input.fill(wrongChannel); const dropdown = page.locator('[data-qa-id="AdminRoomDropdownInput"]'); await dropdown.click(); From fecfb1925479c2d46ee5b923a9df643f122642d5 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Mon, 15 Apr 2024 14:39:26 -0300 Subject: [PATCH 04/27] fix: removing not necessary test --- .../tests/e2e/app-modal-interaction.spec.ts | 74 ------------------- 1 file changed, 74 deletions(-) delete mode 100644 apps/meteor/tests/e2e/app-modal-interaction.spec.ts diff --git a/apps/meteor/tests/e2e/app-modal-interaction.spec.ts b/apps/meteor/tests/e2e/app-modal-interaction.spec.ts deleted file mode 100644 index 2124a9607e13..000000000000 --- a/apps/meteor/tests/e2e/app-modal-interaction.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Users } from './fixtures/userStates'; -import { HomeChannel } from './page-objects'; -import { createTargetChannel } from './utils/create-target-channel'; -import { test, expect } from './utils/test'; - -test.use({ storageState: Users.admin.state }); - -test.describe.serial('app-surfaces-interaction', () => { - let poHomeChannel: HomeChannel; - let targetChannel: string; - - test.beforeAll(async ({ api }) => { - targetChannel = await createTargetChannel(api); - }); - - test.beforeEach(async ({ page }) => { - poHomeChannel = new HomeChannel(page); - - await page.goto('/home'); - }); - - test('expect to submit an success modal', async ({ page }) => { - await poHomeChannel.sidenav.openChat(targetChannel); - await page.locator('role=button[name="Options"]').click(); - await page.locator('[data-key="success"]').click(); - await page.locator('role=button[name="success"]').click(); - - const updatedButton = page.locator('role=button[name="success"]'); - await expect(updatedButton).not.toBeVisible(); - }); - - test('expect to not close the modal and there is an error in the modal', async ({ page }) => { - await poHomeChannel.sidenav.openChat(targetChannel); - await page.locator('role=button[name="Options"]').click(); - await page.locator('[data-key="error"]').click(); - await page.locator('role=button[name="error"]').click(); - - const updatedTitle = page.locator('role=button[name="error"]'); - expect(updatedTitle).toBeDefined(); - - const input = page.locator('input[type="text"]'); - - await input.click(); - await input.fill('fixed'); - - await page.locator('role=button[name="error"]').click(); - await expect(input).not.toBeVisible(); - }); - - test('expect to show the toaster error for modal that timeout the execution', async ({ page }) => { - await poHomeChannel.sidenav.openChat(targetChannel); - await page.locator('role=button[name="Options"]').click(); - await page.locator('[data-key="timeout"]').click(); - await page.locator('role=button[name="timeout"]').click(); - await page.locator('role=alert').waitFor(); - - const toaster = page.locator('role=alert'); - - await expect(toaster).toBeVisible(); - }); - - test('expect change the modal and then submit the updated modal', async ({ page }) => { - await poHomeChannel.sidenav.openChat(targetChannel); - await page.locator('role=button[name="Options"]').click(); - await page.locator('[data-key="update"]').click(); - await page.locator('role=button[name="update"]').click(); - - const updatedTitle = page.locator('role=button[name="title updated"]'); - expect(updatedTitle).toBeDefined(); - - const updatedButton = page.locator('role=button[name="updated"]'); - expect(updatedButton).toBeDefined(); - }); -}); From 4e7731c071f1e2dfc85035f20f6aa5ec2853c883 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Mon, 15 Apr 2024 14:46:25 -0300 Subject: [PATCH 05/27] fix: fix changest text --- .changeset/brown-lobsters-join.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.changeset/brown-lobsters-join.md b/.changeset/brown-lobsters-join.md index b6fecbf01958..36f66a9193df 100644 --- a/.changeset/brown-lobsters-join.md +++ b/.changeset/brown-lobsters-join.md @@ -2,6 +2,4 @@ '@rocket.chat/meteor': patch --- -Update: Fixed Room Filter Persistence in Admin Navigation - Resolved an issue causing room filter settings to reset on admin page navigation. Now, filter selections remain intact across different admin sections, streamlining task management and reducing redundant inputs. From c4749b76308fbc89a440bc81f7ff1f4ed0b03970 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Tue, 16 Apr 2024 14:25:45 -0300 Subject: [PATCH 06/27] feat: adding unit test and fixing some test locators --- apps/meteor/.meteorMocks/index.js | 151 ++++++++++++++++++ .../client/views/admin/rooms/RoomsTable.tsx | 2 +- .../views/admin/rooms/RoomsTableFilters.tsx | 2 +- apps/meteor/jest.config.ts | 1 + apps/meteor/tests/e2e/admin-room.spec.ts | 42 ++--- apps/meteor/tests/e2e/page-objects/admin.ts | 4 + .../views/admin/rooms/RoomsTable.spec.tsx | 110 +++++++++++++ 7 files changed, 285 insertions(+), 27 deletions(-) create mode 100644 apps/meteor/.meteorMocks/index.js create mode 100644 apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx diff --git a/apps/meteor/.meteorMocks/index.js b/apps/meteor/.meteorMocks/index.js new file mode 100644 index 000000000000..0dc4cb96d7f5 --- /dev/null +++ b/apps/meteor/.meteorMocks/index.js @@ -0,0 +1,151 @@ +import sinon from 'sinon'; +const events = require('events'); + +const subscribeMock = jest.fn(() => ({ + ready: jest.fn(() => true), + stop: sinon.stub(), +})); +const callMock = sinon.stub(); +const applyMock = sinon.stub(); + +class MockLocalStorage { + constructor() { + this.store = {}; + } + + getItem(key) { + return this.store[key] || null; + } + + setItem(key, value) { + this.store[key] = String(value); + } + + removeItem(key) { + delete this.store[key]; + } + + clear() { + this.store = {}; + } +} + +module.exports = { + Meteor: { + loginWithSamlToken: sinon.stub(), + subscribe: subscribeMock, + call: callMock, + apply: applyMock, + methods: sinon.stub(), + connection: { + subscribe: subscribeMock, + call: callMock, + apply: applyMock, + status: sinon.stub(() => ({ connected: true })), + reconnect: sinon.stub(), + disconnect: sinon.stub(), + _stream: new events.EventEmitter(), + }, + isClient: true, + isServer: false, + _localStorage: new MockLocalStorage(), + }, + Mongo: { + Collection: class Collection { + constructor(name) { + this.name = name; + this.find = sinon.stub().returns({ + fetch: () => [{ _id: '1', name: 'Document' }], + count: () => 1, + observe: sinon.stub(), + }); + this.findOne = sinon.stub().returns({ _id: '1', name: 'Document' }); + this.insert = sinon.stub(); + this.update = sinon.stub(); + this.remove = sinon.stub(); + } + }, + }, + Accounts: { + createUser: sinon.stub(), + findUserByEmail: sinon.stub(), + findUserByUsername: sinon.stub(), + validateLoginAttempt: sinon.stub(), + onLogin: sinon.stub(), + onLoginFailure: sinon.stub(), + }, + check: sinon.stub(), + Match: { + Maybe: sinon.stub(), + OneOf: sinon.stub(), + ObjectIncluding: sinon.stub(), + Where: sinon.stub(), + }, + Tracker: { + autorun: sinon.stub(), + nonreactive: sinon.stub(), + Dependency: class { + constructor() { + this.depend = sinon.stub(); + this.changed = sinon.stub(); + } + }, + }, + ReactiveVar: class ReactiveVar { + constructor(initialValue) { + this.value = initialValue; + this.dep = new (require('meteor/tracker').Tracker.Dependency)(); + } + + get() { + this.dep.depend(); + return this.value; + } + + set(newValue) { + if (this.value !== newValue) { + this.value = newValue; + this.dep.changed(); + } + } + }, + ReactiveDict: class ReactiveDict { + constructor(initialValues) { + this.dict = new Map(); + this.deps = {}; + + if (initialValues) { + for (const [key, value] of Object.entries(initialValues)) { + this.set(key, value); + } + } + } + + get(key) { + if (!this.deps[key]) { + this.deps[key] = new Tracker.Dependency(); + } + this.deps[key].depend(); + return this.dict.get(key); + } + + set(key, value) { + const oldValue = this.dict.get(key); + if (oldValue !== value) { + this.dict.set(key, value); + if (this.deps[key]) { + this.deps[key].changed(); + } + } + } + + equals(key, value) { + const currentValue = this.get(key); + return currentValue === value; + } + + all() { + return Object.fromEntries(this.dict); + } + }, +}; diff --git a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx index ed229c518aad..aff38fe0a67f 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx @@ -133,7 +133,7 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React {headers} - + )} diff --git a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx index caee521a40e6..4038020a8205 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx @@ -92,7 +92,7 @@ const RoomsTableFilters = ({ > /node_modules/react$1', '^@tanstack/(.+)': '/node_modules/@tanstack/$1', + '^meteor/(.*)$': '/.meteorMocks/index.js', }, }, { diff --git a/apps/meteor/tests/e2e/admin-room.spec.ts b/apps/meteor/tests/e2e/admin-room.spec.ts index d7c851acb703..7b79551e1acd 100644 --- a/apps/meteor/tests/e2e/admin-room.spec.ts +++ b/apps/meteor/tests/e2e/admin-room.spec.ts @@ -2,6 +2,7 @@ import { faker } from '@faker-js/faker'; import { Page } from '@playwright/test'; import { Users } from './fixtures/userStates'; +import { Admin } from './page-objects'; import { createTargetChannel, createTargetPrivateChannel } from './utils'; import { expect, test } from './utils/test'; @@ -16,6 +17,12 @@ test.describe.serial('admin-rooms', () => { await page.goto('/admin/rooms'); }); + let admin: Admin; + + test.beforeEach(async ({ page }) => { + admin = new Admin(page); + }); + test.beforeAll(async ({ browser, api }) => { adminPage = await browser.newPage({ storageState: Users.admin.state }); await adminPage.goto('/home'); @@ -29,17 +36,13 @@ test.describe.serial('admin-rooms', () => { }); test('should filter room by name', async ({ page }) => { - const input = page.locator('[data-qa-id="AdminRoomSearchInput"]'); - - await input.click(); - await input.fill(channel); + await admin.inputSearchRooms.fill(channel); await expect(page.locator(`[qa-room-name="${channel}"]`)).toBeVisible(); }); test('should filter rooms by type', async ({ page }) => { - const dropdown = page.locator('[data-qa-id="AdminRoomDropdownInput"]'); - await dropdown.click(); + await admin.dropdownFilterRoomType.click(); const privateOption = page.locator('text=Private channels'); @@ -50,13 +53,9 @@ test.describe.serial('admin-rooms', () => { }); test('should filter rooms by type and name', async ({ page }) => { - const input = page.locator('[data-qa-id="AdminRoomSearchInput"]'); - - await input.click(); - await input.fill(privateRoom); + await admin.inputSearchRooms.fill(privateRoom); - const dropdown = page.locator('[data-qa-id="AdminRoomDropdownInput"]'); - await dropdown.click(); + await admin.dropdownFilterRoomType.click(); const privateOption = page.locator('text=Private channels'); @@ -67,14 +66,11 @@ test.describe.serial('admin-rooms', () => { }); test('should be empty in case of the search does not find any room', async ({ page }) => { - const input = page.locator('[data-qa-id="AdminRoomSearchInput"]'); + const nonExistingChannel = faker.string.alpha(10); - await input.click(); - const wrongChannel = faker.string.alpha(10); - await input.fill(wrongChannel); + await admin.inputSearchRooms.fill(nonExistingChannel); - const dropdown = page.locator('[data-qa-id="AdminRoomDropdownInput"]'); - await dropdown.click(); + await admin.dropdownFilterRoomType.click(); const privateOption = page.locator('text=Private channels'); @@ -85,13 +81,9 @@ test.describe.serial('admin-rooms', () => { }); test('should filter rooms by type and name and keep the filter after changing section', async ({ page }) => { - const input = page.locator('[data-qa-id="AdminRoomSearchInput"]'); - - await input.click(); - await input.fill(privateRoom); + await admin.inputSearchRooms.fill(privateRoom); - const dropdown = page.locator('[data-qa-id="AdminRoomDropdownInput"]'); - await dropdown.click(); + await admin.dropdownFilterRoomType.click(); const privateOption = page.locator('text=Private channels'); @@ -111,6 +103,6 @@ test.describe.serial('admin-rooms', () => { expect(isChecked).toBe(true); - await expect(input).toHaveValue(privateRoom); + await expect(admin.inputSearchRooms).toHaveValue(privateRoom); }); }); diff --git a/apps/meteor/tests/e2e/page-objects/admin.ts b/apps/meteor/tests/e2e/page-objects/admin.ts index b036fa339575..c1280704ca67 100644 --- a/apps/meteor/tests/e2e/page-objects/admin.ts +++ b/apps/meteor/tests/e2e/page-objects/admin.ts @@ -184,4 +184,8 @@ export class Admin { get inputAssetsLogo(): Locator { return this.page.locator('//label[@title="Assets_logo"]/following-sibling::span >> input[type="file"]'); } + + get dropdownFilterRoomType(): Locator { + return this.page.locator('[data-qa-id="AdminRoomDropdownInput"]'); + } } diff --git a/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx b/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx new file mode 100644 index 000000000000..6f09306d45cd --- /dev/null +++ b/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx @@ -0,0 +1,110 @@ +/* eslint-disable no-restricted-properties */ +import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; +import * as Hooks from '@rocket.chat/fuselage-hooks'; +import * as Contexts from '@rocket.chat/ui-contexts'; +import * as ReactQuery from '@tanstack/react-query'; +import '@testing-library/jest-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { render, screen } from '@testing-library/react'; +import { expect } from 'chai'; +import React from 'react'; +import sinon from 'sinon'; + +import RoomsTable from '../../../../../../client/views/admin/rooms/RoomsTable'; + +jest.mock('../../../../../../client/views/admin/rooms/RoomRow', () => { + return ({ someProp }: any) =>
MockedRoomRow {someProp}
; +}); + +const createQueryClientWrapper = () => { + const queryClient = new QueryClient(); + const provider = ({ children }: any) => { + return {children}; + }; + provider.displayName = 'provider'; + + return provider; +}; + +describe('RoomsTable', () => { + let useQueryStub: any; + + beforeEach(() => { + sinon.stub(Contexts, 'useEndpoint').returns( + sinon.fake(() => + Promise.resolve({ + data: { rooms: [], total: 0 }, + isSuccess: true, + isLoading: false, + isError: false, + refetch: sinon.fake(), + } as never), + ), + ); + useQueryStub = sinon.stub(ReactQuery, 'useQuery').returns({} as any); + sinon.stub(Hooks, 'useMediaQuery').returns(true); + sinon.stub(Hooks, 'useLocalStorage').returns([[], sinon.fake()]); + sinon.stub(Hooks, 'useDebouncedValue').callsFake((value) => value); + sinon.stub(Contexts, 'useTranslation').returns({} as any); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('renders loading state initially', async () => { + useQueryStub.returns({ + isLoading: true, + data: undefined, + error: undefined, + }); + + render(, { wrapper: createQueryClientWrapper() }); + + expect(screen.queryByTestId('RoomGenericTableLoadingTable')).not.to.be.undefined; + + sinon.restore(); + }); + + it('renders no results when data is empty', async () => { + useQueryStub.returns({ + isLoading: false, + data: { + rooms: [], + }, + error: undefined, + isSuccess: true, + }); + render(, { wrapper: createQueryClientWrapper() }); + expect(screen.getByText('No_results_found')).to.exist; + }); + + it('renders room data when available', async () => { + useQueryStub.returns({ + data: { rooms: [{ _id: '1', name: 'Room 1', t: RoomType.DIRECT_MESSAGE }], total: 1 }, + isSuccess: true, + isLoading: false, + isError: false, + refetch: sinon.fake(), + }); + + render(, { wrapper: createQueryClientWrapper() }); + + expect(screen.queryByTestId('RoomRow')).not.be.undefined; + expect(screen.queryByTestId('RoomGenericTableLoadingTable')).to.be.null; + }); + + it('renders error state', async () => { + useQueryStub.returns({ + data: undefined, + isSuccess: false, + isLoading: false, + isError: true, + refetch: sinon.fake(), + }); + + render(, { wrapper: createQueryClientWrapper() }); + expect(screen.getByText('Something_went_wrong')).not.be.undefined; + expect(screen.getByText('Reload_page')).not.be.undefined; + }); +}); From 72f0ca5fc463b1cc333c9ab3955fc85f41943dd1 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Wed, 17 Apr 2024 15:18:42 -0300 Subject: [PATCH 07/27] feat: improving page selectors to the tests --- apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx | 2 +- apps/meteor/tests/e2e/page-objects/admin.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx index 4038020a8205..2872b4894666 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx @@ -101,7 +101,7 @@ const RoomsTableFilters = ({ value={prevRoomFilters.current.searchText} />
- + Date: Thu, 18 Apr 2024 11:30:07 -0300 Subject: [PATCH 08/27] feat: set proper roles to the select component --- apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx | 2 +- .../components/MultiSelectCustom/MultiSelectCustomAnchor.tsx | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx index 2872b4894666..3cfe81730ffd 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx @@ -101,7 +101,7 @@ const RoomsTableFilters = ({ value={prevRoomFilters.current.searchText} /> - + (e.code === 'Enter' || e.code === 'Space') && onClick} display='flex' justifyContent='space-between' alignItems='center' From 24862f0f8add3e8f7d3cf60841cd0b25e3f1732d Mon Sep 17 00:00:00 2001 From: dougfabris Date: Thu, 18 Apr 2024 12:56:34 -0300 Subject: [PATCH 09/27] chore: sanitize `MultiSelectCustom` --- .../MultiSelectCustom/MultiSelectCustom.tsx | 3 +- .../MultiSelectCustomAnchor.tsx | 45 +++++-------------- .../MultiSelectCustomListWrapper.tsx | 2 +- 3 files changed, 13 insertions(+), 37 deletions(-) diff --git a/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustom.tsx b/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustom.tsx index 6c5e12be8622..affe467cd965 100644 --- a/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustom.tsx +++ b/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustom.tsx @@ -105,8 +105,9 @@ export const MultiSelectCustom = ({ toggleCollapsed(!collapsed)} + onKeyDown={(e) => (e.code === 'Enter' || e.code === 'Space') && toggleCollapsed(!collapsed)} defaultTitle={defaultTitle} selectedOptionsTitle={selectedOptionsTitle} selectedOptionsCount={count} diff --git a/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomAnchor.tsx b/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomAnchor.tsx index a87ed176c38a..0a8aee69344b 100644 --- a/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomAnchor.tsx +++ b/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomAnchor.tsx @@ -1,42 +1,28 @@ import { css } from '@rocket.chat/css-in-js'; -import { Box, Button, Icon, Palette } from '@rocket.chat/fuselage'; +import { Box, Icon } from '@rocket.chat/fuselage'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ComponentProps } from 'react'; import { forwardRef } from 'react'; type MultiSelectCustomAnchorProps = { - onClick?: (value: boolean) => void; collapsed: boolean; defaultTitle: TranslationKey; selectedOptionsTitle: TranslationKey; selectedOptionsCount: number; maxCount: number; -} & ComponentProps; +} & ComponentProps; const MultiSelectCustomAnchor = forwardRef(function MultiSelectCustomAnchor( - { onClick, collapsed, selectedOptionsCount, selectedOptionsTitle, defaultTitle, maxCount, ...props }, + { collapsed, selectedOptionsCount, selectedOptionsTitle, defaultTitle, maxCount, ...props }, ref, ) { const t = useTranslation(); - - const inputStyle = collapsed - ? css` - &, - &:hover, - &:active, - &:focus { - cursor: pointer; - border-color: ${Palette.stroke['stroke-highlight'].toString()}!important; - box-shadow: 0 0 0 2px ${Palette.shadow['shadow-highlight'].toString()}; - } - ` - : css` - & { - cursor: pointer; - } - `; - + const customStyle = css` + &:hover { + cursor: pointer; + } + `; const isDirty = selectedOptionsCount > 0 && selectedOptionsCount !== maxCount - 1; return ( @@ -44,26 +30,15 @@ const MultiSelectCustomAnchor = forwardRef (e.code === 'Enter' || e.code === 'Space') && onClick} display='flex' justifyContent='space-between' alignItems='center' - flexDirection='row' - borderColor={Palette.stroke['stroke-light'].toString()} - borderWidth='x1' - borderRadius={4} - bg={Palette.surface['surface-light'].toString()} h='x40' - w='full' - pb={10} - pi={16} - color={isDirty ? Palette.text['font-default'].toString() : Palette.text['font-annotation'].toString()} - className={inputStyle} + className={['rcx-input-box__wrapper', customStyle].filter(Boolean)} {...props} > {isDirty ? `${t(selectedOptionsTitle)} (${selectedOptionsCount})` : t(defaultTitle)} - + ); }); diff --git a/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomListWrapper.tsx b/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomListWrapper.tsx index c579480a3c66..d8e2379772cc 100644 --- a/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomListWrapper.tsx +++ b/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomListWrapper.tsx @@ -6,7 +6,7 @@ const MultiSelectCustomListWrapper = forwardRef + {children} ); From 18be844d83320f9b4741ac8621794e7899576e41 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Thu, 18 Apr 2024 14:15:37 -0300 Subject: [PATCH 10/27] feat: improving the test selectors --- apps/meteor/tests/e2e/admin-room.spec.ts | 18 ++++++++++++------ apps/meteor/tests/e2e/page-objects/admin.ts | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/apps/meteor/tests/e2e/admin-room.spec.ts b/apps/meteor/tests/e2e/admin-room.spec.ts index 7b79551e1acd..9056647e538b 100644 --- a/apps/meteor/tests/e2e/admin-room.spec.ts +++ b/apps/meteor/tests/e2e/admin-room.spec.ts @@ -42,20 +42,25 @@ test.describe.serial('admin-rooms', () => { }); test('should filter rooms by type', async ({ page }) => { - await admin.dropdownFilterRoomType.click(); + const dropdown = await admin.dropdownFilterRoomType(); + await dropdown.click(); const privateOption = page.locator('text=Private channels'); await privateOption.waitFor(); await privateOption.click(); + const selectedDropdown = await admin.dropdownFilterRoomType('Rooms (1)'); + await expect(selectedDropdown).toBeVisible(); + await expect(page.locator('text=Private Channel').first()).toBeVisible(); }); test('should filter rooms by type and name', async ({ page }) => { await admin.inputSearchRooms.fill(privateRoom); - await admin.dropdownFilterRoomType.click(); + const dropdown = await admin.dropdownFilterRoomType(); + await dropdown.click(); const privateOption = page.locator('text=Private channels'); @@ -70,7 +75,8 @@ test.describe.serial('admin-rooms', () => { await admin.inputSearchRooms.fill(nonExistingChannel); - await admin.dropdownFilterRoomType.click(); + const dropdown = await admin.dropdownFilterRoomType(); + await dropdown.click(); const privateOption = page.locator('text=Private channels'); @@ -82,8 +88,8 @@ test.describe.serial('admin-rooms', () => { test('should filter rooms by type and name and keep the filter after changing section', async ({ page }) => { await admin.inputSearchRooms.fill(privateRoom); - - await admin.dropdownFilterRoomType.click(); + const dropdown = await admin.dropdownFilterRoomType(); + await dropdown.click(); const privateOption = page.locator('text=Private channels'); @@ -94,7 +100,7 @@ test.describe.serial('admin-rooms', () => { await page.goto('/admin/rooms'); - const selectDropdown = page.locator('text=Rooms (1)'); + const selectDropdown = await admin.dropdownFilterRoomType('Rooms (1)'); await expect(selectDropdown).toBeVisible(); await selectDropdown.click(); diff --git a/apps/meteor/tests/e2e/page-objects/admin.ts b/apps/meteor/tests/e2e/page-objects/admin.ts index 57da66c2c379..1d42b6b75ac7 100644 --- a/apps/meteor/tests/e2e/page-objects/admin.ts +++ b/apps/meteor/tests/e2e/page-objects/admin.ts @@ -185,7 +185,7 @@ export class Admin { return this.page.locator('//label[@title="Assets_logo"]/following-sibling::span >> input[type="file"]'); } - get dropdownFilterRoomType(): Locator { - return this.page.locator('div[data-qa-id="AdminRoomDropdownInput"][role="listbox"]'); + async dropdownFilterRoomType(text = 'All rooms'): Promise { + return this.page.locator(`div[role="button"]:has-text("${text}")`); } } From e835cf41a450aff114d822be4885db86b361da5c Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Fri, 19 Apr 2024 12:58:17 -0300 Subject: [PATCH 11/27] feat: improving the test assertions --- apps/meteor/tests/e2e/admin-room.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/meteor/tests/e2e/admin-room.spec.ts b/apps/meteor/tests/e2e/admin-room.spec.ts index 9056647e538b..6f2a3b2ba333 100644 --- a/apps/meteor/tests/e2e/admin-room.spec.ts +++ b/apps/meteor/tests/e2e/admin-room.spec.ts @@ -110,5 +110,6 @@ test.describe.serial('admin-rooms', () => { expect(isChecked).toBe(true); await expect(admin.inputSearchRooms).toHaveValue(privateRoom); + await expect(page.locator(`[qa-room-name="${privateRoom}"]`)).toBeVisible(); }); }); From bd2e23cf80830b488bd0e2d880d716164164197d Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Fri, 19 Apr 2024 15:55:10 -0300 Subject: [PATCH 12/27] fix: setting mocking file to ts and fix unit test warnings --- apps/meteor/.meteorMocks/{index.js => index.ts} | 13 +++++++++++++ .../client/views/admin/rooms/RoomsTable.spec.tsx | 6 +++++- 2 files changed, 18 insertions(+), 1 deletion(-) rename apps/meteor/.meteorMocks/{index.js => index.ts} (89%) diff --git a/apps/meteor/.meteorMocks/index.js b/apps/meteor/.meteorMocks/index.ts similarity index 89% rename from apps/meteor/.meteorMocks/index.js rename to apps/meteor/.meteorMocks/index.ts index 0dc4cb96d7f5..83b979853ce2 100644 --- a/apps/meteor/.meteorMocks/index.js +++ b/apps/meteor/.meteorMocks/index.ts @@ -9,6 +9,7 @@ const callMock = sinon.stub(); const applyMock = sinon.stub(); class MockLocalStorage { + store: {}; constructor() { this.store = {}; } @@ -52,6 +53,12 @@ module.exports = { }, Mongo: { Collection: class Collection { + name: any; + find: sinon.SinonStub; + findOne: sinon.SinonStub; + insert: sinon.SinonStub; + update: sinon.SinonStub; + remove: sinon.SinonStub; constructor(name) { this.name = name; this.find = sinon.stub().returns({ @@ -85,6 +92,8 @@ module.exports = { autorun: sinon.stub(), nonreactive: sinon.stub(), Dependency: class { + depend: sinon.SinonStub; + changed: sinon.SinonStub; constructor() { this.depend = sinon.stub(); this.changed = sinon.stub(); @@ -92,6 +101,8 @@ module.exports = { }, }, ReactiveVar: class ReactiveVar { + value: any; + dep: any; constructor(initialValue) { this.value = initialValue; this.dep = new (require('meteor/tracker').Tracker.Dependency)(); @@ -110,6 +121,8 @@ module.exports = { } }, ReactiveDict: class ReactiveDict { + dict: Map; + deps: {}; constructor(initialValues) { this.dict = new Map(); this.deps = {}; diff --git a/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx b/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx index 6f09306d45cd..a19a1131e995 100644 --- a/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx +++ b/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx @@ -13,7 +13,11 @@ import sinon from 'sinon'; import RoomsTable from '../../../../../../client/views/admin/rooms/RoomsTable'; jest.mock('../../../../../../client/views/admin/rooms/RoomRow', () => { - return ({ someProp }: any) =>
MockedRoomRow {someProp}
; + return ({ someProp }: any) => ( + + MockedRoomRow {someProp} + + ); }); const createQueryClientWrapper = () => { From fefad0b4c7fe487baec166c5ecd401717ad22682 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Fri, 19 Apr 2024 15:57:20 -0300 Subject: [PATCH 13/27] fix: adding one sigle before each to setup the the test --- apps/meteor/tests/e2e/admin-room.spec.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/meteor/tests/e2e/admin-room.spec.ts b/apps/meteor/tests/e2e/admin-room.spec.ts index 6f2a3b2ba333..e69a85125455 100644 --- a/apps/meteor/tests/e2e/admin-room.spec.ts +++ b/apps/meteor/tests/e2e/admin-room.spec.ts @@ -12,15 +12,11 @@ test.describe.serial('admin-rooms', () => { let adminPage: Page; let channel: string; let privateRoom: string; - - test.beforeEach(async ({ page }) => { - await page.goto('/admin/rooms'); - }); - let admin: Admin; test.beforeEach(async ({ page }) => { admin = new Admin(page); + await page.goto('/admin/rooms'); }); test.beforeAll(async ({ browser, api }) => { From 2e44a61f272e1e9a2aeff2a3b9fad7d4aff8f7b6 Mon Sep 17 00:00:00 2001 From: dougfabris Date: Thu, 18 Apr 2024 12:56:34 -0300 Subject: [PATCH 14/27] fix: cleaning the filters after changing sections --- .../client/views/admin/rooms/RoomsTable.tsx | 42 ++++++--------- .../views/admin/rooms/RoomsTableFilters.tsx | 54 +++++++++---------- apps/meteor/tests/e2e/admin-room.spec.ts | 14 ++--- 3 files changed, 43 insertions(+), 67 deletions(-) diff --git a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx index aff38fe0a67f..3beb3b88b2e6 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx @@ -1,10 +1,10 @@ import { Pagination, States, StatesIcon, StatesTitle, StatesActions, StatesAction } from '@rocket.chat/fuselage'; -import { useMediaQuery, useDebouncedValue, useLocalStorage } from '@rocket.chat/fuselage-hooks'; +import { useMediaQuery, useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import type { OptionProp } from '@rocket.chat/ui-client'; import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; import type { ReactElement, MutableRefObject } from 'react'; -import React, { useRef, useEffect, useMemo } from 'react'; +import React, { useRef, useState, useEffect, useMemo } from 'react'; import GenericNoResults from '../../../components/GenericNoResults'; import { @@ -19,47 +19,38 @@ import { useSort } from '../../../components/GenericTable/hooks/useSort'; import RoomRow from './RoomRow'; import RoomsTableFilters from './RoomsTableFilters'; -const DEFAULT_TYPES = ['d', 'p', 'c', 'l', 'discussions', 'teams']; - -export type SearchFilters = { +type RoomFilters = { searchText: string; types: OptionProp[]; }; +const DEFAULT_TYPES = ['d', 'p', 'c', 'l', 'discussions', 'teams']; + const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): ReactElement => { const t = useTranslation(); const mediaQuery = useMediaQuery('(min-width: 1024px)'); - const [roomSearchText, setRoomSearchText] = useLocalStorage('roomSearchText', ''); - const [roomSearchTypes, setRoomSearchTypes] = useLocalStorage('roomSearchTypes', []); + const [roomFilters, setRoomFilters] = useState({ searchText: '', types: [] }); - const prevRoomFilters = useRef({ - searchText: roomSearchText, - types: roomSearchTypes, - }); + const prevRoomFilterText = useRef(roomFilters.searchText); const { sortBy, sortDirection, setSort } = useSort<'name' | 't' | 'usersCount' | 'msgs' | 'default' | 'featured'>('name'); const { current, itemsPerPage, setItemsPerPage, setCurrent, ...paginationProps } = usePagination(); - const searchText = useDebouncedValue(roomSearchText, 500); + const searchText = useDebouncedValue(roomFilters.searchText, 500); const query = useDebouncedValue( useMemo(() => { - const filtersChanged = - searchText !== prevRoomFilters.current.searchText || - JSON.stringify(roomSearchTypes) !== JSON.stringify(prevRoomFilters.current.types); - - if (filtersChanged) { + if (searchText !== prevRoomFilterText.current) { setCurrent(0); } - return { filter: searchText || '', sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`, count: itemsPerPage, - offset: filtersChanged ? 0 : current, - types: roomSearchTypes.length ? [...roomSearchTypes.map((roomType: OptionProp) => roomType.id)] : DEFAULT_TYPES, + offset: searchText === prevRoomFilterText.current ? current : 0, + types: roomFilters.types.length ? [...roomFilters.types.map((roomType) => roomType.id)] : DEFAULT_TYPES, }; - }, [searchText, roomSearchTypes, sortBy, sortDirection, itemsPerPage, current, setCurrent]), + }, [searchText, sortBy, sortDirection, itemsPerPage, current, roomFilters.types, setCurrent]), 500, ); @@ -72,11 +63,8 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React }, [reload, refetch]); useEffect(() => { - prevRoomFilters.current = { - searchText: roomSearchText, - types: roomSearchTypes, - }; - }, [roomSearchText, roomSearchTypes]); + prevRoomFilterText.current = searchText; + }, [searchText]); const headers = ( <> @@ -128,7 +116,7 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React return ( <> - + {isLoading && ( {headers} diff --git a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx index 3cfe81730ffd..1f6916f3f4a8 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx @@ -2,11 +2,10 @@ import { Box, Icon, TextInput } from '@rocket.chat/fuselage'; import type { OptionProp } from '@rocket.chat/ui-client'; import { MultiSelectCustom } from '@rocket.chat/ui-client'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useCallback, useEffect, type Dispatch, type MutableRefObject, type ReactElement, type SetStateAction } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; +import type { Dispatch, ReactElement, SetStateAction } from 'react'; -import type { SearchFilters } from './RoomsTable'; - -const roomTypeFilterStructure = [ +const initialRoomTypeFilterStructure = [ { id: 'filter_by_room', text: 'Filter_by_room', @@ -44,42 +43,39 @@ const roomTypeFilterStructure = [ }, ] as OptionProp[]; -const RoomsTableFilters = ({ - setRoomSearchText, - setRoomSearchTypes, - prevRoomFilters, -}: { - setRoomSearchText: Dispatch>; - setRoomSearchTypes: Dispatch>; - prevRoomFilters: MutableRefObject; -}): ReactElement => { +const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch> }): ReactElement => { const t = useTranslation(); + const [text, setText] = useState(''); + + const [roomTypeSelectedOptions, setRoomTypeSelectedOptions] = useState([]); + const [roomTypeFilterStructure, setRoomTypeFilterStructure] = useState(initialRoomTypeFilterStructure); + + useEffect(() => { + const updatedStructure = roomTypeFilterStructure.map((option: OptionProp) => ({ + ...option, + checked: roomTypeSelectedOptions.some(selectedOption => selectedOption.id === option.id) + })); + + setRoomTypeFilterStructure(updatedStructure as OptionProp[]); + }, [roomTypeSelectedOptions]); const handleSearchTextChange = useCallback( (event) => { const text = event.currentTarget.value; - setRoomSearchText(text); + setFilters({ searchText: text, types: roomTypeSelectedOptions }); + setText(text); }, - [setRoomSearchText], + [roomTypeSelectedOptions, setFilters], ); const handleRoomTypeChange = useCallback( (options: OptionProp[]) => { - setRoomSearchTypes(options); + setFilters({ searchText: text, types: options }); + setRoomTypeSelectedOptions(options); }, - [setRoomSearchTypes], + [text, setFilters], ) as Dispatch>; - useEffect(() => { - roomTypeFilterStructure.forEach((type, index) => { - prevRoomFilters.current.types.forEach((selectedType) => { - if (type.id === selectedType.id) { - roomTypeFilterStructure[index] = selectedType; - } - }); - }); - }, [prevRoomFilters]); - return ( } onChange={handleSearchTextChange} - value={prevRoomFilters.current.searchText} + value={text} /> @@ -107,7 +103,7 @@ const RoomsTableFilters = ({ defaultTitle={'All_rooms' as any} selectedOptionsTitle='Rooms' setSelectedOptions={handleRoomTypeChange} - selectedOptions={prevRoomFilters.current.types} + selectedOptions={roomTypeSelectedOptions} />
diff --git a/apps/meteor/tests/e2e/admin-room.spec.ts b/apps/meteor/tests/e2e/admin-room.spec.ts index e69a85125455..07c31e9fee9b 100644 --- a/apps/meteor/tests/e2e/admin-room.spec.ts +++ b/apps/meteor/tests/e2e/admin-room.spec.ts @@ -27,6 +27,7 @@ test.describe.serial('admin-rooms', () => { channel = await createTargetChannel(api); privateRoom = await createTargetPrivateChannel(api); }); + test('should display the Rooms Table', async ({ page }) => { await expect(page.locator('[data-qa-type="PageHeader-title"]')).toContainText('Rooms'); }); @@ -82,7 +83,7 @@ test.describe.serial('admin-rooms', () => { await expect(page.locator('text=No results found')).toBeVisible(); }); - test('should filter rooms by type and name and keep the filter after changing section', async ({ page }) => { + test('should filter rooms by type and name and clean the filter after changing section', async ({ page }) => { await admin.inputSearchRooms.fill(privateRoom); const dropdown = await admin.dropdownFilterRoomType(); await dropdown.click(); @@ -97,15 +98,6 @@ test.describe.serial('admin-rooms', () => { await page.goto('/admin/rooms'); const selectDropdown = await admin.dropdownFilterRoomType('Rooms (1)'); - await expect(selectDropdown).toBeVisible(); - - await selectDropdown.click(); - - const isChecked = await privateOption.isChecked(); - - expect(isChecked).toBe(true); - - await expect(admin.inputSearchRooms).toHaveValue(privateRoom); - await expect(page.locator(`[qa-room-name="${privateRoom}"]`)).toBeVisible(); + await expect(selectDropdown).not.toBeVisible(); }); }); From 497a7ec59eb4d46b6a118f75a81cfbecd69fd950 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Wed, 24 Apr 2024 11:57:03 -0300 Subject: [PATCH 15/27] chore: update changeset --- .changeset/brown-lobsters-join.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/brown-lobsters-join.md b/.changeset/brown-lobsters-join.md index 36f66a9193df..ac0e52c8f829 100644 --- a/.changeset/brown-lobsters-join.md +++ b/.changeset/brown-lobsters-join.md @@ -2,4 +2,4 @@ '@rocket.chat/meteor': patch --- -Resolved an issue causing room filter settings to reset on admin page navigation. Now, filter selections remain intact across different admin sections, streamlining task management and reducing redundant inputs. +Resolved an issue with the room type filter not being reset after navigating between admin sections. From f69d3309c43ab7878e1cb020388ed39fc9eee07b Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Wed, 24 Apr 2024 15:05:48 -0300 Subject: [PATCH 16/27] fix: fix lint issues --- apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx index 1f6916f3f4a8..c8a7a529b30f 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx @@ -53,7 +53,7 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch { const updatedStructure = roomTypeFilterStructure.map((option: OptionProp) => ({ ...option, - checked: roomTypeSelectedOptions.some(selectedOption => selectedOption.id === option.id) + checked: roomTypeSelectedOptions.some((selectedOption) => selectedOption.id === option.id) })); setRoomTypeFilterStructure(updatedStructure as OptionProp[]); From c75ea2f3f3c99d25ba47e7ee8ec02e73dd08e941 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Wed, 24 Apr 2024 15:29:40 -0300 Subject: [PATCH 17/27] fix: fix lint issues --- apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx index c8a7a529b30f..8b7f31e6eb0c 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx @@ -53,7 +53,7 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch { const updatedStructure = roomTypeFilterStructure.map((option: OptionProp) => ({ ...option, - checked: roomTypeSelectedOptions.some((selectedOption) => selectedOption.id === option.id) + checked: roomTypeSelectedOptions.some((selectedOption) => selectedOption.id === option.id), })); setRoomTypeFilterStructure(updatedStructure as OptionProp[]); From 43a65d3aec20047ec7277b1410ad4b55c66d26d6 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Fri, 26 Apr 2024 17:14:22 -0300 Subject: [PATCH 18/27] chore: changing unit test verification logic --- apps/meteor/tests/e2e/admin-room.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/tests/e2e/admin-room.spec.ts b/apps/meteor/tests/e2e/admin-room.spec.ts index 07c31e9fee9b..c23e342747da 100644 --- a/apps/meteor/tests/e2e/admin-room.spec.ts +++ b/apps/meteor/tests/e2e/admin-room.spec.ts @@ -97,7 +97,7 @@ test.describe.serial('admin-rooms', () => { await page.goto('/admin/rooms'); - const selectDropdown = await admin.dropdownFilterRoomType('Rooms (1)'); - await expect(selectDropdown).not.toBeVisible(); + const selectDropdown = await admin.dropdownFilterRoomType('All rooms'); + await expect(selectDropdown).toBeVisible(); }); }); From 7b76abf85210eb279fda9c0f2413c0fdab17f37e Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Tue, 30 Apr 2024 13:50:39 -0300 Subject: [PATCH 19/27] feat: adding more assertions for the unit tests --- .../views/admin/rooms/RoomsTable.spec.tsx | 70 +++++++++++++++---- .../unit/lib/utils/queryClientWrapper.tsx | 12 ++++ 2 files changed, 67 insertions(+), 15 deletions(-) create mode 100644 apps/meteor/tests/unit/lib/utils/queryClientWrapper.tsx diff --git a/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx b/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx index a19a1131e995..9b1276a20f3c 100644 --- a/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx +++ b/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx @@ -1,16 +1,15 @@ -/* eslint-disable no-restricted-properties */ import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; import * as Hooks from '@rocket.chat/fuselage-hooks'; import * as Contexts from '@rocket.chat/ui-contexts'; import * as ReactQuery from '@tanstack/react-query'; import '@testing-library/jest-dom'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen } from '@testing-library/react'; import { expect } from 'chai'; import React from 'react'; import sinon from 'sinon'; import RoomsTable from '../../../../../../client/views/admin/rooms/RoomsTable'; +import { createQueryClientWrapper } from '../../../../lib/utils/queryClientWrapper'; jest.mock('../../../../../../client/views/admin/rooms/RoomRow', () => { return ({ someProp }: any) => ( @@ -20,21 +19,13 @@ jest.mock('../../../../../../client/views/admin/rooms/RoomRow', () => { ); }); -const createQueryClientWrapper = () => { - const queryClient = new QueryClient(); - const provider = ({ children }: any) => { - return {children}; - }; - provider.displayName = 'provider'; - - return provider; -}; - describe('RoomsTable', () => { let useQueryStub: any; + let useEndpointStub: any; + let useDebouncedValueStub: any; beforeEach(() => { - sinon.stub(Contexts, 'useEndpoint').returns( + useEndpointStub = sinon.stub(Contexts, 'useEndpoint').returns( sinon.fake(() => Promise.resolve({ data: { rooms: [], total: 0 }, @@ -47,9 +38,9 @@ describe('RoomsTable', () => { ); useQueryStub = sinon.stub(ReactQuery, 'useQuery').returns({} as any); sinon.stub(Hooks, 'useMediaQuery').returns(true); - sinon.stub(Hooks, 'useLocalStorage').returns([[], sinon.fake()]); - sinon.stub(Hooks, 'useDebouncedValue').callsFake((value) => value); + useDebouncedValueStub = sinon.stub(Hooks, 'useDebouncedValue').callsFake((value) => value); sinon.stub(Contexts, 'useTranslation').returns({} as any); + sinon.stub(React, 'memo').returns({} as any); }); afterEach(() => { @@ -67,6 +58,22 @@ describe('RoomsTable', () => { expect(screen.queryByTestId('RoomGenericTableLoadingTable')).not.to.be.undefined; + sinon.assert.calledOnceWithMatch( + useQueryStub, + [ + 'rooms', + { + filter: '', + sort: '{ "name": 1 }', + count: 25, + offset: 0, + types: ['d', 'p', 'c', 'l', 'discussions', 'teams'], + }, + 'admin', + ], + async () => useEndpointStub, + ); + sinon.restore(); }); @@ -81,6 +88,21 @@ describe('RoomsTable', () => { }); render(, { wrapper: createQueryClientWrapper() }); expect(screen.getByText('No_results_found')).to.exist; + sinon.assert.calledOnceWithMatch( + useQueryStub, + [ + 'rooms', + { + filter: '', + sort: '{ "name": 1 }', + count: 25, + offset: 0, + types: ['d', 'p', 'c', 'l', 'discussions', 'teams'], + }, + 'admin', + ], + async () => useEndpointStub, + ); }); it('renders room data when available', async () => { @@ -92,10 +114,13 @@ describe('RoomsTable', () => { refetch: sinon.fake(), }); + useDebouncedValueStub.returns('Room'); + render(, { wrapper: createQueryClientWrapper() }); expect(screen.queryByTestId('RoomRow')).not.be.undefined; expect(screen.queryByTestId('RoomGenericTableLoadingTable')).to.be.null; + sinon.assert.calledWithMatch(useQueryStub, ['rooms', 'Room', 'admin'], async () => useEndpointStub); }); it('renders error state', async () => { @@ -110,5 +135,20 @@ describe('RoomsTable', () => { render(, { wrapper: createQueryClientWrapper() }); expect(screen.getByText('Something_went_wrong')).not.be.undefined; expect(screen.getByText('Reload_page')).not.be.undefined; + sinon.assert.calledOnceWithMatch( + useQueryStub, + [ + 'rooms', + { + filter: '', + sort: '{ "name": 1 }', + count: 25, + offset: 0, + types: ['d', 'p', 'c', 'l', 'discussions', 'teams'], + }, + 'admin', + ], + async () => useEndpointStub, + ); }); }); diff --git a/apps/meteor/tests/unit/lib/utils/queryClientWrapper.tsx b/apps/meteor/tests/unit/lib/utils/queryClientWrapper.tsx new file mode 100644 index 000000000000..e27bc9876f53 --- /dev/null +++ b/apps/meteor/tests/unit/lib/utils/queryClientWrapper.tsx @@ -0,0 +1,12 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import React from 'react'; + +export const createQueryClientWrapper = () => { + const queryClient = new QueryClient(); + const provider = ({ children }: any) => { + return {children}; + }; + provider.displayName = 'provider'; + + return provider; +}; From 2a831dc16aaea0982722f9bcbcda2d6424ab5f19 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Fri, 3 May 2024 16:44:13 -0300 Subject: [PATCH 20/27] feat: change the application to use useMemo instead useEffect to avoid rerender --- .../client/views/admin/rooms/RoomsTableFilters.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx index 8b7f31e6eb0c..bbbb83751b43 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx @@ -2,7 +2,7 @@ import { Box, Icon, TextInput } from '@rocket.chat/fuselage'; import type { OptionProp } from '@rocket.chat/ui-client'; import { MultiSelectCustom } from '@rocket.chat/ui-client'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import type { Dispatch, ReactElement, SetStateAction } from 'react'; const initialRoomTypeFilterStructure = [ @@ -48,17 +48,15 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch([]); - const [roomTypeFilterStructure, setRoomTypeFilterStructure] = useState(initialRoomTypeFilterStructure); - useEffect(() => { - const updatedStructure = roomTypeFilterStructure.map((option: OptionProp) => ({ + const roomTypeFilterStructure = useMemo(() => { + return initialRoomTypeFilterStructure.map(option => ({ ...option, - checked: roomTypeSelectedOptions.some((selectedOption) => selectedOption.id === option.id), + checked: roomTypeSelectedOptions.some(selectedOption => selectedOption.id === option.id), })); - - setRoomTypeFilterStructure(updatedStructure as OptionProp[]); }, [roomTypeSelectedOptions]); + const handleSearchTextChange = useCallback( (event) => { const text = event.currentTarget.value; @@ -99,7 +97,7 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch Date: Fri, 3 May 2024 17:13:05 -0300 Subject: [PATCH 21/27] fix: lint issues --- apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx index bbbb83751b43..95068ce327e3 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx @@ -50,13 +50,12 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch([]); const roomTypeFilterStructure = useMemo(() => { - return initialRoomTypeFilterStructure.map(option => ({ + return initialRoomTypeFilterStructure.map((option) => ({ ...option, - checked: roomTypeSelectedOptions.some(selectedOption => selectedOption.id === option.id), + checked: roomTypeSelectedOptions.some((selectedOption) => selectedOption.id === option.id), })); }, [roomTypeSelectedOptions]); - const handleSearchTextChange = useCallback( (event) => { const text = event.currentTarget.value; From f9df2e3cb42d2aa2b5b651df8455162ba565992f Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Tue, 7 May 2024 15:22:17 -0300 Subject: [PATCH 22/27] chore: adding the mocking client wrapper into the mock package --- .../unit/client/views/admin/rooms/RoomsTable.spec.tsx | 11 ++++++----- .../mock-providers/src/MockedQueryClientWrapper.tsx | 6 +++--- packages/mock-providers/src/index.ts | 1 + 3 files changed, 10 insertions(+), 8 deletions(-) rename apps/meteor/tests/unit/lib/utils/queryClientWrapper.tsx => packages/mock-providers/src/MockedQueryClientWrapper.tsx (60%) diff --git a/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx b/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx index 9b1276a20f3c..5a4420d62d2a 100644 --- a/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx +++ b/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx @@ -9,7 +9,8 @@ import React from 'react'; import sinon from 'sinon'; import RoomsTable from '../../../../../../client/views/admin/rooms/RoomsTable'; -import { createQueryClientWrapper } from '../../../../lib/utils/queryClientWrapper'; +import { MockedQueryClientWrapper } from '@rocket.chat/mock-providers'; + jest.mock('../../../../../../client/views/admin/rooms/RoomRow', () => { return ({ someProp }: any) => ( @@ -54,7 +55,7 @@ describe('RoomsTable', () => { error: undefined, }); - render(, { wrapper: createQueryClientWrapper() }); + render(, { wrapper: MockedQueryClientWrapper() }); expect(screen.queryByTestId('RoomGenericTableLoadingTable')).not.to.be.undefined; @@ -86,7 +87,7 @@ describe('RoomsTable', () => { error: undefined, isSuccess: true, }); - render(, { wrapper: createQueryClientWrapper() }); + render(, { wrapper: MockedQueryClientWrapper() }); expect(screen.getByText('No_results_found')).to.exist; sinon.assert.calledOnceWithMatch( useQueryStub, @@ -116,7 +117,7 @@ describe('RoomsTable', () => { useDebouncedValueStub.returns('Room'); - render(, { wrapper: createQueryClientWrapper() }); + render(, { wrapper: MockedQueryClientWrapper() }); expect(screen.queryByTestId('RoomRow')).not.be.undefined; expect(screen.queryByTestId('RoomGenericTableLoadingTable')).to.be.null; @@ -132,7 +133,7 @@ describe('RoomsTable', () => { refetch: sinon.fake(), }); - render(, { wrapper: createQueryClientWrapper() }); + render(, { wrapper: MockedQueryClientWrapper() }); expect(screen.getByText('Something_went_wrong')).not.be.undefined; expect(screen.getByText('Reload_page')).not.be.undefined; sinon.assert.calledOnceWithMatch( diff --git a/apps/meteor/tests/unit/lib/utils/queryClientWrapper.tsx b/packages/mock-providers/src/MockedQueryClientWrapper.tsx similarity index 60% rename from apps/meteor/tests/unit/lib/utils/queryClientWrapper.tsx rename to packages/mock-providers/src/MockedQueryClientWrapper.tsx index e27bc9876f53..6669bf18438e 100644 --- a/apps/meteor/tests/unit/lib/utils/queryClientWrapper.tsx +++ b/packages/mock-providers/src/MockedQueryClientWrapper.tsx @@ -1,7 +1,7 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import React from 'react'; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import React from "react"; -export const createQueryClientWrapper = () => { +export const MockedQueryClientWrapper = () => { const queryClient = new QueryClient(); const provider = ({ children }: any) => { return {children}; diff --git a/packages/mock-providers/src/index.ts b/packages/mock-providers/src/index.ts index 22e4427cea1c..cf1ab57b5985 100644 --- a/packages/mock-providers/src/index.ts +++ b/packages/mock-providers/src/index.ts @@ -7,3 +7,4 @@ export * from './MockedModalContext'; export * from './MockedServerContext'; export * from './MockedSettingsContext'; export * from './MockedUserContext'; +export * from './MockedQueryClientWrapper'; From 0f30b2465a9ccd6ccd25e85fab76d4ac9e474b54 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Tue, 7 May 2024 16:31:40 -0300 Subject: [PATCH 23/27] feat: improving test --- apps/meteor/tests/e2e/admin-room.spec.ts | 25 ++++++++++-------- apps/meteor/tests/e2e/page-objects/admin.ts | 26 +++++++++++++++++++ .../src/MockedQueryClientWrapper.tsx | 4 +-- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/apps/meteor/tests/e2e/admin-room.spec.ts b/apps/meteor/tests/e2e/admin-room.spec.ts index c23e342747da..c00ee521af27 100644 --- a/apps/meteor/tests/e2e/admin-room.spec.ts +++ b/apps/meteor/tests/e2e/admin-room.spec.ts @@ -1,15 +1,13 @@ import { faker } from '@faker-js/faker'; -import { Page } from '@playwright/test'; import { Users } from './fixtures/userStates'; -import { Admin } from './page-objects'; +import { Admin, AdminSectionsHref } from './page-objects'; import { createTargetChannel, createTargetPrivateChannel } from './utils'; import { expect, test } from './utils/test'; test.use({ storageState: Users.admin.state }); test.describe.serial('admin-rooms', () => { - let adminPage: Page; let channel: string; let privateRoom: string; let admin: Admin; @@ -20,12 +18,15 @@ test.describe.serial('admin-rooms', () => { }); test.beforeAll(async ({ browser, api }) => { - adminPage = await browser.newPage({ storageState: Users.admin.state }); - await adminPage.goto('/home'); - await adminPage.waitForSelector('[data-qa-id="home-header"]'); - - channel = await createTargetChannel(api); - privateRoom = await createTargetPrivateChannel(api); + [channel, privateRoom] = await Promise.all([ + createTargetChannel(api), + createTargetPrivateChannel(api), + browser.newPage({ storageState: Users.admin.state }).then(async (page) => { + await page.goto('/home'); + await page.waitForSelector('[data-qa-id="home-header"]'); + return page; + }), + ]); }); test('should display the Rooms Table', async ({ page }) => { @@ -93,9 +94,11 @@ test.describe.serial('admin-rooms', () => { await privateOption.waitFor(); await privateOption.click(); - await page.goto('/admin/info'); + const workspaceButton = await admin.adminSectionButton(AdminSectionsHref.Workspace); + await workspaceButton.click(); - await page.goto('/admin/rooms'); + const roomsButton = await admin.adminSectionButton(AdminSectionsHref.Rooms); + await roomsButton.click(); const selectDropdown = await admin.dropdownFilterRoomType('All rooms'); await expect(selectDropdown).toBeVisible(); diff --git a/apps/meteor/tests/e2e/page-objects/admin.ts b/apps/meteor/tests/e2e/page-objects/admin.ts index 1d42b6b75ac7..476048df95d6 100644 --- a/apps/meteor/tests/e2e/page-objects/admin.ts +++ b/apps/meteor/tests/e2e/page-objects/admin.ts @@ -2,6 +2,28 @@ import type { Locator, Page } from '@playwright/test'; import { AdminFlextab } from './fragments/admin-flextab'; +export enum AdminSectionsHref { + Workspace = '/admin/info', + Subscription = '/admin/subscription', + Engagement = '/admin/engagement/users', + Moderation = '/admin/moderation', + Federation = '/admin/federation', + Rooms = '/admin/rooms', + Users = '/admin/users', + Invites = '/admin/invites', + User_Status = '/admin/user-status', + Permissions = '/admin/permissions', + Device_Management = '/admin/device-management', + Email_Inboxes = '/admin/email-inboxes', + Mailer = '/admin/mailer', + Third_party_login = '/admin/third-party-login', + Integrations = '/admin/integrations', + Import = '/admin/import', + Reports = '/admin/reports', + Sounds = '/admin/sounds', + Emoji = '/admin/emoji', + Settings = '/admin/settings', +} export class Admin { public readonly page: Page; @@ -188,4 +210,8 @@ export class Admin { async dropdownFilterRoomType(text = 'All rooms'): Promise { return this.page.locator(`div[role="button"]:has-text("${text}")`); } + + async adminSectionButton(href: AdminSectionsHref): Promise { + return this.page.locator(`a[href="${href}"]`); + } } diff --git a/packages/mock-providers/src/MockedQueryClientWrapper.tsx b/packages/mock-providers/src/MockedQueryClientWrapper.tsx index 6669bf18438e..93d5f2cee15a 100644 --- a/packages/mock-providers/src/MockedQueryClientWrapper.tsx +++ b/packages/mock-providers/src/MockedQueryClientWrapper.tsx @@ -1,5 +1,5 @@ -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import React from "react"; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import React from 'react'; export const MockedQueryClientWrapper = () => { const queryClient = new QueryClient(); From 35aacbd44d4fdd490581c32a743bd7e27a148a22 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Wed, 8 May 2024 14:26:17 -0300 Subject: [PATCH 24/27] fix: lint issues --- .../unit/client/views/admin/rooms/RoomsTable.spec.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx b/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx index 5a4420d62d2a..5454473e7147 100644 --- a/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx +++ b/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx @@ -1,5 +1,6 @@ import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; import * as Hooks from '@rocket.chat/fuselage-hooks'; +import { MockedQueryClientWrapper as mockedQueryClientWrapper } from '@rocket.chat/mock-providers'; import * as Contexts from '@rocket.chat/ui-contexts'; import * as ReactQuery from '@tanstack/react-query'; import '@testing-library/jest-dom'; @@ -9,8 +10,6 @@ import React from 'react'; import sinon from 'sinon'; import RoomsTable from '../../../../../../client/views/admin/rooms/RoomsTable'; -import { MockedQueryClientWrapper } from '@rocket.chat/mock-providers'; - jest.mock('../../../../../../client/views/admin/rooms/RoomRow', () => { return ({ someProp }: any) => ( @@ -55,7 +54,7 @@ describe('RoomsTable', () => { error: undefined, }); - render(, { wrapper: MockedQueryClientWrapper() }); + render(, { wrapper: mockedQueryClientWrapper() }); expect(screen.queryByTestId('RoomGenericTableLoadingTable')).not.to.be.undefined; @@ -87,7 +86,7 @@ describe('RoomsTable', () => { error: undefined, isSuccess: true, }); - render(, { wrapper: MockedQueryClientWrapper() }); + render(, { wrapper: mockedQueryClientWrapper() }); expect(screen.getByText('No_results_found')).to.exist; sinon.assert.calledOnceWithMatch( useQueryStub, @@ -117,7 +116,7 @@ describe('RoomsTable', () => { useDebouncedValueStub.returns('Room'); - render(, { wrapper: MockedQueryClientWrapper() }); + render(, { wrapper: mockedQueryClientWrapper() }); expect(screen.queryByTestId('RoomRow')).not.be.undefined; expect(screen.queryByTestId('RoomGenericTableLoadingTable')).to.be.null; @@ -133,7 +132,7 @@ describe('RoomsTable', () => { refetch: sinon.fake(), }); - render(, { wrapper: MockedQueryClientWrapper() }); + render(, { wrapper: mockedQueryClientWrapper() }); expect(screen.getByText('Something_went_wrong')).not.be.undefined; expect(screen.getByText('Reload_page')).not.be.undefined; sinon.assert.calledOnceWithMatch( From f94fbd1d97824a68b127fcd5a15a532027b9c1f9 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Thu, 6 Jun 2024 20:13:05 -0300 Subject: [PATCH 25/27] fix: some fixes in the test selectors --- .../views/admin/rooms/RoomsTableFilters.tsx | 1 - apps/meteor/tests/e2e/admin-room.spec.ts | 5 +---- .../views/admin/rooms/RoomsTable.spec.tsx | 18 +++++++++++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx index 95068ce327e3..1ed21c1234a9 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx @@ -85,7 +85,6 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch { const dropdown = await admin.dropdownFilterRoomType(); await dropdown.click(); - const privateOption = page.locator('text=Private channels'); - - await privateOption.waitFor(); - await privateOption.click(); + await page.locator('text=Private channels').click(); await expect(page.locator(`[qa-room-name="${privateRoom}"]`)).toBeVisible(); }); diff --git a/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx b/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx index 5454473e7147..8efa0279e9a8 100644 --- a/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx +++ b/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx @@ -11,6 +11,18 @@ import sinon from 'sinon'; import RoomsTable from '../../../../../../client/views/admin/rooms/RoomsTable'; +jest.mock('../../../../../../../meteor/client/components/GenericTable', () => ({ + GenericTable: ({ children, ...props }: any) => {children}
, + GenericTableBody: ({ children }: any) => {children}, + GenericTableHeader: ({ children }: any) => {children}, + GenericTableHeaderCell: ({ children, ...props }: any) => {children}, + GenericTableLoadingTable: () => ( + + Loading + + ), +})); + jest.mock('../../../../../../client/views/admin/rooms/RoomRow', () => { return ({ someProp }: any) => ( @@ -55,8 +67,8 @@ describe('RoomsTable', () => { }); render(, { wrapper: mockedQueryClientWrapper() }); - - expect(screen.queryByTestId('RoomGenericTableLoadingTable')).not.to.be.undefined; + const loading = await screen.findByText('Loading'); + expect(loading).to.exist; sinon.assert.calledOnceWithMatch( useQueryStub, @@ -119,7 +131,7 @@ describe('RoomsTable', () => { render(, { wrapper: mockedQueryClientWrapper() }); expect(screen.queryByTestId('RoomRow')).not.be.undefined; - expect(screen.queryByTestId('RoomGenericTableLoadingTable')).to.be.null; + expect(screen.queryByText(/loading/i)).to.be.null; sinon.assert.calledWithMatch(useQueryStub, ['rooms', 'Room', 'admin'], async () => useEndpointStub); }); From 05765b1f962cd0c6b2dd7c19ac22e032685d0eb2 Mon Sep 17 00:00:00 2001 From: AllanPazRibeiro Date: Fri, 7 Jun 2024 10:36:50 -0300 Subject: [PATCH 26/27] chore: removing unit test --- apps/meteor/.meteorMocks/index.ts | 163 +---------------- .../client/views/admin/rooms/RoomsTable.tsx | 2 +- .../views/admin/rooms/RoomsTable.spec.tsx | 166 ------------------ .../src/MockedQueryClientWrapper.tsx | 12 -- packages/mock-providers/src/index.ts | 1 - 5 files changed, 3 insertions(+), 341 deletions(-) delete mode 100644 apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx delete mode 100644 packages/mock-providers/src/MockedQueryClientWrapper.tsx diff --git a/apps/meteor/.meteorMocks/index.ts b/apps/meteor/.meteorMocks/index.ts index 83b979853ce2..e70ffa7f7c46 100644 --- a/apps/meteor/.meteorMocks/index.ts +++ b/apps/meteor/.meteorMocks/index.ts @@ -1,164 +1,5 @@ import sinon from 'sinon'; -const events = require('events'); -const subscribeMock = jest.fn(() => ({ - ready: jest.fn(() => true), - stop: sinon.stub(), -})); -const callMock = sinon.stub(); -const applyMock = sinon.stub(); - -class MockLocalStorage { - store: {}; - constructor() { - this.store = {}; - } - - getItem(key) { - return this.store[key] || null; - } - - setItem(key, value) { - this.store[key] = String(value); - } - - removeItem(key) { - delete this.store[key]; - } - - clear() { - this.store = {}; - } -} - -module.exports = { - Meteor: { - loginWithSamlToken: sinon.stub(), - subscribe: subscribeMock, - call: callMock, - apply: applyMock, - methods: sinon.stub(), - connection: { - subscribe: subscribeMock, - call: callMock, - apply: applyMock, - status: sinon.stub(() => ({ connected: true })), - reconnect: sinon.stub(), - disconnect: sinon.stub(), - _stream: new events.EventEmitter(), - }, - isClient: true, - isServer: false, - _localStorage: new MockLocalStorage(), - }, - Mongo: { - Collection: class Collection { - name: any; - find: sinon.SinonStub; - findOne: sinon.SinonStub; - insert: sinon.SinonStub; - update: sinon.SinonStub; - remove: sinon.SinonStub; - constructor(name) { - this.name = name; - this.find = sinon.stub().returns({ - fetch: () => [{ _id: '1', name: 'Document' }], - count: () => 1, - observe: sinon.stub(), - }); - this.findOne = sinon.stub().returns({ _id: '1', name: 'Document' }); - this.insert = sinon.stub(); - this.update = sinon.stub(); - this.remove = sinon.stub(); - } - }, - }, - Accounts: { - createUser: sinon.stub(), - findUserByEmail: sinon.stub(), - findUserByUsername: sinon.stub(), - validateLoginAttempt: sinon.stub(), - onLogin: sinon.stub(), - onLoginFailure: sinon.stub(), - }, - check: sinon.stub(), - Match: { - Maybe: sinon.stub(), - OneOf: sinon.stub(), - ObjectIncluding: sinon.stub(), - Where: sinon.stub(), - }, - Tracker: { - autorun: sinon.stub(), - nonreactive: sinon.stub(), - Dependency: class { - depend: sinon.SinonStub; - changed: sinon.SinonStub; - constructor() { - this.depend = sinon.stub(); - this.changed = sinon.stub(); - } - }, - }, - ReactiveVar: class ReactiveVar { - value: any; - dep: any; - constructor(initialValue) { - this.value = initialValue; - this.dep = new (require('meteor/tracker').Tracker.Dependency)(); - } - - get() { - this.dep.depend(); - return this.value; - } - - set(newValue) { - if (this.value !== newValue) { - this.value = newValue; - this.dep.changed(); - } - } - }, - ReactiveDict: class ReactiveDict { - dict: Map; - deps: {}; - constructor(initialValues) { - this.dict = new Map(); - this.deps = {}; - - if (initialValues) { - for (const [key, value] of Object.entries(initialValues)) { - this.set(key, value); - } - } - } - - get(key) { - if (!this.deps[key]) { - this.deps[key] = new Tracker.Dependency(); - } - this.deps[key].depend(); - return this.dict.get(key); - } - - set(key, value) { - const oldValue = this.dict.get(key); - if (oldValue !== value) { - this.dict.set(key, value); - if (this.deps[key]) { - this.deps[key].changed(); - } - } - } - - equals(key, value) { - const currentValue = this.get(key); - return currentValue === value; - } - - all() { - return Object.fromEntries(this.dict); - } - }, +export const Meteor = { + loginWithSamlToken: sinon.stub(), }; diff --git a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx index 3beb3b88b2e6..094ccb95857a 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx @@ -121,7 +121,7 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React {headers} - + )} diff --git a/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx b/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx deleted file mode 100644 index 8efa0279e9a8..000000000000 --- a/apps/meteor/tests/unit/client/views/admin/rooms/RoomsTable.spec.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; -import * as Hooks from '@rocket.chat/fuselage-hooks'; -import { MockedQueryClientWrapper as mockedQueryClientWrapper } from '@rocket.chat/mock-providers'; -import * as Contexts from '@rocket.chat/ui-contexts'; -import * as ReactQuery from '@tanstack/react-query'; -import '@testing-library/jest-dom'; -import { render, screen } from '@testing-library/react'; -import { expect } from 'chai'; -import React from 'react'; -import sinon from 'sinon'; - -import RoomsTable from '../../../../../../client/views/admin/rooms/RoomsTable'; - -jest.mock('../../../../../../../meteor/client/components/GenericTable', () => ({ - GenericTable: ({ children, ...props }: any) => {children}
, - GenericTableBody: ({ children }: any) => {children}, - GenericTableHeader: ({ children }: any) => {children}, - GenericTableHeaderCell: ({ children, ...props }: any) => {children}, - GenericTableLoadingTable: () => ( - - Loading - - ), -})); - -jest.mock('../../../../../../client/views/admin/rooms/RoomRow', () => { - return ({ someProp }: any) => ( - - MockedRoomRow {someProp} - - ); -}); - -describe('RoomsTable', () => { - let useQueryStub: any; - let useEndpointStub: any; - let useDebouncedValueStub: any; - - beforeEach(() => { - useEndpointStub = sinon.stub(Contexts, 'useEndpoint').returns( - sinon.fake(() => - Promise.resolve({ - data: { rooms: [], total: 0 }, - isSuccess: true, - isLoading: false, - isError: false, - refetch: sinon.fake(), - } as never), - ), - ); - useQueryStub = sinon.stub(ReactQuery, 'useQuery').returns({} as any); - sinon.stub(Hooks, 'useMediaQuery').returns(true); - useDebouncedValueStub = sinon.stub(Hooks, 'useDebouncedValue').callsFake((value) => value); - sinon.stub(Contexts, 'useTranslation').returns({} as any); - sinon.stub(React, 'memo').returns({} as any); - }); - - afterEach(() => { - sinon.restore(); - }); - - it('renders loading state initially', async () => { - useQueryStub.returns({ - isLoading: true, - data: undefined, - error: undefined, - }); - - render(, { wrapper: mockedQueryClientWrapper() }); - const loading = await screen.findByText('Loading'); - expect(loading).to.exist; - - sinon.assert.calledOnceWithMatch( - useQueryStub, - [ - 'rooms', - { - filter: '', - sort: '{ "name": 1 }', - count: 25, - offset: 0, - types: ['d', 'p', 'c', 'l', 'discussions', 'teams'], - }, - 'admin', - ], - async () => useEndpointStub, - ); - - sinon.restore(); - }); - - it('renders no results when data is empty', async () => { - useQueryStub.returns({ - isLoading: false, - data: { - rooms: [], - }, - error: undefined, - isSuccess: true, - }); - render(, { wrapper: mockedQueryClientWrapper() }); - expect(screen.getByText('No_results_found')).to.exist; - sinon.assert.calledOnceWithMatch( - useQueryStub, - [ - 'rooms', - { - filter: '', - sort: '{ "name": 1 }', - count: 25, - offset: 0, - types: ['d', 'p', 'c', 'l', 'discussions', 'teams'], - }, - 'admin', - ], - async () => useEndpointStub, - ); - }); - - it('renders room data when available', async () => { - useQueryStub.returns({ - data: { rooms: [{ _id: '1', name: 'Room 1', t: RoomType.DIRECT_MESSAGE }], total: 1 }, - isSuccess: true, - isLoading: false, - isError: false, - refetch: sinon.fake(), - }); - - useDebouncedValueStub.returns('Room'); - - render(, { wrapper: mockedQueryClientWrapper() }); - - expect(screen.queryByTestId('RoomRow')).not.be.undefined; - expect(screen.queryByText(/loading/i)).to.be.null; - sinon.assert.calledWithMatch(useQueryStub, ['rooms', 'Room', 'admin'], async () => useEndpointStub); - }); - - it('renders error state', async () => { - useQueryStub.returns({ - data: undefined, - isSuccess: false, - isLoading: false, - isError: true, - refetch: sinon.fake(), - }); - - render(, { wrapper: mockedQueryClientWrapper() }); - expect(screen.getByText('Something_went_wrong')).not.be.undefined; - expect(screen.getByText('Reload_page')).not.be.undefined; - sinon.assert.calledOnceWithMatch( - useQueryStub, - [ - 'rooms', - { - filter: '', - sort: '{ "name": 1 }', - count: 25, - offset: 0, - types: ['d', 'p', 'c', 'l', 'discussions', 'teams'], - }, - 'admin', - ], - async () => useEndpointStub, - ); - }); -}); diff --git a/packages/mock-providers/src/MockedQueryClientWrapper.tsx b/packages/mock-providers/src/MockedQueryClientWrapper.tsx deleted file mode 100644 index 93d5f2cee15a..000000000000 --- a/packages/mock-providers/src/MockedQueryClientWrapper.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import React from 'react'; - -export const MockedQueryClientWrapper = () => { - const queryClient = new QueryClient(); - const provider = ({ children }: any) => { - return {children}; - }; - provider.displayName = 'provider'; - - return provider; -}; diff --git a/packages/mock-providers/src/index.ts b/packages/mock-providers/src/index.ts index cf1ab57b5985..22e4427cea1c 100644 --- a/packages/mock-providers/src/index.ts +++ b/packages/mock-providers/src/index.ts @@ -7,4 +7,3 @@ export * from './MockedModalContext'; export * from './MockedServerContext'; export * from './MockedSettingsContext'; export * from './MockedUserContext'; -export * from './MockedQueryClientWrapper'; From bac931e84da164228f765ed4b9247942eb5204ea Mon Sep 17 00:00:00 2001 From: gabriellsh Date: Fri, 7 Jun 2024 11:36:40 -0300 Subject: [PATCH 27/27] missing review --- apps/meteor/tests/e2e/admin-room.spec.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/meteor/tests/e2e/admin-room.spec.ts b/apps/meteor/tests/e2e/admin-room.spec.ts index 29e42abf80bb..2e79a0e745ea 100644 --- a/apps/meteor/tests/e2e/admin-room.spec.ts +++ b/apps/meteor/tests/e2e/admin-room.spec.ts @@ -73,10 +73,7 @@ test.describe.serial('admin-rooms', () => { const dropdown = await admin.dropdownFilterRoomType(); await dropdown.click(); - const privateOption = page.locator('text=Private channels'); - - await privateOption.waitFor(); - await privateOption.click(); + await page.locator('text=Private channels').click(); await expect(page.locator('text=No results found')).toBeVisible(); }); @@ -86,10 +83,7 @@ test.describe.serial('admin-rooms', () => { const dropdown = await admin.dropdownFilterRoomType(); await dropdown.click(); - const privateOption = page.locator('text=Private channels'); - - await privateOption.waitFor(); - await privateOption.click(); + await page.locator('text=Private channels').click(); const workspaceButton = await admin.adminSectionButton(AdminSectionsHref.Workspace); await workspaceButton.click();