mv element.io @types __mocks__/ debian docker module_system/ playwright res src test webapp Dockerfile .dockerignore .eslintignore .stylelintrc.cjs babel.config.cjs recorder-worklet-loader.cjs .modernizr.json components.json config.json config.sample.json package.json project.json tsconfig.json tsconfig.module_system.json jest.config.ts playwright.config.ts webpack.config.ts build_config.sample.yaml apps/web/
mkdir apps/web/scripts
mv scripts/{cleanup.sh,ci_package.sh,copy-res.ts,deploy.py,package.sh} apps/web/scripts
And a couple of gitignore tweaks
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2024 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 { fireEvent, render, screen } from "jest-matrix-react";
|
||||
|
||||
import BaseCard from "../../../../../src/components/views/right_panel/BaseCard.tsx";
|
||||
import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore.ts";
|
||||
|
||||
jest.mock("../../../../../src/stores/right-panel/RightPanelStore", () => ({
|
||||
instance: {
|
||||
popCard: jest.fn(),
|
||||
roomPhaseHistory: [],
|
||||
},
|
||||
}));
|
||||
|
||||
describe("<BaseCard />", () => {
|
||||
it("should close when clicking X button", async () => {
|
||||
const { asFragment } = render(
|
||||
<BaseCard header="Heading text">
|
||||
<div>Content</div>
|
||||
</BaseCard>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("heading")).toHaveTextContent("Heading text");
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
fireEvent.click(screen.getByTestId("base-card-close-button"));
|
||||
expect(RightPanelStore.instance.popCard).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 { mocked, type Mocked } from "jest-mock";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import { type 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 { type 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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,396 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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, act, type RenderResult, waitForElementToBeRemoved, screen, waitFor } from "jest-matrix-react";
|
||||
import { mocked, type MockedObject } from "jest-mock";
|
||||
import {
|
||||
MatrixEvent,
|
||||
RoomStateEvent,
|
||||
Room,
|
||||
type IMinimalEvent,
|
||||
EventType,
|
||||
RelationType,
|
||||
MsgType,
|
||||
M_POLL_KIND_DISCLOSED,
|
||||
EventTimeline,
|
||||
type MatrixClient,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
|
||||
import { PollResponseEvent } from "matrix-js-sdk/src/extensible_events_v1/PollResponseEvent";
|
||||
import { PollEndEvent } from "matrix-js-sdk/src/extensible_events_v1/PollEndEvent";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { stubClient, mkEvent, mkMessage, flushPromises } from "../../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { PinnedMessagesCard } from "../../../../../src/components/views/right_panel/PinnedMessagesCard";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import Modal from "../../../../../src/Modal";
|
||||
import { UnpinAllDialog } from "../../../../../src/components/views/dialogs/UnpinAllDialog";
|
||||
|
||||
describe("<PinnedMessagesCard />", () => {
|
||||
let cli: MockedObject<MatrixClient>;
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = mocked(MatrixClientPeg.safeGet());
|
||||
cli.getUserId.mockReturnValue("@alice:example.org");
|
||||
cli.setRoomAccountData.mockResolvedValue({});
|
||||
cli.relations.mockResolvedValue({ originalEvent: {} as unknown as MatrixEvent, events: [] });
|
||||
});
|
||||
|
||||
const mkRoom = (localPins: MatrixEvent[], nonLocalPins: MatrixEvent[]): Room => {
|
||||
const room = new Room("!room:example.org", cli, "@me:example.org");
|
||||
// Deferred since we may be adding or removing pins later
|
||||
const pins = () => [...localPins, ...nonLocalPins];
|
||||
|
||||
// Insert pin IDs into room state
|
||||
jest.spyOn(room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, "getStateEvents").mockImplementation(
|
||||
(): any =>
|
||||
mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomPinnedEvents,
|
||||
content: {
|
||||
pinned: pins().map((e) => e.getId()),
|
||||
},
|
||||
user: "@user:example.org",
|
||||
room: "!room:example.org",
|
||||
}),
|
||||
);
|
||||
|
||||
jest.spyOn(room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, "mayClientSendStateEvent").mockReturnValue(
|
||||
true,
|
||||
);
|
||||
// poll end event validates against this
|
||||
jest.spyOn(
|
||||
room.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
|
||||
"maySendRedactionForEvent",
|
||||
).mockReturnValue(true);
|
||||
|
||||
// Return all pins over fetchRoomEvent
|
||||
cli.fetchRoomEvent.mockImplementation((roomId, eventId) => {
|
||||
const event = pins().find((e) => e.getId() === eventId)?.event;
|
||||
return Promise.resolve(event as IMinimalEvent);
|
||||
});
|
||||
|
||||
cli.getRoom.mockReturnValue(room);
|
||||
|
||||
return room;
|
||||
};
|
||||
|
||||
async function renderMessagePinList(room: Room): Promise<RenderResult> {
|
||||
const renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<PinnedMessagesCard
|
||||
room={room}
|
||||
onClose={jest.fn()}
|
||||
permalinkCreator={new RoomPermalinkCreator(room, room.roomId)}
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
// Wait a tick for state updates
|
||||
await act(() => sleep(0));
|
||||
|
||||
return renderResult;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param room
|
||||
*/
|
||||
async function emitPinUpdate(room: Room) {
|
||||
await act(async () => {
|
||||
const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
||||
roomState.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({ type: EventType.RoomPinnedEvents }),
|
||||
roomState,
|
||||
null,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the pinned messages card with the given pinned messages.
|
||||
* Return the room, testing library helpers and functions to add and remove pinned messages.
|
||||
* @param localPins
|
||||
* @param nonLocalPins
|
||||
*/
|
||||
async function initPinnedMessagesCard(localPins: MatrixEvent[], nonLocalPins: MatrixEvent[]) {
|
||||
const room = mkRoom(localPins, nonLocalPins);
|
||||
const addLocalPinEvent = async (event: MatrixEvent) => {
|
||||
localPins.push(event);
|
||||
await emitPinUpdate(room);
|
||||
};
|
||||
const removeLastLocalPinEvent = async () => {
|
||||
localPins.pop();
|
||||
await emitPinUpdate(room);
|
||||
};
|
||||
const addNonLocalPinEvent = async (event: MatrixEvent) => {
|
||||
nonLocalPins.push(event);
|
||||
await emitPinUpdate(room);
|
||||
};
|
||||
const removeLastNonLocalPinEvent = async () => {
|
||||
nonLocalPins.pop();
|
||||
await emitPinUpdate(room);
|
||||
};
|
||||
const renderResult = await renderMessagePinList(room);
|
||||
|
||||
return {
|
||||
...renderResult,
|
||||
addLocalPinEvent,
|
||||
removeLastLocalPinEvent,
|
||||
addNonLocalPinEvent,
|
||||
removeLastNonLocalPinEvent,
|
||||
room,
|
||||
};
|
||||
}
|
||||
|
||||
const pin1 = mkMessage({
|
||||
event: true,
|
||||
room: "!room:example.org",
|
||||
user: "@alice:example.org",
|
||||
msg: "First pinned message",
|
||||
ts: 2,
|
||||
});
|
||||
const pin2 = mkMessage({
|
||||
event: true,
|
||||
room: "!room:example.org",
|
||||
user: "@alice:example.org",
|
||||
msg: "The second one",
|
||||
ts: 1,
|
||||
});
|
||||
|
||||
it("should show spinner whilst loading", async () => {
|
||||
const room = mkRoom([], [pin1]);
|
||||
render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<PinnedMessagesCard
|
||||
room={room}
|
||||
onClose={jest.fn()}
|
||||
permalinkCreator={new RoomPermalinkCreator(room, room.roomId)}
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
|
||||
});
|
||||
|
||||
it("should show the empty state when there are no pins", async () => {
|
||||
const { asFragment } = await initPinnedMessagesCard([], []);
|
||||
|
||||
expect(screen.getByText("Pin important messages so that they can be easily discovered")).toBeInTheDocument();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should show two pinned messages", async () => {
|
||||
const { asFragment } = await initPinnedMessagesCard([pin1], [pin2]);
|
||||
|
||||
await waitFor(() => expect(screen.queryAllByRole("listitem")).toHaveLength(2));
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should not show more than 100 messages", async () => {
|
||||
const events = Array.from({ length: 120 }, (_, i) =>
|
||||
mkMessage({
|
||||
event: true,
|
||||
room: "!room:example.org",
|
||||
user: "@alice:example.org",
|
||||
msg: `The message ${i}`,
|
||||
ts: i,
|
||||
}),
|
||||
);
|
||||
await initPinnedMessagesCard(events, []);
|
||||
|
||||
await waitFor(() => expect(screen.queryAllByRole("listitem")).toHaveLength(100));
|
||||
}, 15000);
|
||||
|
||||
it("should updates when messages are pinned", async () => {
|
||||
// Start with nothing pinned
|
||||
const { addLocalPinEvent, addNonLocalPinEvent } = await initPinnedMessagesCard([], []);
|
||||
|
||||
await waitFor(() => expect(screen.queryAllByRole("listitem")).toHaveLength(0));
|
||||
|
||||
// Pin the first message
|
||||
await addLocalPinEvent(pin1);
|
||||
await waitFor(() => expect(screen.queryAllByRole("listitem")).toHaveLength(1));
|
||||
|
||||
// Pin the second message
|
||||
await addNonLocalPinEvent(pin2);
|
||||
await waitFor(() => expect(screen.queryAllByRole("listitem")).toHaveLength(2));
|
||||
});
|
||||
|
||||
it("should updates when messages are unpinned", async () => {
|
||||
// Start with two pins
|
||||
const { removeLastLocalPinEvent, removeLastNonLocalPinEvent } = await initPinnedMessagesCard([pin1], [pin2]);
|
||||
await waitFor(() => expect(screen.queryAllByRole("listitem")).toHaveLength(2));
|
||||
|
||||
// Unpin the first message
|
||||
await removeLastLocalPinEvent();
|
||||
await waitFor(() => expect(screen.queryAllByRole("listitem")).toHaveLength(1));
|
||||
|
||||
// Unpin the second message
|
||||
await removeLastNonLocalPinEvent();
|
||||
await waitFor(() => expect(screen.queryAllByRole("listitem")).toHaveLength(0));
|
||||
});
|
||||
|
||||
it("should display an edited pinned event", async () => {
|
||||
const messageEvent = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
room: "!room:example.org",
|
||||
user: "@alice:example.org",
|
||||
content: {
|
||||
"msgtype": MsgType.Text,
|
||||
"body": " * First pinned message, edited",
|
||||
"m.new_content": {
|
||||
msgtype: MsgType.Text,
|
||||
body: "First pinned message, edited",
|
||||
},
|
||||
"m.relates_to": {
|
||||
rel_type: RelationType.Replace,
|
||||
event_id: pin1.getId(),
|
||||
},
|
||||
},
|
||||
});
|
||||
cli.relations.mockResolvedValue({
|
||||
originalEvent: pin1,
|
||||
events: [messageEvent],
|
||||
});
|
||||
|
||||
await initPinnedMessagesCard([], [pin1]);
|
||||
expect(screen.getByText("First pinned message, edited")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("unpinnable event", () => {
|
||||
it("should hide unpinnable events found in local timeline", async () => {
|
||||
// Redacted messages are unpinnable
|
||||
const pin = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomCreate,
|
||||
content: {},
|
||||
room: "!room:example.org",
|
||||
user: "@alice:example.org",
|
||||
});
|
||||
await initPinnedMessagesCard([pin], []);
|
||||
expect(screen.queryAllByRole("listitem")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("hides unpinnable events not found in local timeline", async () => {
|
||||
// Redacted messages are unpinnable
|
||||
const pin = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomCreate,
|
||||
content: {},
|
||||
room: "!room:example.org",
|
||||
user: "@alice:example.org",
|
||||
});
|
||||
await initPinnedMessagesCard([], [pin]);
|
||||
expect(screen.queryAllByRole("listitem")).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("unpin all", () => {
|
||||
it("should not allow to unpinall", async () => {
|
||||
const room = mkRoom([pin1], [pin2]);
|
||||
jest.spyOn(
|
||||
room.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
|
||||
"mayClientSendStateEvent",
|
||||
).mockReturnValue(false);
|
||||
|
||||
const { asFragment } = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<PinnedMessagesCard
|
||||
room={room}
|
||||
onClose={jest.fn()}
|
||||
permalinkCreator={new RoomPermalinkCreator(room, room.roomId)}
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
// Wait a tick for state updates
|
||||
await act(() => sleep(0));
|
||||
|
||||
expect(screen.queryByText("Unpin all messages")).toBeNull();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should allow unpinning all messages", async () => {
|
||||
jest.spyOn(Modal, "createDialog");
|
||||
|
||||
const { room } = await initPinnedMessagesCard([pin1], [pin2]);
|
||||
expect(screen.getByText("Unpin all messages")).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByText("Unpin all messages"));
|
||||
// Should open the UnpinAllDialog dialog
|
||||
expect(Modal.createDialog).toHaveBeenCalledWith(UnpinAllDialog, { roomId: room.roomId, matrixClient: cli });
|
||||
});
|
||||
});
|
||||
|
||||
it("should displays votes on polls not found in local timeline", async () => {
|
||||
const poll = mkEvent({
|
||||
...PollStartEvent.from("A poll", ["Option 1", "Option 2"], M_POLL_KIND_DISCLOSED).serialize(),
|
||||
event: true,
|
||||
room: "!room:example.org",
|
||||
user: "@alice:example.org",
|
||||
});
|
||||
|
||||
const answers = (poll.unstableExtensibleEvent as PollStartEvent).answers;
|
||||
const responses = [
|
||||
["@alice:example.org", 0] as [string, number],
|
||||
["@bob:example.org", 0] as [string, number],
|
||||
["@eve:example.org", 1] as [string, number],
|
||||
].map(([user, option], i) =>
|
||||
mkEvent({
|
||||
...PollResponseEvent.from([answers[option as number].id], poll.getId()!).serialize(),
|
||||
event: true,
|
||||
room: "!room:example.org",
|
||||
user,
|
||||
}),
|
||||
);
|
||||
|
||||
const end = mkEvent({
|
||||
...PollEndEvent.from(poll.getId()!, "Closing the poll").serialize(),
|
||||
event: true,
|
||||
room: "!room:example.org",
|
||||
user: "@alice:example.org",
|
||||
});
|
||||
|
||||
// Make the responses available
|
||||
cli.relations.mockImplementation(async (roomId, eventId, relationType, eventType, opts) => {
|
||||
if (eventId === poll.getId() && relationType === RelationType.Reference) {
|
||||
// Paginate the results, for added challenge
|
||||
return opts?.from === "page2"
|
||||
? { originalEvent: poll, events: responses.slice(2) }
|
||||
: { originalEvent: poll, events: [...responses.slice(0, 2), end], nextBatch: "page2" };
|
||||
}
|
||||
// type does not allow originalEvent to be falsy
|
||||
// but code seems to
|
||||
// so still test that
|
||||
return { originalEvent: undefined as unknown as MatrixEvent, events: [] };
|
||||
});
|
||||
|
||||
const { room } = await initPinnedMessagesCard([], [poll]);
|
||||
|
||||
// two pages of results
|
||||
await flushPromises();
|
||||
await flushPromises();
|
||||
|
||||
const pollInstance = room.polls.get(poll.getId()!);
|
||||
expect(pollInstance).toBeTruthy();
|
||||
|
||||
expect(screen.getByText("A poll")).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText("Option 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("2 votes")).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText("Option 2")).toBeInTheDocument();
|
||||
expect(screen.getByText("1 vote")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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, fireEvent, screen } from "jest-matrix-react";
|
||||
import { Room, type MatrixClient, JoinRule, MatrixEvent, HistoryVisibility } from "matrix-js-sdk/src/matrix";
|
||||
import { mocked, type MockedObject } from "jest-mock";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import RoomSummaryCardView from "../../../../../src/components/views/right_panel/RoomSummaryCardView";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { flushPromises, stubClient } from "../../../../test-utils";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import { _t } from "../../../../../src/languageHandler";
|
||||
import {
|
||||
type RoomSummaryCardState,
|
||||
useRoomSummaryCardViewModel,
|
||||
} from "../../../../../src/components/viewmodels/right_panel/RoomSummaryCardViewModel";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
|
||||
// Mock the viewmodel hooks
|
||||
jest.mock("../../../../../src/components/viewmodels/right_panel/RoomSummaryCardViewModel", () => ({
|
||||
useRoomSummaryCardViewModel: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("<RoomSummaryCard />", () => {
|
||||
const userId = "@alice:domain.org";
|
||||
|
||||
const roomId = "!room:domain.org";
|
||||
let mockClient!: MockedObject<MatrixClient>;
|
||||
let room!: Room;
|
||||
|
||||
const getComponent = (props = {}) => {
|
||||
const defaultProps = {
|
||||
room,
|
||||
onClose: jest.fn(),
|
||||
permalinkCreator: new RoomPermalinkCreator(room),
|
||||
};
|
||||
|
||||
return render(<RoomSummaryCardView {...defaultProps} {...props} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
// Setup mock view models
|
||||
const vmDefaultValues: RoomSummaryCardState = {
|
||||
isDirectMessage: false,
|
||||
isRoomEncrypted: false,
|
||||
e2eStatus: undefined,
|
||||
isVideoRoom: false,
|
||||
roomJoinRule: JoinRule.Public,
|
||||
historyVisibility: HistoryVisibility.Shared,
|
||||
alias: "",
|
||||
isFavorite: false,
|
||||
canInviteToState: true,
|
||||
pinCount: 0,
|
||||
searchInputRef: { current: null },
|
||||
onUpdateSearchInput: jest.fn(),
|
||||
onRoomMembersClick: jest.fn(),
|
||||
onRoomThreadsClick: jest.fn(),
|
||||
onRoomFilesClick: jest.fn(),
|
||||
onRoomExtensionsClick: jest.fn(),
|
||||
onRoomPinsClick: jest.fn(),
|
||||
onRoomSettingsClick: jest.fn(),
|
||||
onLeaveRoomClick: jest.fn(),
|
||||
onShareRoomClick: jest.fn(),
|
||||
onRoomExportClick: jest.fn(),
|
||||
onRoomPollHistoryClick: jest.fn(),
|
||||
onReportRoomClick: jest.fn(),
|
||||
onFavoriteToggleClick: jest.fn(),
|
||||
onInviteToRoomClick: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient = mocked(stubClient());
|
||||
room = new Room(roomId, mockClient, userId);
|
||||
mocked(useRoomSummaryCardViewModel).mockReturnValue(vmDefaultValues);
|
||||
DMRoomMap.makeShared(mockClient);
|
||||
|
||||
mockClient.getRoom.mockReturnValue(room);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("renders the room summary", () => {
|
||||
const { container } = getComponent();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the room topic in the summary", () => {
|
||||
room.currentState.setStateEvents([
|
||||
new MatrixEvent({
|
||||
type: "m.room.topic",
|
||||
room_id: roomId,
|
||||
sender: userId,
|
||||
content: {
|
||||
topic: "This is the room's topic.",
|
||||
},
|
||||
state_key: "",
|
||||
}),
|
||||
]);
|
||||
const { container } = getComponent();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("has button to edit topic", () => {
|
||||
room.currentState.setStateEvents([
|
||||
new MatrixEvent({
|
||||
type: "m.room.topic",
|
||||
room_id: roomId,
|
||||
sender: userId,
|
||||
content: {
|
||||
topic: "This is the room's topic.",
|
||||
},
|
||||
state_key: "",
|
||||
}),
|
||||
]);
|
||||
const { container, getByText } = getComponent();
|
||||
expect(getByText("Edit")).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("search", () => {
|
||||
it("has the search field", async () => {
|
||||
const onSearchChange = jest.fn();
|
||||
const { getByPlaceholderText } = getComponent({
|
||||
onSearchChange,
|
||||
});
|
||||
expect(getByPlaceholderText("Search messages…")).toBeVisible();
|
||||
});
|
||||
|
||||
it("should focus the search field if focusRoomSearch=true", () => {
|
||||
const onSearchChange = jest.fn();
|
||||
const { getByPlaceholderText } = getComponent({
|
||||
onSearchChange,
|
||||
focusRoomSearch: true,
|
||||
});
|
||||
expect(getByPlaceholderText("Search messages…")).toHaveFocus();
|
||||
});
|
||||
|
||||
it("should cancel search on escape", () => {
|
||||
const onSearchChange = jest.fn();
|
||||
const onSearchCancel = jest.fn();
|
||||
|
||||
const { getByPlaceholderText } = getComponent({
|
||||
onSearchChange,
|
||||
onSearchCancel,
|
||||
focusRoomSearch: true,
|
||||
});
|
||||
expect(getByPlaceholderText("Search messages…")).toHaveFocus();
|
||||
fireEvent.keyDown(getByPlaceholderText("Search messages…"), { key: "Escape" });
|
||||
expect(vmDefaultValues.onUpdateSearchInput).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should update the search field value correctly", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
const onSearchChange = jest.fn();
|
||||
const { getByPlaceholderText } = getComponent({
|
||||
onSearchChange,
|
||||
});
|
||||
|
||||
const searchInput = getByPlaceholderText("Search messages…");
|
||||
await user.type(searchInput, "test query");
|
||||
|
||||
expect(onSearchChange).toHaveBeenCalledWith("test query");
|
||||
expect(searchInput).toHaveValue("test query");
|
||||
});
|
||||
});
|
||||
|
||||
it("opens room file panel on button click", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
fireEvent.click(getByText("Files"));
|
||||
|
||||
expect(vmDefaultValues.onRoomFilesClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("opens room export dialog on button click", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
fireEvent.click(getByText(_t("export_chat|title")));
|
||||
|
||||
expect(vmDefaultValues.onRoomExportClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("opens share room dialog on button click", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
fireEvent.click(getByText(_t("action|copy_link")));
|
||||
|
||||
expect(vmDefaultValues.onShareRoomClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("opens invite dialog on button click", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
fireEvent.click(getByText(_t("action|invite")));
|
||||
|
||||
expect(vmDefaultValues.onInviteToRoomClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("fires favourite dispatch on button click", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
fireEvent.click(getByText(_t("room|context_menu|favourite")));
|
||||
|
||||
expect(vmDefaultValues.onFavoriteToggleClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("opens room settings on button click", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
fireEvent.click(getByText(_t("common|settings")));
|
||||
|
||||
expect(vmDefaultValues.onRoomSettingsClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("opens room member list on button click", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
fireEvent.click(getByText("People"));
|
||||
|
||||
expect(vmDefaultValues.onRoomMembersClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("opens room threads list on button click", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
fireEvent.click(getByText("Threads"));
|
||||
|
||||
expect(vmDefaultValues.onRoomThreadsClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("opens room pinned messages on button click", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
fireEvent.click(getByText("Pinned messages"));
|
||||
|
||||
expect(vmDefaultValues.onRoomPinsClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not render irrelevant options if video room", () => {
|
||||
mocked(useRoomSummaryCardViewModel).mockReturnValue({
|
||||
...vmDefaultValues,
|
||||
isVideoRoom: true,
|
||||
});
|
||||
const { queryByText } = getComponent();
|
||||
|
||||
// options not rendered
|
||||
expect(queryByText("Files")).not.toBeInTheDocument();
|
||||
expect(queryByText("Pinned")).not.toBeInTheDocument();
|
||||
expect(queryByText("Export chat")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("pinning", () => {
|
||||
it("renders pins options", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
expect(getByText("Pinned messages")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("poll history", () => {
|
||||
it("renders poll history option", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
expect(getByText("Polls")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("opens poll history dialog on button click", () => {
|
||||
const permalinkCreator = new RoomPermalinkCreator(room);
|
||||
const { getByText } = getComponent({ permalinkCreator });
|
||||
|
||||
fireEvent.click(getByText("Polls"));
|
||||
|
||||
expect(vmDefaultValues.onRoomPollHistoryClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("public room label", () => {
|
||||
it("does not show public room label for a DM", async () => {
|
||||
mocked(useRoomSummaryCardViewModel).mockReturnValue({
|
||||
...vmDefaultValues,
|
||||
isDirectMessage: true,
|
||||
});
|
||||
|
||||
getComponent();
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.queryByText("Public room")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not show public room label for non public room", async () => {
|
||||
mocked(useRoomSummaryCardViewModel).mockReturnValue({
|
||||
...vmDefaultValues,
|
||||
isDirectMessage: false,
|
||||
roomJoinRule: JoinRule.Invite,
|
||||
});
|
||||
getComponent();
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.queryByText("Public room")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows a public room label for a public room", async () => {
|
||||
getComponent();
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.queryByText("Public room")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,488 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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, act, waitForElementToBeRemoved } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { type Mocked, mocked } from "jest-mock";
|
||||
import {
|
||||
type Room,
|
||||
User,
|
||||
type MatrixClient,
|
||||
RoomMember,
|
||||
Device,
|
||||
ProfileKeyTimezone,
|
||||
ProfileKeyMSC4175Timezone,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { EventEmitter } from "events";
|
||||
import {
|
||||
UserVerificationStatus,
|
||||
type VerificationRequest,
|
||||
VerificationPhase as Phase,
|
||||
VerificationRequestEvent,
|
||||
type CryptoApi,
|
||||
} from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import UserInfo, { disambiguateDevices } from "../../../../../src/components/views/right_panel/UserInfo";
|
||||
import { getPowerLevels } from "../../../../../src/components/viewmodels/right_panel/user_info/UserInfoBasicViewModel";
|
||||
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import Modal from "../../../../../src/Modal";
|
||||
import { clearAllModals, flushPromises } from "../../../../test-utils";
|
||||
import ErrorDialog from "../../../../../src/components/views/dialogs/ErrorDialog";
|
||||
import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
|
||||
import { UIComponent } from "../../../../../src/settings/UIFeature";
|
||||
|
||||
jest.mock("../../../../../src/utils/direct-messages", () => ({
|
||||
...jest.requireActual("../../../../../src/utils/direct-messages"),
|
||||
startDmOnFirstMessage: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../src/dispatcher/dispatcher");
|
||||
|
||||
jest.mock("../../../../../src/customisations/UserIdentifier", () => {
|
||||
return {
|
||||
getDisplayUserIdentifier: jest.fn().mockReturnValue("customUserIdentifier"),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock("../../../../../src/utils/DMRoomMap", () => {
|
||||
const mock = {
|
||||
getUserIdForRoomId: jest.fn(),
|
||||
getDMRoomsForUserId: jest.fn(),
|
||||
};
|
||||
|
||||
return {
|
||||
shared: jest.fn().mockReturnValue(mock),
|
||||
sharedInstance: mock,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock("../../../../../src/customisations/helpers/UIComponents", () => {
|
||||
const original = jest.requireActual("../../../../../src/customisations/helpers/UIComponents");
|
||||
return {
|
||||
shouldShowComponent: jest.fn().mockImplementation(original.shouldShowComponent),
|
||||
};
|
||||
});
|
||||
|
||||
const defaultRoomId = "!fkfk";
|
||||
const defaultUserId = "@user:example.com";
|
||||
const defaultUser = new User(defaultUserId);
|
||||
|
||||
let mockRoom: Mocked<Room>;
|
||||
let mockClient: Mocked<MatrixClient>;
|
||||
let mockCrypto: Mocked<CryptoApi>;
|
||||
const origDate = global.Date.prototype.toLocaleString;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRoom = mocked({
|
||||
roomId: defaultRoomId,
|
||||
getType: jest.fn().mockReturnValue(undefined),
|
||||
isSpaceRoom: jest.fn().mockReturnValue(false),
|
||||
getMember: jest.fn().mockReturnValue(undefined),
|
||||
getMxcAvatarUrl: jest.fn().mockReturnValue("mock-avatar-url"),
|
||||
name: "test room",
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
currentState: {
|
||||
getStateEvents: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
},
|
||||
getEventReadUpTo: jest.fn(),
|
||||
} as unknown as Room);
|
||||
|
||||
mockCrypto = mocked({
|
||||
getDeviceVerificationStatus: jest.fn(),
|
||||
getUserDeviceInfo: jest.fn(),
|
||||
userHasCrossSigningKeys: jest.fn().mockResolvedValue(false),
|
||||
getUserVerificationStatus: jest.fn(),
|
||||
isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false),
|
||||
} as unknown as CryptoApi);
|
||||
|
||||
mockClient = mocked({
|
||||
getUser: jest.fn(),
|
||||
isGuest: jest.fn().mockReturnValue(false),
|
||||
isUserIgnored: jest.fn(),
|
||||
getIgnoredUsers: jest.fn(),
|
||||
setIgnoredUsers: jest.fn(),
|
||||
getUserId: jest.fn(),
|
||||
getSafeUserId: jest.fn(),
|
||||
getDomain: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
isSynapseAdministrator: jest.fn().mockResolvedValue(false),
|
||||
doesServerSupportUnstableFeature: jest.fn().mockReturnValue(false),
|
||||
doesServerSupportExtendedProfiles: jest.fn().mockResolvedValue(false),
|
||||
getExtendedProfile: jest.fn().mockRejectedValue(new Error("Not supported")),
|
||||
mxcUrlToHttp: jest.fn().mockReturnValue("mock-mxcUrlToHttp"),
|
||||
removeListener: jest.fn(),
|
||||
currentState: {
|
||||
on: jest.fn(),
|
||||
},
|
||||
getRoom: jest.fn(),
|
||||
credentials: {},
|
||||
setPowerLevel: jest.fn(),
|
||||
getCrypto: jest.fn().mockReturnValue(mockCrypto),
|
||||
} as unknown as MatrixClient);
|
||||
|
||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
||||
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(mockClient);
|
||||
});
|
||||
|
||||
describe("<UserInfo />", () => {
|
||||
class MockVerificationRequest extends EventEmitter {
|
||||
pending = true;
|
||||
phase: Phase = Phase.Ready;
|
||||
cancellationCode: string | null = null;
|
||||
|
||||
constructor(opts: Partial<VerificationRequest>) {
|
||||
super();
|
||||
Object.assign(this, {
|
||||
channel: { transactionId: 1 },
|
||||
otherPartySupportsMethod: jest.fn(),
|
||||
generateQRCode: jest.fn().mockReturnValue(new Promise(() => {})),
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
}
|
||||
let verificationRequest: MockVerificationRequest;
|
||||
|
||||
const defaultProps = {
|
||||
user: defaultUser,
|
||||
// idk what is wrong with this type
|
||||
phase: RightPanelPhases.MemberInfo as RightPanelPhases.MemberInfo,
|
||||
onClose: jest.fn(),
|
||||
};
|
||||
|
||||
const renderComponent = (props = {}) => {
|
||||
const Wrapper = (wrapperProps = {}) => {
|
||||
return <MatrixClientContext.Provider value={mockClient} {...wrapperProps} />;
|
||||
};
|
||||
|
||||
return render(<UserInfo {...defaultProps} {...props} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
verificationRequest = new MockVerificationRequest({});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await clearAllModals();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("closes on close button click", async () => {
|
||||
renderComponent();
|
||||
|
||||
await userEvent.click(screen.getByTestId("base-card-close-button"));
|
||||
|
||||
expect(defaultProps.onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("without a room", () => {
|
||||
it("does not render space header", () => {
|
||||
renderComponent();
|
||||
expect(screen.queryByTestId("space-header")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders user info", () => {
|
||||
renderComponent();
|
||||
expect(screen.getByRole("heading", { name: defaultUserId })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe.each([[ProfileKeyTimezone], [ProfileKeyMSC4175Timezone]])("timezone rendering (%s)", (profileKey) => {
|
||||
it("renders user timezone if set", async () => {
|
||||
// For timezone, force a consistent locale.
|
||||
jest.spyOn(global.Date.prototype, "toLocaleString").mockImplementation(function (
|
||||
this: Date,
|
||||
_locale,
|
||||
opts,
|
||||
) {
|
||||
return origDate.call(this, "en-US", {
|
||||
...opts,
|
||||
hourCycle: "h12",
|
||||
});
|
||||
});
|
||||
mockClient.doesServerSupportExtendedProfiles.mockResolvedValue(true);
|
||||
mockClient.getExtendedProfile.mockResolvedValue({ [profileKey]: "Europe/London" });
|
||||
renderComponent();
|
||||
await expect(screen.findByText(/\d\d:\d\d (AM|PM)/)).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not renders user timezone if timezone is invalid", async () => {
|
||||
mockClient.doesServerSupportExtendedProfiles.mockResolvedValue(true);
|
||||
mockClient.getExtendedProfile.mockResolvedValue({ [profileKey]: "invalid-tz" });
|
||||
renderComponent();
|
||||
expect(screen.queryByText(/\d\d:\d\d (AM|PM)/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders encryption info panel without pending verification", () => {
|
||||
renderComponent({ phase: RightPanelPhases.EncryptionPanel });
|
||||
expect(screen.getByRole("heading", { name: /encryption/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders encryption verification panel with pending verification", () => {
|
||||
renderComponent({ phase: RightPanelPhases.EncryptionPanel, verificationRequest });
|
||||
|
||||
expect(screen.queryByRole("heading", { name: /encryption/i })).not.toBeInTheDocument();
|
||||
// the verificationRequest has phase of Phase.Ready but .otherPartySupportsMethod
|
||||
// will not return true, so we expect to see the noCommonMethod error from VerificationPanel
|
||||
expect(screen.getByText(/try with a different client/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show error modal when the verification request is cancelled with a mismatch", () => {
|
||||
renderComponent({ phase: RightPanelPhases.EncryptionPanel, verificationRequest });
|
||||
|
||||
const spy = jest.spyOn(Modal, "createDialog");
|
||||
act(() => {
|
||||
verificationRequest.phase = Phase.Cancelled;
|
||||
verificationRequest.cancellationCode = "m.key_mismatch";
|
||||
verificationRequest.emit(VerificationRequestEvent.Change);
|
||||
});
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
ErrorDialog,
|
||||
expect.objectContaining({ title: "Your messages are not secure" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("should not show error modal when the verification request is changed for some other reason", () => {
|
||||
renderComponent({ phase: RightPanelPhases.EncryptionPanel, verificationRequest });
|
||||
|
||||
const spy = jest.spyOn(Modal, "createDialog");
|
||||
|
||||
// change to "started"
|
||||
act(() => {
|
||||
verificationRequest.phase = Phase.Started;
|
||||
verificationRequest.emit(VerificationRequestEvent.Change);
|
||||
});
|
||||
|
||||
// cancelled for some other reason
|
||||
act(() => {
|
||||
verificationRequest.phase = Phase.Cancelled;
|
||||
verificationRequest.cancellationCode = "changed my mind";
|
||||
verificationRequest.emit(VerificationRequestEvent.Change);
|
||||
});
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders close button correctly when encryption panel with a pending verification request", async () => {
|
||||
renderComponent({ phase: RightPanelPhases.EncryptionPanel, verificationRequest });
|
||||
screen.getByTestId("base-card-close-button").focus();
|
||||
expect(screen.getByText("Cancel")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("with a room", () => {
|
||||
it("renders user info", () => {
|
||||
renderComponent({ room: mockRoom });
|
||||
expect(screen.getByRole("heading", { name: defaultUserId })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render space header when room is not a space room", () => {
|
||||
renderComponent({ room: mockRoom });
|
||||
expect(screen.queryByTestId("space-header")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders encryption info panel without pending verification", () => {
|
||||
renderComponent({ phase: RightPanelPhases.EncryptionPanel, room: mockRoom });
|
||||
expect(screen.getByRole("heading", { name: /encryption/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders encryption verification panel with pending verification", () => {
|
||||
renderComponent({ phase: RightPanelPhases.EncryptionPanel, verificationRequest, room: mockRoom });
|
||||
|
||||
expect(screen.queryByRole("heading", { name: /encryption/i })).not.toBeInTheDocument();
|
||||
// the verificationRequest has phase of Phase.Ready but .otherPartySupportsMethod
|
||||
// will not return true, so we expect to see the noCommonMethod error from VerificationPanel
|
||||
expect(screen.getByText(/try with a different client/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the message button", () => {
|
||||
render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<UserInfo {...defaultProps} />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
screen.getByRole("button", { name: "Send message" });
|
||||
});
|
||||
|
||||
it("hides the message button if the visibility customisation hides all create room features", () => {
|
||||
mocked(shouldShowComponent).withImplementation(
|
||||
(component) => {
|
||||
return component !== UIComponent.CreateRooms;
|
||||
},
|
||||
() => {
|
||||
render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<UserInfo {...defaultProps} />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
expect(screen.queryByRole("button", { name: "Message" })).toBeNull();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("Ignore", () => {
|
||||
const member = new RoomMember(defaultRoomId, defaultUserId);
|
||||
|
||||
it("shows block button when member userId does not match client userId", () => {
|
||||
// call to client.getUserId returns undefined, which will not match member.userId
|
||||
renderComponent();
|
||||
|
||||
expect(screen.getByRole("button", { name: "Ignore" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows a modal before ignoring the user", async () => {
|
||||
const originalCreateDialog = Modal.createDialog;
|
||||
const modalSpy = (Modal.createDialog = jest.fn().mockReturnValue({
|
||||
finished: Promise.resolve([true]),
|
||||
close: () => {},
|
||||
}));
|
||||
|
||||
try {
|
||||
mockClient.getIgnoredUsers.mockReturnValue([]);
|
||||
renderComponent();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Ignore" }));
|
||||
expect(modalSpy).toHaveBeenCalled();
|
||||
expect(mockClient.setIgnoredUsers).toHaveBeenLastCalledWith([member.userId]);
|
||||
} finally {
|
||||
Modal.createDialog = originalCreateDialog;
|
||||
}
|
||||
});
|
||||
|
||||
it("cancels ignoring the user", async () => {
|
||||
const originalCreateDialog = Modal.createDialog;
|
||||
const modalSpy = (Modal.createDialog = jest.fn().mockReturnValue({
|
||||
finished: Promise.resolve([false]),
|
||||
close: () => {},
|
||||
}));
|
||||
|
||||
try {
|
||||
mockClient.getIgnoredUsers.mockReturnValue([]);
|
||||
renderComponent();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Ignore" }));
|
||||
expect(modalSpy).toHaveBeenCalled();
|
||||
expect(mockClient.setIgnoredUsers).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
Modal.createDialog = originalCreateDialog;
|
||||
}
|
||||
});
|
||||
|
||||
it("unignores the user", async () => {
|
||||
mockClient.isUserIgnored.mockReturnValue(true);
|
||||
mockClient.getIgnoredUsers.mockReturnValue([member.userId]);
|
||||
renderComponent();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Unignore" }));
|
||||
expect(mockClient.setIgnoredUsers).toHaveBeenCalledWith([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("with crypto enabled", () => {
|
||||
beforeEach(() => {
|
||||
mockClient.doesServerSupportUnstableFeature.mockResolvedValue(true);
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||
|
||||
const device = new Device({
|
||||
deviceId: "d1",
|
||||
userId: defaultUserId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
});
|
||||
const devicesMap = new Map<string, Device>([[device.deviceId, device]]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
});
|
||||
|
||||
it("renders <BasicUserInfo />", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||
|
||||
const { container } = renderComponent({
|
||||
phase: RightPanelPhases.MemberInfo,
|
||||
verificationRequest,
|
||||
room: mockRoom,
|
||||
});
|
||||
await flushPromises();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render a deactivate button for users of the same server if we are a server admin", async () => {
|
||||
mockClient.isSynapseAdministrator.mockResolvedValue(true);
|
||||
mockClient.getDomain.mockReturnValue("example.com");
|
||||
|
||||
const { container } = renderComponent({
|
||||
phase: RightPanelPhases.MemberInfo,
|
||||
room: mockRoom,
|
||||
});
|
||||
|
||||
await expect(screen.findByRole("button", { name: "Deactivate user" })).resolves.toBeInTheDocument();
|
||||
if (screen.queryAllByRole("progressbar").length) {
|
||||
await act(() => waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar")));
|
||||
}
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("disambiguateDevices", () => {
|
||||
it("does not add ambiguous key to unique names", () => {
|
||||
const initialDevices = [
|
||||
{ deviceId: "id1", displayName: "name1" } as Device,
|
||||
{ deviceId: "id2", displayName: "name2" } as Device,
|
||||
{ deviceId: "id3", displayName: "name3" } as Device,
|
||||
];
|
||||
disambiguateDevices(initialDevices);
|
||||
|
||||
// mutates input so assert against initialDevices
|
||||
initialDevices.forEach((device) => {
|
||||
expect(device).not.toHaveProperty("ambiguous");
|
||||
});
|
||||
});
|
||||
|
||||
it("adds ambiguous key to all ids with non-unique names", () => {
|
||||
const uniqueNameDevices = [
|
||||
{ deviceId: "id3", displayName: "name3" } as Device,
|
||||
{ deviceId: "id4", displayName: "name4" } as Device,
|
||||
{ deviceId: "id6", displayName: "name6" } as Device,
|
||||
];
|
||||
const nonUniqueNameDevices = [
|
||||
{ deviceId: "id1", displayName: "nonUnique" } as Device,
|
||||
{ deviceId: "id2", displayName: "nonUnique" } as Device,
|
||||
{ deviceId: "id5", displayName: "nonUnique" } as Device,
|
||||
];
|
||||
const initialDevices = [...uniqueNameDevices, ...nonUniqueNameDevices];
|
||||
disambiguateDevices(initialDevices);
|
||||
|
||||
// mutates input so assert against initialDevices
|
||||
uniqueNameDevices.forEach((device) => {
|
||||
expect(device).not.toHaveProperty("ambiguous");
|
||||
});
|
||||
nonUniqueNameDevices.forEach((device) => {
|
||||
expect(device).toHaveProperty("ambiguous", true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPowerLevels", () => {
|
||||
it("returns an empty object when room.currentState.getStateEvents return null", () => {
|
||||
mockRoom.currentState.getStateEvents.mockReturnValueOnce(null);
|
||||
expect(getPowerLevels(mockRoom)).toEqual({});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 { act, render, waitFor } from "jest-matrix-react";
|
||||
import React, { type ComponentProps } from "react";
|
||||
import { User, TypedEventEmitter, Device, type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { mocked, type Mocked } from "jest-mock";
|
||||
import {
|
||||
type EmojiMapping,
|
||||
type ShowSasCallbacks,
|
||||
VerificationPhase as Phase,
|
||||
type VerificationRequest,
|
||||
type VerificationRequestEvent,
|
||||
type Verifier,
|
||||
VerifierEvent,
|
||||
type VerifierEventHandlerMap,
|
||||
} from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import VerificationPanel from "../../../../../src/components/views/right_panel/VerificationPanel";
|
||||
import { flushPromises, stubClient } from "../../../../test-utils";
|
||||
|
||||
describe("<VerificationPanel />", () => {
|
||||
let client: MatrixClient;
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
});
|
||||
|
||||
describe("'Ready' phase (dialog mode)", () => {
|
||||
it("should show a 'Start' button", () => {
|
||||
const container = renderComponent({
|
||||
request: makeMockVerificationRequest({
|
||||
phase: Phase.Ready,
|
||||
}),
|
||||
layout: "dialog",
|
||||
});
|
||||
container.getByRole("button", { name: "Start" });
|
||||
});
|
||||
|
||||
it("should show a QR code if the other side can scan and QR bytes are calculated", async () => {
|
||||
const request = makeMockVerificationRequest({
|
||||
phase: Phase.Ready,
|
||||
});
|
||||
request.generateQRCode.mockResolvedValue(new Uint8ClampedArray(Buffer.from("test", "utf-8")));
|
||||
const container = renderComponent({
|
||||
request: request,
|
||||
layout: "dialog",
|
||||
});
|
||||
container.getByText("Scan this unique code");
|
||||
// it shows a spinner at first; wait for the update which makes it show the QR code
|
||||
await waitFor(() => {
|
||||
container.getByAltText("QR Code");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("'Ready' phase (regular mode)", () => {
|
||||
it("should show a 'Verify by emoji' button", () => {
|
||||
const container = renderComponent({
|
||||
request: makeMockVerificationRequest({ phase: Phase.Ready }),
|
||||
});
|
||||
container.getByRole("button", { name: "Verify by emoji" });
|
||||
});
|
||||
|
||||
it("should show a QR code if the other side can scan and QR bytes are calculated", async () => {
|
||||
const request = makeMockVerificationRequest({
|
||||
phase: Phase.Ready,
|
||||
});
|
||||
request.generateQRCode.mockResolvedValue(new Uint8ClampedArray(Buffer.from("test", "utf-8")));
|
||||
const container = renderComponent({
|
||||
request: request,
|
||||
member: new User("@other:user"),
|
||||
});
|
||||
container.getByText("Ask @other:user to scan your code:");
|
||||
// it shows a spinner at first; wait for the update which makes it show the QR code
|
||||
await waitFor(() => {
|
||||
container.getByAltText("QR Code");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("'Verify by emoji' flow", () => {
|
||||
let mockVerifier: Mocked<Verifier>;
|
||||
let mockRequest: Mocked<VerificationRequest>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockVerifier = makeMockVerifier();
|
||||
mockRequest = makeMockVerificationRequest({
|
||||
verifier: mockVerifier as unknown as VerificationRequest["verifier"],
|
||||
chosenMethod: "m.sas.v1",
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a spinner initially", () => {
|
||||
const { container } = renderComponent({
|
||||
request: mockRequest,
|
||||
phase: Phase.Started,
|
||||
});
|
||||
expect(container.getElementsByClassName("mx_Spinner").length).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should show some emojis once keys are exchanged", () => {
|
||||
const { getAllByText } = renderComponent({
|
||||
request: mockRequest,
|
||||
phase: Phase.Started,
|
||||
});
|
||||
|
||||
// fire the ShowSas event
|
||||
const sasEvent = makeMockSasCallbacks();
|
||||
mockVerifier.getShowSasCallbacks.mockReturnValue(sasEvent);
|
||||
act(() => {
|
||||
mockVerifier.emit(VerifierEvent.ShowSas, sasEvent);
|
||||
});
|
||||
|
||||
expect(getAllByText("🦄")).toHaveLength(7);
|
||||
expect(getAllByText("Unicorn")).toHaveLength(7);
|
||||
});
|
||||
|
||||
describe("'Verify own device' flow", () => {
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(mockRequest, "isSelfVerification", { get: () => true });
|
||||
Object.defineProperty(mockRequest, "otherDeviceId", { get: () => "other_device" });
|
||||
|
||||
const otherDeviceDetails = new Device({
|
||||
algorithms: [],
|
||||
deviceId: "other_device",
|
||||
keys: new Map(),
|
||||
userId: "",
|
||||
displayName: "my other device",
|
||||
});
|
||||
|
||||
mocked(client.getCrypto()!).getUserDeviceInfo.mockResolvedValue(
|
||||
new Map([[client.getSafeUserId(), new Map([["other_device", otherDeviceDetails]])]]),
|
||||
);
|
||||
});
|
||||
|
||||
it("should show 'Waiting for you to verify' after confirming", async () => {
|
||||
const rendered = renderComponent({
|
||||
request: mockRequest,
|
||||
phase: Phase.Started,
|
||||
});
|
||||
|
||||
// wait for the device to be looked up
|
||||
await act(() => flushPromises());
|
||||
|
||||
// fire the ShowSas event
|
||||
const sasEvent = makeMockSasCallbacks();
|
||||
mockVerifier.getShowSasCallbacks.mockReturnValue(sasEvent);
|
||||
act(() => {
|
||||
mockVerifier.emit(VerifierEvent.ShowSas, sasEvent);
|
||||
});
|
||||
|
||||
// confirm
|
||||
act(() => {
|
||||
rendered.getByRole("button", { name: "They match" }).click();
|
||||
});
|
||||
|
||||
expect(rendered.container).toHaveTextContent(
|
||||
"Waiting for you to verify on your other device, my other device (other_device)…",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function renderComponent(props: Partial<ComponentProps<typeof VerificationPanel>> & { request: VerificationRequest }) {
|
||||
const defaultProps = {
|
||||
layout: "",
|
||||
member: {} as User,
|
||||
onClose: () => {},
|
||||
isRoomEncrypted: false,
|
||||
inDialog: false,
|
||||
phase: props.request.phase,
|
||||
};
|
||||
return render(<VerificationPanel {...defaultProps} {...props} />);
|
||||
}
|
||||
|
||||
function makeMockVerificationRequest(props: Partial<VerificationRequest> = {}): Mocked<VerificationRequest> {
|
||||
const request = new TypedEventEmitter<VerificationRequestEvent, any>();
|
||||
Object.assign(request, {
|
||||
cancel: jest.fn(),
|
||||
otherPartySupportsMethod: jest.fn().mockReturnValue(true),
|
||||
generateQRCode: jest.fn().mockResolvedValue(undefined),
|
||||
...props,
|
||||
});
|
||||
return request as unknown as Mocked<VerificationRequest>;
|
||||
}
|
||||
|
||||
function makeMockVerifier(): Mocked<Verifier> {
|
||||
const verifier = new TypedEventEmitter<VerifierEvent, VerifierEventHandlerMap>();
|
||||
Object.assign(verifier, {
|
||||
cancel: jest.fn(),
|
||||
verify: jest.fn(),
|
||||
getShowSasCallbacks: jest.fn(),
|
||||
getReciprocateQrCodeCallbacks: jest.fn(),
|
||||
});
|
||||
return verifier as unknown as Mocked<Verifier>;
|
||||
}
|
||||
|
||||
function makeMockSasCallbacks(): ShowSasCallbacks {
|
||||
const unicorn: EmojiMapping = ["🦄", "unicorn"];
|
||||
return {
|
||||
sas: {
|
||||
emoji: new Array<EmojiMapping>(7).fill(unicorn),
|
||||
},
|
||||
cancel: jest.fn(),
|
||||
confirm: jest.fn(),
|
||||
mismatch: jest.fn(),
|
||||
};
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`<BaseCard /> should close when clicking X button 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_BaseCard"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header_title"
|
||||
>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
|
||||
role="heading"
|
||||
>
|
||||
Heading text
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby="_r_0_"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="secondary"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 28px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
Content
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`<ExtensionsCard /> should render empty state 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_BaseCard mx_ExtensionsCard"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header_title"
|
||||
>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
|
||||
role="heading"
|
||||
>
|
||||
Extensions
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby="_r_0_"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="secondary"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 28px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar"
|
||||
tabindex="-1"
|
||||
>
|
||||
<button
|
||||
class="_button_13vu4_8 _has-icon_13vu4_60"
|
||||
data-kind="secondary"
|
||||
data-size="sm"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11 13H6a.97.97 0 0 1-.713-.287A.97.97 0 0 1 5 12q0-.424.287-.713A.97.97 0 0 1 6 11h5V6q0-.424.287-.713A.97.97 0 0 1 12 5q.424 0 .713.287Q13 5.576 13 6v5h5q.424 0 .712.287.288.288.288.713 0 .424-.288.713A.97.97 0 0 1 18 13h-5v5q0 .424-.287.712A.97.97 0 0 1 12 19a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 18z"
|
||||
/>
|
||||
</svg>
|
||||
Add extensions
|
||||
</button>
|
||||
<div
|
||||
class="_flex_4dswl_9 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); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="32px"
|
||||
viewBox="0 0 24 24"
|
||||
width="32px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.25 11.672a.9.9 0 0 1-.663-.282L12.61 7.413a.9.9 0 0 1-.282-.663q0-.381.282-.663l3.977-3.977a.9.9 0 0 1 .663-.282q.381 0 .663.282l3.977 3.977a.9.9 0 0 1 .282.663.9.9 0 0 1-.282.663l-3.977 3.977a.9.9 0 0 1-.663.282m2.475-4.922L17.25 4.275 14.775 6.75l2.475 2.475zM4 11a.97.97 0 0 1-.712-.287A.97.97 0 0 1 3 10V4q0-.424.288-.712A.97.97 0 0 1 4 3h6q.424 0 .713.288Q11 3.575 11 4v6q0 .424-.287.713A.97.97 0 0 1 10 11zm5-2V5H5v4zm5 12a.97.97 0 0 1-.713-.288A.97.97 0 0 1 13 20v-6q0-.424.287-.713A.97.97 0 0 1 14 13h6q.424 0 .712.287.288.288.288.713v6q0 .424-.288.712A.97.97 0 0 1 20 21zm5-2v-4h-4v4zM4 21a.97.97 0 0 1-.712-.288A.97.97 0 0 1 3 20v-6q0-.424.288-.713A.97.97 0 0 1 4 13h6q.424 0 .713.287.287.288.287.713v6q0 .424-.287.712A.97.97 0 0 1 10 21zm5-2v-4H5v4z"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74"
|
||||
>
|
||||
Boost productivity with more tools, widgets and bots
|
||||
</p>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50"
|
||||
>
|
||||
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"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header_title"
|
||||
>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
|
||||
role="heading"
|
||||
>
|
||||
Extensions
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby="_r_6_"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="secondary"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 28px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar"
|
||||
tabindex="-1"
|
||||
>
|
||||
<button
|
||||
class="_button_13vu4_8 _has-icon_13vu4_60"
|
||||
data-kind="secondary"
|
||||
data-size="sm"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11 13H6a.97.97 0 0 1-.713-.287A.97.97 0 0 1 5 12q0-.424.287-.713A.97.97 0 0 1 6 11h5V6q0-.424.287-.713A.97.97 0 0 1 12 5q.424 0 .713.287Q13 5.576 13 6v5h5q.424 0 .712.287.288.288.288.713 0 .424-.288.713A.97.97 0 0 1 18 13h-5v5q0 .424-.287.712A.97.97 0 0 1 12 19a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 18z"
|
||||
/>
|
||||
</svg>
|
||||
Add extensions
|
||||
</button>
|
||||
<div
|
||||
class="_separator_cqpyv_8"
|
||||
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_zysgz_8 mx_BaseAvatar mx_WidgetAvatar"
|
||||
data-color="1"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="img"
|
||||
style="--cpd-avatar-size: 24px;"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="_image_zysgz_43"
|
||||
data-type="round"
|
||||
height="24px"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
src="image-file-stub"
|
||||
width="24px"
|
||||
/>
|
||||
</span>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_lineClamp"
|
||||
>
|
||||
Custom Widget
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Pin"
|
||||
class="mx_AccessibleButton mx_ExtensionsCard_app_pinToggle"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</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_zysgz_8 mx_BaseAvatar mx_WidgetAvatar"
|
||||
data-color="1"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="img"
|
||||
style="--cpd-avatar-size: 24px;"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="_image_zysgz_43"
|
||||
data-type="round"
|
||||
height="24px"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
src="image-file-stub"
|
||||
width="24px"
|
||||
/>
|
||||
</span>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_lineClamp"
|
||||
>
|
||||
Jitsi
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Pin"
|
||||
class="mx_AccessibleButton mx_ExtensionsCard_app_pinToggle"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.769 2.857A.5.5 0 0 1 6.119 2h11.762a.5.5 0 0 1 .35.857L16.15 4.9a.5.5 0 0 0-.15.357v4.487a.5.5 0 0 0 .15.356l3.7 3.644a.5.5 0 0 1 .15.356v1.4a.5.5 0 0 1-.5.5H13v6a1 1 0 1 1-2 0v-6H4.5a.5.5 0 0 1-.5-.5v-1.4a.5.5 0 0 1 .15-.356l3.7-3.644A.5.5 0 0 0 8 9.744V5.257a.5.5 0 0 0-.15-.357z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<ExtensionsCard /> should show context menu on widget row 1`] = `null`;
|
||||
+526
@@ -0,0 +1,526 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`<PinnedMessagesCard /> should show the empty state when there are no pins 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_BaseCard mx_PinnedMessagesCard"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header_title"
|
||||
>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
|
||||
role="heading"
|
||||
>
|
||||
Pinned messages
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby="_r_f_"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="secondary"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 28px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="_flex_4dswl_9 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); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="32px"
|
||||
viewBox="0 0 24 24"
|
||||
width="32px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M6.119 2a.5.5 0 0 0-.35.857L7.85 4.9a.5.5 0 0 1 .15.357v4.487a.5.5 0 0 1-.15.356l-3.7 3.644A.5.5 0 0 0 4 14.1v1.4a.5.5 0 0 0 .5.5H11v6a1 1 0 1 0 2 0v-6h6.5a.5.5 0 0 0 .5-.5v-1.4a.5.5 0 0 0-.15-.356l-3.7-3.644a.5.5 0 0 1-.15-.356V5.257a.5.5 0 0 1 .15-.357l2.081-2.043a.5.5 0 0 0-.35-.857zM10 4h4v5.744a2.5 2.5 0 0 0 .746 1.781L17.26 14H6.74l2.514-2.475A2.5 2.5 0 0 0 10 9.744z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74"
|
||||
>
|
||||
Pin important messages so that they can be easily discovered
|
||||
</p>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50"
|
||||
>
|
||||
Select a message and choose “Pin” to it include here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<PinnedMessagesCard /> should show two pinned messages 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_BaseCard mx_PinnedMessagesCard"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header_title"
|
||||
>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
|
||||
role="heading"
|
||||
>
|
||||
2 Pinned messages
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby="_r_l_"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="secondary"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 28px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessagesCard_wrapper mx_PinnedMessagesCard_wrapper_unpin_all"
|
||||
role="list"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedEventTile"
|
||||
role="listitem"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
class="_avatar_zysgz_8 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_zysgz_55"
|
||||
data-color="3"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 32px;"
|
||||
title="@alice:example.org"
|
||||
>
|
||||
a
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PinnedEventTile_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedEventTile_top"
|
||||
>
|
||||
<span
|
||||
aria-labelledby="_r_s_"
|
||||
class="mx_PinnedEventTile_sender mx_Username_color3"
|
||||
>
|
||||
@alice:example.org
|
||||
</span>
|
||||
<button
|
||||
aria-describedby="_r_r_"
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="menu"
|
||||
aria-label="Open menu"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="primary"
|
||||
data-state="closed"
|
||||
id="radix-_r_11_"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 24px;"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MTextBody mx_EventTile_content"
|
||||
id="_r_r_"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTile_body translate"
|
||||
dir="auto"
|
||||
>
|
||||
The second one
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_separator_cqpyv_8 mx_PinnedMessagesCard_Separator"
|
||||
data-kind="primary"
|
||||
data-orientation="horizontal"
|
||||
role="separator"
|
||||
/>
|
||||
<div
|
||||
class="mx_PinnedEventTile"
|
||||
role="listitem"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
class="_avatar_zysgz_8 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_zysgz_55"
|
||||
data-color="3"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 32px;"
|
||||
title="@alice:example.org"
|
||||
>
|
||||
a
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PinnedEventTile_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedEventTile_top"
|
||||
>
|
||||
<span
|
||||
aria-labelledby="_r_14_"
|
||||
class="mx_PinnedEventTile_sender mx_Username_color3"
|
||||
>
|
||||
@alice:example.org
|
||||
</span>
|
||||
<button
|
||||
aria-describedby="_r_13_"
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="menu"
|
||||
aria-label="Open menu"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="primary"
|
||||
data-state="closed"
|
||||
id="radix-_r_19_"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 24px;"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MTextBody mx_EventTile_content"
|
||||
id="_r_13_"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTile_body translate"
|
||||
dir="auto"
|
||||
>
|
||||
First pinned message
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PinnedMessagesCard_unpin"
|
||||
>
|
||||
<button
|
||||
class="_button_13vu4_8"
|
||||
data-kind="tertiary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Unpin all messages
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<PinnedMessagesCard /> unpin all should not allow to unpinall 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_BaseCard mx_PinnedMessagesCard"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header_title"
|
||||
>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
|
||||
role="heading"
|
||||
>
|
||||
2 Pinned messages
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby="_r_10f_"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="secondary"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 28px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedMessagesCard_wrapper"
|
||||
role="list"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedEventTile"
|
||||
role="listitem"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
class="_avatar_zysgz_8 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_zysgz_55"
|
||||
data-color="3"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 32px;"
|
||||
title="@alice:example.org"
|
||||
>
|
||||
a
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PinnedEventTile_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedEventTile_top"
|
||||
>
|
||||
<span
|
||||
aria-labelledby="_r_10m_"
|
||||
class="mx_PinnedEventTile_sender mx_Username_color3"
|
||||
>
|
||||
@alice:example.org
|
||||
</span>
|
||||
<button
|
||||
aria-describedby="_r_10l_"
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="menu"
|
||||
aria-label="Open menu"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="primary"
|
||||
data-state="closed"
|
||||
id="radix-_r_10r_"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 24px;"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MTextBody mx_EventTile_content"
|
||||
id="_r_10l_"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTile_body translate"
|
||||
dir="auto"
|
||||
>
|
||||
The second one
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_separator_cqpyv_8 mx_PinnedMessagesCard_Separator"
|
||||
data-kind="primary"
|
||||
data-orientation="horizontal"
|
||||
role="separator"
|
||||
/>
|
||||
<div
|
||||
class="mx_PinnedEventTile"
|
||||
role="listitem"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
class="_avatar_zysgz_8 mx_BaseAvatar mx_PinnedEventTile_senderAvatar _avatar-imageless_zysgz_55"
|
||||
data-color="3"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 32px;"
|
||||
title="@alice:example.org"
|
||||
>
|
||||
a
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PinnedEventTile_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_PinnedEventTile_top"
|
||||
>
|
||||
<span
|
||||
aria-labelledby="_r_10u_"
|
||||
class="mx_PinnedEventTile_sender mx_Username_color3"
|
||||
>
|
||||
@alice:example.org
|
||||
</span>
|
||||
<button
|
||||
aria-describedby="_r_10t_"
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="menu"
|
||||
aria-label="Open menu"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="primary"
|
||||
data-state="closed"
|
||||
id="radix-_r_113_"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 24px;"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 14q-.824 0-1.412-.588A1.93 1.93 0 0 1 4 12q0-.825.588-1.412A1.93 1.93 0 0 1 6 10q.824 0 1.412.588Q8 11.175 8 12t-.588 1.412A1.93 1.93 0 0 1 6 14m6 0q-.825 0-1.412-.588A1.93 1.93 0 0 1 10 12q0-.825.588-1.412A1.93 1.93 0 0 1 12 10q.825 0 1.412.588Q14 11.175 14 12t-.588 1.412A1.93 1.93 0 0 1 12 14m6 0q-.824 0-1.413-.588A1.93 1.93 0 0 1 16 12q0-.825.587-1.412A1.93 1.93 0 0 1 18 10q.824 0 1.413.588Q20 11.175 20 12t-.587 1.412A1.93 1.93 0 0 1 18 14"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MTextBody mx_EventTile_content"
|
||||
id="_r_10t_"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTile_body translate"
|
||||
dir="auto"
|
||||
>
|
||||
First pinned message
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
+2168
File diff suppressed because it is too large
Load Diff
+646
@@ -0,0 +1,646 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_BaseCard mx_UserInfo"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header_title"
|
||||
>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
|
||||
role="heading"
|
||||
>
|
||||
Profile
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby="_r_7c_"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="secondary"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 28px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar_transition"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar_transition_child"
|
||||
>
|
||||
<button
|
||||
aria-label="Profile picture"
|
||||
aria-live="off"
|
||||
class="_avatar_zysgz_8 mx_BaseAvatar _avatar-imageless_zysgz_55"
|
||||
data-color="3"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="button"
|
||||
style="--cpd-avatar-size: 120px;"
|
||||
>
|
||||
u
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container mx_UserInfo_header"
|
||||
>
|
||||
<div
|
||||
class="_flex_4dswl_9 mx_UserInfo_profile"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<h1
|
||||
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
|
||||
dir="auto"
|
||||
>
|
||||
<div
|
||||
class="_flex_4dswl_9 mx_UserInfo_profile_name"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
@user:example.com
|
||||
</div>
|
||||
</h1>
|
||||
<div
|
||||
class="mx_PresenceLabel mx_UserInfo_profileStatus"
|
||||
>
|
||||
Unknown
|
||||
</div>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
|
||||
>
|
||||
<div
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
customUserIdentifier
|
||||
<div
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14 5H5v9h1a1 1 0 1 1 0 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1a1 1 0 1 1-2 0z"
|
||||
/>
|
||||
<path
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="_flex_4dswl_9 mx_UserInfo_verification"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31 mx_UserInfo_verification_unavailable"
|
||||
>
|
||||
(
|
||||
User verification unavailable
|
||||
)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container"
|
||||
>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m1.5 21.25 1.45-4.95a10.2 10.2 0 0 1-.712-2.1A10.2 10.2 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 22q-1.125 0-2.2-.238a10.2 10.2 0 0 1-2.1-.712L2.75 22.5a.94.94 0 0 1-1-.25.94.94 0 0 1-.25-1m2.45-1.2 3.2-.95a1 1 0 0 1 .275-.062q.15-.013.275-.013.225 0 .438.038.212.036.412.137a7.4 7.4 0 0 0 1.675.6Q11.1 20 12 20q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12q0 .9.2 1.775t.6 1.675q.176.325.188.688t-.088.712z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Send message
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26 _disabled_lqfwq_118"
|
||||
data-kind="primary"
|
||||
disabled=""
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Jump to read receipt
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 16a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 15V7.85L9.125 9.725q-.3.3-.7.3T7.7 9.7a.93.93 0 0 1-.288-.713A.98.98 0 0 1 7.7 8.3l3.6-3.6q.15-.15.325-.213.175-.062.375-.062t.375.062a.9.9 0 0 1 .325.213l3.6 3.6q.3.3.287.712a.98.98 0 0 1-.287.688q-.3.3-.713.313a.93.93 0 0 1-.712-.288L13 7.85V15q0 .424-.287.713A.97.97 0 0 1 12 16m-6 4q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 18v-2q0-.424.287-.713A.97.97 0 0 1 5 15q.424 0 .713.287Q6 15.576 6 16v2h12v-2q0-.424.288-.713A.97.97 0 0 1 19 15q.424 0 .712.287.288.288.288.713v2q0 .824-.587 1.413A1.93 1.93 0 0 1 18 20z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Share profile
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container"
|
||||
>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26"
|
||||
data-kind="critical"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 22a9.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 12q0-1.35-.437-2.6A8 8 0 0 0 18.3 7.1L7.1 18.3q1.05.825 2.3 1.262T12 20m-6.3-3.1L16.9 5.7a8 8 0 0 0-2.3-1.263A7.8 7.8 0 0 0 12 4Q8.65 4 6.325 6.325T4 12q0 1.35.438 2.6A8 8 0 0 0 5.7 16.9"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Ignore
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<UserInfo /> with crypto enabled should render a deactivate button for users of the same server if we are a server admin 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_BaseCard mx_UserInfo"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header"
|
||||
>
|
||||
<div
|
||||
class="mx_BaseCard_header_title"
|
||||
>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 mx_BaseCard_header_title_heading"
|
||||
role="heading"
|
||||
>
|
||||
Profile
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby="_r_7m_"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="secondary"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 28px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar_transition"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar_transition_child"
|
||||
>
|
||||
<button
|
||||
aria-label="Profile picture"
|
||||
aria-live="off"
|
||||
class="_avatar_zysgz_8 mx_BaseAvatar _avatar-imageless_zysgz_55"
|
||||
data-color="3"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="button"
|
||||
style="--cpd-avatar-size: 120px;"
|
||||
>
|
||||
u
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container mx_UserInfo_header"
|
||||
>
|
||||
<div
|
||||
class="_flex_4dswl_9 mx_UserInfo_profile"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<h1
|
||||
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
|
||||
dir="auto"
|
||||
>
|
||||
<div
|
||||
class="_flex_4dswl_9 mx_UserInfo_profile_name"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
@user:example.com
|
||||
</div>
|
||||
</h1>
|
||||
<div
|
||||
class="mx_PresenceLabel mx_UserInfo_profileStatus"
|
||||
>
|
||||
Unknown
|
||||
</div>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
|
||||
>
|
||||
<div
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
customUserIdentifier
|
||||
<div
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14 5H5v9h1a1 1 0 1 1 0 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1a1 1 0 1 1-2 0z"
|
||||
/>
|
||||
<path
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="_flex_4dswl_9 mx_UserInfo_verification"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31 mx_UserInfo_verification_unavailable"
|
||||
>
|
||||
(
|
||||
User verification unavailable
|
||||
)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container"
|
||||
>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m1.5 21.25 1.45-4.95a10.2 10.2 0 0 1-.712-2.1A10.2 10.2 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 22q-1.125 0-2.2-.238a10.2 10.2 0 0 1-2.1-.712L2.75 22.5a.94.94 0 0 1-1-.25.94.94 0 0 1-.25-1m2.45-1.2 3.2-.95a1 1 0 0 1 .275-.062q.15-.013.275-.013.225 0 .438.038.212.036.412.137a7.4 7.4 0 0 0 1.675.6Q11.1 20 12 20q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12q0 .9.2 1.775t.6 1.675q.176.325.188.688t-.088.712z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Send message
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26 _disabled_lqfwq_118"
|
||||
data-kind="primary"
|
||||
disabled=""
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Jump to read receipt
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 16a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 15V7.85L9.125 9.725q-.3.3-.7.3T7.7 9.7a.93.93 0 0 1-.288-.713A.98.98 0 0 1 7.7 8.3l3.6-3.6q.15-.15.325-.213.175-.062.375-.062t.375.062a.9.9 0 0 1 .325.213l3.6 3.6q.3.3.287.712a.98.98 0 0 1-.287.688q-.3.3-.713.313a.93.93 0 0 1-.712-.288L13 7.85V15q0 .424-.287.713A.97.97 0 0 1 12 16m-6 4q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 18v-2q0-.424.287-.713A.97.97 0 0 1 5 15q.424 0 .713.287Q6 15.576 6 16v2h12v-2q0-.424.288-.713A.97.97 0 0 1 19 15q.424 0 .712.287.288.288.288.713v2q0 .824-.587 1.413A1.93 1.93 0 0 1 18 20z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Share profile
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container"
|
||||
>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26"
|
||||
data-kind="critical"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7 21q-.824 0-1.412-.587A1.93 1.93 0 0 1 5 19V6a.97.97 0 0 1-.713-.287A.97.97 0 0 1 4 5q0-.424.287-.713A.97.97 0 0 1 5 4h4q0-.424.287-.712A.97.97 0 0 1 10 3h4q.424 0 .713.288Q15 3.575 15 4h4q.424 0 .712.287Q20 4.576 20 5t-.288.713A.97.97 0 0 1 19 6v13q0 .824-.587 1.413A1.93 1.93 0 0 1 17 21zM7 6v13h10V6zm2 10q0 .424.287.712Q9.576 17 10 17t.713-.288A.97.97 0 0 0 11 16V9a.97.97 0 0 0-.287-.713A.97.97 0 0 0 10 8a.97.97 0 0 0-.713.287A.97.97 0 0 0 9 9zm4 0q0 .424.287.712.288.288.713.288.424 0 .713-.288A.97.97 0 0 0 15 16V9a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 8a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 9z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Deactivate user
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container"
|
||||
>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26"
|
||||
data-kind="critical"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 22a9.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 12q0-1.35-.437-2.6A8 8 0 0 0 18.3 7.1L7.1 18.3q1.05.825 2.3 1.262T12 20m-6.3-3.1L16.9 5.7a8 8 0 0 0-2.3-1.263A7.8 7.8 0 0 0 12 4Q8.65 4 6.325 6.325T4 12q0 1.35.438 2.6A8 8 0 0 0 5.7 16.9"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Ignore
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
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, fireEvent } from "jest-matrix-react";
|
||||
import { type Room, type RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import { UserInfoAdminToolsContainer } from "../../../../../../src/components/views/right_panel/user_info/UserInfoAdminToolsContainer";
|
||||
import { useUserInfoAdminToolsContainerViewModel } from "../../../../../../src/components/viewmodels/right_panel/user_info/admin/UserInfoAdminToolsContainerViewModel";
|
||||
import { useRoomKickButtonViewModel } from "../../../../../../src/components/viewmodels/right_panel/user_info/admin/UserInfoKickButtonViewModel";
|
||||
import { useBanButtonViewModel } from "../../../../../../src/components/viewmodels/right_panel/user_info/admin/UserInfoBanButtonViewModel";
|
||||
import { useMuteButtonViewModel } from "../../../../../../src/components/viewmodels/right_panel/user_info/admin/UserInfoMuteButtonViewModel";
|
||||
import { useRedactMessagesButtonViewModel } from "../../../../../../src/components/viewmodels/right_panel/user_info/admin/UserInfoRedactButtonViewModel";
|
||||
import { stubClient } from "../../../../../test-utils";
|
||||
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
||||
|
||||
jest.mock("../../../../../../src/utils/DMRoomMap", () => {
|
||||
const mock = {
|
||||
getUserIdForRoomId: jest.fn(),
|
||||
getDMRoomsForUserId: jest.fn(),
|
||||
};
|
||||
|
||||
return {
|
||||
shared: jest.fn().mockReturnValue(mock),
|
||||
sharedInstance: mock,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock(
|
||||
"../../../../../../src/components/viewmodels/right_panel/user_info/admin/UserInfoAdminToolsContainerViewModel",
|
||||
() => ({
|
||||
useUserInfoAdminToolsContainerViewModel: jest.fn().mockReturnValue({
|
||||
isCurrentUserInTheRoom: true,
|
||||
shouldShowKickButton: true,
|
||||
shouldShowBanButton: true,
|
||||
shouldShowMuteButton: true,
|
||||
shouldShowRedactButton: true,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
jest.mock(
|
||||
"../../../../../../src/components/viewmodels/right_panel/user_info/admin/UserInfoKickButtonViewModel",
|
||||
() => ({
|
||||
useRoomKickButtonViewModel: jest.fn().mockReturnValue({
|
||||
canUserBeKicked: true,
|
||||
kickLabel: "Kick",
|
||||
onKickClick: jest.fn(),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
jest.mock("../../../../../../src/components/viewmodels/right_panel/user_info/admin/UserInfoBanButtonViewModel", () => ({
|
||||
useBanButtonViewModel: jest.fn().mockReturnValue({
|
||||
banLabel: "Ban",
|
||||
onBanOrUnbanClick: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
"../../../../../../src/components/viewmodels/right_panel/user_info/admin/UserInfoMuteButtonViewModel",
|
||||
() => ({
|
||||
useMuteButtonViewModel: jest.fn().mockReturnValue({
|
||||
isMemberInTheRoom: true,
|
||||
muteLabel: "Mute",
|
||||
onMuteButtonClick: jest.fn(),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
jest.mock(
|
||||
"../../../../../../src/components/viewmodels/right_panel/user_info/admin/UserInfoRedactButtonViewModel",
|
||||
() => ({
|
||||
useRedactMessagesButtonViewModel: jest.fn().mockReturnValue({
|
||||
onRedactAllMessagesClick: jest.fn(),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const defaultRoomId = "!fkfk";
|
||||
|
||||
describe("UserInfoAdminToolsContainer", () => {
|
||||
// Setup it data
|
||||
const mockRoom = mocked({
|
||||
roomId: defaultRoomId,
|
||||
getType: jest.fn().mockReturnValue(undefined),
|
||||
isSpaceRoom: jest.fn().mockReturnValue(false),
|
||||
getMember: jest.fn().mockReturnValue(undefined),
|
||||
getMxcAvatarUrl: jest.fn().mockReturnValue("mock-avatar-url"),
|
||||
name: "test room",
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
currentState: {
|
||||
getStateEvents: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
},
|
||||
getEventReadUpTo: jest.fn(),
|
||||
} as unknown as Room);
|
||||
|
||||
const mockMember = {
|
||||
userId: "@user:example.com",
|
||||
membership: "join",
|
||||
powerLevel: 0,
|
||||
} as unknown as RoomMember;
|
||||
|
||||
const mockPowerLevels = {
|
||||
users: {
|
||||
"@currentuser:example.com": 100,
|
||||
},
|
||||
events: {},
|
||||
state_default: 50,
|
||||
ban: 50,
|
||||
kick: 50,
|
||||
redact: 50,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
room: mockRoom,
|
||||
member: mockMember,
|
||||
powerLevels: mockPowerLevels,
|
||||
isUpdating: false,
|
||||
startUpdating: jest.fn(),
|
||||
stopUpdating: jest.fn(),
|
||||
};
|
||||
|
||||
const mockMatrixClient = stubClient();
|
||||
|
||||
const renderComponent = (props = defaultProps) => {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={mockMatrixClient}>
|
||||
<UserInfoAdminToolsContainer {...props} />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mocked(useUserInfoAdminToolsContainerViewModel).mockReturnValue({
|
||||
isCurrentUserInTheRoom: true,
|
||||
shouldShowKickButton: true,
|
||||
shouldShowBanButton: true,
|
||||
shouldShowMuteButton: true,
|
||||
shouldShowRedactButton: true,
|
||||
});
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders all admin tools when user has permissions", () => {
|
||||
renderComponent();
|
||||
|
||||
// Check that all buttons are rendered
|
||||
expect(screen.getByText("Mute")).toBeInTheDocument();
|
||||
expect(screen.getByText("Kick")).toBeInTheDocument();
|
||||
expect(screen.getByText("Ban")).toBeInTheDocument();
|
||||
expect(screen.getByText("Remove messages")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders no admin tools when current user is not in the room", () => {
|
||||
mocked(useUserInfoAdminToolsContainerViewModel).mockReturnValue({
|
||||
isCurrentUserInTheRoom: false,
|
||||
shouldShowKickButton: false,
|
||||
shouldShowBanButton: false,
|
||||
shouldShowMuteButton: false,
|
||||
shouldShowRedactButton: false,
|
||||
});
|
||||
|
||||
const { container } = renderComponent();
|
||||
|
||||
// Should render an empty div
|
||||
expect(container.firstChild).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("renders children when provided", () => {
|
||||
render(
|
||||
<UserInfoAdminToolsContainer {...defaultProps}>
|
||||
<div data-testid="child-element">Custom Child</div>
|
||||
</UserInfoAdminToolsContainer>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("child-element")).toBeInTheDocument();
|
||||
expect(screen.getByText("Custom Child")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("Kick behavior", () => {
|
||||
it("clicking kick button calls the appropriate handler", () => {
|
||||
const mockedOnKickClick = jest.fn();
|
||||
mocked(useRoomKickButtonViewModel).mockReturnValue({
|
||||
canUserBeKicked: true,
|
||||
kickLabel: "Kick",
|
||||
onKickClick: mockedOnKickClick,
|
||||
});
|
||||
renderComponent();
|
||||
|
||||
const kickButton = screen.getByText("Kick");
|
||||
fireEvent.click(kickButton);
|
||||
|
||||
expect(mockedOnKickClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not display kick buttun if user can't be kicked", () => {
|
||||
mocked(useRoomKickButtonViewModel).mockReturnValue({
|
||||
canUserBeKicked: false,
|
||||
kickLabel: "Kick",
|
||||
onKickClick: jest.fn(),
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
|
||||
expect(screen.queryByText("Kick")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display the correct label when user can be disinvited", () => {
|
||||
mocked(useRoomKickButtonViewModel).mockReturnValue({
|
||||
canUserBeKicked: true,
|
||||
kickLabel: "Disinvite",
|
||||
onKickClick: jest.fn(),
|
||||
});
|
||||
|
||||
renderComponent({
|
||||
...defaultProps,
|
||||
member: mockMember,
|
||||
});
|
||||
|
||||
expect(screen.getByText("Disinvite")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Ban behavior", () => {
|
||||
it("clicking ban button calls the appropriate handler", () => {
|
||||
const mockedOnBanOrUnbanClick = jest.fn();
|
||||
mocked(useBanButtonViewModel).mockReturnValue({
|
||||
banLabel: "Ban",
|
||||
onBanOrUnbanClick: mockedOnBanOrUnbanClick,
|
||||
});
|
||||
renderComponent();
|
||||
|
||||
const banButton = screen.getByText("Ban");
|
||||
fireEvent.click(banButton);
|
||||
|
||||
expect(mockedOnBanOrUnbanClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should display the correct label", () => {
|
||||
const mockedOnBanOrUnbanClick = jest.fn();
|
||||
mocked(useBanButtonViewModel).mockReturnValue({
|
||||
banLabel: "Unban",
|
||||
onBanOrUnbanClick: mockedOnBanOrUnbanClick,
|
||||
});
|
||||
renderComponent();
|
||||
|
||||
// The label should be "Unban"
|
||||
expect(screen.getByText("Unban")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Mute behavior", () => {
|
||||
it("clicking mute button calls the appropriate handler", () => {
|
||||
const mockedOnMuteButtonClick = jest.fn();
|
||||
mocked(useMuteButtonViewModel).mockReturnValue({
|
||||
isMemberInTheRoom: true,
|
||||
muteLabel: "Mute",
|
||||
onMuteButtonClick: mockedOnMuteButtonClick,
|
||||
});
|
||||
renderComponent();
|
||||
|
||||
const muteButton = screen.getByText("Mute");
|
||||
fireEvent.click(muteButton);
|
||||
|
||||
expect(mockedOnMuteButtonClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not display mute button if user is not in the room", () => {
|
||||
mocked(useMuteButtonViewModel).mockReturnValue({
|
||||
isMemberInTheRoom: false,
|
||||
muteLabel: "Mute",
|
||||
onMuteButtonClick: jest.fn(),
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
|
||||
expect(screen.queryByText("Mute")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display the correct label", () => {
|
||||
mocked(useMuteButtonViewModel).mockReturnValue({
|
||||
isMemberInTheRoom: true,
|
||||
muteLabel: "Mute",
|
||||
onMuteButtonClick: jest.fn(),
|
||||
});
|
||||
renderComponent();
|
||||
|
||||
expect(screen.getByText("Mute")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Redact behavior", () => {
|
||||
it("clicking redact button calls the appropriate handler", () => {
|
||||
const mockedOnRedactAllMessagesClick = jest.fn();
|
||||
mocked(useRedactMessagesButtonViewModel).mockReturnValue({
|
||||
onRedactAllMessagesClick: mockedOnRedactAllMessagesClick,
|
||||
});
|
||||
renderComponent();
|
||||
|
||||
const redactButton = screen.getByText("Remove messages");
|
||||
fireEvent.click(redactButton);
|
||||
|
||||
expect(mockedOnRedactAllMessagesClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
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 { mocked } from "jest-mock";
|
||||
import { type MatrixClient, type Room, RoomMember, type User } from "matrix-js-sdk/src/matrix";
|
||||
import { logRoles, render, screen } from "jest-matrix-react";
|
||||
|
||||
import { createTestClient, mkStubRoom } from "../../../../../test-utils";
|
||||
import {
|
||||
type UserInfoBasicState,
|
||||
useUserInfoBasicViewModel,
|
||||
} from "../../../../../../src/components/viewmodels/right_panel/user_info/UserInfoBasicViewModel";
|
||||
import { UserInfoBasicView } from "../../../../../../src/components/views/right_panel/user_info/UserInfoBasicView";
|
||||
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
||||
|
||||
const defaultRoomPermissions = {
|
||||
canEdit: true,
|
||||
canInvite: true,
|
||||
modifyLevelMax: -1,
|
||||
};
|
||||
jest.mock("../../../../../../src/components/viewmodels/right_panel/user_info/UserInfoBasicViewModel", () => ({
|
||||
useUserInfoBasicViewModel: jest.fn(),
|
||||
useRoomPermissions: () => defaultRoomPermissions,
|
||||
}));
|
||||
|
||||
describe("<UserInfoBasic />", () => {
|
||||
const defaultValue: UserInfoBasicState = {
|
||||
powerLevels: {},
|
||||
roomPermissions: defaultRoomPermissions,
|
||||
pendingUpdateCount: 0,
|
||||
isMe: false,
|
||||
isRoomDMForMember: false,
|
||||
showDeactivateButton: true,
|
||||
onSynapseDeactivate: jest.fn(),
|
||||
startUpdating: jest.fn(),
|
||||
stopUpdating: jest.fn(),
|
||||
};
|
||||
|
||||
const defaultRoomId = "!fkfk";
|
||||
const defaultUserId = "@user:example.com";
|
||||
|
||||
const defaultMember = new RoomMember(defaultRoomId, defaultUserId);
|
||||
let defaultRoom: Room;
|
||||
|
||||
let defaultProps: { member: User | RoomMember; room: Room };
|
||||
let matrixClient: MatrixClient;
|
||||
|
||||
const renderComponent = (props = defaultProps) => {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={matrixClient}>
|
||||
<UserInfoBasicView {...props} />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
beforeEach(() => {
|
||||
matrixClient = createTestClient();
|
||||
defaultRoom = mkStubRoom(defaultRoomId, defaultRoomId, matrixClient);
|
||||
defaultProps = {
|
||||
member: defaultMember,
|
||||
room: defaultRoom,
|
||||
};
|
||||
});
|
||||
|
||||
it("should display the defaut values", () => {
|
||||
mocked(useUserInfoBasicViewModel).mockReturnValue(defaultValue);
|
||||
const { container } = renderComponent();
|
||||
logRoles(container);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should not show ignore button if user is me", () => {
|
||||
const state: UserInfoBasicState = { ...defaultValue, isMe: true };
|
||||
mocked(useUserInfoBasicViewModel).mockReturnValue(state);
|
||||
renderComponent();
|
||||
|
||||
const ignoreButton = screen.queryByRole("button", { name: "Ignore" });
|
||||
expect(ignoreButton).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not show deactivate button", () => {
|
||||
const state: UserInfoBasicState = { ...defaultValue, showDeactivateButton: false };
|
||||
mocked(useUserInfoBasicViewModel).mockReturnValue(state);
|
||||
renderComponent();
|
||||
|
||||
const deactivateButton = screen.queryByRole("button", { name: "Deactivate user" });
|
||||
expect(deactivateButton).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not show powerlevels selector for dm", () => {
|
||||
const state: UserInfoBasicState = { ...defaultValue, isRoomDMForMember: true };
|
||||
mocked(useUserInfoBasicViewModel).mockReturnValue(state);
|
||||
const { container } = renderComponent();
|
||||
|
||||
logRoles(container);
|
||||
const powserlevel = screen.queryByRole("option", { name: "Default" });
|
||||
expect(powserlevel).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show spinner if pending update is > 0", () => {
|
||||
const state: UserInfoBasicState = { ...defaultValue, pendingUpdateCount: 2 };
|
||||
mocked(useUserInfoBasicViewModel).mockReturnValue(state);
|
||||
renderComponent();
|
||||
|
||||
const spinner = screen.getByTestId("spinner");
|
||||
expect(spinner).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
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 { mocked } from "jest-mock";
|
||||
import { type Room, RoomMember, type User } from "matrix-js-sdk/src/matrix";
|
||||
import { fireEvent, render, screen } from "jest-matrix-react";
|
||||
|
||||
import { mkStubRoom, stubClient } from "../../../../../test-utils";
|
||||
import {
|
||||
useUserInfoBasicOptionsViewModel,
|
||||
type UserInfoBasicOptionsState,
|
||||
} from "../../../../../../src/components/viewmodels/right_panel/user_info/UserInfoBasicOptionsViewModel";
|
||||
import { UserInfoBasicOptionsView } from "../../../../../../src/components/views/right_panel/user_info/UserInfoBasicOptionsView";
|
||||
import { UIComponent } from "../../../../../../src/settings/UIFeature";
|
||||
import { shouldShowComponent } from "../../../../../../src/customisations/helpers/UIComponents";
|
||||
import { type Member } from "../../../../../../src/components/views/right_panel/UserInfo";
|
||||
|
||||
jest.mock("../../../../../../src/components/viewmodels/right_panel/user_info/UserInfoBasicOptionsViewModel", () => ({
|
||||
useUserInfoBasicOptionsViewModel: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => {
|
||||
const original = jest.requireActual("../../../../../../src/customisations/helpers/UIComponents");
|
||||
return {
|
||||
shouldShowComponent: jest.fn().mockImplementation(original.shouldShowComponent),
|
||||
};
|
||||
});
|
||||
|
||||
describe("<UserOptionsSection />", () => {
|
||||
const defaultValue: UserInfoBasicOptionsState = {
|
||||
isMe: false,
|
||||
showInviteButton: false,
|
||||
showInsertPillButton: false,
|
||||
readReceiptButtonDisabled: false,
|
||||
onInsertPillButton: () => jest.fn(),
|
||||
onReadReceiptButton: () => jest.fn(),
|
||||
onShareUserClick: () => jest.fn(),
|
||||
onInviteUserButton: (fallbackRoomId: string, evt: Event) => Promise.resolve(),
|
||||
onOpenDmForUser: (member: Member) => Promise.resolve(),
|
||||
};
|
||||
|
||||
const defaultRoomId = "!fkfk";
|
||||
const defaultUserId = "@user:example.com";
|
||||
|
||||
const defaultMember = new RoomMember(defaultRoomId, defaultUserId);
|
||||
let defaultRoom: Room;
|
||||
|
||||
let defaultProps: { member: User | RoomMember; room: Room };
|
||||
|
||||
beforeEach(() => {
|
||||
const matrixClient = stubClient();
|
||||
defaultRoom = mkStubRoom(defaultRoomId, defaultRoomId, matrixClient);
|
||||
defaultProps = {
|
||||
member: defaultMember,
|
||||
room: defaultRoom,
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should always display sharedButton when user is not me", () => {
|
||||
// User is not me by default
|
||||
mocked(useUserInfoBasicOptionsViewModel).mockReturnValue({ ...defaultValue });
|
||||
render(<UserInfoBasicOptionsView {...defaultProps} />);
|
||||
const sharedButton = screen.getByRole("button", { name: "Share profile" });
|
||||
expect(sharedButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should always display sharedButton when user is me", () => {
|
||||
const propsWithMe = { ...defaultProps };
|
||||
const onShareUserClick = jest.fn();
|
||||
const state = { ...defaultValue, isMe: true, onShareUserClick };
|
||||
|
||||
mocked(useUserInfoBasicOptionsViewModel).mockReturnValue(state);
|
||||
render(<UserInfoBasicOptionsView {...propsWithMe} />);
|
||||
|
||||
const sharedButton2 = screen.getByRole("button", { name: "Share profile" });
|
||||
expect(sharedButton2).toBeInTheDocument();
|
||||
|
||||
// clicking on the share profile button
|
||||
fireEvent.click(sharedButton2);
|
||||
|
||||
expect(onShareUserClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should show insert pill button when user is not me and showinsertpill is true", () => {
|
||||
const onInsertPillButton = jest.fn();
|
||||
const state = { ...defaultValue, showInsertPillButton: true, onInsertPillButton };
|
||||
// User is not me and showInsertpill is true
|
||||
mocked(useUserInfoBasicOptionsViewModel).mockReturnValue(state);
|
||||
render(<UserInfoBasicOptionsView {...defaultProps} />);
|
||||
|
||||
const insertPillButton = screen.getByRole("button", { name: "Mention" });
|
||||
expect(insertPillButton).toBeInTheDocument();
|
||||
|
||||
// clicking on the insert pill button
|
||||
fireEvent.click(insertPillButton);
|
||||
|
||||
expect(onInsertPillButton).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not show insert pill button when user is not me and showinsertpill is false", () => {
|
||||
mocked(useUserInfoBasicOptionsViewModel).mockReturnValue({ ...defaultValue, showInsertPillButton: false });
|
||||
render(<UserInfoBasicOptionsView {...defaultProps} />);
|
||||
const insertPillButton = screen.queryByRole("button", { name: "Mention" });
|
||||
expect(insertPillButton).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not show insert pill button when user is me", () => {
|
||||
// User is me, should not see the insert button even when show insertpill is true
|
||||
mocked(useUserInfoBasicOptionsViewModel).mockReturnValue({
|
||||
...defaultValue,
|
||||
showInsertPillButton: true,
|
||||
isMe: true,
|
||||
});
|
||||
const propsWithMe = { ...defaultProps };
|
||||
render(<UserInfoBasicOptionsView {...propsWithMe} />);
|
||||
const insertPillButton = screen.queryByRole("button", { name: "Mention" });
|
||||
expect(insertPillButton).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not show readreceiptbutton when user is me", () => {
|
||||
mocked(useUserInfoBasicOptionsViewModel).mockReturnValue({
|
||||
...defaultValue,
|
||||
readReceiptButtonDisabled: true,
|
||||
isMe: true,
|
||||
});
|
||||
const propsWithMe = { ...defaultProps };
|
||||
render(<UserInfoBasicOptionsView {...propsWithMe} />);
|
||||
|
||||
const readReceiptButton = screen.queryByRole("button", { name: "Jump to read receipt" });
|
||||
expect(readReceiptButton).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show disable readreceiptbutton when readReceiptButtonDisabled is true", () => {
|
||||
mocked(useUserInfoBasicOptionsViewModel).mockReturnValue({ ...defaultValue, readReceiptButtonDisabled: true });
|
||||
render(<UserInfoBasicOptionsView {...defaultProps} />);
|
||||
|
||||
const readReceiptButton = screen.getByRole("button", { name: "Jump to read receipt" });
|
||||
expect(readReceiptButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should not show disable readreceiptbutton when readReceiptButtonDisabled is false", () => {
|
||||
const onReadReceiptButton = jest.fn();
|
||||
const state = { ...defaultValue, readReceiptButtonDisabled: false, onReadReceiptButton };
|
||||
mocked(useUserInfoBasicOptionsViewModel).mockReturnValue(state);
|
||||
render(<UserInfoBasicOptionsView {...defaultProps} />);
|
||||
|
||||
const readReceiptButton = screen.getByRole("button", { name: "Jump to read receipt" });
|
||||
expect(readReceiptButton).not.toBeDisabled();
|
||||
|
||||
// clicking on the read receipt button
|
||||
fireEvent.click(readReceiptButton);
|
||||
|
||||
expect(onReadReceiptButton).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should show not show invite button if shouldShowComponent is false", () => {
|
||||
mocked(useUserInfoBasicOptionsViewModel).mockReturnValue({ ...defaultValue, showInviteButton: true });
|
||||
mocked(shouldShowComponent).mockReturnValue(false);
|
||||
render(<UserInfoBasicOptionsView {...defaultProps} />);
|
||||
|
||||
const inviteButton = screen.queryByRole("button", { name: "Invite" });
|
||||
expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.InviteUsers);
|
||||
expect(inviteButton).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show show invite button if shouldShowComponent is true", () => {
|
||||
const onInviteUserButton = jest.fn();
|
||||
const state = { ...defaultValue, showInviteButton: true, onInviteUserButton };
|
||||
mocked(useUserInfoBasicOptionsViewModel).mockReturnValue(state);
|
||||
mocked(shouldShowComponent).mockReturnValue(true);
|
||||
render(<UserInfoBasicOptionsView {...defaultProps} />);
|
||||
|
||||
const inviteButton = screen.getByRole("button", { name: "Invite" });
|
||||
expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.InviteUsers);
|
||||
expect(inviteButton).toBeInTheDocument();
|
||||
|
||||
// clicking on the invite button
|
||||
fireEvent.click(inviteButton);
|
||||
expect(onInviteUserButton).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should show directMessageButton when user is not me", () => {
|
||||
// User is not me, direct message button should display
|
||||
mocked(useUserInfoBasicOptionsViewModel).mockReturnValue(defaultValue);
|
||||
mocked(shouldShowComponent).mockReturnValue(true);
|
||||
render(<UserInfoBasicOptionsView {...defaultProps} />);
|
||||
const dmButton = screen.getByRole("button", { name: "Send message" });
|
||||
expect(dmButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not show directMessageButton when user is me", () => {
|
||||
mocked(useUserInfoBasicOptionsViewModel).mockReturnValue({ ...defaultValue, isMe: true });
|
||||
mocked(shouldShowComponent).mockReturnValue(true);
|
||||
const propsWithMe = { ...defaultProps };
|
||||
render(<UserInfoBasicOptionsView {...propsWithMe} />);
|
||||
const dmButton = screen.queryByRole("button", { name: "Send message" });
|
||||
expect(dmButton).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked, type Mocked } from "jest-mock";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { UserVerificationStatus, type CryptoApi } from "matrix-js-sdk/src/crypto-api";
|
||||
import { Device, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { render, waitFor, screen } from "jest-matrix-react";
|
||||
import React from "react";
|
||||
|
||||
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
||||
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
||||
import { UserInfoHeaderVerificationView } from "../../../../../../src/components/views/right_panel/user_info/UserInfoHeaderVerificationView";
|
||||
import { createTestClient } from "../../../../../test-utils";
|
||||
|
||||
describe("<UserInfoHeaderVerificationView />", () => {
|
||||
const defaultRoomId = "!fkfk";
|
||||
const defaultUserId = "@user:example.com";
|
||||
|
||||
const defaultMember = new RoomMember(defaultRoomId, defaultUserId);
|
||||
|
||||
let mockClient: MatrixClient;
|
||||
let mockCrypto: Mocked<CryptoApi>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockCrypto = mocked({
|
||||
bootstrapSecretStorage: jest.fn(),
|
||||
bootstrapCrossSigning: jest.fn(),
|
||||
getCrossSigningKeyId: jest.fn(),
|
||||
getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
|
||||
getUserDeviceInfo: jest.fn(),
|
||||
getDeviceVerificationStatus: jest.fn(),
|
||||
getUserVerificationStatus: jest.fn(),
|
||||
isDehydrationSupported: jest.fn().mockResolvedValue(false),
|
||||
startDehydration: jest.fn(),
|
||||
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
|
||||
userHasCrossSigningKeys: jest.fn().mockResolvedValue(false),
|
||||
} as unknown as CryptoApi);
|
||||
|
||||
mockClient = createTestClient();
|
||||
jest.spyOn(mockClient, "doesServerSupportUnstableFeature").mockResolvedValue(true);
|
||||
jest.spyOn(mockClient.secretStorage, "hasKey").mockResolvedValue(true);
|
||||
jest.spyOn(mockClient, "getCrypto").mockReturnValue(mockCrypto);
|
||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
||||
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(mockClient);
|
||||
});
|
||||
|
||||
const renderComponent = () => {
|
||||
const device1 = new Device({
|
||||
deviceId: "d1",
|
||||
userId: defaultUserId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
});
|
||||
const devicesMap = new Map<string, Device>([[device1.deviceId, device1]]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
|
||||
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
jest.spyOn(mockClient, "doesServerSupportUnstableFeature").mockResolvedValue(true);
|
||||
const Wrapper = (wrapperProps = {}) => {
|
||||
return <MatrixClientContext.Provider value={mockClient} {...wrapperProps} />;
|
||||
};
|
||||
|
||||
return render(<UserInfoHeaderVerificationView member={defaultMember} devices={[device1]} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
};
|
||||
|
||||
it("renders verified badge when user is verified", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, true, false));
|
||||
const { container } = renderComponent();
|
||||
await waitFor(() => expect(screen.getByText("Verified")).toBeInTheDocument());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders verify button", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||
mockCrypto.userHasCrossSigningKeys.mockResolvedValue(true);
|
||||
const { container } = renderComponent();
|
||||
await waitFor(() => expect(screen.getByText("Verify User")).toBeInTheDocument());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders verification unavailable message", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||
mockCrypto.userHasCrossSigningKeys.mockResolvedValue(false);
|
||||
const { container } = renderComponent();
|
||||
await waitFor(() => expect(screen.getByText("(User verification unavailable)")).toBeInTheDocument());
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked, type Mocked } from "jest-mock";
|
||||
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
|
||||
import { Device, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { fireEvent, render, screen } from "jest-matrix-react";
|
||||
import React from "react";
|
||||
|
||||
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
||||
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
||||
import { UserInfoHeaderView } from "../../../../../../src/components/views/right_panel/user_info/UserInfoHeaderView";
|
||||
import { createTestClient } from "../../../../../test-utils";
|
||||
import { useUserfoHeaderViewModel } from "../../../../../../src/components/viewmodels/right_panel/user_info/UserInfoHeaderViewModel";
|
||||
|
||||
// Mock the viewmodel hooks
|
||||
jest.mock("../../../../../../src/components/viewmodels/right_panel/user_info/UserInfoHeaderViewModel", () => ({
|
||||
useUserfoHeaderViewModel: jest.fn().mockReturnValue({
|
||||
onMemberAvatarClick: jest.fn(),
|
||||
precenseInfo: {
|
||||
lastActiveAgo: undefined,
|
||||
currentlyActive: undefined,
|
||||
state: undefined,
|
||||
},
|
||||
showPresence: false,
|
||||
timezoneInfo: null,
|
||||
userIdentifier: "customUserIdentifier",
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("<UserInfoHeaderView />", () => {
|
||||
const defaultRoomId = "!fkfk";
|
||||
const defaultUserId = "@user:example.com";
|
||||
|
||||
const defaultMember = new RoomMember(defaultRoomId, defaultUserId);
|
||||
const defaultProps = {
|
||||
member: defaultMember,
|
||||
roomId: defaultRoomId,
|
||||
};
|
||||
|
||||
let mockClient: MatrixClient;
|
||||
let mockCrypto: Mocked<CryptoApi>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockCrypto = mocked({
|
||||
bootstrapSecretStorage: jest.fn(),
|
||||
bootstrapCrossSigning: jest.fn(),
|
||||
getCrossSigningKeyId: jest.fn(),
|
||||
getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
|
||||
getUserDeviceInfo: jest.fn(),
|
||||
getDeviceVerificationStatus: jest.fn(),
|
||||
getUserVerificationStatus: jest.fn(),
|
||||
isDehydrationSupported: jest.fn().mockResolvedValue(false),
|
||||
startDehydration: jest.fn(),
|
||||
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
|
||||
userHasCrossSigningKeys: jest.fn().mockResolvedValue(false),
|
||||
} as unknown as CryptoApi);
|
||||
|
||||
mockClient = createTestClient();
|
||||
mockClient.doesServerSupportExtendedProfiles = () => Promise.resolve(false);
|
||||
|
||||
jest.spyOn(mockClient, "doesServerSupportUnstableFeature").mockResolvedValue(true);
|
||||
jest.spyOn(mockClient.secretStorage, "hasKey").mockResolvedValue(true);
|
||||
jest.spyOn(mockClient, "getCrypto").mockReturnValue(mockCrypto);
|
||||
jest.spyOn(mockClient, "doesServerSupportUnstableFeature").mockResolvedValue(true);
|
||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
||||
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(mockClient);
|
||||
});
|
||||
|
||||
const renderComponent = (
|
||||
props = {
|
||||
hideVerificationSection: false,
|
||||
},
|
||||
) => {
|
||||
const device1 = new Device({
|
||||
deviceId: "d1",
|
||||
userId: defaultUserId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map(),
|
||||
});
|
||||
|
||||
const devicesMap = new Map<string, Device>([[device1.deviceId, device1]]);
|
||||
const userDeviceMap = new Map<string, Map<string, Device>>([[defaultUserId, devicesMap]]);
|
||||
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
|
||||
const Wrapper = (wrapperProps = {}) => {
|
||||
return <MatrixClientContext.Provider value={mockClient} {...wrapperProps} />;
|
||||
};
|
||||
|
||||
return render(
|
||||
<UserInfoHeaderView
|
||||
{...defaultProps}
|
||||
{...props}
|
||||
devices={[device1]}
|
||||
hideVerificationSection={props.hideVerificationSection}
|
||||
/>,
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
it("renders custom user identifiers in the header", () => {
|
||||
const { container } = renderComponent();
|
||||
expect(screen.getByText("customUserIdentifier")).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should not render verification view if hideVerificationSection is true", () => {
|
||||
mocked(useUserfoHeaderViewModel).mockReturnValue({
|
||||
onMemberAvatarClick: jest.fn(),
|
||||
precenseInfo: {
|
||||
lastActiveAgo: undefined,
|
||||
currentlyActive: undefined,
|
||||
state: undefined,
|
||||
},
|
||||
showPresence: false,
|
||||
timezoneInfo: null,
|
||||
userIdentifier: "null",
|
||||
});
|
||||
|
||||
const { container } = renderComponent({ hideVerificationSection: true });
|
||||
const verificationClass = container.getElementsByClassName("mx_UserInfo_verification").length;
|
||||
|
||||
expect(verificationClass).toEqual(0);
|
||||
});
|
||||
|
||||
it("should render timezone if it exist", () => {
|
||||
mocked(useUserfoHeaderViewModel).mockReturnValue({
|
||||
onMemberAvatarClick: jest.fn(),
|
||||
precenseInfo: {
|
||||
lastActiveAgo: undefined,
|
||||
currentlyActive: undefined,
|
||||
state: undefined,
|
||||
},
|
||||
showPresence: false,
|
||||
timezoneInfo: {
|
||||
timezone: "FR",
|
||||
friendly: "paris",
|
||||
},
|
||||
userIdentifier: null,
|
||||
});
|
||||
|
||||
renderComponent({ hideVerificationSection: false });
|
||||
expect(screen.getByText("paris")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render correct presence label", () => {
|
||||
mocked(useUserfoHeaderViewModel).mockReturnValue({
|
||||
onMemberAvatarClick: jest.fn(),
|
||||
precenseInfo: {
|
||||
lastActiveAgo: 0,
|
||||
currentlyActive: true,
|
||||
state: "online",
|
||||
},
|
||||
showPresence: true,
|
||||
timezoneInfo: null,
|
||||
userIdentifier: null,
|
||||
});
|
||||
|
||||
renderComponent({ hideVerificationSection: false });
|
||||
expect(screen.getByText("Online")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should be able to click on member avatar", () => {
|
||||
const onMemberAvatarClick = jest.fn();
|
||||
mocked(useUserfoHeaderViewModel).mockReturnValue({
|
||||
onMemberAvatarClick,
|
||||
precenseInfo: {
|
||||
lastActiveAgo: undefined,
|
||||
currentlyActive: undefined,
|
||||
state: undefined,
|
||||
},
|
||||
showPresence: false,
|
||||
timezoneInfo: {
|
||||
timezone: "FR",
|
||||
friendly: "paris",
|
||||
},
|
||||
userIdentifier: null,
|
||||
});
|
||||
renderComponent();
|
||||
const avatar = screen.getByRole("button", { name: "Profile picture" });
|
||||
|
||||
fireEvent.click(avatar);
|
||||
|
||||
expect(onMemberAvatarClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { fireEvent, render, screen } from "jest-matrix-react";
|
||||
import { type Mocked, mocked } from "jest-mock";
|
||||
import { MatrixEvent, type MatrixClient, RoomMember, type Room, EventType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
||||
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
||||
import { type IRoomPermissions } from "../../../../../../src/components/views/right_panel/UserInfo";
|
||||
import { PowerLevelSection } from "../../../../../../src/components/views/right_panel/user_info/UserInfoPowerLevels";
|
||||
|
||||
describe("<PowerLevelEditor />", () => {
|
||||
const defaultRoomId = "!fkfk";
|
||||
const defaultUserId = "@user:example.com";
|
||||
const defaultMember = new RoomMember(defaultRoomId, defaultUserId);
|
||||
|
||||
let mockClient: Mocked<MatrixClient>;
|
||||
let mockRoom: Mocked<Room>;
|
||||
let defaultProps: {
|
||||
user: RoomMember;
|
||||
room: Room;
|
||||
roomPermissions: IRoomPermissions;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
defaultProps = {
|
||||
user: defaultMember,
|
||||
room: mockRoom,
|
||||
roomPermissions: {
|
||||
modifyLevelMax: 100,
|
||||
canEdit: false,
|
||||
canInvite: false,
|
||||
},
|
||||
};
|
||||
|
||||
mockRoom = mocked({
|
||||
roomId: defaultRoomId,
|
||||
getType: jest.fn().mockReturnValue(undefined),
|
||||
isSpaceRoom: jest.fn().mockReturnValue(false),
|
||||
getMember: jest.fn().mockReturnValue(undefined),
|
||||
getMxcAvatarUrl: jest.fn().mockReturnValue("mock-avatar-url"),
|
||||
name: "test room",
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
currentState: {
|
||||
getStateEvents: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
},
|
||||
getEventReadUpTo: jest.fn(),
|
||||
} as unknown as Room);
|
||||
|
||||
mockClient = mocked({
|
||||
getUser: jest.fn(),
|
||||
isGuest: jest.fn().mockReturnValue(false),
|
||||
isUserIgnored: jest.fn(),
|
||||
getIgnoredUsers: jest.fn(),
|
||||
setIgnoredUsers: jest.fn(),
|
||||
getUserId: jest.fn(),
|
||||
getSafeUserId: jest.fn(),
|
||||
getDomain: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
isSynapseAdministrator: jest.fn().mockResolvedValue(false),
|
||||
doesServerSupportUnstableFeature: jest.fn().mockReturnValue(false),
|
||||
doesServerSupportExtendedProfiles: jest.fn().mockResolvedValue(false),
|
||||
getExtendedProfileProperty: jest.fn().mockRejectedValue(new Error("Not supported")),
|
||||
mxcUrlToHttp: jest.fn().mockReturnValue("mock-mxcUrlToHttp"),
|
||||
removeListener: jest.fn(),
|
||||
currentState: {
|
||||
on: jest.fn(),
|
||||
},
|
||||
getRoom: jest.fn(),
|
||||
credentials: {},
|
||||
setPowerLevel: jest.fn().mockResolvedValueOnce({ event_id: "123" }),
|
||||
} as unknown as MatrixClient);
|
||||
|
||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
||||
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(mockClient);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
defaultProps = {
|
||||
user: defaultMember,
|
||||
room: mockRoom,
|
||||
roomPermissions: {
|
||||
modifyLevelMax: 100,
|
||||
canEdit: false,
|
||||
canInvite: false,
|
||||
},
|
||||
};
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const renderComponent = (props = defaultProps) => {
|
||||
const Wrapper = (wrapperProps = {}) => {
|
||||
return <MatrixClientContext.Provider value={mockClient} {...wrapperProps} />;
|
||||
};
|
||||
|
||||
return render(<PowerLevelSection {...props} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
};
|
||||
|
||||
it("renders a power level combobox if can edit is true", () => {
|
||||
const startPowerLevel = 999;
|
||||
const powerLevelEvent = new MatrixEvent({
|
||||
type: EventType.RoomPowerLevels,
|
||||
content: { users: { [defaultUserId]: startPowerLevel }, users_default: 1 },
|
||||
});
|
||||
mockRoom.currentState.getStateEvents.mockReturnValue(powerLevelEvent);
|
||||
|
||||
renderComponent({
|
||||
...defaultProps,
|
||||
room: mockRoom,
|
||||
roomPermissions: { ...defaultProps.roomPermissions, canEdit: true },
|
||||
});
|
||||
|
||||
expect(screen.getByRole("combobox", { name: "Power level" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders a user role if can edit is false", () => {
|
||||
const member = new RoomMember(defaultRoomId, defaultUserId);
|
||||
member.powerLevel = 100;
|
||||
renderComponent({ ...defaultProps, user: member });
|
||||
|
||||
expect(screen.getByText("Admin")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders a combobox and attempts to change power level on change of the combobox", async () => {
|
||||
const startPowerLevel = 999;
|
||||
const powerLevelEvent = new MatrixEvent({
|
||||
type: EventType.RoomPowerLevels,
|
||||
content: { users: { [defaultUserId]: startPowerLevel }, users_default: 1 },
|
||||
});
|
||||
mockRoom.currentState.getStateEvents.mockReturnValue(powerLevelEvent);
|
||||
mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
|
||||
mockClient.getUserId.mockReturnValueOnce(defaultUserId);
|
||||
renderComponent({
|
||||
...defaultProps,
|
||||
room: mockRoom,
|
||||
roomPermissions: { ...defaultProps.roomPermissions, canEdit: true },
|
||||
});
|
||||
|
||||
const changedPowerLevel = 100;
|
||||
|
||||
fireEvent.change(screen.getByRole("combobox", { name: "Power level" }), {
|
||||
target: { value: changedPowerLevel },
|
||||
});
|
||||
|
||||
await screen.findByText("Demote", { exact: true });
|
||||
|
||||
// firing the event will raise a dialog warning about self demotion, wait for this to appear then click on it
|
||||
await userEvent.click(await screen.findByText("Demote", { exact: true }));
|
||||
expect(mockClient.setPowerLevel).toHaveBeenCalledTimes(1);
|
||||
expect(mockClient.setPowerLevel).toHaveBeenCalledWith(mockRoom.roomId, defaultMember.userId, changedPowerLevel);
|
||||
});
|
||||
});
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`<UserInfoBasic /> should display the defaut values 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_UserInfo_container"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_profileField"
|
||||
>
|
||||
<div
|
||||
class="mx_PowerSelector"
|
||||
>
|
||||
<div
|
||||
class="mx_Field mx_Field_select"
|
||||
>
|
||||
<select
|
||||
data-testid="power-level-select-element"
|
||||
id="mx_Field_1"
|
||||
label="Power level"
|
||||
placeholder="Power level"
|
||||
type="text"
|
||||
>
|
||||
<option
|
||||
data-testid="power-level-option-0"
|
||||
value="0"
|
||||
>
|
||||
Default
|
||||
</option>
|
||||
<option
|
||||
data-testid="power-level-option-SELECT_VALUE_CUSTOM"
|
||||
value="SELECT_VALUE_CUSTOM"
|
||||
>
|
||||
Custom level
|
||||
</option>
|
||||
</select>
|
||||
<label
|
||||
for="mx_Field_1"
|
||||
>
|
||||
Power level
|
||||
</label>
|
||||
<svg
|
||||
class="mx_Field_select_chevron"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m1.5 21.25 1.45-4.95a10.2 10.2 0 0 1-.712-2.1A10.2 10.2 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 22q-1.125 0-2.2-.238a10.2 10.2 0 0 1-2.1-.712L2.75 22.5a.94.94 0 0 1-1-.25.94.94 0 0 1-.25-1m2.45-1.2 3.2-.95a1 1 0 0 1 .275-.062q.15-.013.275-.013.225 0 .438.038.212.036.412.137a7.4 7.4 0 0 0 1.675.6Q11.1 20 12 20q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12q0 .9.2 1.775t.6 1.675q.176.325.188.688t-.088.712z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Send message
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10 12q-1.65 0-2.825-1.175T6 8t1.175-2.825T10 4t2.825 1.175T14 8t-1.175 2.825T10 12m-8 6v-.8q0-.85.438-1.562.437-.713 1.162-1.088a14.8 14.8 0 0 1 3.15-1.163A13.8 13.8 0 0 1 10 13q1.65 0 3.25.387 1.6.388 3.15 1.163.724.375 1.163 1.087Q18 16.35 18 17.2v.8q0 .824-.587 1.413A1.93 1.93 0 0 1 16 20H4q-.824 0-1.412-.587A1.93 1.93 0 0 1 2 18m2 0h12v-.8a.97.97 0 0 0-.5-.85q-1.35-.675-2.725-1.012a11.6 11.6 0 0 0-5.55 0Q5.85 15.675 4.5 16.35a.97.97 0 0 0-.5.85zm6-8q.825 0 1.412-.588Q12 8.826 12 8q0-.824-.588-1.412A1.93 1.93 0 0 0 10 6q-.825 0-1.412.588A1.93 1.93 0 0 0 8 8q0 .825.588 1.412Q9.175 10 10 10m7 1h2v2q0 .424.288.713.287.287.712.287.424 0 .712-.287A.97.97 0 0 0 21 13v-2h2q.424 0 .712-.287A.97.97 0 0 0 24 10a.97.97 0 0 0-.288-.713A.97.97 0 0 0 23 9h-2V7a.97.97 0 0 0-.288-.713A.97.97 0 0 0 20 6a.97.97 0 0 0-.712.287A.97.97 0 0 0 19 7v2h-2a.97.97 0 0 0-.712.287A.97.97 0 0 0 16 10q0 .424.288.713.287.287.712.287"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Invite
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26 _disabled_lqfwq_118"
|
||||
data-kind="primary"
|
||||
disabled=""
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Jump to read receipt
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 16a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 15V7.85L9.125 9.725q-.3.3-.7.3T7.7 9.7a.93.93 0 0 1-.288-.713A.98.98 0 0 1 7.7 8.3l3.6-3.6q.15-.15.325-.213.175-.062.375-.062t.375.062a.9.9 0 0 1 .325.213l3.6 3.6q.3.3.287.712a.98.98 0 0 1-.287.688q-.3.3-.713.313a.93.93 0 0 1-.712-.288L13 7.85V15q0 .424-.287.713A.97.97 0 0 1 12 16m-6 4q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 18v-2q0-.424.287-.713A.97.97 0 0 1 5 15q.424 0 .713.287Q6 15.576 6 16v2h12v-2q0-.424.288-.713A.97.97 0 0 1 19 15q.424 0 .712.287.288.288.288.713v2q0 .824-.587 1.413A1.93 1.93 0 0 1 18 20z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Share profile
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
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="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Mention
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container"
|
||||
>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26"
|
||||
data-kind="critical"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7 21q-.824 0-1.412-.587A1.93 1.93 0 0 1 5 19V6a.97.97 0 0 1-.713-.287A.97.97 0 0 1 4 5q0-.424.287-.713A.97.97 0 0 1 5 4h4q0-.424.287-.712A.97.97 0 0 1 10 3h4q.424 0 .713.288Q15 3.575 15 4h4q.424 0 .712.287Q20 4.576 20 5t-.288.713A.97.97 0 0 1 19 6v13q0 .824-.587 1.413A1.93 1.93 0 0 1 17 21zM7 6v13h10V6zm2 10q0 .424.287.712Q9.576 17 10 17t.713-.288A.97.97 0 0 0 11 16V9a.97.97 0 0 0-.287-.713A.97.97 0 0 0 10 8a.97.97 0 0 0-.713.287A.97.97 0 0 0 9 9zm4 0q0 .424.287.712.288.288.713.288.424 0 .713-.288A.97.97 0 0 0 15 16V9a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 8a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 9z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Deactivate user
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container"
|
||||
>
|
||||
<button
|
||||
class="_item_lqfwq_8 _interactive_lqfwq_26"
|
||||
data-kind="critical"
|
||||
role="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_icon_lqfwq_50"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 22a9.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 12q0-1.35-.437-2.6A8 8 0 0 0 18.3 7.1L7.1 18.3q1.05.825 2.3 1.262T12 20m-6.3-3.1L16.9 5.7a8 8 0 0 0-2.3-1.263A7.8 7.8 0 0 0 12 4Q8.65 4 6.325 6.325T4 12q0 1.35.438 2.6A8 8 0 0 0 5.7 16.9"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_lqfwq_34"
|
||||
>
|
||||
Ignore
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="_nav-hint_lqfwq_59"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="8 0 8 24"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`<UserInfoHeaderVerificationView /> renders verification unavailable message 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="_flex_4dswl_9 mx_UserInfo_verification"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31 mx_UserInfo_verification_unavailable"
|
||||
>
|
||||
(
|
||||
User verification unavailable
|
||||
)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<UserInfoHeaderVerificationView /> renders verified badge when user is verified 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="_flex_4dswl_9 mx_UserInfo_verification"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<span
|
||||
class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_18gm1_8 mx_UserInfo_verified_badge"
|
||||
data-kind="green"
|
||||
>
|
||||
<svg
|
||||
class="mx_UserInfo_verified_icon"
|
||||
fill="currentColor"
|
||||
height="16px"
|
||||
viewBox="0 0 24 24"
|
||||
width="16px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.15 21.75 6.7 19.3l-2.75-.6a.94.94 0 0 1-.6-.387.93.93 0 0 1-.175-.688L3.45 14.8l-1.875-2.15a.93.93 0 0 1-.25-.65q0-.375.25-.65L3.45 9.2l-.275-2.825a.93.93 0 0 1 .175-.687.94.94 0 0 1 .6-.388l2.75-.6 1.45-2.45a.98.98 0 0 1 .55-.437.97.97 0 0 1 .7.037l2.6 1.1 2.6-1.1a.97.97 0 0 1 .7-.038q.35.112.55.438L17.3 4.7l2.75.6q.375.075.6.388.225.312.175.687L20.55 9.2l1.875 2.15q.25.275.25.65t-.25.65L20.55 14.8l.275 2.825a.93.93 0 0 1-.175.688.94.94 0 0 1-.6.387l-2.75.6-1.45 2.45a.98.98 0 0 1-.55.438.97.97 0 0 1-.7-.038l-2.6-1.1-2.6 1.1a.97.97 0 0 1-.7.038.98.98 0 0 1-.55-.438m2.8-9.05L9.5 11.275A.93.93 0 0 0 8.812 11q-.412 0-.712.3a.95.95 0 0 0-.275.7q0 .425.275.7l2.15 2.15q.3.3.7.3t.7-.3l4.25-4.25q.3-.3.287-.7a1.06 1.06 0 0 0-.287-.7 1.02 1.02 0 0 0-.713-.312.93.93 0 0 0-.712.287z"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 mx_UserInfo_verified_label"
|
||||
>
|
||||
Verified
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<UserInfoHeaderVerificationView /> renders verify button 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="_flex_4dswl_9 mx_UserInfo_verification"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_container_verifyButton"
|
||||
>
|
||||
<button
|
||||
class="_button_13vu4_8 mx_UserInfo_verify_button"
|
||||
data-kind="tertiary"
|
||||
data-size="sm"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Verify User
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`<UserInfoHeaderView /> renders custom user identifiers in the header 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_UserInfo_avatar"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar_transition"
|
||||
>
|
||||
<div
|
||||
class="mx_UserInfo_avatar_transition_child"
|
||||
>
|
||||
<button
|
||||
aria-label="Profile picture"
|
||||
aria-live="off"
|
||||
class="_avatar_zysgz_8 mx_BaseAvatar _avatar-imageless_zysgz_55"
|
||||
data-color="3"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="button"
|
||||
style="--cpd-avatar-size: 120px;"
|
||||
title="@user:example.com"
|
||||
>
|
||||
u
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_UserInfo_container mx_UserInfo_header"
|
||||
>
|
||||
<div
|
||||
class="_flex_4dswl_9 mx_UserInfo_profile"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<h1
|
||||
class="_typography_6v6n8_153 _font-heading-sm-semibold_6v6n8_93"
|
||||
dir="auto"
|
||||
>
|
||||
<div
|
||||
class="_flex_4dswl_9 mx_UserInfo_profile_name"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row-reverse; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
@user:example.com
|
||||
</div>
|
||||
</h1>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
|
||||
>
|
||||
<div
|
||||
class="mx_CopyableText"
|
||||
>
|
||||
customUserIdentifier
|
||||
<div
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14 5H5v9h1a1 1 0 1 1 0 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1a1 1 0 1 1-2 0z"
|
||||
/>
|
||||
<path
|
||||
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="_flex_4dswl_9 mx_UserInfo_verification"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<svg
|
||||
class="_icon_11k6c_18"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
style="width: 24px; height: 24px;"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M12 4.031a8 8 0 1 0 8 8 1 1 0 0 1 2 0c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10a1 1 0 1 1 0 2"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
Reference in New Issue
Block a user