diff --git a/res/css/views/dialogs/_SpotlightDialog.pcss b/res/css/views/dialogs/_SpotlightDialog.pcss index d00acd6786..592431c2f1 100644 --- a/res/css/views/dialogs/_SpotlightDialog.pcss +++ b/res/css/views/dialogs/_SpotlightDialog.pcss @@ -412,7 +412,8 @@ Please see LICENSE files in the repository root for full details. .mx_SpotlightDialog_joinRoomAlias, .mx_SpotlightDialog_explorePublicRooms, .mx_SpotlightDialog_explorePublicSpaces, - .mx_SpotlightDialog_startGroupChat { + .mx_SpotlightDialog_startGroupChat, + .mx_SpotlightDialog_searchMessages { padding-left: $spacing-32; position: relative; @@ -451,22 +452,14 @@ Please see LICENSE files in the repository root for full details. mask-image: url("$(res)/img/element-icons/group-members.svg"); } + .mx_SpotlightDialog_searchMessages::before { + mask-image: url("$(res)/img/element-icons/room/search-inset.svg"); + } + .mx_SpotlightDialog_otherSearches_messageSearchText { font-size: $font-15px; line-height: $font-24px; } - - .mx_SpotlightDialog_otherSearches_messageSearchIcon { - display: inline-block; - width: 24px; - height: 24px; - background-color: $secondary-content; - vertical-align: text-bottom; - mask-repeat: no-repeat; - mask-position: center; - mask-size: contain; - mask-image: url("$(res)/img/element-icons/room/search-inset.svg"); - } } .mx_SpotlightDialog_result_details { diff --git a/res/themes/light-high-contrast/css/_light-high-contrast.pcss b/res/themes/light-high-contrast/css/_light-high-contrast.pcss index 213c641440..94774bc5b8 100644 --- a/res/themes/light-high-contrast/css/_light-high-contrast.pcss +++ b/res/themes/light-high-contrast/css/_light-high-contrast.pcss @@ -163,7 +163,8 @@ $accent-1400: var(--cpd-color-green-1400); &.mx_SpotlightDialog_startChat::before, &.mx_SpotlightDialog_joinRoomAlias::before, &.mx_SpotlightDialog_explorePublicRooms::before, - &.mx_SpotlightDialog_startGroupChat::before { + &.mx_SpotlightDialog_startGroupChat::before, + &.mx_SpotlightDialog_searchMessages::before { background-color: $background !important; } diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 21e2a2ee71..f67ebbadb8 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type ChangeEvent } from "react"; +import React from "react"; import { type Room, type RoomState, RoomStateEvent, RoomMember, type MatrixEvent } from "matrix-js-sdk/src/matrix"; import { throttle } from "lodash"; @@ -49,8 +49,9 @@ interface RoomlessProps extends BaseProps { interface RoomProps extends BaseProps { room: Room; permalinkCreator: RoomPermalinkCreator; - onSearchChange?: (e: ChangeEvent) => void; + onSearchChange?: (term: string) => void; onSearchCancel?: () => void; + searchTerm?: string; } type Props = XOR; @@ -260,6 +261,7 @@ export default class RightPanel extends React.Component { permalinkCreator={this.props.permalinkCreator!} onSearchChange={this.props.onSearchChange} onSearchCancel={this.props.onSearchCancel} + searchTerm={this.props.searchTerm} focusRoomSearch={cardState?.focusRoomSearch} /> ); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 0dbc6aaf3f..0a27dd4f4e 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -10,7 +10,6 @@ Please see LICENSE files in the repository root for full details. */ import React, { - type ChangeEvent, type ComponentProps, createRef, type ReactElement, @@ -133,6 +132,7 @@ import RoomSearchAuxPanel from "../views/rooms/RoomSearchAuxPanel"; import { PinnedMessageBanner } from "../views/rooms/PinnedMessageBanner"; import { ScopedRoomContextProvider, useScopedRoomContext } from "../../contexts/ScopedRoomContext"; import { DeclineAndBlockInviteDialog } from "../views/dialogs/DeclineAndBlockInviteDialog"; +import { type FocusMessageSearchPayload } from "../../dispatcher/payloads/FocusMessageSearchPayload.ts"; const DEBUG = false; const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000; @@ -1244,6 +1244,11 @@ export class RoomView extends React.Component { case Action.View3pidInvite: onView3pidInvite(payload, RightPanelStore.instance); break; + case Action.FocusMessageSearch: + if ((payload as FocusMessageSearchPayload).initialText) { + this.onSearch(payload.initialText); + } + break; } }; @@ -1806,8 +1811,7 @@ export class RoomView extends React.Component { defaultDispatcher.fire(Action.ViewRoomDirectory); }; - private onSearchChange = debounce((e: ChangeEvent): void => { - const term = (e.target as HTMLInputElement).value; + private onSearchChange = debounce((term: string): void => { this.onSearch(term); }, 300); @@ -2487,6 +2491,7 @@ export class RoomView extends React.Component { e2eStatus={this.state.e2eStatus} onSearchChange={this.onSearchChange} onSearchCancel={this.onCancelSearchClick} + searchTerm={this.state.search?.term ?? ""} /> ) : undefined; diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index a545b15337..419a12df89 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -608,6 +608,21 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n {filterToLabel(Filter.People)} )} + {filter === null && ( + + )} ); @@ -997,28 +1012,6 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n ); } - let messageSearchSection: JSX.Element | undefined; - if (filter === null) { - messageSearchSection = ( -
-

- {_t("spotlight_dialog|message_search_section_title")} -

-
- {_t( - "spotlight_dialog|search_messages_hint", - {}, - { icon: () =>
}, - )} -
-
- ); - } - content = ( <> {peopleSection} @@ -1031,7 +1024,6 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n {hiddenResultsSection} {otherSearchesSection} {groupChatSection} - {messageSearchSection} ); } else { diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 7894ec0d89..52664ac9cb 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, type ChangeEvent, useContext, useEffect, useRef, useState } from "react"; +import React, { type JSX, useContext, useEffect, useRef, useState } from "react"; import classNames from "classnames"; import { MenuItem, @@ -52,7 +52,6 @@ import { ShareDialog } from "../dialogs/ShareDialog"; import { useEventEmitterState } from "../../../hooks/useEventEmitter"; import { E2EStatus } from "../../../utils/ShieldUtils"; import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; -import { TimelineRenderingType } from "../../../contexts/RoomContext"; import RoomName from "../elements/RoomName"; import ExportDialog from "../dialogs/ExportDialog"; import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; @@ -71,7 +70,6 @@ import { Box } from "../../utils/Box"; import { useDispatcher } from "../../../hooks/useDispatcher"; import { Action } from "../../../dispatcher/actions"; import { Key } from "../../../Keyboard"; -import { useTransition } from "../../../hooks/useTransition"; import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; import { usePinnedEvents } from "../../../hooks/usePinnedEvents"; import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx"; @@ -82,9 +80,10 @@ import { useRoomTopicViewModel } from "../../viewmodels/right_panel/RoomSummaryC interface IProps { room: Room; permalinkCreator: RoomPermalinkCreator; - onSearchChange?: (e: ChangeEvent) => void; + onSearchChange?: (term: string) => void; onSearchCancel?: () => void; focusRoomSearch?: boolean; + searchTerm?: string; } const onRoomMembersClick = (): void => { @@ -180,6 +179,7 @@ const RoomSummaryCard: React.FC = ({ onSearchChange, onSearchCancel, focusRoomSearch, + searchTerm, }) => { const cli = useContext(MatrixClientContext); @@ -244,19 +244,6 @@ const RoomSummaryCard: React.FC = ({ searchInputRef.current?.focus(); } }); - // Clear the search field when the user leaves the search view - useTransition( - (prevTimelineRenderingType) => { - if ( - prevTimelineRenderingType === TimelineRenderingType.Search && - roomContext.timelineRenderingType !== TimelineRenderingType.Search && - searchInputRef.current - ) { - searchInputRef.current.value = ""; - } - }, - [roomContext.timelineRenderingType], - ); const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || ""; const roomInfo = ( @@ -332,7 +319,10 @@ const RoomSummaryCard: React.FC = ({ { + onSearchChange(e.currentTarget.value); + }} + value={searchTerm} className="mx_no_textinput" ref={searchInputRef} autoFocus={focusRoomSearch} diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index d3a6bc38a1..41021f956a 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -362,7 +362,7 @@ export enum Action { View3pidInvite = "view_3pid_invite", /** - * Opens right panel room summary and focuses the search input + * Opens right panel room summary and focuses the search input. Use with a FocusMessageSearchPayload. */ FocusMessageSearch = "focus_search", diff --git a/src/dispatcher/payloads/FocusMessageSearchPayload.ts b/src/dispatcher/payloads/FocusMessageSearchPayload.ts new file mode 100644 index 0000000000..a06f7f5174 --- /dev/null +++ b/src/dispatcher/payloads/FocusMessageSearchPayload.ts @@ -0,0 +1,15 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { type Action } from "../actions"; +import { type ActionPayload } from "../payloads"; + +export interface FocusMessageSearchPayload extends ActionPayload { + action: Action.FocusMessageSearch; + + initialText?: string; +} diff --git a/src/hooks/useTransition.ts b/src/hooks/useTransition.ts deleted file mode 100644 index d5e8df5f5d..0000000000 --- a/src/hooks/useTransition.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2024 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -// Based on https://stackoverflow.com/a/61680184 - -import { type DependencyList, useEffect, useRef } from "react"; - -export const useTransition = (callback: (...params: D) => void, deps: D): void => { - const func = useRef<(...params: D) => void>(callback); - - useEffect(() => { - func.current = callback; - }, [callback]); - - const args = useRef(null); - - useEffect(() => { - if (args.current !== null) func.current(...args.current); - args.current = deps; - // eslint-disable-next-line react-compiler/react-compiler,react-hooks/exhaustive-deps - }, deps); -}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 31fc8a07a0..01b50f23a6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3230,7 +3230,7 @@ "heading_without_query": "Search for", "join_button_text": "Join %(roomAddress)s", "keyboard_scroll_hint": "Use to scroll", - "message_search_section_title": "Other searches", + "messages_label": "Messages", "other_rooms_in_space": "Other rooms in %(spaceName)s", "public_rooms_label": "Public rooms", "public_spaces_label": "Public spaces", @@ -3240,7 +3240,6 @@ "result_may_be_hidden_privacy_warning": "Some results may be hidden for privacy", "result_may_be_hidden_warning": "Some results may be hidden", "search_dialog": "Search Dialog", - "search_messages_hint": "To search messages, look for this icon at the top of a room ", "spaces_title": "Spaces you're in", "start_group_chat_button": "Start a group chat" }, diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 9ead26987a..4d36082731 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -309,6 +309,8 @@ export function createTestClient(): MatrixClient { pushProcessor: { getPushRuleById: jest.fn(), }, + search: jest.fn().mockResolvedValue({}), + processRoomEventsSearch: jest.fn().mockResolvedValue({ highlights: [], results: [] }), } as unknown as MatrixClient; client.reEmitter = new ReEmitter(client); diff --git a/test/unit-tests/components/structures/RoomView-test.tsx b/test/unit-tests/components/structures/RoomView-test.tsx index d4010700a7..1d6d3a8a9d 100644 --- a/test/unit-tests/components/structures/RoomView-test.tsx +++ b/test/unit-tests/components/structures/RoomView-test.tsx @@ -608,129 +608,148 @@ describe("RoomView", () => { }); }); - it("should close search results when edit is clicked", async () => { - room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join); + describe("message search", () => { + it("should close search results when edit is clicked", async () => { + room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join); - const eventMapper = (obj: Partial) => new MatrixEvent(obj); + const eventMapper = (obj: Partial) => new MatrixEvent(obj); - const roomViewRef = createRef(); - const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef); - await waitFor(() => expect(roomViewRef.current).toBeTruthy()); - // @ts-ignore - triggering a search organically is a lot of work - act(() => - roomViewRef.current!.setState({ - search: { - searchId: 1, - roomId: room.roomId, - term: "search term", - scope: SearchScope.Room, - promise: Promise.resolve({ - results: [ - SearchResult.fromJson( - { - rank: 1, - result: { - content: { - body: "search term", - msgtype: "m.text", + const roomViewRef = createRef(); + const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef); + await waitFor(() => expect(roomViewRef.current).toBeTruthy()); + // @ts-ignore - triggering a search organically is a lot of work + act(() => + roomViewRef.current!.setState({ + search: { + searchId: 1, + roomId: room.roomId, + term: "search term", + scope: SearchScope.Room, + promise: Promise.resolve({ + results: [ + SearchResult.fromJson( + { + rank: 1, + result: { + content: { + body: "search term", + msgtype: "m.text", + }, + type: "m.room.message", + event_id: "$eventId", + sender: cli.getSafeUserId(), + origin_server_ts: 123456789, + room_id: room.roomId, }, - type: "m.room.message", - event_id: "$eventId", - sender: cli.getSafeUserId(), - origin_server_ts: 123456789, - room_id: room.roomId, - }, - context: { - events_before: [], - events_after: [], - profile_info: {}, - }, - }, - eventMapper, - ), - ], - highlights: [], - count: 1, - }), - inProgress: false, - count: 1, - }, - }), - ); - - await waitFor(() => { - expect(container.querySelector(".mx_RoomView_searchResultsPanel")).toBeVisible(); - }); - const prom = waitForElementToBeRemoved(() => container.querySelector(".mx_RoomView_searchResultsPanel")); - - await userEvent.hover(getByText("search term")); - await userEvent.click(await findByLabelText("Edit")); - - await prom; - }); - - it("should switch rooms when edit is clicked on a search result for a different room", async () => { - const room2 = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org"); - rooms.set(room2.roomId, room2); - - room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join); - - const eventMapper = (obj: Partial) => new MatrixEvent(obj); - - const roomViewRef = createRef(); - const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef); - await waitFor(() => expect(roomViewRef.current).toBeTruthy()); - // @ts-ignore - triggering a search organically is a lot of work - act(() => - roomViewRef.current!.setState({ - search: { - searchId: 1, - roomId: room.roomId, - term: "search term", - scope: SearchScope.All, - promise: Promise.resolve({ - results: [ - SearchResult.fromJson( - { - rank: 1, - result: { - content: { - body: "search term", - msgtype: "m.text", + context: { + events_before: [], + events_after: [], + profile_info: {}, }, - type: "m.room.message", - event_id: "$eventId", - sender: cli.getSafeUserId(), - origin_server_ts: 123456789, - room_id: room2.roomId, }, - context: { - events_before: [], - events_after: [], - profile_info: {}, - }, - }, - eventMapper, - ), - ], - highlights: [], + eventMapper, + ), + ], + highlights: [], + count: 1, + }), + inProgress: false, count: 1, - }), - inProgress: false, - count: 1, - }, - }), - ); + }, + }), + ); - await waitFor(() => { - expect(container.querySelector(".mx_RoomView_searchResultsPanel")).toBeVisible(); + await waitFor(() => { + expect(container.querySelector(".mx_RoomView_searchResultsPanel")).toBeVisible(); + }); + const prom = waitForElementToBeRemoved(() => container.querySelector(".mx_RoomView_searchResultsPanel")); + + await userEvent.hover(getByText("search term")); + await userEvent.click(await findByLabelText("Edit")); + + await prom; }); - const prom = untilDispatch(Action.ViewRoom, defaultDispatcher); - await userEvent.hover(getByText("search term")); - await userEvent.click(await findByLabelText("Edit")); + it("should switch rooms when edit is clicked on a search result for a different room", async () => { + const room2 = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org"); + rooms.set(room2.roomId, room2); - await expect(prom).resolves.toEqual(expect.objectContaining({ room_id: room2.roomId })); + room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join); + + const eventMapper = (obj: Partial) => new MatrixEvent(obj); + + const roomViewRef = createRef(); + const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef); + await waitFor(() => expect(roomViewRef.current).toBeTruthy()); + // @ts-ignore - triggering a search organically is a lot of work + act(() => + roomViewRef.current!.setState({ + search: { + searchId: 1, + roomId: room.roomId, + term: "search term", + scope: SearchScope.All, + promise: Promise.resolve({ + results: [ + SearchResult.fromJson( + { + rank: 1, + result: { + content: { + body: "search term", + msgtype: "m.text", + }, + type: "m.room.message", + event_id: "$eventId", + sender: cli.getSafeUserId(), + origin_server_ts: 123456789, + room_id: room2.roomId, + }, + context: { + events_before: [], + events_after: [], + profile_info: {}, + }, + }, + eventMapper, + ), + ], + highlights: [], + count: 1, + }), + inProgress: false, + count: 1, + }, + }), + ); + + await waitFor(() => { + expect(container.querySelector(".mx_RoomView_searchResultsPanel")).toBeVisible(); + }); + const prom = untilDispatch(Action.ViewRoom, defaultDispatcher); + + await userEvent.hover(getByText("search term")); + await userEvent.click(await findByLabelText("Edit")); + + await expect(prom).resolves.toEqual(expect.objectContaining({ room_id: room2.roomId })); + }); + + it("should pre-fill search field on FocusMessageSearch dispatch", async () => { + room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join); + + const roomViewRef = createRef(); + const { findByPlaceholderText } = await mountRoomView(roomViewRef); + await waitFor(() => expect(roomViewRef.current).toBeTruthy()); + + act(() => + defaultDispatcher.dispatch({ + action: Action.FocusMessageSearch, + initialText: "search term", + }), + ); + + await expect(findByPlaceholderText("Search messages…")).resolves.toHaveValue("search term"); + }); }); it("fires Action.RoomLoaded", async () => { diff --git a/test/unit-tests/components/views/dialogs/SpotlightDialog-test.tsx b/test/unit-tests/components/views/dialogs/SpotlightDialog-test.tsx index 76f342c8f6..1c343a30a7 100644 --- a/test/unit-tests/components/views/dialogs/SpotlightDialog-test.tsx +++ b/test/unit-tests/components/views/dialogs/SpotlightDialog-test.tsx @@ -353,12 +353,12 @@ describe("Spotlight Dialog", () => { }); it("should find Rooms", () => { - expect(options).toHaveLength(4); + expect(options).toHaveLength(5); expect(options[0]!.innerHTML).toContain(testRoom.name); }); it("should not find LocalRooms", () => { - expect(options).toHaveLength(4); + expect(options).toHaveLength(5); expect(options[0]!.innerHTML).not.toContain(testLocalRoom.name); }); }); @@ -648,4 +648,20 @@ describe("Spotlight Dialog", () => { }); }); }); + + it("should allow jumping into message search", async () => { + const onFinished = jest.fn(); + render(); + jest.advanceTimersByTime(200); + await flushPromisesWithFakeTimers(); + + fireEvent.click(screen.getByText("Messages")); + + expect(defaultDispatcher.dispatch).toHaveBeenCalledWith( + expect.objectContaining({ + action: Action.FocusMessageSearch, + initialText: "search term", + }), + ); + }); }); diff --git a/test/unit-tests/components/views/right_panel/RoomSummaryCard-test.tsx b/test/unit-tests/components/views/right_panel/RoomSummaryCard-test.tsx index 079a047228..be1b99a608 100644 --- a/test/unit-tests/components/views/right_panel/RoomSummaryCard-test.tsx +++ b/test/unit-tests/components/views/right_panel/RoomSummaryCard-test.tsx @@ -11,7 +11,6 @@ import { render, fireEvent, screen, waitFor } from "jest-matrix-react"; import { EventType, MatrixEvent, Room, type MatrixClient, JoinRule } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; import { mocked, type MockedObject } from "jest-mock"; -import userEvent from "@testing-library/user-event"; import DMRoomMap from "../../../../../src/utils/DMRoomMap"; import RoomSummaryCard from "../../../../../src/components/views/right_panel/RoomSummaryCard"; @@ -30,8 +29,6 @@ import { _t } from "../../../../../src/languageHandler"; import { tagRoom } from "../../../../../src/utils/room/tagRoom"; import { DefaultTagID } from "../../../../../src/stores/room-list/models"; import { Action } from "../../../../../src/dispatcher/actions"; -import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext"; -import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx"; import { ReportRoomDialog } from "../../../../../src/components/views/dialogs/ReportRoomDialog.tsx"; jest.mock("../../../../../src/utils/room/tagRoom"); @@ -169,38 +166,6 @@ describe("", () => { fireEvent.keyDown(getByPlaceholderText("Search messages…"), { key: "Escape" }); expect(onSearchCancel).toHaveBeenCalled(); }); - - it("should empty search field when the timeline rendering type changes away", async () => { - const onSearchChange = jest.fn(); - const { rerender } = render( - - - - - , - ); - - await userEvent.type(screen.getByPlaceholderText("Search messages…"), "test"); - expect(screen.getByPlaceholderText("Search messages…")).toHaveValue("test"); - - rerender( - - - - - , - ); - expect(screen.getByPlaceholderText("Search messages…")).toHaveValue(""); - }); }); it("opens room file panel on button click", () => {