New UX for Share dialog (#28598)

* New UX for `ShareDialog`

* Use new named import

* Rewrite tests

* Add e2e tests

* Use `box-sizing` for social buttons

* Update e2e tests
pull/28619/head
Florian Duros 2024-12-02 19:10:17 +01:00 committed by GitHub
parent 06fa3481df
commit 0429809c00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1244 additions and 282 deletions

View File

@ -0,0 +1,67 @@
/*
* Copyright 2024 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 { test, expect } from "../../element-web-test";
test.describe("Share dialog", () => {
test.use({
displayName: "Alice",
room: async ({ app, user, bot }, use) => {
const roomId = await app.client.createRoom({ name: "Alice room" });
await use({ roomId });
},
});
test("should share a room", async ({ page, app, room }) => {
await app.viewRoomById(room.roomId);
await app.toggleRoomInfoPanel();
await page.getByRole("menuitem", { name: "Copy link" }).click();
const dialog = page.getByRole("dialog", { name: "Share room" });
await expect(dialog.getByText(`https://matrix.to/#/${room.roomId}`)).toBeVisible();
expect(dialog).toMatchScreenshot("share-dialog-room.png", {
// QRCode and url changes at every run
mask: [page.locator(".mx_QRCode"), page.locator(".mx_ShareDialog_top > span")],
});
});
test("should share a room member", async ({ page, app, room, user }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, { body: "hello", msgtype: "m.text" });
const rightPanel = await app.toggleRoomInfoPanel();
await rightPanel.getByRole("menuitem", { name: "People" }).click();
await rightPanel.getByRole("button", { name: `${user.userId} (power 100)` }).click();
await rightPanel.getByRole("button", { name: "Share profile" }).click();
const dialog = page.getByRole("dialog", { name: "Share User" });
await expect(dialog.getByText(`https://matrix.to/#/${user.userId}`)).toBeVisible();
expect(dialog).toMatchScreenshot("share-dialog-user.png", {
// QRCode changes at every run
mask: [page.locator(".mx_QRCode")],
});
});
test("should share an event", async ({ page, app, room }) => {
await app.viewRoomById(room.roomId);
await app.client.sendMessage(room.roomId, { body: "hello", msgtype: "m.text" });
const timelineMessage = page.locator(".mx_MTextBody", { hasText: "hello" });
await timelineMessage.hover();
await page.getByRole("button", { name: "Options", exact: true }).click();
await page.getByRole("menuitem", { name: "Share" }).click();
const dialog = page.getByRole("dialog", { name: "Share Room Message" });
await expect(dialog.getByRole("checkbox", { name: "Link to selected message" })).toBeChecked();
expect(dialog).toMatchScreenshot("share-dialog-event.png", {
// QRCode and url changes at every run
mask: [page.locator(".mx_QRCode"), page.locator(".mx_ShareDialog_top > span")],
});
await dialog.getByRole("checkbox", { name: "Link to selected message" }).click();
await expect(dialog.getByRole("checkbox", { name: "Link to selected message" })).not.toBeChecked();
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -596,7 +596,7 @@ legend {
.mx_Dialog
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button),
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button),
.mx_Dialog input[type="submit"],
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton),
.mx_Dialog_buttons input[type="submit"] {
@ -616,14 +616,16 @@ legend {
.mx_Dialog
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):last-child {
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(
.mx_ShareDialog button
):last-child {
margin-right: 0px;
}
.mx_Dialog
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):focus,
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):focus,
.mx_Dialog input[type="submit"]:focus,
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):focus,
.mx_Dialog_buttons input[type="submit"]:focus {
@ -635,7 +637,7 @@ legend {
.mx_Dialog_buttons
button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button),
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button),
.mx_Dialog_buttons input[type="submit"].mx_Dialog_primary {
color: var(--cpd-color-text-on-solid-primary);
background-color: var(--cpd-color-bg-action-primary-rest);
@ -648,7 +650,7 @@ legend {
.mx_Dialog_buttons
button.danger:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(.mx_UserProfileSettings button):not(
.mx_ThemeChoicePanel_CustomTheme button
):not(.mx_UnpinAllDialog button),
):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button),
.mx_Dialog_buttons input[type="submit"].danger {
background-color: var(--cpd-color-bg-critical-primary);
border: solid 1px var(--cpd-color-bg-critical-primary);
@ -664,7 +666,7 @@ legend {
.mx_Dialog
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):disabled,
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):disabled,
.mx_Dialog input[type="submit"]:disabled,
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):disabled,
.mx_Dialog_buttons input[type="submit"]:disabled {

View File

@ -5,50 +5,73 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
.mx_ShareDialog hr {
margin-top: 25px;
margin-bottom: 25px;
border-color: $light-fg-color;
}
.mx_ShareDialog {
/* Value from figma design */
width: 416px;
.mx_ShareDialog .mx_ShareDialog_content {
margin: 10px 0;
.mx_Dialog_header {
text-align: center;
margin-bottom: var(--cpd-space-6x);
/* Override dialog header padding to able to center it */
padding-inline-end: 0;
}
.mx_CopyableText {
width: unset; /* full width */
.mx_ShareDialog_content {
display: flex;
flex-direction: column;
gap: var(--cpd-space-6x);
align-items: center;
> a {
text-decoration: none;
flex-shrink: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.mx_ShareDialog_top {
display: flex;
flex-direction: column;
gap: var(--cpd-space-4x);
align-items: center;
width: 100%;
span {
text-align: center;
font: var(--cpd-font-body-sm-semibold);
color: var(--cpd-color-text-secondary);
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
width: 100%;
}
}
label {
display: inline-flex;
gap: var(--cpd-space-3x);
justify-content: center;
align-items: center;
font: var(--cpd-font-body-md-medium);
}
button {
width: 100%;
}
.mx_ShareDialog_social {
display: flex;
gap: var(--cpd-space-3x);
justify-content: center;
a {
width: 48px;
height: 48px;
border-radius: 99px;
box-sizing: border-box;
border: 1px solid var(--cpd-color-border-interactive-secondary);
display: flex;
justify-content: center;
align-items: center;
img {
width: 24px;
height: 24px;
}
}
}
}
}
.mx_ShareDialog_split {
display: flex;
flex-wrap: wrap;
}
.mx_ShareDialog_qrcode_container {
float: left;
height: 256px;
width: 256px;
margin-right: 64px;
}
.mx_ShareDialog_qrcode_container + .mx_ShareDialog_social_container {
width: 299px;
}
.mx_ShareDialog_social_container {
display: inline-block;
}
.mx_ShareDialog_social_icon {
display: inline-grid;
margin-right: 10px;
margin-bottom: 10px;
}

View File

@ -38,7 +38,7 @@ import ContextMenu, { toRightOf, MenuProps } from "../../structures/ContextMenu"
import ReactionPicker from "../emojipicker/ReactionPicker";
import ViewSource from "../../structures/ViewSource";
import { createRedactEventDialog } from "../dialogs/ConfirmRedactDialog";
import ShareDialog from "../dialogs/ShareDialog";
import { ShareDialog } from "../dialogs/ShareDialog";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import EndPollDialog from "../dialogs/EndPollDialog";
import { isPollEnded } from "../messages/MPollBody";

View File

@ -7,22 +7,23 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import * as React from "react";
import React, { JSX, useMemo, useRef, useState } from "react";
import { Room, RoomMember, MatrixEvent, User } from "matrix-js-sdk/src/matrix";
import { Checkbox, Button } from "@vector-im/compound-web";
import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link";
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
import { _t } from "../../../languageHandler";
import QRCode from "../elements/QRCode";
import { RoomPermalinkCreator, makeUserPermalink } from "../../../utils/permalinks/Permalinks";
import { selectText } from "../../../utils/strings";
import StyledCheckbox from "../elements/StyledCheckbox";
import SettingsStore from "../../../settings/SettingsStore";
import { copyPlaintext } from "../../../utils/strings";
import { UIFeature } from "../../../settings/UIFeature";
import BaseDialog from "./BaseDialog";
import CopyableText from "../elements/CopyableText";
import { XOR } from "../../../@types/common";
import { useSettingValue } from "../../../hooks/useSettings.ts";
/* eslint-disable @typescript-eslint/no-require-imports */
const socials = [
const SOCIALS = [
{
name: "Facebook",
img: require("../../../../res/img/social/facebook.png"),
@ -33,11 +34,7 @@ const socials = [
img: require("../../../../res/img/social/twitter-2.png"),
url: (url: string) => `https://twitter.com/home?status=${url}`,
},
/* // icon missing
name: 'Google Plus',
img: 'img/social/',
url: (url) => `https://plus.google.com/share?url=${url}`,
},*/ {
{
name: "LinkedIn",
img: require("../../../../res/img/social/linkedin.png"),
url: (url: string) => `https://www.linkedin.com/shareArticle?mini=true&url=${url}`,
@ -78,160 +75,153 @@ interface Props extends BaseProps {
* A <u>matrix.to</u> link will be generated out of it if it's not already a url.
*/
target: Room | User | RoomMember | URL;
/**
* Optional when the target is a Room, User, RoomMember or a URL.
* Mandatory when the target is a MatrixEvent.
*/
permalinkCreator?: RoomPermalinkCreator;
}
interface EventProps extends BaseProps {
/**
* The target to link to.
*/
target: MatrixEvent;
/**
* Optional when the target is a Room, User, RoomMember or a URL.
* Mandatory when the target is a MatrixEvent.
*/
permalinkCreator: RoomPermalinkCreator;
}
interface IState {
linkSpecificEvent: boolean;
permalinkCreator: RoomPermalinkCreator | null;
type ShareDialogProps = XOR<Props, EventProps>;
/**
* A dialog to share a link to a room, user, room member or a matrix event.
*/
export function ShareDialog({ target, customTitle, onFinished, permalinkCreator }: ShareDialogProps): JSX.Element {
const showQrCode = useSettingValue<boolean>(UIFeature.ShareQRCode);
const showSocials = useSettingValue<boolean>(UIFeature.ShareSocial);
const timeoutIdRef = useRef<number>();
const [isCopied, setIsCopied] = useState(false);
const [linkToSpecificEvent, setLinkToSpecificEvent] = useState(target instanceof MatrixEvent);
const { title, url, checkboxLabel } = useTargetValues(target, linkToSpecificEvent, permalinkCreator);
const newTitle = customTitle ?? title;
return (
<BaseDialog
title={newTitle}
className="mx_ShareDialog"
contentId="mx_Dialog_content"
onFinished={onFinished}
fixedWidth={false}
>
<div className="mx_ShareDialog_content">
<div className="mx_ShareDialog_top">
{showQrCode && <QRCode data={url} width={200} />}
<span>{url}</span>
</div>
{checkboxLabel && (
<label>
<Checkbox
defaultChecked={linkToSpecificEvent}
onChange={(evt) => setLinkToSpecificEvent(evt.target.checked)}
/>
{checkboxLabel}
</label>
)}
<Button
Icon={isCopied ? CheckIcon : LinkIcon}
onClick={async () => {
clearTimeout(timeoutIdRef.current);
await copyPlaintext(url);
setIsCopied(true);
timeoutIdRef.current = setTimeout(() => setIsCopied(false), 2000);
}}
>
{isCopied ? _t("share|link_copied") : _t("action|copy_link")}
</Button>
{showSocials && <SocialLinks url={url} />}
</div>
</BaseDialog>
);
}
export default class ShareDialog extends React.PureComponent<XOR<Props, EventProps>, IState> {
public constructor(props: XOR<Props, EventProps>) {
super(props);
/**
* Social links to share the link on different platforms.
*/
interface SocialLinksProps {
/**
* The URL to share.
*/
url: string;
}
let permalinkCreator: RoomPermalinkCreator | null = null;
if (props.target instanceof Room) {
permalinkCreator = new RoomPermalinkCreator(props.target);
permalinkCreator.load();
/**
* The socials to share the link on.
*/
function SocialLinks({ url }: SocialLinksProps): JSX.Element {
return (
<div className="mx_ShareDialog_social">
{SOCIALS.map((social) => (
<a
key={social.name}
href={social.url(url)}
target="_blank"
rel="noreferrer noopener"
title={social.name}
>
<img src={social.img} alt={social.name} />
</a>
))}
</div>
);
}
/**
* Get the title, url and checkbox label for the dialog based on the target.
* @param target
* @param linkToSpecificEvent
* @param permalinkCreator
*/
function useTargetValues(
target: ShareDialogProps["target"],
linkToSpecificEvent: boolean,
permalinkCreator?: RoomPermalinkCreator,
): { title: string; url: string; checkboxLabel?: string } {
return useMemo(() => {
if (target instanceof URL) return { title: _t("share|title_link"), url: target.toString() };
if (target instanceof User || target instanceof RoomMember)
return {
title: _t("share|title_user"),
url: makeUserPermalink(target.userId),
};
if (target instanceof Room) {
const title = _t("share|title_room");
const newPermalinkCreator = new RoomPermalinkCreator(target);
newPermalinkCreator.load();
const events = target.getLiveTimeline().getEvents();
return {
title,
url: linkToSpecificEvent
? newPermalinkCreator.forEvent(events[events.length - 1].getId()!)
: newPermalinkCreator.forShareableRoom(),
...(events.length > 0 && { checkboxLabel: _t("share|permalink_most_recent") }),
};
}
this.state = {
// MatrixEvent defaults to share linkSpecificEvent
linkSpecificEvent: this.props.target instanceof MatrixEvent,
permalinkCreator,
// MatrixEvent is remaining and should have a permalinkCreator
const url = linkToSpecificEvent
? permalinkCreator!.forEvent(target.getId()!)
: permalinkCreator!.forShareableRoom();
return {
title: _t("share|title_message"),
url,
checkboxLabel: _t("share|permalink_message"),
};
}
public static onLinkClick(e: React.MouseEvent): void {
e.preventDefault();
selectText(e.currentTarget);
}
private onLinkSpecificEventCheckboxClick = (): void => {
this.setState({
linkSpecificEvent: !this.state.linkSpecificEvent,
});
};
private getUrl(): string {
if (this.props.target instanceof URL) {
return this.props.target.toString();
} else if (this.props.target instanceof Room) {
if (this.state.linkSpecificEvent) {
const events = this.props.target.getLiveTimeline().getEvents();
return this.state.permalinkCreator!.forEvent(events[events.length - 1].getId()!);
} else {
return this.state.permalinkCreator!.forShareableRoom();
}
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
return makeUserPermalink(this.props.target.userId);
} else if (this.state.linkSpecificEvent) {
return this.props.permalinkCreator!.forEvent(this.props.target.getId()!);
} else {
return this.props.permalinkCreator!.forShareableRoom();
}
}
public render(): React.ReactNode {
let title: string | undefined;
let checkbox: JSX.Element | undefined;
if (this.props.target instanceof URL) {
title = this.props.customTitle ?? _t("share|title_link");
} else if (this.props.target instanceof Room) {
title = this.props.customTitle ?? _t("share|title_room");
const events = this.props.target.getLiveTimeline().getEvents();
if (events.length > 0) {
checkbox = (
<div>
<StyledCheckbox
checked={this.state.linkSpecificEvent}
onChange={this.onLinkSpecificEventCheckboxClick}
>
{_t("share|permalink_most_recent")}
</StyledCheckbox>
</div>
);
}
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
title = this.props.customTitle ?? _t("share|title_user");
} else if (this.props.target instanceof MatrixEvent) {
title = this.props.customTitle ?? _t("share|title_message");
checkbox = (
<div>
<StyledCheckbox
checked={this.state.linkSpecificEvent}
onChange={this.onLinkSpecificEventCheckboxClick}
>
{_t("share|permalink_message")}
</StyledCheckbox>
</div>
);
}
const matrixToUrl = this.getUrl();
const encodedUrl = encodeURIComponent(matrixToUrl);
const showQrCode = SettingsStore.getValue(UIFeature.ShareQRCode);
const showSocials = SettingsStore.getValue(UIFeature.ShareSocial);
let qrSocialSection;
if (showQrCode || showSocials) {
qrSocialSection = (
<>
<hr />
<div className="mx_ShareDialog_split">
{showQrCode && (
<div className="mx_ShareDialog_qrcode_container">
<QRCode data={matrixToUrl} width={256} />
</div>
)}
{showSocials && (
<div className="mx_ShareDialog_social_container">
{socials.map((social) => (
<a
rel="noreferrer noopener"
target="_blank"
key={social.name}
title={social.name}
href={social.url(encodedUrl)}
className="mx_ShareDialog_social_icon"
>
<img src={social.img} alt={social.name} height={64} width={64} />
</a>
))}
</div>
)}
</div>
</>
);
}
return (
<BaseDialog
title={title}
className="mx_ShareDialog"
contentId="mx_Dialog_content"
onFinished={this.props.onFinished}
>
{this.props.subtitle && <p>{this.props.subtitle}</p>}
<div className="mx_ShareDialog_content">
<CopyableText getTextToCopy={() => matrixToUrl}>
<a title={_t("share|link_title")} href={matrixToUrl} onClick={ShareDialog.onLinkClick}>
{matrixToUrl}
</a>
</CopyableText>
{checkbox}
{qrSocialSection}
</div>
</BaseDialog>
);
}
}, [target, linkToSpecificEvent, permalinkCreator]);
}

View File

@ -47,7 +47,7 @@ import RoomAvatar from "../avatars/RoomAvatar";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
import Modal from "../../../Modal";
import ShareDialog from "../dialogs/ShareDialog";
import { ShareDialog } from "../dialogs/ShareDialog";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import { E2EStatus } from "../../../utils/ShieldUtils";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";

View File

@ -63,7 +63,7 @@ import PowerSelector from "../elements/PowerSelector";
import MemberAvatar from "../avatars/MemberAvatar";
import PresenceLabel from "../rooms/PresenceLabel";
import BulkRedactDialog from "../dialogs/BulkRedactDialog";
import ShareDialog from "../dialogs/ShareDialog";
import { ShareDialog } from "../dialogs/ShareDialog";
import ErrorDialog from "../dialogs/ErrorDialog";
import QuestionDialog from "../dialogs/QuestionDialog";
import ConfirmUserActionDialog from "../dialogs/ConfirmUserActionDialog";

View File

@ -12,7 +12,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { EventType, JoinRule, Room } from "matrix-js-sdk/src/matrix";
import Modal from "../../../../Modal";
import ShareDialog from "../../dialogs/ShareDialog";
import { ShareDialog } from "../../dialogs/ShareDialog";
import { _t } from "../../../../languageHandler";
import SettingsStore from "../../../../settings/SettingsStore";
import { calculateRoomVia } from "../../../../utils/permalinks/Permalinks";

View File

@ -2944,7 +2944,7 @@
"warning": "<w>WARNING:</w> <description/>"
},
"share": {
"link_title": "Link to room",
"link_copied": "Link copied",
"permalink_message": "Link to selected message",
"permalink_most_recent": "Link to most recent message",
"share_call": "Conference invite link",

View File

@ -7,111 +7,139 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { EventTimeline, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import { render, RenderOptions } from "jest-matrix-react";
import { MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import { render, screen, act } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { waitFor } from "@testing-library/dom";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import SettingsStore from "../../../../../src/settings/SettingsStore";
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
import { _t } from "../../../../../src/languageHandler";
import ShareDialog from "../../../../../src/components/views/dialogs/ShareDialog";
import { ShareDialog } from "../../../../../src/components/views/dialogs/ShareDialog";
import { UIFeature } from "../../../../../src/settings/UIFeature";
import { stubClient } from "../../../../test-utils";
jest.mock("../../../../../src/utils/ShieldUtils");
function getWrapper(): RenderOptions {
return {
wrapper: ({ children }) => (
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>{children}</MatrixClientContext.Provider>
),
};
}
import { stubClient, withClientContextRenderOptions } from "../../../../test-utils";
import * as StringsModule from "../../../../../src/utils/strings";
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks.ts";
describe("ShareDialog", () => {
let client: MatrixClient;
let room: Room;
const ROOM_ID = "!1:example.org";
const copyTextFunc = jest.fn();
beforeEach(async () => {
stubClient();
room = new Room(ROOM_ID, MatrixClientPeg.get()!, "@alice:example.org");
client = stubClient();
room = new Room("!1:example.org", client, "@alice:example.org");
jest.spyOn(StringsModule, "copyPlaintext").mockImplementation(copyTextFunc);
});
afterEach(() => {
jest.restoreAllMocks();
copyTextFunc.mockClear();
});
it("renders room share dialog", () => {
const { container: withoutEvents } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
expect(withoutEvents).toHaveTextContent(_t("share|title_room"));
function renderComponent(target: Room | RoomMember | URL) {
return render(<ShareDialog target={target} onFinished={jest.fn()} />, withClientContextRenderOptions(client));
}
jest.spyOn(room, "getLiveTimeline").mockReturnValue({ getEvents: () => [{} as MatrixEvent] } as EventTimeline);
const { container: withEvents } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
expect(withEvents).toHaveTextContent(_t("share|permalink_most_recent"));
const getUrl = () => new URL("https://matrix.org/");
const getRoomMember = () => new RoomMember(room.roomId, "@alice:example.org");
test.each([
{ name: "an URL", title: "Share Link", url: "https://matrix.org/", getTarget: getUrl },
{
name: "a room member",
title: "Share User",
url: "https://matrix.to/#/@alice:example.org",
getTarget: getRoomMember,
},
])("should render a share dialog for $name", async ({ title, url, getTarget }) => {
const { asFragment } = renderComponent(getTarget());
expect(screen.getByRole("heading", { name: title })).toBeInTheDocument();
expect(screen.getByText(url)).toBeInTheDocument();
expect(asFragment()).toMatchSnapshot();
await userEvent.click(screen.getByRole("button", { name: "Copy link" }));
expect(copyTextFunc).toHaveBeenCalledWith(url);
});
it("renders user share dialog", () => {
mockRoomMembers(room, 1);
const { container } = render(
<ShareDialog target={room.getJoinedMembers()[0]} onFinished={jest.fn()} />,
getWrapper(),
it("should render a share dialog for a room", async () => {
const expectedURL = "https://matrix.to/#/!1:example.org";
jest.spyOn(room.getLiveTimeline(), "getEvents").mockReturnValue([new MatrixEvent({ event_id: "!eventId" })]);
const { asFragment } = renderComponent(room);
expect(screen.getByRole("heading", { name: "Share Room" })).toBeInTheDocument();
expect(screen.getByText(expectedURL)).toBeInTheDocument();
expect(screen.getByRole("checkbox", { name: "Link to most recent message" })).toBeInTheDocument();
expect(asFragment()).toMatchSnapshot();
await userEvent.click(screen.getByRole("button", { name: "Copy link" }));
expect(copyTextFunc).toHaveBeenCalledWith(expectedURL);
// Click on the checkbox to link to the most recent message
await userEvent.click(screen.getByRole("checkbox", { name: "Link to most recent message" }));
const newExpectedURL = "https://matrix.to/#/!1:example.org/!eventId";
expect(screen.getByText(newExpectedURL)).toBeInTheDocument();
});
it("should render a share dialog for a matrix event", async () => {
const matrixEvent = new MatrixEvent({ event_id: "!eventId" });
const permalinkCreator = new RoomPermalinkCreator(room);
const expectedURL = "https://matrix.to/#/!1:example.org/!eventId";
const { asFragment } = render(
<ShareDialog target={matrixEvent} permalinkCreator={permalinkCreator} onFinished={jest.fn()} />,
withClientContextRenderOptions(client),
);
expect(container).toHaveTextContent(_t("share|title_user"));
expect(screen.getByRole("heading", { name: "Share Room Message" })).toBeInTheDocument();
expect(screen.getByText(expectedURL)).toBeInTheDocument();
expect(screen.getByRole("checkbox", { name: "Link to selected message" })).toBeChecked();
expect(asFragment()).toMatchSnapshot();
await userEvent.click(screen.getByRole("button", { name: "Copy link" }));
expect(copyTextFunc).toHaveBeenCalledWith(expectedURL);
// Click on the checkbox to link to the room
await userEvent.click(screen.getByRole("checkbox", { name: "Link to selected message" }));
expect(screen.getByText("https://matrix.to/#/!1:example.org")).toBeInTheDocument();
});
it("renders link share dialog", () => {
mockRoomMembers(room, 1);
const { container } = render(
<ShareDialog target={new URL("https://matrix.org")} onFinished={jest.fn()} />,
getWrapper(),
);
expect(container).toHaveTextContent(_t("share|title_link"));
it("should change the copy button text when clicked", async () => {
jest.useFakeTimers();
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
// To not be bother with rtl warnings about QR code state update
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
renderComponent(room);
await user.click(screen.getByRole("button", { name: "Copy link" }));
// Move after `copyPlaintext`
await jest.advanceTimersToNextTimerAsync();
expect(screen.getByRole("button", { name: "Link copied" })).toBeInTheDocument();
// 2 sec after the button should be back to normal
act(() => jest.advanceTimersByTime(2000));
await waitFor(() => expect(screen.getByRole("button", { name: "Copy link" })).toBeInTheDocument());
});
it("renders the QR code if configured", () => {
it("should not render the QR code if disabled", () => {
const originalGetValue = SettingsStore.getValue;
jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => {
if (feature === UIFeature.ShareQRCode) return true;
if (feature === UIFeature.ShareQRCode) return false;
return originalGetValue(feature);
});
const { container } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
const qrCodesVisible = container.getElementsByClassName("mx_ShareDialog_qrcode_container").length > 0;
expect(qrCodesVisible).toBe(true);
const { asFragment } = renderComponent(room);
expect(screen.queryByRole("img", { name: "QR code" })).toBeNull();
expect(asFragment()).toMatchSnapshot();
});
it("renders the social button if configured", () => {
it("should not render the socials if disabled", () => {
const originalGetValue = SettingsStore.getValue;
jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => {
if (feature === UIFeature.ShareSocial) return true;
if (feature === UIFeature.ShareSocial) return false;
return originalGetValue(feature);
});
const { container } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
const qrCodesVisible = container.getElementsByClassName("mx_ShareDialog_social_container").length > 0;
expect(qrCodesVisible).toBe(true);
});
it("renders custom title and subtitle", () => {
const { container } = render(
<ShareDialog
target={room}
customTitle="test_title_123"
subtitle="custom_subtitle_1234"
onFinished={jest.fn()}
/>,
getWrapper(),
);
expect(container).toHaveTextContent("test_title_123");
expect(container).toHaveTextContent("custom_subtitle_1234");
const { asFragment } = renderComponent(room);
expect(screen.queryByRole("link", { name: "Reddit" })).toBeNull();
expect(asFragment()).toMatchSnapshot();
});
});
/**
*
* @param count the number of users to create
*/
function mockRoomMembers(room: Room, count: number) {
const members = Array(count)
.fill(0)
.map((_, index) => new RoomMember(room.roomId, "@alice:example.org"));
room.currentState.setJoinedMemberCount(members.length);
room.getJoinedMembers = jest.fn().mockReturnValue(members);
}

View File

@ -0,0 +1,852 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ShareDialog should not render the QR code if disabled 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-describedby="mx_Dialog_content"
aria-labelledby="mx_BaseDialog_title"
class="mx_ShareDialog"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Share Room
</h1>
</div>
<div
class="mx_ShareDialog_content"
>
<div
class="mx_ShareDialog_top"
>
<span>
https://matrix.to/#/!1:example.org
</span>
</div>
<button
class="_button_i91xf_17 _has-icon_i91xf_66"
data-kind="primary"
data-size="lg"
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="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
/>
</svg>
Copy link
</button>
<div
class="mx_ShareDialog_social"
>
<a
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.to/#/!1:example.org"
rel="noreferrer noopener"
target="_blank"
title="Facebook"
>
<img
alt="Facebook"
src="image-file-stub"
/>
</a>
<a
href="https://twitter.com/home?status=https://matrix.to/#/!1:example.org"
rel="noreferrer noopener"
target="_blank"
title="Twitter"
>
<img
alt="Twitter"
src="image-file-stub"
/>
</a>
<a
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.to/#/!1:example.org"
rel="noreferrer noopener"
target="_blank"
title="LinkedIn"
>
<img
alt="LinkedIn"
src="image-file-stub"
/>
</a>
<a
href="https://www.reddit.com/submit?url=https://matrix.to/#/!1:example.org"
rel="noreferrer noopener"
target="_blank"
title="Reddit"
>
<img
alt="Reddit"
src="image-file-stub"
/>
</a>
<a
href="mailto:?body=https://matrix.to/#/!1:example.org"
rel="noreferrer noopener"
target="_blank"
title="email"
>
<img
alt="email"
src="image-file-stub"
/>
</a>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`ShareDialog should not render the socials if disabled 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-describedby="mx_Dialog_content"
aria-labelledby="mx_BaseDialog_title"
class="mx_ShareDialog"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Share Room
</h1>
</div>
<div
class="mx_ShareDialog_content"
>
<div
class="mx_ShareDialog_top"
>
<div
class="mx_QRCode"
>
<div
class="mx_Spinner"
>
<div
aria-label="Loading…"
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 32px; height: 32px;"
/>
</div>
</div>
<span>
https://matrix.to/#/!1:example.org
</span>
</div>
<button
class="_button_i91xf_17 _has-icon_i91xf_66"
data-kind="primary"
data-size="lg"
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="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
/>
</svg>
Copy link
</button>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`ShareDialog should render a share dialog for a matrix event 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-describedby="mx_Dialog_content"
aria-labelledby="mx_BaseDialog_title"
class="mx_ShareDialog"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Share Room Message
</h1>
</div>
<div
class="mx_ShareDialog_content"
>
<div
class="mx_ShareDialog_top"
>
<div
class="mx_QRCode"
>
<div
class="mx_Spinner"
>
<div
aria-label="Loading…"
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 32px; height: 32px;"
/>
</div>
</div>
<span>
https://matrix.to/#/!1:example.org/!eventId
</span>
</div>
<label>
<div
class="_container_1wloq_18"
>
<input
checked=""
class="_input_1wloq_26"
type="checkbox"
/>
<div
class="_ui_1wloq_27"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
/>
</svg>
</div>
</div>
Link to selected message
</label>
<button
class="_button_i91xf_17 _has-icon_i91xf_66"
data-kind="primary"
data-size="lg"
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="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
/>
</svg>
Copy link
</button>
<div
class="mx_ShareDialog_social"
>
<a
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.to/#/!1:example.org/!eventId"
rel="noreferrer noopener"
target="_blank"
title="Facebook"
>
<img
alt="Facebook"
src="image-file-stub"
/>
</a>
<a
href="https://twitter.com/home?status=https://matrix.to/#/!1:example.org/!eventId"
rel="noreferrer noopener"
target="_blank"
title="Twitter"
>
<img
alt="Twitter"
src="image-file-stub"
/>
</a>
<a
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.to/#/!1:example.org/!eventId"
rel="noreferrer noopener"
target="_blank"
title="LinkedIn"
>
<img
alt="LinkedIn"
src="image-file-stub"
/>
</a>
<a
href="https://www.reddit.com/submit?url=https://matrix.to/#/!1:example.org/!eventId"
rel="noreferrer noopener"
target="_blank"
title="Reddit"
>
<img
alt="Reddit"
src="image-file-stub"
/>
</a>
<a
href="mailto:?body=https://matrix.to/#/!1:example.org/!eventId"
rel="noreferrer noopener"
target="_blank"
title="email"
>
<img
alt="email"
src="image-file-stub"
/>
</a>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`ShareDialog should render a share dialog for a room 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-describedby="mx_Dialog_content"
aria-labelledby="mx_BaseDialog_title"
class="mx_ShareDialog"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Share Room
</h1>
</div>
<div
class="mx_ShareDialog_content"
>
<div
class="mx_ShareDialog_top"
>
<div
class="mx_QRCode"
>
<div
class="mx_Spinner"
>
<div
aria-label="Loading…"
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 32px; height: 32px;"
/>
</div>
</div>
<span>
https://matrix.to/#/!1:example.org
</span>
</div>
<label>
<div
class="_container_1wloq_18"
>
<input
class="_input_1wloq_26"
type="checkbox"
/>
<div
class="_ui_1wloq_27"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
/>
</svg>
</div>
</div>
Link to most recent message
</label>
<button
class="_button_i91xf_17 _has-icon_i91xf_66"
data-kind="primary"
data-size="lg"
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="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
/>
</svg>
Copy link
</button>
<div
class="mx_ShareDialog_social"
>
<a
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.to/#/!1:example.org"
rel="noreferrer noopener"
target="_blank"
title="Facebook"
>
<img
alt="Facebook"
src="image-file-stub"
/>
</a>
<a
href="https://twitter.com/home?status=https://matrix.to/#/!1:example.org"
rel="noreferrer noopener"
target="_blank"
title="Twitter"
>
<img
alt="Twitter"
src="image-file-stub"
/>
</a>
<a
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.to/#/!1:example.org"
rel="noreferrer noopener"
target="_blank"
title="LinkedIn"
>
<img
alt="LinkedIn"
src="image-file-stub"
/>
</a>
<a
href="https://www.reddit.com/submit?url=https://matrix.to/#/!1:example.org"
rel="noreferrer noopener"
target="_blank"
title="Reddit"
>
<img
alt="Reddit"
src="image-file-stub"
/>
</a>
<a
href="mailto:?body=https://matrix.to/#/!1:example.org"
rel="noreferrer noopener"
target="_blank"
title="email"
>
<img
alt="email"
src="image-file-stub"
/>
</a>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`ShareDialog should render a share dialog for a room member 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-describedby="mx_Dialog_content"
aria-labelledby="mx_BaseDialog_title"
class="mx_ShareDialog"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Share User
</h1>
</div>
<div
class="mx_ShareDialog_content"
>
<div
class="mx_ShareDialog_top"
>
<div
class="mx_QRCode"
>
<div
class="mx_Spinner"
>
<div
aria-label="Loading…"
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 32px; height: 32px;"
/>
</div>
</div>
<span>
https://matrix.to/#/@alice:example.org
</span>
</div>
<button
class="_button_i91xf_17 _has-icon_i91xf_66"
data-kind="primary"
data-size="lg"
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="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
/>
</svg>
Copy link
</button>
<div
class="mx_ShareDialog_social"
>
<a
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.to/#/@alice:example.org"
rel="noreferrer noopener"
target="_blank"
title="Facebook"
>
<img
alt="Facebook"
src="image-file-stub"
/>
</a>
<a
href="https://twitter.com/home?status=https://matrix.to/#/@alice:example.org"
rel="noreferrer noopener"
target="_blank"
title="Twitter"
>
<img
alt="Twitter"
src="image-file-stub"
/>
</a>
<a
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.to/#/@alice:example.org"
rel="noreferrer noopener"
target="_blank"
title="LinkedIn"
>
<img
alt="LinkedIn"
src="image-file-stub"
/>
</a>
<a
href="https://www.reddit.com/submit?url=https://matrix.to/#/@alice:example.org"
rel="noreferrer noopener"
target="_blank"
title="Reddit"
>
<img
alt="Reddit"
src="image-file-stub"
/>
</a>
<a
href="mailto:?body=https://matrix.to/#/@alice:example.org"
rel="noreferrer noopener"
target="_blank"
title="email"
>
<img
alt="email"
src="image-file-stub"
/>
</a>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`ShareDialog should render a share dialog for an URL 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-describedby="mx_Dialog_content"
aria-labelledby="mx_BaseDialog_title"
class="mx_ShareDialog"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Share Link
</h1>
</div>
<div
class="mx_ShareDialog_content"
>
<div
class="mx_ShareDialog_top"
>
<div
class="mx_QRCode"
>
<div
class="mx_Spinner"
>
<div
aria-label="Loading…"
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 32px; height: 32px;"
/>
</div>
</div>
<span>
https://matrix.org/
</span>
</div>
<button
class="_button_i91xf_17 _has-icon_i91xf_66"
data-kind="primary"
data-size="lg"
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="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
/>
</svg>
Copy link
</button>
<div
class="mx_ShareDialog_social"
>
<a
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.org/"
rel="noreferrer noopener"
target="_blank"
title="Facebook"
>
<img
alt="Facebook"
src="image-file-stub"
/>
</a>
<a
href="https://twitter.com/home?status=https://matrix.org/"
rel="noreferrer noopener"
target="_blank"
title="Twitter"
>
<img
alt="Twitter"
src="image-file-stub"
/>
</a>
<a
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.org/"
rel="noreferrer noopener"
target="_blank"
title="LinkedIn"
>
<img
alt="LinkedIn"
src="image-file-stub"
/>
</a>
<a
href="https://www.reddit.com/submit?url=https://matrix.org/"
rel="noreferrer noopener"
target="_blank"
title="Reddit"
>
<img
alt="Reddit"
src="image-file-stub"
/>
</a>
<a
href="mailto:?body=https://matrix.org/"
rel="noreferrer noopener"
target="_blank"
title="email"
>
<img
alt="email"
src="image-file-stub"
/>
</a>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;

View File

@ -15,7 +15,7 @@ import userEvent from "@testing-library/user-event";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import RoomSummaryCard from "../../../../../src/components/views/right_panel/RoomSummaryCard";
import ShareDialog from "../../../../../src/components/views/dialogs/ShareDialog";
import { ShareDialog } from "../../../../../src/components/views/dialogs/ShareDialog";
import ExportDialog from "../../../../../src/components/views/dialogs/ExportDialog";
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";

View File

@ -49,7 +49,7 @@ import ErrorDialog from "../../../../../src/components/views/dialogs/ErrorDialog
import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
import { UIComponent } from "../../../../../src/settings/UIFeature";
import { Action } from "../../../../../src/dispatcher/actions";
import ShareDialog from "../../../../../src/components/views/dialogs/ShareDialog";
import { ShareDialog } from "../../../../../src/components/views/dialogs/ShareDialog";
import BulkRedactDialog from "../../../../../src/components/views/dialogs/BulkRedactDialog";
jest.mock("../../../../../src/utils/direct-messages", () => ({

View File

@ -19,7 +19,7 @@ import {
} from "../../../../../../src/components/views/rooms/RoomHeader/CallGuestLinkButton";
import Modal from "../../../../../../src/Modal";
import SdkConfig from "../../../../../../src/SdkConfig";
import ShareDialog from "../../../../../../src/components/views/dialogs/ShareDialog";
import { ShareDialog } from "../../../../../../src/components/views/dialogs/ShareDialog";
import { _t } from "../../../../../../src/languageHandler";
import SettingsStore from "../../../../../../src/settings/SettingsStore";