Iterate design of right panel empty state (#12796)
* Add reusable empty state for the right panel Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve coverage Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
d202295015
commit
0fc1c53a8e
21 changed files with 266 additions and 280 deletions
|
@ -50,7 +50,7 @@ test.describe("FilePanel", () => {
|
|||
test.describe("render", () => {
|
||||
test("should render empty state", async ({ page }) => {
|
||||
// Wait until the information about the empty state is rendered
|
||||
await expect(page.locator(".mx_FilePanel_empty")).toBeVisible();
|
||||
await expect(page.locator(".mx_EmptyState")).toBeVisible();
|
||||
|
||||
// Take a snapshot of RightPanel - fix https://github.com/vector-im/element-web/issues/25332
|
||||
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("empty.png");
|
||||
|
|
|
@ -35,7 +35,7 @@ test.describe("NotificationPanel", () => {
|
|||
await page.getByRole("button", { name: "Notifications" }).click();
|
||||
|
||||
// Wait until the information about the empty state is rendered
|
||||
await expect(page.locator(".mx_NotificationPanel_empty")).toBeVisible();
|
||||
await expect(page.locator(".mx_EmptyState")).toBeVisible();
|
||||
|
||||
// Take a snapshot of RightPanel
|
||||
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("empty.png");
|
||||
|
|
|
@ -104,7 +104,7 @@ test.describe("RightPanel", () => {
|
|||
|
||||
await page.getByRole("menuitem", { name: "Files" }).click();
|
||||
await expect(page.locator(".mx_FilePanel")).toBeVisible();
|
||||
await expect(page.locator(".mx_FilePanel_empty")).toBeVisible();
|
||||
await expect(page.locator(".mx_EmptyState")).toBeVisible();
|
||||
|
||||
await page.getByTestId("base-card-back-button").click();
|
||||
await checkRoomSummaryCard(page, ROOM_NAME);
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 71 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 70 KiB |
|
@ -73,7 +73,6 @@
|
|||
@import "./structures/_MatrixChat.pcss";
|
||||
@import "./structures/_MessagePanel.pcss";
|
||||
@import "./structures/_NonUrgentToastContainer.pcss";
|
||||
@import "./structures/_NotificationPanel.pcss";
|
||||
@import "./structures/_QuickSettingsButton.pcss";
|
||||
@import "./structures/_RightPanel.pcss";
|
||||
@import "./structures/_RoomSearch.pcss";
|
||||
|
@ -259,6 +258,7 @@
|
|||
@import "./views/polls/pollHistory/_PollHistory.pcss";
|
||||
@import "./views/polls/pollHistory/_PollHistoryList.pcss";
|
||||
@import "./views/right_panel/_BaseCard.pcss";
|
||||
@import "./views/right_panel/_EmptyState.pcss";
|
||||
@import "./views/right_panel/_EncryptionInfo.pcss";
|
||||
@import "./views/right_panel/_PinnedMessagesCard.pcss";
|
||||
@import "./views/right_panel/_RightPanelTabs.pcss";
|
||||
|
|
|
@ -102,7 +102,3 @@ limitations under the License.
|
|||
padding-inline-start: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_FilePanel_empty::before {
|
||||
--maskImage: url("$(res)/img/element-icons/room/files.svg"); /* See: _RightPanel.pcss */
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_NotificationPanel_empty::before {
|
||||
--maskImage: url("$(res)/img/element-icons/notifications.svg"); /* See: _RightPanel.pcss */
|
||||
}
|
|
@ -72,30 +72,3 @@ limitations under the License.
|
|||
order: 2;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.mx_RightPanel_empty {
|
||||
margin-right: -28px;
|
||||
|
||||
h2 {
|
||||
font-weight: 700;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
h2,
|
||||
p {
|
||||
font: var(--cpd-font-body-md-regular);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: block;
|
||||
margin: 11px auto 29px auto;
|
||||
height: 42px;
|
||||
width: 42px;
|
||||
background-color: $header-panel-text-primary-color;
|
||||
mask-image: var(--maskImage);
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
mask-position: center;
|
||||
}
|
||||
}
|
||||
|
|
45
res/css/views/right_panel/_EmptyState.pcss
Normal file
45
res/css/views/right_panel/_EmptyState.pcss
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_EmptyState {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: var(--cpd-space-4x);
|
||||
text-align: center;
|
||||
|
||||
svg {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
padding: var(--cpd-space-3x);
|
||||
background-color: $panel-actions;
|
||||
}
|
||||
|
||||
&::before {
|
||||
/* Bloom using magic numbers directly out of Figma */
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
width: 642px;
|
||||
height: 775px;
|
||||
right: -253.77px;
|
||||
top: 0;
|
||||
background: radial-gradient(49.95% 49.95% at 50% 50%, rgba(13, 189, 139, 0.12) 0%, rgba(18, 115, 235, 0) 100%);
|
||||
transform: rotate(-89.69deg);
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
|
@ -106,10 +106,17 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_RoomView_messagePanel {
|
||||
/* To avoid the rule from being applied to .mx_ThreadPanel_empty */
|
||||
&.mx_RoomView_messageListWrapper {
|
||||
position: initial;
|
||||
}
|
||||
|
||||
.mx_RoomView_messageListWrapper {
|
||||
width: calc(100% + 6px); /* 8px - 2px */
|
||||
}
|
||||
|
||||
.mx_RoomView_empty {
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomView_MessageList {
|
||||
|
@ -168,72 +175,6 @@ limitations under the License.
|
|||
mask-image: url("$(res)/img/element-icons/link.svg");
|
||||
}
|
||||
|
||||
.mx_ThreadPanel_empty {
|
||||
border-radius: 8px;
|
||||
background: $background;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 20px;
|
||||
box-sizing: border-box; /* Include padding and border */
|
||||
width: 100%;
|
||||
|
||||
h2 {
|
||||
color: $primary-content;
|
||||
font-weight: var(--cpd-font-weight-semibold);
|
||||
font-size: $font-18px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: $font-15px;
|
||||
color: $secondary-content;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background: none;
|
||||
color: $accent;
|
||||
font-size: $font-15px;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_ThreadPanel_empty_tip {
|
||||
font-size: $font-12px;
|
||||
line-height: $font-15px;
|
||||
|
||||
> b {
|
||||
font-weight: var(--cpd-font-weight-semibold);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_ThreadPanel_largeIcon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 18px;
|
||||
background: $system;
|
||||
border-radius: 50%;
|
||||
|
||||
&::after {
|
||||
@mixin ThreadSummaryIcon;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_ContextualMenu_wrapper {
|
||||
.mx_ThreadPanel_Header_FilterOptionItem {
|
||||
display: flex;
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
TimelineWindow,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Icon as FilesIcon } from "@vector-im/compound-design-tokens/icons/files.svg";
|
||||
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
||||
|
@ -40,6 +41,7 @@ import Spinner from "../views/elements/Spinner";
|
|||
import { Layout } from "../../settings/enums/Layout";
|
||||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import Measured from "../views/elements/Measured";
|
||||
import EmptyState from "../views/right_panel/EmptyState";
|
||||
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
|
@ -255,10 +257,11 @@ class FilePanel extends React.Component<IProps, IState> {
|
|||
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
||||
|
||||
const emptyState = (
|
||||
<div className="mx_RightPanel_empty mx_FilePanel_empty">
|
||||
<h2>{_t("file_panel|empty_heading")}</h2>
|
||||
<p>{_t("file_panel|empty_description")}</p>
|
||||
</div>
|
||||
<EmptyState
|
||||
Icon={FilesIcon}
|
||||
title={_t("file_panel|empty_heading")}
|
||||
description={_t("file_panel|empty_description")}
|
||||
/>
|
||||
);
|
||||
|
||||
const isRoomEncrypted = this.noRoom ? false : MatrixClientPeg.safeGet().isRoomEncrypted(this.props.roomId);
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Icon as NotificationsIcon } from "@vector-im/compound-design-tokens/icons/notifications.svg";
|
||||
|
||||
import { _t } from "../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
|
@ -26,6 +27,7 @@ import { Layout } from "../../settings/enums/Layout";
|
|||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import Measured from "../views/elements/Measured";
|
||||
import Heading from "../views/typography/Heading";
|
||||
import EmptyState from "../views/right_panel/EmptyState";
|
||||
|
||||
interface IProps {
|
||||
onClose(): void;
|
||||
|
@ -57,10 +59,11 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
|
|||
|
||||
public render(): React.ReactNode {
|
||||
const emptyState = (
|
||||
<div className="mx_RightPanel_empty mx_NotificationPanel_empty">
|
||||
<h2>{_t("notif_panel|empty_heading")}</h2>
|
||||
<p>{_t("notif_panel|empty_description")}</p>
|
||||
</div>
|
||||
<EmptyState
|
||||
Icon={NotificationsIcon}
|
||||
title={_t("notif_panel|empty_heading")}
|
||||
description={_t("notif_panel|empty_description")}
|
||||
/>
|
||||
);
|
||||
|
||||
let content: JSX.Element;
|
||||
|
|
|
@ -19,6 +19,7 @@ import React, { useContext, useEffect, useRef, useState } from "react";
|
|||
import { EventTimelineSet, Room, Thread } from "matrix-js-sdk/src/matrix";
|
||||
import { IconButton, Tooltip } from "@vector-im/compound-web";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Icon as ThreadsIcon } from "@vector-im/compound-design-tokens/icons/threads.svg";
|
||||
|
||||
import { Icon as MarkAllThreadsReadIcon } from "../../../res/img/element-icons/check-all.svg";
|
||||
import BaseCard from "../views/right_panel/BaseCard";
|
||||
|
@ -37,6 +38,7 @@ import { ButtonEvent } from "../views/elements/AccessibleButton";
|
|||
import Spinner from "../views/elements/Spinner";
|
||||
import Heading from "../views/typography/Heading";
|
||||
import { clearRoomNotification } from "../../utils/notifications";
|
||||
import EmptyState from "../views/right_panel/EmptyState";
|
||||
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
|
@ -73,8 +75,7 @@ export const ThreadPanelHeaderFilterOptionItem: React.FC<
|
|||
export const ThreadPanelHeader: React.FC<{
|
||||
filterOption: ThreadFilterType;
|
||||
setFilterOption: (filterOption: ThreadFilterType) => void;
|
||||
empty: boolean;
|
||||
}> = ({ filterOption, setFilterOption, empty }) => {
|
||||
}> = ({ filterOption, setFilterOption }) => {
|
||||
const mxClient = useMatrixClientContext();
|
||||
const roomContext = useRoomContext();
|
||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu<HTMLElement>();
|
||||
|
@ -140,86 +141,24 @@ export const ThreadPanelHeader: React.FC<{
|
|||
<Heading size="4" className="mx_BaseCard_header_title_heading">
|
||||
{_t("common|threads")}
|
||||
</Heading>
|
||||
{!empty && (
|
||||
<>
|
||||
<Tooltip label={_t("threads|mark_all_read")}>
|
||||
<IconButton
|
||||
onClick={onMarkAllThreadsReadClick}
|
||||
aria-label={_t("threads|mark_all_read")}
|
||||
size="24px"
|
||||
>
|
||||
<MarkAllThreadsReadIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<div className="mx_ThreadPanel_vertical_separator" />
|
||||
<ContextMenuButton
|
||||
className="mx_ThreadPanel_dropdown"
|
||||
ref={button}
|
||||
isExpanded={menuDisplayed}
|
||||
onClick={(ev: ButtonEvent) => {
|
||||
openMenu();
|
||||
PosthogTrackers.trackInteraction("WebRightPanelThreadPanelFilterDropdown", ev);
|
||||
}}
|
||||
>
|
||||
{`${_t("threads|show_thread_filter")} ${value?.label}`}
|
||||
</ContextMenuButton>
|
||||
{contextMenu}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface EmptyThreadIProps {
|
||||
hasThreads: boolean;
|
||||
filterOption: ThreadFilterType;
|
||||
showAllThreadsCallback: () => void;
|
||||
}
|
||||
|
||||
const EmptyThread: React.FC<EmptyThreadIProps> = ({ hasThreads, filterOption, showAllThreadsCallback }) => {
|
||||
let body: JSX.Element;
|
||||
if (hasThreads) {
|
||||
body = (
|
||||
<>
|
||||
<p>
|
||||
{_t("threads|empty_has_threads_tip", {
|
||||
replyInThread: _t("action|reply_in_thread"),
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{/* Always display that paragraph to prevent layout shift when hiding the button */}
|
||||
{filterOption === ThreadFilterType.My ? (
|
||||
<button onClick={showAllThreadsCallback}>{_t("threads|show_all_threads")}</button>
|
||||
) : (
|
||||
<> </>
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
body = (
|
||||
<>
|
||||
<p>{_t("threads|empty_explainer")}</p>
|
||||
<p className="mx_ThreadPanel_empty_tip">
|
||||
{_t(
|
||||
"threads|empty_tip",
|
||||
{
|
||||
replyInThread: _t("action|reply_in_thread"),
|
||||
},
|
||||
{
|
||||
b: (sub) => <b>{sub}</b>,
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_ThreadPanel_empty">
|
||||
<div className="mx_ThreadPanel_largeIcon" />
|
||||
<h2>{_t("threads|empty_heading")}</h2>
|
||||
{body}
|
||||
<Tooltip label={_t("threads|mark_all_read")}>
|
||||
<IconButton onClick={onMarkAllThreadsReadClick} aria-label={_t("threads|mark_all_read")} size="24px">
|
||||
<MarkAllThreadsReadIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<div className="mx_ThreadPanel_vertical_separator" />
|
||||
<ContextMenuButton
|
||||
className="mx_ThreadPanel_dropdown"
|
||||
ref={button}
|
||||
isExpanded={menuDisplayed}
|
||||
onClick={(ev: ButtonEvent) => {
|
||||
openMenu();
|
||||
PosthogTrackers.trackInteraction("WebRightPanelThreadPanelFilterDropdown", ev);
|
||||
}}
|
||||
>
|
||||
{`${_t("threads|show_thread_filter")} ${value?.label}`}
|
||||
</ContextMenuButton>
|
||||
{contextMenu}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -268,11 +207,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
|
|||
<BaseCard
|
||||
hideHeaderButtons
|
||||
header={
|
||||
<ThreadPanelHeader
|
||||
filterOption={filterOption}
|
||||
setFilterOption={setFilterOption}
|
||||
empty={!hasThreads}
|
||||
/>
|
||||
hasThreads && <ThreadPanelHeader filterOption={filterOption} setFilterOption={setFilterOption} />
|
||||
}
|
||||
id="thread-panel"
|
||||
className="mx_ThreadPanel"
|
||||
|
@ -295,10 +230,12 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
|
|||
timelineSet={timelineSet}
|
||||
showUrlPreview={false} // No URL previews at the threads list level
|
||||
empty={
|
||||
<EmptyThread
|
||||
hasThreads={hasThreads}
|
||||
filterOption={filterOption}
|
||||
showAllThreadsCallback={() => setFilterOption(ThreadFilterType.All)}
|
||||
<EmptyState
|
||||
Icon={ThreadsIcon}
|
||||
title={_t("threads|empty_title")}
|
||||
description={_t("threads|empty_description", {
|
||||
replyInThread: _t("action|reply_in_thread"),
|
||||
})}
|
||||
/>
|
||||
}
|
||||
alwaysShowTimestamps={true}
|
||||
|
|
42
src/components/views/right_panel/EmptyState.tsx
Normal file
42
src/components/views/right_panel/EmptyState.tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentType } from "react";
|
||||
import { Text } from "@vector-im/compound-web";
|
||||
|
||||
import { Flex } from "../../utils/Flex";
|
||||
|
||||
interface Props {
|
||||
Icon: ComponentType<React.SVGAttributes<SVGElement>>;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const EmptyState: React.FC<Props> = ({ Icon, title, description }) => {
|
||||
return (
|
||||
<Flex className="mx_EmptyState" direction="column" gap="var(--cpd-space-4x)" align="center" justify="center">
|
||||
<Icon width="32px" height="32px" />
|
||||
<Text size="lg" weight="semibold">
|
||||
{title}
|
||||
</Text>
|
||||
<Text size="md" weight="regular">
|
||||
{description}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptyState;
|
|
@ -3193,16 +3193,13 @@
|
|||
"one": "%(count)s reply",
|
||||
"other": "%(count)s replies"
|
||||
},
|
||||
"empty_explainer": "Threads help keep your conversations on-topic and easy to track.",
|
||||
"empty_has_threads_tip": "Reply to an ongoing thread or use “%(replyInThread)s” when hovering over a message to start a new one.",
|
||||
"empty_heading": "Keep discussions organised with threads",
|
||||
"empty_tip": "<b>Tip:</b> Use “%(replyInThread)s” when hovering over a message.",
|
||||
"empty_description": "Use “%(replyInThread)s” when hovering over a message.",
|
||||
"empty_title": "Threads help keep your conversations on-topic and easy to track.",
|
||||
"error_start_thread_existing_relation": "Can't create a thread from an event with an existing relation",
|
||||
"mark_all_read": "Mark all as read",
|
||||
"my_threads": "My threads",
|
||||
"my_threads_description": "Shows all threads you've participated in",
|
||||
"open_thread": "Open thread",
|
||||
"show_all_threads": "Show all threads",
|
||||
"show_thread_filter": "Show:"
|
||||
},
|
||||
"threads_activity_centre": {
|
||||
|
|
58
test/components/structures/FilePanel-test.tsx
Normal file
58
test/components/structures/FilePanel-test.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { EventTimelineSet, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { screen, render, waitFor } from "@testing-library/react";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import FilePanel from "../../../src/components/structures/FilePanel";
|
||||
import ResizeNotifier from "../../../src/utils/ResizeNotifier";
|
||||
import { stubClient } from "../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
|
||||
jest.mock("matrix-js-sdk/src/matrix", () => ({
|
||||
...jest.requireActual("matrix-js-sdk/src/matrix"),
|
||||
TimelineWindow: jest.fn().mockReturnValue({
|
||||
load: jest.fn().mockResolvedValue(null),
|
||||
getEvents: jest.fn().mockReturnValue([]),
|
||||
canPaginate: jest.fn().mockReturnValue(false),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("FilePanel", () => {
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
});
|
||||
|
||||
it("renders empty state", async () => {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const room = new Room("!room:server", cli, cli.getSafeUserId(), {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
const timelineSet = new EventTimelineSet(room);
|
||||
room.getOrCreateFilteredTimelineSet = jest.fn().mockReturnValue(timelineSet);
|
||||
mocked(cli.getRoom).mockReturnValue(room);
|
||||
|
||||
const { asFragment } = render(
|
||||
<FilePanel roomId={room.roomId} onClose={jest.fn()} resizeNotifier={new ResizeNotifier()} />,
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("No files visible in this room")).toBeInTheDocument();
|
||||
});
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -43,44 +43,21 @@ describe("ThreadPanel", () => {
|
|||
describe("Header", () => {
|
||||
it("expect that All filter for ThreadPanelHeader properly renders Show: All threads", () => {
|
||||
const { asFragment } = render(
|
||||
<ThreadPanelHeader
|
||||
empty={false}
|
||||
filterOption={ThreadFilterType.All}
|
||||
setFilterOption={() => undefined}
|
||||
/>,
|
||||
<ThreadPanelHeader filterOption={ThreadFilterType.All} setFilterOption={() => undefined} />,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("expect that My filter for ThreadPanelHeader properly renders Show: My threads", () => {
|
||||
const { asFragment } = render(
|
||||
<ThreadPanelHeader
|
||||
empty={false}
|
||||
filterOption={ThreadFilterType.My}
|
||||
setFilterOption={() => undefined}
|
||||
/>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot when no threads", () => {
|
||||
const { asFragment } = render(
|
||||
<ThreadPanelHeader
|
||||
empty={true}
|
||||
filterOption={ThreadFilterType.All}
|
||||
setFilterOption={() => undefined}
|
||||
/>,
|
||||
<ThreadPanelHeader filterOption={ThreadFilterType.My} setFilterOption={() => undefined} />,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("expect that ThreadPanelHeader properly opens a context menu when clicked on the button", () => {
|
||||
const { container } = render(
|
||||
<ThreadPanelHeader
|
||||
empty={false}
|
||||
filterOption={ThreadFilterType.All}
|
||||
setFilterOption={() => undefined}
|
||||
/>,
|
||||
<ThreadPanelHeader filterOption={ThreadFilterType.All} setFilterOption={() => undefined} />,
|
||||
);
|
||||
const found = container.querySelector(".mx_ThreadPanel_dropdown");
|
||||
expect(found).toBeTruthy();
|
||||
|
@ -91,11 +68,7 @@ describe("ThreadPanel", () => {
|
|||
|
||||
it("expect that ThreadPanelHeader has the correct option selected in the context menu", () => {
|
||||
const { container } = render(
|
||||
<ThreadPanelHeader
|
||||
empty={false}
|
||||
filterOption={ThreadFilterType.All}
|
||||
setFilterOption={() => undefined}
|
||||
/>,
|
||||
<ThreadPanelHeader filterOption={ThreadFilterType.All} setFilterOption={() => undefined} />,
|
||||
);
|
||||
fireEvent.click(container.querySelector(".mx_ThreadPanel_dropdown")!);
|
||||
const found = screen.queryAllByRole("menuitemradio");
|
||||
|
@ -118,11 +91,7 @@ describe("ThreadPanel", () => {
|
|||
const { container } = render(
|
||||
<RoomContext.Provider value={roomContextObject}>
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<ThreadPanelHeader
|
||||
empty={false}
|
||||
filterOption={ThreadFilterType.All}
|
||||
setFilterOption={() => undefined}
|
||||
/>
|
||||
<ThreadPanelHeader filterOption={ThreadFilterType.All} setFilterOption={() => undefined} />
|
||||
</MatrixClientContext.Provider>
|
||||
</RoomContext.Provider>,
|
||||
);
|
||||
|
@ -136,11 +105,7 @@ describe("ThreadPanel", () => {
|
|||
const mockClient = createTestClient();
|
||||
const { container } = render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<ThreadPanelHeader
|
||||
empty={false}
|
||||
filterOption={ThreadFilterType.All}
|
||||
setFilterOption={() => undefined}
|
||||
/>
|
||||
<ThreadPanelHeader filterOption={ThreadFilterType.All} setFilterOption={() => undefined} />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
fireEvent.click(getByRole(container, "button", { name: "Mark all as read" }));
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FilePanel renders empty state 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_BaseCard mx_FilePanel"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header_spacer"
|
||||
/>
|
||||
<button
|
||||
class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 28px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_133tf_26"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_RoomView_messagePanel mx_RoomView_messageListWrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomView_empty"
|
||||
>
|
||||
<div
|
||||
class="mx_Flex mx_EmptyState"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-4x);"
|
||||
>
|
||||
<div
|
||||
height="32px"
|
||||
width="32px"
|
||||
/>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83"
|
||||
>
|
||||
No files visible in this room
|
||||
</p>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
|
||||
>
|
||||
Attach files from chat or just drag and drop them anywhere in a room.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
|
@ -95,17 +95,3 @@ exports[`ThreadPanel Header expect that ThreadPanelHeader has the correct option
|
|||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ThreadPanel Header matches snapshot when no threads 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_BaseCard_header_title"
|
||||
>
|
||||
<h4
|
||||
class="mx_Heading_h4 mx_BaseCard_header_title_heading"
|
||||
>
|
||||
Threads
|
||||
</h4>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
|
|
@ -274,6 +274,7 @@ export function createTestClient(): MatrixClient {
|
|||
matrixRTC: createStubMatrixRTC(),
|
||||
isFallbackICEServerAllowed: jest.fn().mockReturnValue(false),
|
||||
getAuthIssuer: jest.fn(),
|
||||
getOrCreateFilter: jest.fn(),
|
||||
} as unknown as MatrixClient;
|
||||
|
||||
client.reEmitter = new ReEmitter(client);
|
||||
|
|
Loading…
Reference in a new issue