Extract Extensions into their own right panel tab (#12844)
* Extract useIsVideoRoom hook Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Move useWidgets hook to WidgetUtils Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Extract Extensions into their own right panel tab Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove unused components & classes Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update screenshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
fae5bf1612
commit
b55653ddf0
25 changed files with 820 additions and 475 deletions
159
test/components/views/right_panel/ExtensionsCard-test.tsx
Normal file
159
test/components/views/right_panel/ExtensionsCard-test.tsx
Normal file
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
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 { mocked, Mocked } from "jest-mock";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixWidgetType } from "matrix-widget-api";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import ExtensionsCard from "../../../../src/components/views/right_panel/ExtensionsCard";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { IApp } from "../../../../src/stores/WidgetStore";
|
||||
import WidgetUtils, { useWidgets } from "../../../../src/utils/WidgetUtils";
|
||||
import { WidgetLayoutStore } from "../../../../src/stores/widgets/WidgetLayoutStore";
|
||||
import { IntegrationManagers } from "../../../../src/integrations/IntegrationManagers";
|
||||
|
||||
jest.mock("../../../../src/utils/WidgetUtils");
|
||||
|
||||
describe("<ExtensionsCard />", () => {
|
||||
let client: Mocked<MatrixClient>;
|
||||
let room: Room;
|
||||
|
||||
beforeEach(() => {
|
||||
client = mocked(stubClient());
|
||||
room = new Room("!room:server", client, client.getSafeUserId());
|
||||
mocked(WidgetUtils.getWidgetName).mockImplementation((app) => app?.name ?? "No Name");
|
||||
});
|
||||
|
||||
it("should render empty state", () => {
|
||||
mocked(useWidgets).mockReturnValue([]);
|
||||
const { asFragment } = render(<ExtensionsCard room={room} onClose={jest.fn()} />);
|
||||
expect(screen.getByText("Boost productivity with more tools, widgets and bots")).toBeInTheDocument();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render widgets", async () => {
|
||||
mocked(useWidgets).mockReturnValue([
|
||||
{
|
||||
id: "id",
|
||||
roomId: room.roomId,
|
||||
eventId: "$event1",
|
||||
creatorUserId: client.getSafeUserId(),
|
||||
type: MatrixWidgetType.Custom,
|
||||
name: "Custom Widget",
|
||||
url: "http://url1",
|
||||
},
|
||||
{
|
||||
id: "jitsi",
|
||||
roomId: room.roomId,
|
||||
eventId: "$event2",
|
||||
creatorUserId: client.getSafeUserId(),
|
||||
type: MatrixWidgetType.JitsiMeet,
|
||||
name: "Jitsi",
|
||||
url: "http://jitsi",
|
||||
},
|
||||
] satisfies IApp[]);
|
||||
|
||||
const { asFragment } = render(<ExtensionsCard room={room} onClose={jest.fn()} />);
|
||||
expect(screen.getByText("Custom Widget")).toBeInTheDocument();
|
||||
expect(screen.getByText("Jitsi")).toBeInTheDocument();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should show context menu on widget row", async () => {
|
||||
jest.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(true);
|
||||
mocked(useWidgets).mockReturnValue([
|
||||
{
|
||||
id: "id",
|
||||
roomId: room.roomId,
|
||||
eventId: "$event1",
|
||||
creatorUserId: client.getSafeUserId(),
|
||||
type: MatrixWidgetType.Custom,
|
||||
name: "Custom Widget",
|
||||
url: "http://url1",
|
||||
},
|
||||
] satisfies IApp[]);
|
||||
|
||||
const { container } = render(<ExtensionsCard room={room} onClose={jest.fn()} />);
|
||||
await userEvent.click(container.querySelector(".mx_ExtensionsCard_app_options")!);
|
||||
expect(document.querySelector(".mx_IconizedContextMenu")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should show set room layout button", async () => {
|
||||
jest.spyOn(WidgetLayoutStore.instance, "canCopyLayoutToRoom").mockReturnValue(true);
|
||||
mocked(useWidgets).mockReturnValue([
|
||||
{
|
||||
id: "id",
|
||||
roomId: room.roomId,
|
||||
eventId: "$event1",
|
||||
creatorUserId: client.getSafeUserId(),
|
||||
type: MatrixWidgetType.Custom,
|
||||
name: "Custom Widget",
|
||||
url: "http://url1",
|
||||
},
|
||||
] satisfies IApp[]);
|
||||
|
||||
render(<ExtensionsCard room={room} onClose={jest.fn()} />);
|
||||
expect(screen.getByText("Set layout for everyone")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show widget as pinned", async () => {
|
||||
jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(true);
|
||||
mocked(useWidgets).mockReturnValue([
|
||||
{
|
||||
id: "id",
|
||||
roomId: room.roomId,
|
||||
eventId: "$event1",
|
||||
creatorUserId: client.getSafeUserId(),
|
||||
type: MatrixWidgetType.Custom,
|
||||
name: "Custom Widget",
|
||||
url: "http://url1",
|
||||
},
|
||||
] satisfies IApp[]);
|
||||
|
||||
render(<ExtensionsCard room={room} onClose={jest.fn()} />);
|
||||
expect(screen.getByText("Custom Widget").closest(".mx_ExtensionsCard_Button_pinned")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show cannot pin warning", async () => {
|
||||
jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockReturnValue(false);
|
||||
jest.spyOn(WidgetLayoutStore.instance, "canAddToContainer").mockReturnValue(false);
|
||||
mocked(useWidgets).mockReturnValue([
|
||||
{
|
||||
id: "id",
|
||||
roomId: room.roomId,
|
||||
eventId: "$event1",
|
||||
creatorUserId: client.getSafeUserId(),
|
||||
type: MatrixWidgetType.Custom,
|
||||
name: "Custom Widget",
|
||||
url: "http://url1",
|
||||
},
|
||||
] satisfies IApp[]);
|
||||
|
||||
render(<ExtensionsCard room={room} onClose={jest.fn()} />);
|
||||
expect(screen.getByLabelText("You can only pin up to 3 widgets")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should should open integration manager on click", async () => {
|
||||
jest.spyOn(IntegrationManagers.sharedInstance(), "hasManager").mockReturnValue(false);
|
||||
const spy = jest.spyOn(IntegrationManagers.sharedInstance(), "openNoManagerDialog");
|
||||
render(<ExtensionsCard room={room} onClose={jest.fn()} />);
|
||||
await userEvent.click(screen.getByText("Add extensions"));
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -38,8 +38,8 @@ describe("<RightPanelTabs />", () => {
|
|||
const { container } = render(<RightPanelTabs phase={RightPanelPhases.RoomMemberList} />);
|
||||
expect(container).toMatchSnapshot();
|
||||
// Assert that the active tab is Info
|
||||
expect(container.querySelectorAll("[aria-selected='true'").length).toEqual(1);
|
||||
expect(container.querySelector("[aria-selected='true'")).toHaveAccessibleName("People");
|
||||
expect(container.querySelectorAll("[aria-selected='true']").length).toEqual(1);
|
||||
expect(container.querySelector("[aria-selected='true']")).toHaveAccessibleName("People");
|
||||
});
|
||||
|
||||
it("Renders nothing for some phases, eg: FilePanel", () => {
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ExtensionsCard /> should render empty state 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_BaseCard mx_ExtensionsCard"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header"
|
||||
>
|
||||
<button
|
||||
class="_button_zt6rp_17 _has-icon_zt6rp_61"
|
||||
data-kind="secondary"
|
||||
data-size="sm"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
height="20"
|
||||
width="20"
|
||||
/>
|
||||
Add extensions
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar"
|
||||
tabindex="-1"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
Boost productivity with more tools, widgets and bots
|
||||
</p>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
|
||||
>
|
||||
Select “Add extensions” to browse and add extensions to this room
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<ExtensionsCard /> should render widgets 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_BaseCard mx_ExtensionsCard"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header"
|
||||
>
|
||||
<button
|
||||
class="_button_zt6rp_17 _has-icon_zt6rp_61"
|
||||
data-kind="secondary"
|
||||
data-size="sm"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
height="20"
|
||||
width="20"
|
||||
/>
|
||||
Add extensions
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="_separator_144s5_17"
|
||||
data-kind="primary"
|
||||
data-orientation="horizontal"
|
||||
role="separator"
|
||||
/>
|
||||
<div
|
||||
class="mx_BaseCard_Button mx_ExtensionsCard_Button"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_ExtensionsCard_icon_app"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-label="Avatar"
|
||||
class="_avatar_mcap2_17 mx_BaseAvatar mx_WidgetAvatar"
|
||||
data-color="1"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
style="--cpd-avatar-size: 24px;"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="_image_mcap2_50"
|
||||
data-type="round"
|
||||
height="24px"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
src="image-file-stub"
|
||||
width="24px"
|
||||
/>
|
||||
</span>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_lineClamp"
|
||||
>
|
||||
Custom Widget
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Pin"
|
||||
class="mx_AccessibleButton mx_ExtensionsCard_app_pinToggle"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_BaseCard_Button mx_ExtensionsCard_Button"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_ExtensionsCard_icon_app"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-label="Avatar"
|
||||
class="_avatar_mcap2_17 mx_BaseAvatar mx_WidgetAvatar"
|
||||
data-color="1"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
style="--cpd-avatar-size: 24px;"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="_image_mcap2_50"
|
||||
data-type="round"
|
||||
height="24px"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
src="image-file-stub"
|
||||
width="24px"
|
||||
/>
|
||||
</span>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-md-medium_yh5dq_69 mx_lineClamp"
|
||||
>
|
||||
Jitsi
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Pin"
|
||||
class="mx_AccessibleButton mx_ExtensionsCard_app_pinToggle"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<ExtensionsCard /> should show context menu on widget row 1`] = `
|
||||
<ul
|
||||
class="mx_IconizedContextMenu"
|
||||
role="none"
|
||||
>
|
||||
<div
|
||||
class="mx_IconizedContextMenu_optionList mx_IconizedContextMenu_optionList_notFirst"
|
||||
>
|
||||
<li
|
||||
aria-label="Remove for everyone"
|
||||
class="mx_AccessibleButton mx_IconizedContextMenu_item"
|
||||
role="menuitem"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_IconizedContextMenu_label"
|
||||
>
|
||||
Remove for everyone
|
||||
</span>
|
||||
</li>
|
||||
</div>
|
||||
</ul>
|
||||
`;
|
|
@ -54,6 +54,20 @@ exports[`<RightPanelTabs /> Component renders the correct tabs 1`] = `
|
|||
Threads
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class="_nav-tab_135dy_33"
|
||||
role="presentation"
|
||||
>
|
||||
<button
|
||||
aria-controls="thread-panel"
|
||||
aria-selected="false"
|
||||
class="_nav-item_135dy_55"
|
||||
id="extensions-panel-tab"
|
||||
role="tab"
|
||||
>
|
||||
Extensions
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
@ -113,6 +127,20 @@ exports[`<RightPanelTabs /> Correct tab is active 1`] = `
|
|||
Threads
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class="_nav-tab_135dy_33"
|
||||
role="presentation"
|
||||
>
|
||||
<button
|
||||
aria-controls="thread-panel"
|
||||
aria-selected="false"
|
||||
class="_nav-item_135dy_55"
|
||||
id="extensions-panel-tab"
|
||||
role="tab"
|
||||
>
|
||||
Extensions
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
|
|
@ -414,20 +414,6 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_BaseCard_Group mx_RoomSummaryCard_appsGroup"
|
||||
>
|
||||
<h2>
|
||||
Widgets
|
||||
</h2>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Add widgets, bridges & bots
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -820,20 +806,6 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_BaseCard_Group mx_RoomSummaryCard_appsGroup"
|
||||
>
|
||||
<h2>
|
||||
Widgets
|
||||
</h2>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Add widgets, bridges & bots
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1253,20 +1225,6 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_BaseCard_Group mx_RoomSummaryCard_appsGroup"
|
||||
>
|
||||
<h2>
|
||||
Widgets
|
||||
</h2>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Add widgets, bridges & bots
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue