New room list: add keyboard navigation support (#29805)

* feat: support up/down arrow navigation in the new room list

* feat: support tabbing in the new room list

* test: update snapshots

* test(e2e): fix room list test

* test(new room list): add landmark navigation test

* test(e2e): update screenshot test

* test: add test to `RoomListItemView`

* test(e2e): add keyboard navigation tests

* refactor: rename `setIsHover` on `setIsHoverWithDelay`
This commit is contained in:
Florian Duros
2025-05-06 18:09:23 +02:00
committed by GitHub
parent 6ba21dafa7
commit 74fbd892a1
9 changed files with 211 additions and 47 deletions

View File

@@ -43,7 +43,8 @@ test.describe("Room list", () => {
await expect(roomListView.getByRole("gridcell", { name: "Open room room29" })).toBeVisible(); await expect(roomListView.getByRole("gridcell", { name: "Open room room29" })).toBeVisible();
await expect(roomListView).toMatchScreenshot("room-list.png"); await expect(roomListView).toMatchScreenshot("room-list.png");
await roomListView.hover(); // Put focus on the room list
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
// Scroll to the end of the room list // Scroll to the end of the room list
await page.mouse.wheel(0, 1000); await page.mouse.wheel(0, 1000);
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible(); await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
@@ -105,13 +106,10 @@ test.describe("Room list", () => {
// It should make the room muted // It should make the room muted
await page.getByRole("menuitem", { name: "Mute room" }).click(); await page.getByRole("menuitem", { name: "Mute room" }).click();
// Remove hover on the room list item // Put focus on the room list
await roomListView.hover(); await roomListView.getByRole("gridcell", { name: "Open room room28" }).click();
// Scroll to the end of the room list
// Scroll to the bottom of the list await page.mouse.wheel(0, 1000);
await page.getByRole("grid", { name: "Room list" }).evaluate((e) => {
e.scrollTop = e.scrollHeight;
});
// The room decoration should have the muted icon // The room decoration should have the muted icon
await expect(roomItem.getByTestId("notification-decoration")).toBeVisible(); await expect(roomItem.getByTestId("notification-decoration")).toBeVisible();
@@ -129,7 +127,8 @@ test.describe("Room list", () => {
test("should scroll to the current room", async ({ page, app, user }) => { test("should scroll to the current room", async ({ page, app, user }) => {
const roomListView = getRoomList(page); const roomListView = getRoomList(page);
await roomListView.hover(); // Put focus on the room list
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
// Scroll to the end of the room list // Scroll to the end of the room list
await page.mouse.wheel(0, 1000); await page.mouse.wheel(0, 1000);
@@ -183,6 +182,57 @@ test.describe("Room list", () => {
await expect(page.getByRole("heading", { name: "1 notification", level: 1 })).toBeVisible(); await expect(page.getByRole("heading", { name: "1 notification", level: 1 })).toBeVisible();
}); });
}); });
test.describe("Keyboard navigation", () => {
test("should navigate to the room list", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
const room29 = roomListView.getByRole("gridcell", { name: "Open room room29" });
const room28 = roomListView.getByRole("gridcell", { name: "Open room room28" });
// open the room
await room29.click();
// put focus back on the room list item
await room29.click();
await expect(room29).toBeFocused();
await page.keyboard.press("ArrowDown");
await expect(room28).toBeFocused();
await expect(room29).not.toBeFocused();
await page.keyboard.press("ArrowUp");
await expect(room29).toBeFocused();
await expect(room28).not.toBeFocused();
});
test("should navigate to the notification menu", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
const room29 = roomListView.getByRole("gridcell", { name: "Open room room29" });
const moreButton = room29.getByRole("button", { name: "More options" });
const notificationButton = room29.getByRole("button", { name: "Notification options" });
await room29.click();
// put focus back on the room list item
await room29.click();
await page.keyboard.press("Tab");
await expect(moreButton).toBeFocused();
await page.keyboard.press("Tab");
await expect(notificationButton).toBeFocused();
// Open the menu
await notificationButton.click();
// Wait for the menu to be open
await expect(page.getByRole("menuitem", { name: "Match default settings" })).toHaveAttribute(
"aria-selected",
"true",
);
// Close the menu
await page.keyboard.press("Escape");
// Focus should be back on the room list item
await expect(room29).toBeFocused();
});
});
}); });
test.describe("Avatar decoration", () => { test.describe("Avatar decoration", () => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -18,10 +18,6 @@
all: unset; all: unset;
cursor: pointer; cursor: pointer;
&:hover {
background-color: var(--cpd-color-bg-action-secondary-hovered);
}
.mx_RoomListItemView_container { .mx_RoomListItemView_container {
padding-left: var(--cpd-space-2x); padding-left: var(--cpd-space-2x);
font: var(--cpd-font-body-md-regular); font: var(--cpd-font-body-md-regular);
@@ -56,12 +52,12 @@
} }
} }
.mx_RoomListItemView_menu_open { .mx_RoomListItemView_hover {
background-color: var(--cpd-color-bg-action-secondary-hovered); background-color: var(--cpd-color-bg-action-secondary-hovered);
}
.mx_RoomListItemView_content { .mx_RoomListItemView_menu_open .mx_RoomListItemView_content {
padding-right: var(--cpd-space-1-5x); padding-right: var(--cpd-space-1-5x);
}
} }
.mx_RoomListItemView_selected { .mx_RoomListItemView_selected {

View File

@@ -11,6 +11,10 @@ import { AutoSizer, List, type ListRowProps } from "react-virtualized";
import { type RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel"; import { type RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel";
import { _t } from "../../../../languageHandler"; import { _t } from "../../../../languageHandler";
import { RoomListItemView } from "./RoomListItemView"; import { RoomListItemView } from "./RoomListItemView";
import { RovingTabIndexProvider } from "../../../../accessibility/RovingTabIndex";
import { getKeyBindingsManager } from "../../../../KeyBindingsManager";
import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts";
import { Landmark, LandmarkNavigation } from "../../../../accessibility/LandmarkNavigation";
interface RoomListProps { interface RoomListProps {
/** /**
@@ -32,7 +36,28 @@ export function RoomList({ vm: { rooms, activeIndex } }: RoomListProps): JSX.Ele
// The first div is needed to make the virtualized list take all the remaining space and scroll correctly // The first div is needed to make the virtualized list take all the remaining space and scroll correctly
return ( return (
<div className="mx_RoomList" data-testid="room-list"> <RovingTabIndexProvider handleHomeEnd={true} handleUpDown={true}>
{({ onKeyDownHandler }) => (
<div
className="mx_RoomList"
data-testid="room-list"
onKeyDown={(ev) => {
const navAction = getKeyBindingsManager().getNavigationAction(ev);
if (
navAction === KeyBindingAction.NextLandmark ||
navAction === KeyBindingAction.PreviousLandmark
) {
LandmarkNavigation.findAndFocusNextLandmark(
Landmark.ROOM_LIST,
navAction === KeyBindingAction.PreviousLandmark,
);
ev.stopPropagation();
ev.preventDefault();
return;
}
onKeyDownHandler(ev);
}}
>
<AutoSizer> <AutoSizer>
{({ height, width }) => ( {({ height, width }) => (
<List <List
@@ -44,9 +69,12 @@ export function RoomList({ vm: { rooms, activeIndex } }: RoomListProps): JSX.Ele
height={height} height={height}
width={width} width={width}
scrollToIndex={activeIndex ?? 0} scrollToIndex={activeIndex ?? 0}
tabIndex={-1}
/> />
)} )}
</AutoSizer> </AutoSizer>
</div> </div>
)}
</RovingTabIndexProvider>
); );
} }

View File

@@ -5,7 +5,7 @@
* 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, memo, useState } from "react"; import React, { type JSX, memo, useCallback, useRef, useState } from "react";
import { type Room } from "matrix-js-sdk/src/matrix"; import { type Room } from "matrix-js-sdk/src/matrix";
import classNames from "classnames"; import classNames from "classnames";
@@ -14,6 +14,7 @@ import { Flex } from "../../../utils/Flex";
import { RoomListItemMenuView } from "./RoomListItemMenuView"; import { RoomListItemMenuView } from "./RoomListItemMenuView";
import { NotificationDecoration } from "../NotificationDecoration"; import { NotificationDecoration } from "../NotificationDecoration";
import { RoomAvatarView } from "../../avatars/RoomAvatarView"; import { RoomAvatarView } from "../../avatars/RoomAvatarView";
import { useRovingTabIndex } from "../../../../accessibility/RovingTabIndex";
interface RoomListItemViewProps extends React.HTMLAttributes<HTMLButtonElement> { interface RoomListItemViewProps extends React.HTMLAttributes<HTMLButtonElement> {
/** /**
@@ -34,22 +35,29 @@ export const RoomListItemView = memo(function RoomListItemView({
isSelected, isSelected,
...props ...props
}: RoomListItemViewProps): JSX.Element { }: RoomListItemViewProps): JSX.Element {
const buttonRef = useRef<HTMLButtonElement>(null);
const [onFocus, isActive, ref] = useRovingTabIndex(buttonRef);
const vm = useRoomListItemViewModel(room); const vm = useRoomListItemViewModel(room);
const [isHover, setIsHover] = useState(false); const [isHover, setIsHoverWithDelay] = useIsHover();
const [isMenuOpen, setIsMenuOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false);
// The compound menu in RoomListItemMenuView needs to be rendered when the hover menu is shown // The compound menu in RoomListItemMenuView needs to be rendered when the hover menu is shown
// Using display: none; and then display:flex when hovered in CSS causes the menu to be misaligned // Using display: none; and then display:flex when hovered in CSS causes the menu to be misaligned
const showHoverDecoration = (isMenuOpen || isHover) && vm.showHoverMenu; const showHoverDecoration = isMenuOpen || isHover;
const showHoverMenu = showHoverDecoration && vm.showHoverMenu;
const isNotificationDecorationVisible = !showHoverDecoration && vm.showNotificationDecoration; const isInvitation = vm.notificationState.invited;
const isNotificationDecorationVisible = isInvitation || (!showHoverDecoration && vm.showNotificationDecoration);
return ( return (
<button <button
ref={ref}
className={classNames("mx_RoomListItemView", { className={classNames("mx_RoomListItemView", {
mx_RoomListItemView_empty: !isNotificationDecorationVisible && !showHoverDecoration, mx_RoomListItemView_empty: !isNotificationDecorationVisible && !showHoverDecoration,
mx_RoomListItemView_notification_decoration: isNotificationDecorationVisible, mx_RoomListItemView_notification_decoration: isNotificationDecorationVisible,
mx_RoomListItemView_menu_open: showHoverDecoration, mx_RoomListItemView_hover: showHoverDecoration,
mx_RoomListItemView_menu_open: showHoverMenu,
mx_RoomListItemView_selected: isSelected, mx_RoomListItemView_selected: isSelected,
mx_RoomListItemView_bold: vm.isBold, mx_RoomListItemView_bold: vm.isBold,
})} })}
@@ -57,10 +65,17 @@ export const RoomListItemView = memo(function RoomListItemView({
aria-selected={isSelected} aria-selected={isSelected}
aria-label={vm.a11yLabel} aria-label={vm.a11yLabel}
onClick={() => vm.openRoom()} onClick={() => vm.openRoom()}
onMouseOver={() => setIsHover(true)} onMouseOver={() => setIsHoverWithDelay(true)}
onMouseOut={() => setIsHover(false)} onMouseOut={() => setIsHoverWithDelay(false)}
onFocus={() => setIsHover(true)} onFocus={() => {
onBlur={() => setIsHover(false)} setIsHoverWithDelay(true);
onFocus();
}}
// Adding a timeout because when tabbing to go to the more options and notification menu, the focus moves out of the button
// The blur makes the button lose the hover state and these menu are not shown
// We delay the blur event to give time to the focus to move to the menu
onBlur={() => setIsHoverWithDelay(false, 10)}
tabIndex={isActive ? 0 : -1}
{...props} {...props}
> >
{/* We need this extra div between the button and the content in order to add a padding which is not messing with the virtualized list */} {/* We need this extra div between the button and the content in order to add a padding which is not messing with the virtualized list */}
@@ -79,13 +94,19 @@ export const RoomListItemView = memo(function RoomListItemView({
</div> </div>
<div className="mx_RoomListItemView_messagePreview">{vm.messagePreview}</div> <div className="mx_RoomListItemView_messagePreview">{vm.messagePreview}</div>
</div> </div>
{showHoverDecoration ? ( {showHoverMenu ? (
<RoomListItemMenuView <RoomListItemMenuView
room={room} room={room}
setMenuOpen={(isOpen) => { setMenuOpen={(isOpen) => {
if (isOpen) setIsMenuOpen(isOpen); if (isOpen) {
setIsMenuOpen(isOpen);
} else {
// To avoid icon blinking when closing the menu, we delay the state update // To avoid icon blinking when closing the menu, we delay the state update
else setTimeout(() => setIsMenuOpen(isOpen), 0); setTimeout(() => setIsMenuOpen(isOpen), 0);
// After closing the menu, we need to set the focus back to the button
// 10ms because the focus moves to the body and we put back the focus on the button
setTimeout(() => buttonRef.current?.focus(), 10);
}
}} }}
/> />
) : ( ) : (
@@ -105,3 +126,33 @@ export const RoomListItemView = memo(function RoomListItemView({
</button> </button>
); );
}); });
/**
* Custom hook to manage the hover state of the room list item
* If the timeout is set, it will set the hover state after the timeout
* If the timeout is not set, it will set the hover state immediately
* When the set method is called, it will clear any existing timeout
*
* @returns {boolean} isHover - The hover state
*/
function useIsHover(): [boolean, (value: boolean, timeout?: number) => void] {
const [isHover, setIsHover] = useState(false);
// Store the timeout ID
const timeoutRef = useRef<number | undefined>(undefined);
const setIsHoverWithDelay = useCallback((value: boolean, timeout?: number): void => {
// Clear the timeout if it exists
clearTimeout(timeoutRef.current);
// No delay, set the value immediately
if (timeout === undefined) {
setIsHover(value);
return;
}
// Set a timeout to set the value after the delay
timeoutRef.current = setTimeout(() => setIsHover(value), timeout);
}, []);
return [isHover, setIsHoverWithDelay];
}

View File

@@ -8,6 +8,7 @@
import React from "react"; import React from "react";
import { type MatrixClient } from "matrix-js-sdk/src/matrix"; import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { render } from "jest-matrix-react"; import { render } from "jest-matrix-react";
import { fireEvent } from "@testing-library/dom";
import { mkRoom, stubClient } from "../../../../../test-utils"; import { mkRoom, stubClient } from "../../../../../test-utils";
import { type RoomListViewState } from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel"; import { type RoomListViewState } from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel";
@@ -15,6 +16,7 @@ import { RoomList } from "../../../../../../src/components/views/rooms/RoomListP
import DMRoomMap from "../../../../../../src/utils/DMRoomMap"; import DMRoomMap from "../../../../../../src/utils/DMRoomMap";
import { SecondaryFilters } from "../../../../../../src/components/viewmodels/roomlist/useFilteredRooms"; import { SecondaryFilters } from "../../../../../../src/components/viewmodels/roomlist/useFilteredRooms";
import { SortOption } from "../../../../../../src/components/viewmodels/roomlist/useSorter"; import { SortOption } from "../../../../../../src/components/viewmodels/roomlist/useSorter";
import { Landmark, LandmarkNavigation } from "../../../../../../src/accessibility/LandmarkNavigation";
describe("<RoomList />", () => { describe("<RoomList />", () => {
let matrixClient: MatrixClient; let matrixClient: MatrixClient;
@@ -53,4 +55,16 @@ describe("<RoomList />", () => {
const { asFragment } = render(<RoomList vm={vm} />); const { asFragment } = render(<RoomList vm={vm} />);
expect(asFragment()).toMatchSnapshot(); expect(asFragment()).toMatchSnapshot();
}); });
it.each([
{ shortcut: { key: "F6", ctrlKey: true, shiftKey: true }, isPreviousLandmark: true, label: "PreviousLandmark" },
{ shortcut: { key: "F6", ctrlKey: true }, isPreviousLandmark: false, label: "NextLandmark" },
])("should navigate to the landmark on NextLandmark.$label action", ({ shortcut, isPreviousLandmark }) => {
const spyFindLandmark = jest.spyOn(LandmarkNavigation, "findAndFocusNextLandmark").mockReturnValue();
const { getByTestId } = render(<RoomList vm={vm} />);
const roomList = getByTestId("room-list");
fireEvent.keyDown(roomList, shortcut);
expect(spyFindLandmark).toHaveBeenCalledWith(Landmark.ROOM_LIST, isPreviousLandmark);
});
}); });

View File

@@ -91,6 +91,17 @@ describe("<RoomListItemView />", () => {
await waitFor(() => expect(screen.getByRole("button", { name: "More Options" })).toBeInTheDocument()); await waitFor(() => expect(screen.getByRole("button", { name: "More Options" })).toBeInTheDocument());
}); });
test("should hover decoration if focused", async () => {
const user = userEvent.setup();
render(<RoomListItemView room={room} isSelected={false} />, withClientContextRenderOptions(matrixClient));
const listItem = screen.getByRole("button", { name: `Open room ${room.name}` });
await user.click(listItem);
expect(listItem).toHaveClass("mx_RoomListItemView_hover");
await user.tab();
await waitFor(() => expect(listItem).not.toHaveClass("mx_RoomListItemView_hover"));
});
test("should be selected if isSelected=true", async () => { test("should be selected if isSelected=true", async () => {
const { asFragment } = render(<RoomListItemView room={room} isSelected={true} />); const { asFragment } = render(<RoomListItemView room={room} isSelected={true} />);
expect(screen.queryByRole("button", { name: `Open room ${room.name}` })).toHaveAttribute( expect(screen.queryByRole("button", { name: `Open room ${room.name}` })).toHaveAttribute(

View File

@@ -15,7 +15,7 @@ exports[`<RoomList /> should render a room list 1`] = `
class="ReactVirtualized__Grid ReactVirtualized__List mx_RoomList_List" class="ReactVirtualized__Grid ReactVirtualized__List mx_RoomList_List"
role="grid" role="grid"
style="box-sizing: border-box; direction: ltr; height: 1500px; position: relative; width: 1500px; will-change: transform; overflow-x: hidden; overflow-y: hidden;" style="box-sizing: border-box; direction: ltr; height: 1500px; position: relative; width: 1500px; will-change: transform; overflow-x: hidden; overflow-y: hidden;"
tabindex="0" tabindex="-1"
> >
<div <div
class="ReactVirtualized__Grid__innerScrollContainer" class="ReactVirtualized__Grid__innerScrollContainer"
@@ -28,6 +28,7 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_RoomListItemView mx_RoomListItemView_empty" class="mx_RoomListItemView mx_RoomListItemView_empty"
role="gridcell" role="gridcell"
style="height: 48px; left: 0px; position: absolute; top: 0px; width: 100%;" style="height: 48px; left: 0px; position: absolute; top: 0px; width: 100%;"
tabindex="0"
type="button" type="button"
> >
<div <div
@@ -79,6 +80,7 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_RoomListItemView mx_RoomListItemView_empty" class="mx_RoomListItemView mx_RoomListItemView_empty"
role="gridcell" role="gridcell"
style="height: 48px; left: 0px; position: absolute; top: 48px; width: 100%;" style="height: 48px; left: 0px; position: absolute; top: 48px; width: 100%;"
tabindex="-1"
type="button" type="button"
> >
<div <div
@@ -130,6 +132,7 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_RoomListItemView mx_RoomListItemView_empty" class="mx_RoomListItemView mx_RoomListItemView_empty"
role="gridcell" role="gridcell"
style="height: 48px; left: 0px; position: absolute; top: 96px; width: 100%;" style="height: 48px; left: 0px; position: absolute; top: 96px; width: 100%;"
tabindex="-1"
type="button" type="button"
> >
<div <div
@@ -181,6 +184,7 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_RoomListItemView mx_RoomListItemView_empty" class="mx_RoomListItemView mx_RoomListItemView_empty"
role="gridcell" role="gridcell"
style="height: 48px; left: 0px; position: absolute; top: 144px; width: 100%;" style="height: 48px; left: 0px; position: absolute; top: 144px; width: 100%;"
tabindex="-1"
type="button" type="button"
> >
<div <div
@@ -232,6 +236,7 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_RoomListItemView mx_RoomListItemView_empty" class="mx_RoomListItemView mx_RoomListItemView_empty"
role="gridcell" role="gridcell"
style="height: 48px; left: 0px; position: absolute; top: 192px; width: 100%;" style="height: 48px; left: 0px; position: absolute; top: 192px; width: 100%;"
tabindex="-1"
type="button" type="button"
> >
<div <div
@@ -283,6 +288,7 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_RoomListItemView mx_RoomListItemView_empty" class="mx_RoomListItemView mx_RoomListItemView_empty"
role="gridcell" role="gridcell"
style="height: 48px; left: 0px; position: absolute; top: 240px; width: 100%;" style="height: 48px; left: 0px; position: absolute; top: 240px; width: 100%;"
tabindex="-1"
type="button" type="button"
> >
<div <div
@@ -334,6 +340,7 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_RoomListItemView mx_RoomListItemView_empty" class="mx_RoomListItemView mx_RoomListItemView_empty"
role="gridcell" role="gridcell"
style="height: 48px; left: 0px; position: absolute; top: 288px; width: 100%;" style="height: 48px; left: 0px; position: absolute; top: 288px; width: 100%;"
tabindex="-1"
type="button" type="button"
> >
<div <div
@@ -385,6 +392,7 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_RoomListItemView mx_RoomListItemView_empty" class="mx_RoomListItemView mx_RoomListItemView_empty"
role="gridcell" role="gridcell"
style="height: 48px; left: 0px; position: absolute; top: 336px; width: 100%;" style="height: 48px; left: 0px; position: absolute; top: 336px; width: 100%;"
tabindex="-1"
type="button" type="button"
> >
<div <div
@@ -436,6 +444,7 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_RoomListItemView mx_RoomListItemView_empty" class="mx_RoomListItemView mx_RoomListItemView_empty"
role="gridcell" role="gridcell"
style="height: 48px; left: 0px; position: absolute; top: 384px; width: 100%;" style="height: 48px; left: 0px; position: absolute; top: 384px; width: 100%;"
tabindex="-1"
type="button" type="button"
> >
<div <div
@@ -487,6 +496,7 @@ exports[`<RoomList /> should render a room list 1`] = `
class="mx_RoomListItemView mx_RoomListItemView_empty" class="mx_RoomListItemView mx_RoomListItemView_empty"
role="gridcell" role="gridcell"
style="height: 48px; left: 0px; position: absolute; top: 432px; width: 100%;" style="height: 48px; left: 0px; position: absolute; top: 432px; width: 100%;"
tabindex="-1"
type="button" type="button"
> >
<div <div

View File

@@ -6,6 +6,7 @@ exports[`<RoomListItemView /> should be selected if isSelected=true 1`] = `
aria-label="Open room room1" aria-label="Open room room1"
aria-selected="true" aria-selected="true"
class="mx_RoomListItemView mx_RoomListItemView_empty mx_RoomListItemView_selected" class="mx_RoomListItemView mx_RoomListItemView_empty mx_RoomListItemView_selected"
tabindex="-1"
type="button" type="button"
> >
<div <div
@@ -60,6 +61,7 @@ exports[`<RoomListItemView /> should display notification decoration 1`] = `
aria-label="Open room room1" aria-label="Open room room1"
aria-selected="false" aria-selected="false"
class="mx_RoomListItemView mx_RoomListItemView_notification_decoration" class="mx_RoomListItemView mx_RoomListItemView_notification_decoration"
tabindex="-1"
type="button" type="button"
> >
<div <div
@@ -126,6 +128,7 @@ exports[`<RoomListItemView /> should render a room item 1`] = `
aria-label="Open room room1" aria-label="Open room room1"
aria-selected="false" aria-selected="false"
class="mx_RoomListItemView mx_RoomListItemView_empty" class="mx_RoomListItemView mx_RoomListItemView_empty"
tabindex="-1"
type="button" type="button"
> >
<div <div
@@ -180,6 +183,7 @@ exports[`<RoomListItemView /> should render a room item with a message preview 1
aria-label="Open room room1" aria-label="Open room room1"
aria-selected="false" aria-selected="false"
class="mx_RoomListItemView mx_RoomListItemView_empty" class="mx_RoomListItemView mx_RoomListItemView_empty"
tabindex="-1"
type="button" type="button"
> >
<div <div