Move room list search to shared components (#31502)
* refactor: move room list search to shared components * refactor: add view model * refactor: use view and vm in room list search component * refactor: use room list id instead of class for landmark navigation * refactor: remove old room list search css * test: add screenshots test for room list search view * test: fix e2e test using class as selector...
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
@@ -19,6 +19,7 @@ export * from "./pill-input/Pill";
|
|||||||
export * from "./pill-input/PillInput";
|
export * from "./pill-input/PillInput";
|
||||||
export * from "./rich-list/RichItem";
|
export * from "./rich-list/RichItem";
|
||||||
export * from "./rich-list/RichList";
|
export * from "./rich-list/RichList";
|
||||||
|
export * from "./room-list/RoomListSearchView";
|
||||||
export * from "./utils/Box";
|
export * from "./utils/Box";
|
||||||
export * from "./utils/Flex";
|
export * from "./utils/Flex";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.view {
|
||||||
|
/* From figma, this should be aligned with the room header */
|
||||||
|
min-height: 64px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-bg-subtle-primary);
|
||||||
|
padding: 0 var(--cpd-space-3x);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
/* The search button should take all the remaining space */
|
||||||
|
flex: 1;
|
||||||
|
/* !important is needed to override compound button in EW */
|
||||||
|
font: var(--cpd-font-body-md-regular) !important;
|
||||||
|
color: var(--cpd-color-text-secondary) !important;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--cpd-color-icon-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search_container {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
/* Shrink and truncate the search text */
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search_text {
|
||||||
|
min-width: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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 React, { type JSX } from "react";
|
||||||
|
import { fn } from "storybook/test";
|
||||||
|
|
||||||
|
import type { Meta, StoryFn } from "@storybook/react-vite";
|
||||||
|
import {
|
||||||
|
RoomListSearchView,
|
||||||
|
type RoomListSearchViewActions,
|
||||||
|
type RoomListSearchViewSnapshot,
|
||||||
|
} from "./RoomListSearchView";
|
||||||
|
import { useMockedViewModel } from "../../useMockedViewModel";
|
||||||
|
|
||||||
|
type RoomListSearchProps = RoomListSearchViewSnapshot & RoomListSearchViewActions;
|
||||||
|
|
||||||
|
const RoomListSearchViewWrapper = ({
|
||||||
|
onSearchClick,
|
||||||
|
onDialPadClick,
|
||||||
|
onExploreClick,
|
||||||
|
...rest
|
||||||
|
}: RoomListSearchProps): JSX.Element => {
|
||||||
|
const vm = useMockedViewModel(rest, {
|
||||||
|
onSearchClick,
|
||||||
|
onDialPadClick,
|
||||||
|
onExploreClick,
|
||||||
|
});
|
||||||
|
return <RoomListSearchView vm={vm} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Room List/RoomListSearchView",
|
||||||
|
component: RoomListSearchViewWrapper,
|
||||||
|
tags: ["autodocs"],
|
||||||
|
args: {
|
||||||
|
displayExploreButton: true,
|
||||||
|
displayDialButton: false,
|
||||||
|
searchShortcut: "⌘ K",
|
||||||
|
onSearchClick: fn(),
|
||||||
|
onDialPadClick: fn(),
|
||||||
|
onExploreClick: fn(),
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: "figma",
|
||||||
|
url: "https://www.figma.com/design/vlmt46QDdE4dgXDiyBJXqp/ER-33-Left-Panel-2025?node-id=98-1979&t=vafb4zoYMNLRuAbh-4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Meta<typeof RoomListSearchViewWrapper>;
|
||||||
|
|
||||||
|
const Template: StoryFn<typeof RoomListSearchViewWrapper> = (args) => <RoomListSearchViewWrapper {...args} />;
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
||||||
|
|
||||||
|
export const WithDialPad = Template.bind({});
|
||||||
|
WithDialPad.args = {
|
||||||
|
displayDialButton: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithoutExplore = Template.bind({});
|
||||||
|
WithoutExplore.args = {
|
||||||
|
displayExploreButton: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AllButtons = Template.bind({});
|
||||||
|
AllButtons.args = {
|
||||||
|
displayExploreButton: true,
|
||||||
|
displayDialButton: true,
|
||||||
|
searchShortcut: "⌘ K",
|
||||||
|
};
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* 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 { render, screen } from "jest-matrix-react";
|
||||||
|
import { composeStories } from "@storybook/react-vite";
|
||||||
|
import React from "react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
|
||||||
|
import * as stories from "./RoomListSearchView.stories";
|
||||||
|
import {
|
||||||
|
RoomListSearchView,
|
||||||
|
type RoomListSearchViewActions,
|
||||||
|
type RoomListSearchViewSnapshot,
|
||||||
|
} from "./RoomListSearchView";
|
||||||
|
import { MockViewModel } from "../../viewmodel/MockViewModel";
|
||||||
|
|
||||||
|
const { Default, WithDialPad, WithoutExplore, AllButtons } = composeStories(stories);
|
||||||
|
|
||||||
|
describe("RoomListSearchView", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Storybook snapshots", () => {
|
||||||
|
it("renders the default state", () => {
|
||||||
|
const { container } = render(<Default />);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders with dial pad button", () => {
|
||||||
|
const { container } = render(<WithDialPad />);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders without explore button", () => {
|
||||||
|
const { container } = render(<WithoutExplore />);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders with all buttons visible", () => {
|
||||||
|
const { container } = render(<AllButtons />);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("User interactions", () => {
|
||||||
|
const onSearchClick = jest.fn();
|
||||||
|
const onDialPadClick = jest.fn();
|
||||||
|
const onExploreClick = jest.fn();
|
||||||
|
|
||||||
|
class TestViewModel extends MockViewModel<RoomListSearchViewSnapshot> implements RoomListSearchViewActions {
|
||||||
|
public onSearchClick = onSearchClick;
|
||||||
|
public onDialPadClick = onDialPadClick;
|
||||||
|
public onExploreClick = onExploreClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should call onSearchClick when search button is clicked", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const vm = new TestViewModel({
|
||||||
|
displayExploreButton: false,
|
||||||
|
displayDialButton: false,
|
||||||
|
searchShortcut: "⌘ K",
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<RoomListSearchView vm={vm} />);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole("button", { name: "Search ⌘ K" }));
|
||||||
|
expect(onSearchClick).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call onDialPadClick when dial pad button is clicked", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const vm = new TestViewModel({
|
||||||
|
displayExploreButton: false,
|
||||||
|
displayDialButton: true,
|
||||||
|
searchShortcut: "⌘ K",
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<RoomListSearchView vm={vm} />);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole("button", { name: "Open dial pad" }));
|
||||||
|
expect(onDialPadClick).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call onExploreClick when explore button is clicked", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const vm = new TestViewModel({
|
||||||
|
displayExploreButton: true,
|
||||||
|
displayDialButton: false,
|
||||||
|
searchShortcut: "⌘ K",
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<RoomListSearchView vm={vm} />);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole("button", { name: "Explore rooms" }));
|
||||||
|
expect(onExploreClick).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* 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 React, { type JSX, type MouseEventHandler } from "react";
|
||||||
|
import { Button } from "@vector-im/compound-web";
|
||||||
|
import ExploreIcon from "@vector-im/compound-design-tokens/assets/web/icons/explore";
|
||||||
|
import SearchIcon from "@vector-im/compound-design-tokens/assets/web/icons/search";
|
||||||
|
import DialPadIcon from "@vector-im/compound-design-tokens/assets/web/icons/dial-pad";
|
||||||
|
|
||||||
|
import styles from "./RoomListSearchView.module.css";
|
||||||
|
import { type ViewModel } from "../../viewmodel/ViewModel";
|
||||||
|
import { useViewModel } from "../../useViewModel";
|
||||||
|
import { Flex } from "../../utils/Flex";
|
||||||
|
import { useI18n } from "../../utils/i18nContext";
|
||||||
|
|
||||||
|
export interface RoomListSearchViewSnapshot {
|
||||||
|
/**
|
||||||
|
* Whether to display the explore button.
|
||||||
|
*/
|
||||||
|
displayExploreButton: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to display the dial pad button.
|
||||||
|
*/
|
||||||
|
displayDialButton: boolean;
|
||||||
|
/**
|
||||||
|
* The keyboard shortcut text to display for the search action.
|
||||||
|
* For example: "⌘ K" on macOS or "Ctrl K" on other platforms.
|
||||||
|
*/
|
||||||
|
searchShortcut: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoomListSearchViewActions {
|
||||||
|
/**
|
||||||
|
* Handles the click event on the search button.
|
||||||
|
*/
|
||||||
|
onSearchClick: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
/**
|
||||||
|
* Handles the click event on the dial pad button.
|
||||||
|
*/
|
||||||
|
onDialPadClick: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
/**
|
||||||
|
* Handles the click event on the explore button.
|
||||||
|
*/
|
||||||
|
onExploreClick: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The view model for the room list search component.
|
||||||
|
*/
|
||||||
|
export type RoomListSearchViewModel = ViewModel<RoomListSearchViewSnapshot> & RoomListSearchViewActions;
|
||||||
|
|
||||||
|
interface RoomListSearchViewProps {
|
||||||
|
/**
|
||||||
|
* The view model for the room list search component.
|
||||||
|
*/
|
||||||
|
vm: RoomListSearchViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A search component to be displayed at the top of the room list.
|
||||||
|
* The component provides search functionality, optional dial pad access, and optional room exploration.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <RoomListSearchView vm={roomListSearchViewModel} />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function RoomListSearchView({ vm }: Readonly<RoomListSearchViewProps>): JSX.Element {
|
||||||
|
const { translate: _t } = useI18n();
|
||||||
|
const { displayExploreButton, displayDialButton, searchShortcut } = useViewModel(vm);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
data-testid="room-list-search"
|
||||||
|
className={styles.view}
|
||||||
|
role="search"
|
||||||
|
gap="var(--cpd-space-2x)"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
id="room-list-search-button"
|
||||||
|
className={styles.search}
|
||||||
|
kind="secondary"
|
||||||
|
size="sm"
|
||||||
|
Icon={SearchIcon}
|
||||||
|
onClick={vm.onSearchClick}
|
||||||
|
>
|
||||||
|
<Flex className={styles["search_container"]} as="span" justify="space-between">
|
||||||
|
<span className={styles["search_text"]}>{_t("action|search")}</span>
|
||||||
|
<kbd>{searchShortcut}</kbd>
|
||||||
|
</Flex>
|
||||||
|
</Button>
|
||||||
|
{displayDialButton && (
|
||||||
|
<Button
|
||||||
|
kind="secondary"
|
||||||
|
size="sm"
|
||||||
|
Icon={DialPadIcon}
|
||||||
|
iconOnly={true}
|
||||||
|
aria-label={_t("left_panel|open_dial_pad")}
|
||||||
|
onClick={vm.onDialPadClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{displayExploreButton && (
|
||||||
|
<Button
|
||||||
|
kind="secondary"
|
||||||
|
size="sm"
|
||||||
|
Icon={ExploreIcon}
|
||||||
|
iconOnly={true}
|
||||||
|
aria-label={_t("action|explore_rooms")}
|
||||||
|
onClick={vm.onExploreClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||||
|
|
||||||
|
exports[`RoomListSearchView Storybook snapshots renders the default state 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="flex view"
|
||||||
|
data-testid="room-list-search"
|
||||||
|
role="search"
|
||||||
|
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="_button_187yx_8 search _has-icon_187yx_57"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="sm"
|
||||||
|
id="room-list-search-button"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
class="flex search_container"
|
||||||
|
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="search_text"
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</span>
|
||||||
|
<kbd>
|
||||||
|
⌘ K
|
||||||
|
</kbd>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Explore rooms"
|
||||||
|
class="_button_187yx_8 _has-icon_187yx_57 _icon-only_187yx_50"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="sm"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 12q0-.424.287-.713A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 12 13m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20m0 0q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675T12 20m1.675-5.85q.15-.075.275-.2t.2-.275l2.925-6.25q.125-.25-.062-.437-.188-.188-.438-.063l-6.25 2.925q-.15.075-.275.2t-.2.275l-2.925 6.25q-.125.25.063.438.186.186.437.062z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`RoomListSearchView Storybook snapshots renders with all buttons visible 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="flex view"
|
||||||
|
data-testid="room-list-search"
|
||||||
|
role="search"
|
||||||
|
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="_button_187yx_8 search _has-icon_187yx_57"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="sm"
|
||||||
|
id="room-list-search-button"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
class="flex search_container"
|
||||||
|
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="search_text"
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</span>
|
||||||
|
<kbd>
|
||||||
|
⌘ K
|
||||||
|
</kbd>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Open dial pad"
|
||||||
|
class="_button_187yx_8 _has-icon_187yx_57 _icon-only_187yx_50"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="sm"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 18.6c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8M6.6 2.4c-.99 0-1.8.81-1.8 1.8S5.61 6 6.6 6s1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0 5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0 5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8M17.4 6c.99 0 1.8-.81 1.8-1.8s-.81-1.8-1.8-1.8-1.8.81-1.8 1.8.81 1.8 1.8 1.8M12 13.2c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m5.4 0c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0-5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m-5.4 0c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0-5.4c-.99 0-1.8.81-1.8 1.8S11.01 6 12 6s1.8-.81 1.8-1.8-.81-1.8-1.8-1.8"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Explore rooms"
|
||||||
|
class="_button_187yx_8 _has-icon_187yx_57 _icon-only_187yx_50"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="sm"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 12q0-.424.287-.713A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 12 13m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20m0 0q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675T12 20m1.675-5.85q.15-.075.275-.2t.2-.275l2.925-6.25q.125-.25-.062-.437-.188-.188-.438-.063l-6.25 2.925q-.15.075-.275.2t-.2.275l-2.925 6.25q-.125.25.063.438.186.186.437.062z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`RoomListSearchView Storybook snapshots renders with dial pad button 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="flex view"
|
||||||
|
data-testid="room-list-search"
|
||||||
|
role="search"
|
||||||
|
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="_button_187yx_8 search _has-icon_187yx_57"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="sm"
|
||||||
|
id="room-list-search-button"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
class="flex search_container"
|
||||||
|
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="search_text"
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</span>
|
||||||
|
<kbd>
|
||||||
|
⌘ K
|
||||||
|
</kbd>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Open dial pad"
|
||||||
|
class="_button_187yx_8 _has-icon_187yx_57 _icon-only_187yx_50"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="sm"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 18.6c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8M6.6 2.4c-.99 0-1.8.81-1.8 1.8S5.61 6 6.6 6s1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0 5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0 5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8M17.4 6c.99 0 1.8-.81 1.8-1.8s-.81-1.8-1.8-1.8-1.8.81-1.8 1.8.81 1.8 1.8 1.8M12 13.2c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m5.4 0c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0-5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m-5.4 0c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0-5.4c-.99 0-1.8.81-1.8 1.8S11.01 6 12 6s1.8-.81 1.8-1.8-.81-1.8-1.8-1.8"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Explore rooms"
|
||||||
|
class="_button_187yx_8 _has-icon_187yx_57 _icon-only_187yx_50"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="sm"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 12q0-.424.287-.713A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 12 13m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20m0 0q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675T12 20m1.675-5.85q.15-.075.275-.2t.2-.275l2.925-6.25q.125-.25-.062-.437-.188-.188-.438-.063l-6.25 2.925q-.15.075-.275.2t-.2.275l-2.925 6.25q-.125.25.063.438.186.186.437.062z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`RoomListSearchView Storybook snapshots renders without explore button 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="flex view"
|
||||||
|
data-testid="room-list-search"
|
||||||
|
role="search"
|
||||||
|
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="_button_187yx_8 search _has-icon_187yx_57"
|
||||||
|
data-kind="secondary"
|
||||||
|
data-size="sm"
|
||||||
|
id="room-list-search-button"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
fill="currentColor"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
class="flex search_container"
|
||||||
|
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="search_text"
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</span>
|
||||||
|
<kbd>
|
||||||
|
⌘ K
|
||||||
|
</kbd>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type { RoomListSearchViewModel, RoomListSearchViewSnapshot } from "./RoomListSearchView";
|
||||||
|
export { RoomListSearchView } from "./RoomListSearchView";
|
||||||
@@ -29,7 +29,7 @@ test.describe("Landmark navigation tests", () => {
|
|||||||
|
|
||||||
// Pressing Control+F6 again will focus room search
|
// Pressing Control+F6 again will focus room search
|
||||||
await page.keyboard.press("ControlOrMeta+F6");
|
await page.keyboard.press("ControlOrMeta+F6");
|
||||||
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
|
await expect(page.locator("#room-list-search-button")).toBeFocused();
|
||||||
|
|
||||||
// Pressing Control+F6 again will focus the message composer
|
// Pressing Control+F6 again will focus the message composer
|
||||||
await page.keyboard.press("ControlOrMeta+F6");
|
await page.keyboard.press("ControlOrMeta+F6");
|
||||||
@@ -44,7 +44,7 @@ test.describe("Landmark navigation tests", () => {
|
|||||||
await expect(page.locator(".mx_HomePage")).toBeFocused();
|
await expect(page.locator(".mx_HomePage")).toBeFocused();
|
||||||
|
|
||||||
await page.keyboard.press("ControlOrMeta+Shift+F6");
|
await page.keyboard.press("ControlOrMeta+Shift+F6");
|
||||||
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
|
await expect(page.locator("#room-list-search-button")).toBeFocused();
|
||||||
|
|
||||||
await page.keyboard.press("ControlOrMeta+Shift+F6");
|
await page.keyboard.press("ControlOrMeta+Shift+F6");
|
||||||
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();
|
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();
|
||||||
@@ -75,7 +75,7 @@ test.describe("Landmark navigation tests", () => {
|
|||||||
|
|
||||||
// Pressing Control+F6 again will focus room search
|
// Pressing Control+F6 again will focus room search
|
||||||
await page.keyboard.press("ControlOrMeta+F6");
|
await page.keyboard.press("ControlOrMeta+F6");
|
||||||
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
|
await expect(page.locator("#room-list-search-button")).toBeFocused();
|
||||||
|
|
||||||
// Pressing Control+F6 again will focus the room tile in the room list
|
// Pressing Control+F6 again will focus the room tile in the room list
|
||||||
await page.keyboard.press("ControlOrMeta+F6");
|
await page.keyboard.press("ControlOrMeta+F6");
|
||||||
@@ -97,7 +97,7 @@ test.describe("Landmark navigation tests", () => {
|
|||||||
await expect(page.locator(".mx_RoomListItemView_selected")).toBeFocused();
|
await expect(page.locator(".mx_RoomListItemView_selected")).toBeFocused();
|
||||||
|
|
||||||
await page.keyboard.press("ControlOrMeta+Shift+F6");
|
await page.keyboard.press("ControlOrMeta+Shift+F6");
|
||||||
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
|
await expect(page.locator("#room-list-search-button")).toBeFocused();
|
||||||
|
|
||||||
await page.keyboard.press("ControlOrMeta+Shift+F6");
|
await page.keyboard.press("ControlOrMeta+Shift+F6");
|
||||||
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();
|
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();
|
||||||
@@ -131,7 +131,7 @@ test.describe("Landmark navigation tests", () => {
|
|||||||
|
|
||||||
// Pressing Control+F6 again will focus room search
|
// Pressing Control+F6 again will focus room search
|
||||||
await page.keyboard.press("ControlOrMeta+F6");
|
await page.keyboard.press("ControlOrMeta+F6");
|
||||||
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
|
await expect(page.locator("#room-list-search-button")).toBeFocused();
|
||||||
|
|
||||||
// Pressing Control+F6 again will focus the room tile in the room list
|
// Pressing Control+F6 again will focus the room tile in the room list
|
||||||
await page.keyboard.press("ControlOrMeta+F6");
|
await page.keyboard.press("ControlOrMeta+F6");
|
||||||
@@ -153,7 +153,7 @@ test.describe("Landmark navigation tests", () => {
|
|||||||
await expect(page.locator(".mx_RoomListItemView")).toBeFocused();
|
await expect(page.locator(".mx_RoomListItemView")).toBeFocused();
|
||||||
|
|
||||||
await page.keyboard.press("ControlOrMeta+Shift+F6");
|
await page.keyboard.press("ControlOrMeta+Shift+F6");
|
||||||
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
|
await expect(page.locator("#room-list-search-button")).toBeFocused();
|
||||||
|
|
||||||
await page.keyboard.press("ControlOrMeta+Shift+F6");
|
await page.keyboard.press("ControlOrMeta+Shift+F6");
|
||||||
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();
|
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ test.describe("PSTN", () => {
|
|||||||
await toasts.rejectToast("Notifications");
|
await toasts.rejectToast("Notifications");
|
||||||
await toasts.assertNoToasts();
|
await toasts.assertNoToasts();
|
||||||
|
|
||||||
await expect(page.locator(".mx_RoomListSearch")).toMatchScreenshot("dialpad-trigger.png");
|
await expect(page.getByTestId("room-list-search")).toMatchScreenshot("dialpad-trigger.png");
|
||||||
await page.getByLabel("Open dial pad").click();
|
await page.getByLabel("Open dial pad").click();
|
||||||
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("dialpad.png");
|
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("dialpad.png");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -273,7 +273,6 @@
|
|||||||
@import "./views/rooms/RoomListPanel/_RoomListItemView.pcss";
|
@import "./views/rooms/RoomListPanel/_RoomListItemView.pcss";
|
||||||
@import "./views/rooms/RoomListPanel/_RoomListPanel.pcss";
|
@import "./views/rooms/RoomListPanel/_RoomListPanel.pcss";
|
||||||
@import "./views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss";
|
@import "./views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss";
|
||||||
@import "./views/rooms/RoomListPanel/_RoomListSearch.pcss";
|
|
||||||
@import "./views/rooms/RoomListPanel/_RoomListSecondaryFilters.pcss";
|
@import "./views/rooms/RoomListPanel/_RoomListSecondaryFilters.pcss";
|
||||||
@import "./views/rooms/RoomListPanel/_RoomListSkeleton.pcss";
|
@import "./views/rooms/RoomListPanel/_RoomListSkeleton.pcss";
|
||||||
@import "./views/rooms/_AppsDrawer.pcss";
|
@import "./views/rooms/_AppsDrawer.pcss";
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.mx_RoomListSearch {
|
|
||||||
/* From figma, this should be aligned with the room header */
|
|
||||||
flex: 0 0 64px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-bg-subtle-primary);
|
|
||||||
padding: 0 var(--cpd-space-3x);
|
|
||||||
|
|
||||||
.mx_RoomListSearch_search {
|
|
||||||
/* The search button should take all the remaining space */
|
|
||||||
flex: 1;
|
|
||||||
font: var(--cpd-font-body-md-regular);
|
|
||||||
color: var(--cpd-color-text-secondary);
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
fill: var(--cpd-color-icon-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
kbd {
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Shrink and truncate the search text */
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
.mx_RoomListSearch_search_text {
|
|
||||||
min-width: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
text-align: start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -75,7 +75,7 @@ const landmarkToDomElementMap: Record<Landmark, () => HTMLElement | null | undef
|
|||||||
|
|
||||||
[Landmark.ROOM_SEARCH]: () =>
|
[Landmark.ROOM_SEARCH]: () =>
|
||||||
SettingsStore.getValue("feature_new_room_list")
|
SettingsStore.getValue("feature_new_room_list")
|
||||||
? document.querySelector<HTMLElement>(".mx_RoomListSearch_search")
|
? document.querySelector<HTMLElement>("#room-list-search-button")
|
||||||
: document.querySelector<HTMLElement>(".mx_RoomSearch"),
|
: document.querySelector<HTMLElement>(".mx_RoomSearch"),
|
||||||
[Landmark.ROOM_LIST]: () =>
|
[Landmark.ROOM_LIST]: () =>
|
||||||
SettingsStore.getValue("feature_new_room_list")
|
SettingsStore.getValue("feature_new_room_list")
|
||||||
|
|||||||
@@ -5,24 +5,10 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { type JSX } from "react";
|
import React, { useEffect, type JSX } from "react";
|
||||||
import { Button } from "@vector-im/compound-web";
|
import { RoomListSearchView, useCreateAutoDisposedViewModel } from "@element-hq/web-shared-components";
|
||||||
import ExploreIcon from "@vector-im/compound-design-tokens/assets/web/icons/explore";
|
|
||||||
import SearchIcon from "@vector-im/compound-design-tokens/assets/web/icons/search";
|
|
||||||
import DialPadIcon from "@vector-im/compound-design-tokens/assets/web/icons/dial-pad";
|
|
||||||
import { Flex } from "@element-hq/web-shared-components";
|
|
||||||
|
|
||||||
import { IS_MAC, Key } from "../../../../Keyboard";
|
import { RoomListSearchViewModel } from "../../../../viewmodels/room-list/RoomListSearchViewModel";
|
||||||
import { _t } from "../../../../languageHandler";
|
|
||||||
import { ALTERNATE_KEY_NAME } from "../../../../accessibility/KeyboardShortcuts";
|
|
||||||
import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents";
|
|
||||||
import { UIComponent } from "../../../../settings/UIFeature";
|
|
||||||
import { MetaSpace } from "../../../../stores/spaces";
|
|
||||||
import { Action } from "../../../../dispatcher/actions";
|
|
||||||
import PosthogTrackers from "../../../../PosthogTrackers";
|
|
||||||
import defaultDispatcher from "../../../../dispatcher/dispatcher";
|
|
||||||
import { useTypedEventEmitterState } from "../../../../hooks/useEventEmitter";
|
|
||||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../../LegacyCallHandler";
|
|
||||||
|
|
||||||
type RoomListSearchProps = {
|
type RoomListSearchProps = {
|
||||||
/**
|
/**
|
||||||
@@ -37,53 +23,10 @@ type RoomListSearchProps = {
|
|||||||
* The `Explore` button is displayed only in the Home meta space and when UIComponent.ExploreRooms is enabled.
|
* The `Explore` button is displayed only in the Home meta space and when UIComponent.ExploreRooms is enabled.
|
||||||
*/
|
*/
|
||||||
export function RoomListSearch({ activeSpace }: RoomListSearchProps): JSX.Element {
|
export function RoomListSearch({ activeSpace }: RoomListSearchProps): JSX.Element {
|
||||||
const displayExploreButton = activeSpace === MetaSpace.Home && shouldShowComponent(UIComponent.ExploreRooms);
|
const vm = useCreateAutoDisposedViewModel(() => new RoomListSearchViewModel({ activeSpace }));
|
||||||
// We only display the dial button if the user is can make PSTN calls
|
useEffect(() => {
|
||||||
const displayDialButton = useTypedEventEmitterState(
|
vm.setActiveSpace(activeSpace);
|
||||||
LegacyCallHandler.instance,
|
}, [activeSpace, vm]);
|
||||||
LegacyCallHandlerEvent.ProtocolSupport,
|
|
||||||
() => LegacyCallHandler.instance.getSupportsPstnProtocol(),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return <RoomListSearchView vm={vm} />;
|
||||||
<Flex className="mx_RoomListSearch" role="search" gap="var(--cpd-space-2x)" align="center">
|
|
||||||
<Button
|
|
||||||
className="mx_RoomListSearch_search"
|
|
||||||
kind="secondary"
|
|
||||||
size="sm"
|
|
||||||
Icon={SearchIcon}
|
|
||||||
onClick={() => defaultDispatcher.fire(Action.OpenSpotlight)}
|
|
||||||
>
|
|
||||||
<Flex as="span" justify="space-between">
|
|
||||||
<span className="mx_RoomListSearch_search_text">{_t("action|search")}</span>
|
|
||||||
<kbd>{IS_MAC ? "⌘ K" : _t(ALTERNATE_KEY_NAME[Key.CONTROL]) + " K"}</kbd>
|
|
||||||
</Flex>
|
|
||||||
</Button>
|
|
||||||
{displayDialButton && (
|
|
||||||
<Button
|
|
||||||
kind="secondary"
|
|
||||||
size="sm"
|
|
||||||
Icon={DialPadIcon}
|
|
||||||
iconOnly={true}
|
|
||||||
aria-label={_t("left_panel|open_dial_pad")}
|
|
||||||
onClick={(ev) => {
|
|
||||||
defaultDispatcher.fire(Action.OpenDialPad);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{displayExploreButton && (
|
|
||||||
<Button
|
|
||||||
kind="secondary"
|
|
||||||
size="sm"
|
|
||||||
Icon={ExploreIcon}
|
|
||||||
iconOnly={true}
|
|
||||||
aria-label={_t("action|explore_rooms")}
|
|
||||||
onClick={(ev) => {
|
|
||||||
defaultDispatcher.fire(Action.ViewRoomDirectory);
|
|
||||||
PosthogTrackers.trackInteraction("WebLeftPanelExploreRoomsButton", ev);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
117
src/viewmodels/room-list/RoomListSearchViewModel.ts
Normal file
117
src/viewmodels/room-list/RoomListSearchViewModel.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* 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 MouseEvent } from "react";
|
||||||
|
import {
|
||||||
|
BaseViewModel,
|
||||||
|
type RoomListSearchViewSnapshot,
|
||||||
|
type RoomListSearchViewModel as RoomListSearchViewModelInterface,
|
||||||
|
} from "@element-hq/web-shared-components";
|
||||||
|
|
||||||
|
import { IS_MAC, Key } from "../../Keyboard";
|
||||||
|
import { _t } from "../../languageHandler";
|
||||||
|
import { ALTERNATE_KEY_NAME } from "../../accessibility/KeyboardShortcuts";
|
||||||
|
import { shouldShowComponent } from "../../customisations/helpers/UIComponents";
|
||||||
|
import { UIComponent } from "../../settings/UIFeature";
|
||||||
|
import { MetaSpace } from "../../stores/spaces";
|
||||||
|
import { Action } from "../../dispatcher/actions";
|
||||||
|
import PosthogTrackers from "../../PosthogTrackers";
|
||||||
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
|
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
/**
|
||||||
|
* Current active space
|
||||||
|
* The explore button is only displayed in the Home meta space
|
||||||
|
*/
|
||||||
|
activeSpace: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel for the room list search component.
|
||||||
|
* Manages the state and actions for the search bar, dial pad, and explore buttons.
|
||||||
|
*/
|
||||||
|
export class RoomListSearchViewModel
|
||||||
|
extends BaseViewModel<RoomListSearchViewSnapshot, Props>
|
||||||
|
implements RoomListSearchViewModelInterface
|
||||||
|
{
|
||||||
|
private displayDialButton = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the snapshot based on the current props and PSTN support state.
|
||||||
|
*/
|
||||||
|
private static readonly computeSnapshot = (
|
||||||
|
activeSpace: string,
|
||||||
|
supportsPstn: boolean,
|
||||||
|
): RoomListSearchViewSnapshot => {
|
||||||
|
const displayExploreButton = activeSpace === MetaSpace.Home && shouldShowComponent(UIComponent.ExploreRooms);
|
||||||
|
const searchShortcut = IS_MAC ? "⌘ K" : _t(ALTERNATE_KEY_NAME[Key.CONTROL]) + " K";
|
||||||
|
return {
|
||||||
|
displayExploreButton,
|
||||||
|
displayDialButton: supportsPstn,
|
||||||
|
searchShortcut,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
public constructor(props: Props) {
|
||||||
|
const supportsPstn = LegacyCallHandler.instance.getSupportsPstnProtocol();
|
||||||
|
super(props, RoomListSearchViewModel.computeSnapshot(props.activeSpace, supportsPstn));
|
||||||
|
this.displayDialButton = supportsPstn;
|
||||||
|
|
||||||
|
// Listen for changes in PSTN protocol support
|
||||||
|
this.disposables.trackListener(
|
||||||
|
LegacyCallHandler.instance,
|
||||||
|
LegacyCallHandlerEvent.ProtocolSupport,
|
||||||
|
this.onProtocolSupportChange,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles changes in protocol support (PSTN).
|
||||||
|
*/
|
||||||
|
private readonly onProtocolSupportChange = (): void => {
|
||||||
|
const supportsPstn = LegacyCallHandler.instance.getSupportsPstnProtocol();
|
||||||
|
this.displayDialButton = supportsPstn;
|
||||||
|
this.snapshot.set(RoomListSearchViewModel.computeSnapshot(this.props.activeSpace, supportsPstn));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the search button click event.
|
||||||
|
* Opens the spotlight search dialog.
|
||||||
|
*/
|
||||||
|
public onSearchClick = (): void => {
|
||||||
|
defaultDispatcher.fire(Action.OpenSpotlight);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the dial pad button click event.
|
||||||
|
* Opens the dial pad dialog.
|
||||||
|
*/
|
||||||
|
public onDialPadClick = (): void => {
|
||||||
|
defaultDispatcher.fire(Action.OpenDialPad);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the explore button click event.
|
||||||
|
* Opens the room directory and tracks the interaction.
|
||||||
|
*/
|
||||||
|
public onExploreClick = (ev: MouseEvent<HTMLButtonElement>): void => {
|
||||||
|
defaultDispatcher.fire(Action.ViewRoomDirectory);
|
||||||
|
PosthogTrackers.trackInteraction("WebLeftPanelExploreRoomsButton", ev);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the active space and updates the snapshot accordingly.
|
||||||
|
* @param activeSpace - The new active space ID.
|
||||||
|
*/
|
||||||
|
public setActiveSpace(activeSpace: string): void {
|
||||||
|
if (activeSpace === this.props.activeSpace) return;
|
||||||
|
|
||||||
|
this.props.activeSpace = activeSpace;
|
||||||
|
this.snapshot.set(RoomListSearchViewModel.computeSnapshot(activeSpace, this.displayDialButton));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ describe("KeyboardLandmarkUtils", () => {
|
|||||||
<div tabIndex={0} className="mx_SpaceButton_active" data-testid="mx_SpaceButton_active">
|
<div tabIndex={0} className="mx_SpaceButton_active" data-testid="mx_SpaceButton_active">
|
||||||
SPACE_BUTTON
|
SPACE_BUTTON
|
||||||
</div>
|
</div>
|
||||||
<div tabIndex={0} className="mx_RoomListSearch_search" data-testid="mx_RoomListSearch_search">
|
<div tabIndex={0} id="room-list-search-button" data-testid="mx_RoomListSearch_search">
|
||||||
ROOM_SEARCH
|
ROOM_SEARCH
|
||||||
</div>
|
</div>
|
||||||
<div tabIndex={0} className="mx_RoomListItemView" data-testid="mx_RoomListItemView">
|
<div tabIndex={0} className="mx_RoomListItemView" data-testid="mx_RoomListItemView">
|
||||||
@@ -72,7 +72,7 @@ describe("KeyboardLandmarkUtils", () => {
|
|||||||
<div tabIndex={0} className="mx_SpaceButton_active" data-testid="mx_SpaceButton_active">
|
<div tabIndex={0} className="mx_SpaceButton_active" data-testid="mx_SpaceButton_active">
|
||||||
SPACE_BUTTON
|
SPACE_BUTTON
|
||||||
</div>
|
</div>
|
||||||
<div tabIndex={0} className="mx_RoomListSearch_search" data-testid="mx_RoomListSearch_search">
|
<div tabIndex={0} id="room-list-search-button" data-testid="mx_RoomListSearch_search">
|
||||||
ROOM_SEARCH
|
ROOM_SEARCH
|
||||||
</div>
|
</div>
|
||||||
<div tabIndex={0} className="mx_RoomListItemView_selected" data-testid="mx_RoomListItemView_selected">
|
<div tabIndex={0} className="mx_RoomListItemView_selected" data-testid="mx_RoomListItemView_selected">
|
||||||
|
|||||||
@@ -6,15 +6,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { render, screen } from "jest-matrix-react";
|
import { render } from "jest-matrix-react";
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
import userEvent from "@testing-library/user-event";
|
|
||||||
|
|
||||||
import { RoomListSearch } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListSearch";
|
import { RoomListSearch } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListSearch";
|
||||||
import { MetaSpace } from "../../../../../../src/stores/spaces";
|
import { MetaSpace } from "../../../../../../src/stores/spaces";
|
||||||
import { shouldShowComponent } from "../../../../../../src/customisations/helpers/UIComponents";
|
import { shouldShowComponent } from "../../../../../../src/customisations/helpers/UIComponents";
|
||||||
import defaultDispatcher from "../../../../../../src/dispatcher/dispatcher";
|
|
||||||
import { Action } from "../../../../../../src/dispatcher/actions";
|
|
||||||
import LegacyCallHandler from "../../../../../../src/LegacyCallHandler";
|
import LegacyCallHandler from "../../../../../../src/LegacyCallHandler";
|
||||||
|
|
||||||
jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
|
jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
|
||||||
@@ -32,79 +29,8 @@ describe("<RoomListSearch />", () => {
|
|||||||
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
|
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should display search and explore buttons", () => {
|
it("renders", () => {
|
||||||
const { asFragment } = renderComponent();
|
|
||||||
|
|
||||||
// The search and explore buttons should be displayed
|
|
||||||
expect(screen.getByRole("button", { name: "Search Ctrl K" })).toBeInTheDocument();
|
|
||||||
expect(screen.getByRole("button", { name: "Explore rooms" })).toBeInTheDocument();
|
|
||||||
// The dial button should not be displayed
|
|
||||||
expect(screen.queryByRole("button", { name: "Open dial pad" })).not.toBeInTheDocument();
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should hide the explore button when the active space is not MetaSpace.Home", () => {
|
|
||||||
const { asFragment } = renderComponent(MetaSpace.VideoRooms);
|
const { asFragment } = renderComponent(MetaSpace.VideoRooms);
|
||||||
|
|
||||||
// The search button should be displayed but not the explore button
|
|
||||||
expect(screen.getByRole("button", { name: "Search Ctrl K" })).toBeInTheDocument();
|
|
||||||
expect(screen.queryByRole("button", { name: "Explore rooms" })).not.toBeInTheDocument();
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should hide the explore button when UIComponent.ExploreRooms is disabled", () => {
|
|
||||||
mocked(shouldShowComponent).mockReturnValue(false);
|
|
||||||
const { asFragment } = renderComponent();
|
|
||||||
|
|
||||||
// The search button should be displayed but not the explore button
|
|
||||||
expect(screen.getByRole("button", { name: "Search Ctrl K" })).toBeInTheDocument();
|
|
||||||
expect(screen.queryByRole("button", { name: "Explore rooms" })).not.toBeInTheDocument();
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should display the dial button when the PTSN protocol is not supported", () => {
|
|
||||||
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(true);
|
|
||||||
const { asFragment } = renderComponent();
|
|
||||||
|
|
||||||
// The dial button should be displayed
|
|
||||||
expect(screen.getByRole("button", { name: "Open dial pad" })).toBeInTheDocument();
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should open the spotlight when the search button is clicked", async () => {
|
|
||||||
const fireSpy = jest.spyOn(defaultDispatcher, "fire");
|
|
||||||
const user = userEvent.setup();
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
// Click on the search button
|
|
||||||
await user.click(screen.getByRole("button", { name: "Search Ctrl K" }));
|
|
||||||
|
|
||||||
// The spotlight should be opened
|
|
||||||
expect(fireSpy).toHaveBeenCalledWith(Action.OpenSpotlight);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should open the room directory when the explore button is clicked", async () => {
|
|
||||||
const fireSpy = jest.spyOn(defaultDispatcher, "fire");
|
|
||||||
const user = userEvent.setup();
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
// Click on the search button
|
|
||||||
await user.click(screen.getByRole("button", { name: "Explore rooms" }));
|
|
||||||
|
|
||||||
// The spotlight should be opened
|
|
||||||
expect(fireSpy).toHaveBeenCalledWith(Action.ViewRoomDirectory);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should open the dial pad when the dial button is clicked", async () => {
|
|
||||||
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(true);
|
|
||||||
const fireSpy = jest.spyOn(defaultDispatcher, "fire");
|
|
||||||
const user = userEvent.setup();
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
// Click on the search button
|
|
||||||
await user.click(screen.getByRole("button", { name: "Open dial pad" }));
|
|
||||||
|
|
||||||
// The spotlight should be opened
|
|
||||||
expect(fireSpy).toHaveBeenCalledWith(Action.OpenDialPad);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||||
|
|
||||||
exports[`<RoomListSearch /> should display search and explore buttons 1`] = `
|
exports[`<RoomListSearch /> renders 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<div
|
<div
|
||||||
class="_flex_4dswl_9 mx_RoomListSearch"
|
class="_flex_4dswl_9 _view_z7ks9_8"
|
||||||
|
data-testid="room-list-search"
|
||||||
role="search"
|
role="search"
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="_button_187yx_8 mx_RoomListSearch_search _has-icon_187yx_57"
|
class="_button_187yx_8 _search_z7ks9_16 _has-icon_187yx_57"
|
||||||
data-kind="secondary"
|
data-kind="secondary"
|
||||||
data-size="sm"
|
data-size="sm"
|
||||||
|
id="room-list-search-button"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
@@ -27,206 +29,11 @@ exports[`<RoomListSearch /> should display search and explore buttons 1`] = `
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span
|
<span
|
||||||
class="_flex_4dswl_9"
|
class="_flex_4dswl_9 _search_container_z7ks9_29"
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="mx_RoomListSearch_search_text"
|
class="_search_text_z7ks9_41"
|
||||||
>
|
|
||||||
Search
|
|
||||||
</span>
|
|
||||||
<kbd>
|
|
||||||
Ctrl K
|
|
||||||
</kbd>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-label="Explore rooms"
|
|
||||||
class="_button_187yx_8 _has-icon_187yx_57 _icon-only_187yx_50"
|
|
||||||
data-kind="secondary"
|
|
||||||
data-size="sm"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
fill="currentColor"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="20"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M12 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 12q0-.424.287-.713A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 12 13m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20m0 0q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675T12 20m1.675-5.85q.15-.075.275-.2t.2-.275l2.925-6.25q.125-.25-.062-.437-.188-.188-.438-.063l-6.25 2.925q-.15.075-.275.2t-.2.275l-2.925 6.25q-.125.25.063.438.186.186.437.062z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</DocumentFragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<RoomListSearch /> should display the dial button when the PTSN protocol is not supported 1`] = `
|
|
||||||
<DocumentFragment>
|
|
||||||
<div
|
|
||||||
class="_flex_4dswl_9 mx_RoomListSearch"
|
|
||||||
role="search"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="_button_187yx_8 mx_RoomListSearch_search _has-icon_187yx_57"
|
|
||||||
data-kind="secondary"
|
|
||||||
data-size="sm"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
fill="currentColor"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="20"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span
|
|
||||||
class="_flex_4dswl_9"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="mx_RoomListSearch_search_text"
|
|
||||||
>
|
|
||||||
Search
|
|
||||||
</span>
|
|
||||||
<kbd>
|
|
||||||
Ctrl K
|
|
||||||
</kbd>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-label="Open dial pad"
|
|
||||||
class="_button_187yx_8 _has-icon_187yx_57 _icon-only_187yx_50"
|
|
||||||
data-kind="secondary"
|
|
||||||
data-size="sm"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
fill="currentColor"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="20"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M12 18.6c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8M6.6 2.4c-.99 0-1.8.81-1.8 1.8S5.61 6 6.6 6s1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0 5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0 5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8M17.4 6c.99 0 1.8-.81 1.8-1.8s-.81-1.8-1.8-1.8-1.8.81-1.8 1.8.81 1.8 1.8 1.8M12 13.2c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m5.4 0c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0-5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m-5.4 0c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0-5.4c-.99 0-1.8.81-1.8 1.8S11.01 6 12 6s1.8-.81 1.8-1.8-.81-1.8-1.8-1.8"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-label="Explore rooms"
|
|
||||||
class="_button_187yx_8 _has-icon_187yx_57 _icon-only_187yx_50"
|
|
||||||
data-kind="secondary"
|
|
||||||
data-size="sm"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
fill="currentColor"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="20"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M12 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 12q0-.424.287-.713A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 12 13m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20m0 0q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675T12 20m1.675-5.85q.15-.075.275-.2t.2-.275l2.925-6.25q.125-.25-.062-.437-.188-.188-.438-.063l-6.25 2.925q-.15.075-.275.2t-.2.275l-2.925 6.25q-.125.25.063.438.186.186.437.062z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</DocumentFragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<RoomListSearch /> should hide the explore button when UIComponent.ExploreRooms is disabled 1`] = `
|
|
||||||
<DocumentFragment>
|
|
||||||
<div
|
|
||||||
class="_flex_4dswl_9 mx_RoomListSearch"
|
|
||||||
role="search"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="_button_187yx_8 mx_RoomListSearch_search _has-icon_187yx_57"
|
|
||||||
data-kind="secondary"
|
|
||||||
data-size="sm"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
fill="currentColor"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="20"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span
|
|
||||||
class="_flex_4dswl_9"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="mx_RoomListSearch_search_text"
|
|
||||||
>
|
|
||||||
Search
|
|
||||||
</span>
|
|
||||||
<kbd>
|
|
||||||
Ctrl K
|
|
||||||
</kbd>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</DocumentFragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<RoomListSearch /> should hide the explore button when the active space is not MetaSpace.Home 1`] = `
|
|
||||||
<DocumentFragment>
|
|
||||||
<div
|
|
||||||
class="_flex_4dswl_9 mx_RoomListSearch"
|
|
||||||
role="search"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="_button_187yx_8 mx_RoomListSearch_search _has-icon_187yx_57"
|
|
||||||
data-kind="secondary"
|
|
||||||
data-size="sm"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
fill="currentColor"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="20"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span
|
|
||||||
class="_flex_4dswl_9"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="mx_RoomListSearch_search_text"
|
|
||||||
>
|
>
|
||||||
Search
|
Search
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
114
test/viewmodels/room-list/RoomListSearchViewModel-test.ts
Normal file
114
test/viewmodels/room-list/RoomListSearchViewModel-test.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* 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 { mocked } from "jest-mock";
|
||||||
|
|
||||||
|
import { RoomListSearchViewModel } from "../../../src/viewmodels/room-list/RoomListSearchViewModel";
|
||||||
|
import { MetaSpace } from "../../../src/stores/spaces";
|
||||||
|
import { shouldShowComponent } from "../../../src/customisations/helpers/UIComponents";
|
||||||
|
import defaultDispatcher from "../../../src/dispatcher/dispatcher";
|
||||||
|
import { Action } from "../../../src/dispatcher/actions";
|
||||||
|
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../src/LegacyCallHandler";
|
||||||
|
|
||||||
|
jest.mock("../../../src/customisations/helpers/UIComponents", () => ({
|
||||||
|
shouldShowComponent: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../../src/PosthogTrackers", () => ({
|
||||||
|
trackInteraction: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("RoomListSearchViewModel", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocked(shouldShowComponent).mockReturnValue(true);
|
||||||
|
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("snapshot", () => {
|
||||||
|
it("should show explore button in Home space when UIComponent.ExploreRooms is enabled", () => {
|
||||||
|
mocked(shouldShowComponent).mockReturnValue(true);
|
||||||
|
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||||
|
|
||||||
|
expect(vm.getSnapshot().displayExploreButton).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should hide explore button when not in Home space", () => {
|
||||||
|
mocked(shouldShowComponent).mockReturnValue(true);
|
||||||
|
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.VideoRooms });
|
||||||
|
|
||||||
|
expect(vm.getSnapshot().displayExploreButton).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should hide explore button when UIComponent.ExploreRooms is disabled", () => {
|
||||||
|
mocked(shouldShowComponent).mockReturnValue(false);
|
||||||
|
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||||
|
|
||||||
|
expect(vm.getSnapshot().displayExploreButton).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show dial button when PSTN protocol is supported", () => {
|
||||||
|
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(true);
|
||||||
|
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||||
|
|
||||||
|
expect(vm.getSnapshot().displayDialButton).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should hide dial button when PSTN protocol is not supported", () => {
|
||||||
|
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
|
||||||
|
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||||
|
|
||||||
|
expect(vm.getSnapshot().displayDialButton).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("actions", () => {
|
||||||
|
it("should fire OpenSpotlight action when onSearchClick is called", () => {
|
||||||
|
const fireSpy = jest.spyOn(defaultDispatcher, "fire");
|
||||||
|
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||||
|
|
||||||
|
vm.onSearchClick();
|
||||||
|
expect(fireSpy).toHaveBeenCalledWith(Action.OpenSpotlight);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fire OpenDialPad action when onDialPadClick is called", () => {
|
||||||
|
const fireSpy = jest.spyOn(defaultDispatcher, "fire");
|
||||||
|
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||||
|
|
||||||
|
vm.onDialPadClick();
|
||||||
|
expect(fireSpy).toHaveBeenCalledWith(Action.OpenDialPad);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fire ViewRoomDirectory action and track interaction when onExploreClick is called", () => {
|
||||||
|
const fireSpy = jest.spyOn(defaultDispatcher, "fire");
|
||||||
|
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||||
|
|
||||||
|
const mockEvent = {} as React.MouseEvent<HTMLButtonElement>;
|
||||||
|
vm.onExploreClick(mockEvent);
|
||||||
|
|
||||||
|
expect(fireSpy).toHaveBeenCalledWith(Action.ViewRoomDirectory);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should update snapshot when PSTN protocol support changes", () => {
|
||||||
|
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(false);
|
||||||
|
const vm = new RoomListSearchViewModel({ activeSpace: MetaSpace.Home });
|
||||||
|
|
||||||
|
expect(vm.getSnapshot().displayDialButton).toBe(false);
|
||||||
|
|
||||||
|
// Simulate PSTN protocol support change
|
||||||
|
jest.spyOn(LegacyCallHandler.instance, "getSupportsPstnProtocol").mockReturnValue(true);
|
||||||
|
LegacyCallHandler.instance.emit(LegacyCallHandlerEvent.ProtocolSupport);
|
||||||
|
|
||||||
|
expect(vm.getSnapshot().displayDialButton).toBe(true);
|
||||||
|
|
||||||
|
vm.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user