New room list: add notification decoration (#29552)
* chore: update @compound-web * feat(notification decoration): add NotificationDecoration component * feat(room list item): get notification state in view model * feat(room list item): use notification decoration in RoomListItemView * test(notification decoration): add tests * test(room list item view model): add a11yLabel tests * test(room list item): update tests * test(e2e): add decoration tests
This commit is contained in:
@@ -92,7 +92,7 @@
|
||||
"@types/png-chunks-extract": "^1.0.2",
|
||||
"@types/react-virtualized": "^9.21.30",
|
||||
"@vector-im/compound-design-tokens": "^4.0.0",
|
||||
"@vector-im/compound-web": "^7.7.2",
|
||||
"@vector-im/compound-web": "^7.9.0",
|
||||
"@vector-im/matrix-wysiwyg": "2.38.2",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"@zxcvbn-ts/language-common": "^3.0.4",
|
||||
|
||||
@@ -13,6 +13,9 @@ test.describe("Room list", () => {
|
||||
test.use({
|
||||
displayName: "Alice",
|
||||
labsFlags: ["feature_new_room_list"],
|
||||
botCreateOpts: {
|
||||
displayName: "BotBob",
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -26,6 +29,10 @@ test.describe("Room list", () => {
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
// The notification toast is displayed above the search section
|
||||
await app.closeNotificationToast();
|
||||
});
|
||||
|
||||
test.describe("Room list", () => {
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
for (let i = 0; i < 30; i++) {
|
||||
await app.client.createRoom({ name: `room${i}` });
|
||||
}
|
||||
@@ -94,3 +101,123 @@ test.describe("Room list", () => {
|
||||
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Notification decoration", () => {
|
||||
test("should render the invitation decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
|
||||
const roomListView = getRoomList(page);
|
||||
|
||||
await bot.createRoom({
|
||||
name: "invited room",
|
||||
invite: [user.userId],
|
||||
is_direct: true,
|
||||
});
|
||||
const invitedRoom = roomListView.getByRole("gridcell", { name: "invited room" });
|
||||
await expect(invitedRoom).toBeVisible();
|
||||
await expect(invitedRoom).toMatchScreenshot("room-list-item-invited.png");
|
||||
});
|
||||
|
||||
test("should render the regular decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
|
||||
const roomListView = getRoomList(page);
|
||||
|
||||
const roomId = await app.client.createRoom({ name: "2 notifications" });
|
||||
await app.client.inviteUser(roomId, bot.credentials.userId);
|
||||
await bot.joinRoom(roomId);
|
||||
|
||||
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
||||
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
||||
|
||||
const room = roomListView.getByRole("gridcell", { name: "2 notifications" });
|
||||
await expect(room).toBeVisible();
|
||||
await expect(room.getByTestId("notification-decoration")).toHaveText("2");
|
||||
await expect(room).toMatchScreenshot("room-list-item-notification.png");
|
||||
});
|
||||
|
||||
test("should render the mention decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
|
||||
const roomListView = getRoomList(page);
|
||||
|
||||
const roomId = await app.client.createRoom({ name: "mention" });
|
||||
await app.client.inviteUser(roomId, bot.credentials.userId);
|
||||
await bot.joinRoom(roomId);
|
||||
|
||||
const clientBot = await bot.prepareClient();
|
||||
await clientBot.evaluate(
|
||||
async (client, { roomId, userId }) => {
|
||||
await client.sendMessage(roomId, {
|
||||
// @ts-ignore ignore usage of MsgType.text
|
||||
"msgtype": "m.text",
|
||||
"body": "User",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": `<a href="https://matrix.to/#/${userId}">User</a>`,
|
||||
"m.mentions": {
|
||||
user_ids: [userId],
|
||||
},
|
||||
});
|
||||
},
|
||||
{ roomId, userId: user.userId },
|
||||
);
|
||||
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
||||
|
||||
const room = roomListView.getByRole("gridcell", { name: "mention" });
|
||||
await expect(room).toBeVisible();
|
||||
await expect(room).toMatchScreenshot("room-list-item-mention.png");
|
||||
});
|
||||
|
||||
test("should render an activity decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
|
||||
const roomListView = getRoomList(page);
|
||||
|
||||
const roomId = await app.client.createRoom({ name: "activity" });
|
||||
await app.client.inviteUser(roomId, bot.credentials.userId);
|
||||
await bot.joinRoom(roomId);
|
||||
|
||||
await app.viewRoomById(roomId);
|
||||
await app.settings.openRoomSettings("Notifications");
|
||||
await page.getByText("@mentions & keywords").click();
|
||||
await app.settings.closeDialog();
|
||||
|
||||
await app.settings.openUserSettings("Notifications");
|
||||
await page.getByText("Show all activity in the room list (dots or number of unread messages)").click();
|
||||
await app.settings.closeDialog();
|
||||
|
||||
await bot.sendMessage(roomId, "I am a robot. Beep.");
|
||||
|
||||
const room = roomListView.getByRole("gridcell", { name: "activity" });
|
||||
await expect(room.getByTestId("notification-decoration")).toBeVisible();
|
||||
await expect(room).toMatchScreenshot("room-list-item-activity.png");
|
||||
});
|
||||
|
||||
test("should render a mark as unread decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
|
||||
const roomListView = getRoomList(page);
|
||||
|
||||
const roomId = await app.client.createRoom({ name: "mark as unread" });
|
||||
await app.client.inviteUser(roomId, bot.credentials.userId);
|
||||
await bot.joinRoom(roomId);
|
||||
|
||||
const room = roomListView.getByRole("gridcell", { name: "mark as unread" });
|
||||
await room.hover();
|
||||
await room.getByRole("button", { name: "More Options" }).click();
|
||||
await page.getByRole("menuitem", { name: "mark as unread" }).click();
|
||||
|
||||
// Remove hover on the room list item
|
||||
await roomListView.hover();
|
||||
|
||||
await expect(room).toMatchScreenshot("room-list-item-mark-as-unread.png");
|
||||
});
|
||||
|
||||
test("should render silent decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
|
||||
const roomListView = getRoomList(page);
|
||||
|
||||
const roomId = await app.client.createRoom({ name: "silent" });
|
||||
await app.client.inviteUser(roomId, bot.credentials.userId);
|
||||
await bot.joinRoom(roomId);
|
||||
|
||||
await app.viewRoomById(roomId);
|
||||
await app.settings.openRoomSettings("Notifications");
|
||||
await page.getByText("Off").click();
|
||||
await app.settings.closeDialog();
|
||||
|
||||
const room = roomListView.getByRole("gridcell", { name: "silent" });
|
||||
await expect(room.getByTestId("notification-decoration")).toBeVisible();
|
||||
await expect(room).toMatchScreenshot("room-list-item-silent.png");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
@@ -5,13 +5,16 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import dispatcher from "../../../dispatcher/dispatcher";
|
||||
import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { hasAccessToOptionsMenu } from "./utils";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { type RoomNotificationState } from "../../../stores/notifications/RoomNotificationState";
|
||||
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
||||
|
||||
export interface RoomListItemViewState {
|
||||
/**
|
||||
@@ -22,6 +25,14 @@ export interface RoomListItemViewState {
|
||||
* Open the room having given roomId.
|
||||
*/
|
||||
openRoom: () => void;
|
||||
/**
|
||||
* The a11y label for the room list item.
|
||||
*/
|
||||
a11yLabel: string;
|
||||
/**
|
||||
* The notification state of the room.
|
||||
*/
|
||||
notificationState: RoomNotificationState;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,6 +42,8 @@ export interface RoomListItemViewState {
|
||||
export function useRoomListItemViewModel(room: Room): RoomListItemViewState {
|
||||
// incoming: Check notification menu rights
|
||||
const showHoverMenu = hasAccessToOptionsMenu(room);
|
||||
const notificationState = useMemo(() => RoomNotificationStateStore.instance.getRoomState(room), [room]);
|
||||
const a11yLabel = getA11yLabel(room, notificationState);
|
||||
|
||||
// Actions
|
||||
|
||||
@@ -43,7 +56,38 @@ export function useRoomListItemViewModel(room: Room): RoomListItemViewState {
|
||||
}, [room]);
|
||||
|
||||
return {
|
||||
notificationState,
|
||||
showHoverMenu,
|
||||
openRoom,
|
||||
a11yLabel,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the a11y label for the room list item
|
||||
* @param room
|
||||
* @param notificationState
|
||||
*/
|
||||
function getA11yLabel(room: Room, notificationState: RoomNotificationState): string {
|
||||
if (notificationState.isUnsetMessage) {
|
||||
return _t("a11y|room_messsage_not_sent", {
|
||||
roomName: room.name,
|
||||
});
|
||||
} else if (notificationState.invited) {
|
||||
return _t("a11y|room_n_unread_invite", {
|
||||
roomName: room.name,
|
||||
});
|
||||
} else if (notificationState.isMention) {
|
||||
return _t("a11y|room_n_unread_messages_mentions", {
|
||||
roomName: room.name,
|
||||
count: notificationState.count,
|
||||
});
|
||||
} else if (notificationState.hasUnreadCount) {
|
||||
return _t("a11y|room_n_unread_messages", {
|
||||
roomName: room.name,
|
||||
count: notificationState.count,
|
||||
});
|
||||
} else {
|
||||
return _t("room_list|room|open_room", { roomName: room.name });
|
||||
}
|
||||
}
|
||||
|
||||
59
src/components/views/rooms/NotificationDecoration.tsx
Normal file
59
src/components/views/rooms/NotificationDecoration.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 HTMLProps, type JSX } from "react";
|
||||
import MentionIcon from "@vector-im/compound-design-tokens/assets/web/icons/mention";
|
||||
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
|
||||
import NotificationOffIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-off-solid";
|
||||
import { UnreadCounter, Unread } from "@vector-im/compound-web";
|
||||
|
||||
import { Flex } from "../../utils/Flex";
|
||||
import { type RoomNotificationState } from "../../../stores/notifications/RoomNotificationState";
|
||||
|
||||
interface NotificationDecorationProps extends HTMLProps<HTMLDivElement> {
|
||||
/**
|
||||
* The notification state of the room or thread.
|
||||
*/
|
||||
notificationState: RoomNotificationState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the notification decoration for a room or a thread.
|
||||
*/
|
||||
export function NotificationDecoration({
|
||||
notificationState,
|
||||
...props
|
||||
}: NotificationDecorationProps): JSX.Element | null {
|
||||
const {
|
||||
hasAnyNotificationOrActivity,
|
||||
isUnsetMessage,
|
||||
invited,
|
||||
isMention,
|
||||
isActivityNotification,
|
||||
isNotification,
|
||||
count,
|
||||
muted,
|
||||
} = notificationState;
|
||||
if (!hasAnyNotificationOrActivity && !muted) return null;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
align="center"
|
||||
justify="center"
|
||||
gap="var(--cpd-space-1x)"
|
||||
{...props}
|
||||
data-testid="notification-decoration"
|
||||
>
|
||||
{isUnsetMessage && <ErrorIcon width="20px" height="20px" fill="var(--cpd-color-icon-critical-primary)" />}
|
||||
{invited && <UnreadCounter count={1} />}
|
||||
{isMention && <MentionIcon width="20px" height="20px" fill="var(--cpd-color-icon-accent-primary)" />}
|
||||
{(isMention || isNotification) && <UnreadCounter count={count || null} />}
|
||||
{isActivityNotification && <Unread />}
|
||||
{muted && <NotificationOffIcon width="20px" height="20px" fill="var(--cpd-color-icon-tertiary)" />}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -12,8 +12,8 @@ import classNames from "classnames";
|
||||
import { useRoomListItemViewModel } from "../../../viewmodels/roomlist/RoomListItemViewModel";
|
||||
import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar";
|
||||
import { Flex } from "../../../utils/Flex";
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import { RoomListItemMenuView } from "./RoomListItemMenuView";
|
||||
import { NotificationDecoration } from "../NotificationDecoration";
|
||||
|
||||
interface RoomListItemViewPropsProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
/**
|
||||
@@ -46,7 +46,7 @@ export function RoomListItemView({ room, isSelected, ...props }: RoomListItemVie
|
||||
})}
|
||||
type="button"
|
||||
aria-selected={isSelected}
|
||||
aria-label={_t("room_list|room|open_room", { roomName: room.name })}
|
||||
aria-label={vm.a11yLabel}
|
||||
onClick={() => vm.openRoom()}
|
||||
onMouseOver={() => setIsHover(true)}
|
||||
onMouseOut={() => setIsHover(false)}
|
||||
@@ -65,7 +65,7 @@ export function RoomListItemView({ room, isSelected, ...props }: RoomListItemVie
|
||||
>
|
||||
{/* We truncate the room name when too long. Title here is to show the full name on hover */}
|
||||
<span title={room.name}>{room.name}</span>
|
||||
{showHoverDecoration && (
|
||||
{showHoverDecoration ? (
|
||||
<RoomListItemMenuView
|
||||
room={room}
|
||||
setMenuOpen={(isOpen) => {
|
||||
@@ -74,6 +74,11 @@ export function RoomListItemView({ room, isSelected, ...props }: RoomListItemVie
|
||||
else setTimeout(() => setIsMenuOpen(isOpen), 0);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{/* aria-hidden because we summarise the unread count/notification status in a11yLabel variable */}
|
||||
<NotificationDecoration notificationState={vm.notificationState} aria-hidden={true} />
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -12,6 +12,16 @@
|
||||
"other": "%(count)s unread messages including mentions."
|
||||
},
|
||||
"recent_rooms": "Recent rooms",
|
||||
"room_messsage_not_sent": "Open room %(roomName)s with an unset message.",
|
||||
"room_n_unread_invite": "Open room %(roomName)s invitation.",
|
||||
"room_n_unread_messages": {
|
||||
"one": "Open room %(roomName)s with 1 unread message.",
|
||||
"other": "Open room %(roomName)s with %(count)s unread messages."
|
||||
},
|
||||
"room_n_unread_messages_mentions": {
|
||||
"one": "Open room %(roomName)s with 1 unread mention.",
|
||||
"other": "Open room %(roomName)s with %(count)s unread messages including mentions."
|
||||
},
|
||||
"room_name": "Room %(name)s",
|
||||
"room_status_bar": "Room status bar",
|
||||
"seek_bar_label": "Audio seek bar",
|
||||
|
||||
@@ -14,6 +14,8 @@ import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { useRoomListItemViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel";
|
||||
import { createTestClient, mkStubRoom } from "../../../../test-utils";
|
||||
import { hasAccessToOptionsMenu } from "../../../../../src/components/viewmodels/roomlist/utils";
|
||||
import { RoomNotificationState } from "../../../../../src/stores/notifications/RoomNotificationState";
|
||||
import { RoomNotificationStateStore } from "../../../../../src/stores/notifications/RoomNotificationStateStore";
|
||||
|
||||
jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({
|
||||
hasAccessToOptionsMenu: jest.fn().mockReturnValue(false),
|
||||
@@ -46,4 +48,49 @@ describe("RoomListItemViewModel", () => {
|
||||
const { result: vm } = renderHook(() => useRoomListItemViewModel(room));
|
||||
expect(vm.current.showHoverMenu).toBe(true);
|
||||
});
|
||||
|
||||
describe("a11yLabel", () => {
|
||||
let notificationState: RoomNotificationState;
|
||||
beforeEach(() => {
|
||||
notificationState = new RoomNotificationState(room, false);
|
||||
jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockReturnValue(notificationState);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
label: "unset message",
|
||||
mock: () => jest.spyOn(notificationState, "isUnsetMessage", "get").mockReturnValue(true),
|
||||
expected: "Open room roomName with an unset message.",
|
||||
},
|
||||
{
|
||||
label: "invitation",
|
||||
mock: () => jest.spyOn(notificationState, "invited", "get").mockReturnValue(true),
|
||||
expected: "Open room roomName invitation.",
|
||||
},
|
||||
{
|
||||
label: "mention",
|
||||
mock: () => {
|
||||
jest.spyOn(notificationState, "isMention", "get").mockReturnValue(true);
|
||||
jest.spyOn(notificationState, "count", "get").mockReturnValue(3);
|
||||
},
|
||||
expected: "Open room roomName with 3 unread messages including mentions.",
|
||||
},
|
||||
{
|
||||
label: "unread",
|
||||
mock: () => {
|
||||
jest.spyOn(notificationState, "hasUnreadCount", "get").mockReturnValue(true);
|
||||
jest.spyOn(notificationState, "count", "get").mockReturnValue(3);
|
||||
},
|
||||
expected: "Open room roomName with 3 unread messages.",
|
||||
},
|
||||
{
|
||||
label: "default",
|
||||
expected: "Open room roomName",
|
||||
},
|
||||
])("should return the $label label", ({ mock, expected }) => {
|
||||
mock?.();
|
||||
const { result: vm } = renderHook(() => useRoomListItemViewModel(room));
|
||||
expect(vm.current.a11yLabel).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 from "react";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
|
||||
import { type RoomNotificationState } from "../../../../../src/stores/notifications/RoomNotificationState";
|
||||
import { NotificationDecoration } from "../../../../../src/components/views/rooms/NotificationDecoration";
|
||||
|
||||
describe("<NotificationDecoration />", () => {
|
||||
it("should not render if RoomNotificationState.isSilent=true", () => {
|
||||
const state = { hasAnyNotificationOrActivity: false } as RoomNotificationState;
|
||||
render(<NotificationDecoration notificationState={state} />);
|
||||
expect(screen.queryByTestId("notification-decoration")).toBeNull();
|
||||
});
|
||||
|
||||
it("should render the unset message decoration", () => {
|
||||
const state = { hasAnyNotificationOrActivity: true, isUnsetMessage: true } as RoomNotificationState;
|
||||
const { asFragment } = render(<NotificationDecoration notificationState={state} />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render the invitation decoration", () => {
|
||||
const state = { hasAnyNotificationOrActivity: true, invited: true } as RoomNotificationState;
|
||||
const { asFragment } = render(<NotificationDecoration notificationState={state} />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render the mention decoration", () => {
|
||||
const state = { hasAnyNotificationOrActivity: true, isMention: true, count: 1 } as RoomNotificationState;
|
||||
const { asFragment } = render(<NotificationDecoration notificationState={state} />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render the notification decoration", () => {
|
||||
const state = { hasAnyNotificationOrActivity: true, isNotification: true, count: 1 } as RoomNotificationState;
|
||||
const { asFragment } = render(<NotificationDecoration notificationState={state} />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render the notification decoration without count", () => {
|
||||
const state = { hasAnyNotificationOrActivity: true, isNotification: true, count: 0 } as RoomNotificationState;
|
||||
const { asFragment } = render(<NotificationDecoration notificationState={state} />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render the activity decoration", () => {
|
||||
const state = { hasAnyNotificationOrActivity: true, isActivityNotification: true } as RoomNotificationState;
|
||||
const { asFragment } = render(<NotificationDecoration notificationState={state} />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render the muted decoration", () => {
|
||||
const state = { hasAnyNotificationOrActivity: true, muted: true } as RoomNotificationState;
|
||||
const { asFragment } = render(<NotificationDecoration notificationState={state} />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -18,26 +18,32 @@ import {
|
||||
type RoomListItemViewState,
|
||||
useRoomListItemViewModel,
|
||||
} from "../../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel";
|
||||
import { RoomNotificationState } from "../../../../../../src/stores/notifications/RoomNotificationState";
|
||||
|
||||
jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel", () => ({
|
||||
useRoomListItemViewModel: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("<RoomListItemView />", () => {
|
||||
const defaultValue: RoomListItemViewState = {
|
||||
openRoom: jest.fn(),
|
||||
showHoverMenu: false,
|
||||
};
|
||||
let defaultValue: RoomListItemViewState;
|
||||
let matrixClient: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
beforeEach(() => {
|
||||
mocked(useRoomListItemViewModel).mockReturnValue(defaultValue);
|
||||
matrixClient = stubClient();
|
||||
room = mkRoom(matrixClient, "room1");
|
||||
|
||||
DMRoomMap.makeShared(matrixClient);
|
||||
jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(null);
|
||||
|
||||
defaultValue = {
|
||||
openRoom: jest.fn(),
|
||||
showHoverMenu: false,
|
||||
notificationState: new RoomNotificationState(room, false),
|
||||
a11yLabel: "Open room room1",
|
||||
};
|
||||
|
||||
mocked(useRoomListItemViewModel).mockReturnValue(defaultValue);
|
||||
});
|
||||
|
||||
test("should render a room item", () => {
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<NotificationDecoration /> should render the activity decoration 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_Flex"
|
||||
data-testid="notification-decoration"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<div
|
||||
class="_unread_1k06b_8"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<NotificationDecoration /> should render the invitation decoration 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_Flex"
|
||||
data-testid="notification-decoration"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<span
|
||||
class="_unread-counter_1ibqq_8"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<NotificationDecoration /> should render the mention decoration 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_Flex"
|
||||
data-testid="notification-decoration"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<svg
|
||||
fill="var(--cpd-color-icon-accent-primary)"
|
||||
height="20px"
|
||||
viewBox="0 0 24 24"
|
||||
width="20px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 4a8 8 0 1 0 0 16 1 1 0 1 1 0 2C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10v1.5a3.5 3.5 0 0 1-6.396 1.966A5 5 0 1 1 17 12v1.5a1.5 1.5 0 0 0 3 0V12a8 8 0 0 0-8-8m3 8a3 3 0 1 0-6 0 3 3 0 0 0 6 0"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_unread-counter_1ibqq_8"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<NotificationDecoration /> should render the muted decoration 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_Flex"
|
||||
data-testid="notification-decoration"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<svg
|
||||
fill="var(--cpd-color-icon-tertiary)"
|
||||
height="20px"
|
||||
viewBox="0 0 24 24"
|
||||
width="20px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m4.917 2.083 17 17a1 1 0 0 1-1.414 1.414L19.006 19H4.414c-.89 0-1.337-1.077-.707-1.707L5 16v-6s0-2.034 1.096-3.91L3.504 3.498a1 1 0 0 1 1.414-1.414M19 13.35 9.136 3.484C9.93 3.181 10.874 3 12 3c7 0 7 7 7 7z"
|
||||
/>
|
||||
<path
|
||||
d="M10 20h4a2 2 0 0 1-4 0"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<NotificationDecoration /> should render the notification decoration 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_Flex"
|
||||
data-testid="notification-decoration"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<span
|
||||
class="_unread-counter_1ibqq_8"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<NotificationDecoration /> should render the notification decoration without count 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_Flex"
|
||||
data-testid="notification-decoration"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<div
|
||||
class="_unread-counter_1ibqq_8"
|
||||
/>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<NotificationDecoration /> should render the unset message decoration 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_Flex"
|
||||
data-testid="notification-decoration"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<svg
|
||||
fill="var(--cpd-color-icon-critical-primary)"
|
||||
height="20px"
|
||||
viewBox="0 0 24 24"
|
||||
width="20px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 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 20"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -3597,10 +3597,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-4.0.1.tgz#5c4ea7ad664d8e6206dc42e41649c80ef060a760"
|
||||
integrity sha512-V4AsK1FVFxZ6DmmCoeAi8FyvE7ODMlXPWjqRGotcnVaoGNrDQrVz2ZGV85DCz5ISxB3iynYASe6OXsDVXT1zFA==
|
||||
|
||||
"@vector-im/compound-web@^7.7.2":
|
||||
version "7.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.7.2.tgz#07e04a546b86e568b13263092b324efc76398487"
|
||||
integrity sha512-RhPyKzfPo1HRyFi3wy8oc25IXbLLzTmw6A5QvPJgRlMW+LidwqCCYqmFeZrvWxK3pZPqE7hTJbHgUhGe7kxznw==
|
||||
"@vector-im/compound-web@^7.9.0":
|
||||
version "7.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.9.0.tgz#72eccdd501e54f7b88317ba927bfeca61af72de0"
|
||||
integrity sha512-2rBD+1Mit+kOd7+ZPUxdH7y6V1mi7Fga85cyC2cvUeL/sXBn0Q5HuyJ8whmdgLmgZiI4LkLriCFaeogYipKE+Q==
|
||||
dependencies:
|
||||
"@floating-ui/react" "^0.27.0"
|
||||
"@radix-ui/react-context-menu" "^2.2.1"
|
||||
|
||||
Reference in New Issue
Block a user