Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/components/views/messages/DownloadActionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,21 @@ interface IProps {
mediaEventHelperGet: () => MediaEventHelper | undefined;
}

function useButtonTitle(loading: boolean, isEncrypted: boolean): string {
if (!loading) return _t("action|download");

return isEncrypted ? _t("timeline|download_action_decrypting") : _t("timeline|download_action_downloading");
}

export default function DownloadActionButton({ mxEvent, mediaEventHelperGet }: IProps): ReactElement | null {
const mediaEventHelper = useMemo(() => mediaEventHelperGet(), [mediaEventHelperGet]);
const downloadUrl = mediaEventHelper?.media.srcHttp ?? "";
const fileName = mediaEventHelper?.fileName;

const { download, loading, canDownload } = useDownloadMedia(downloadUrl, fileName, mxEvent);

const buttonTitle = useButtonTitle(loading, mediaEventHelper?.media.isEncrypted ?? false);

if (!canDownload) return null;

const spinner = loading ? <Spinner w={18} h={18} /> : undefined;
Expand All @@ -45,7 +53,7 @@ export default function DownloadActionButton({ mxEvent, mediaEventHelperGet }: I
return (
<RovingAccessibleButton
className={classes}
title={loading ? _t("timeline|download_action_downloading") : _t("action|download")}
title={buttonTitle}
onClick={download}
disabled={loading}
placement="left"
Expand Down
1 change: 1 addition & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -3373,6 +3373,7 @@
"unable_to_decrypt": "Unable to decrypt message"
},
"disambiguated_profile": "%(displayName)s (%(matrixId)s)",
"download_action_decrypting": "Decrypting",
"download_action_downloading": "Downloading",
"download_failed": "Download failed",
"download_failed_description": "An error occurred while downloading this file",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,54 @@ import { mocked } from "jest-mock";
import fetchMockJest from "fetch-mock-jest";
import { fireEvent, render, screen, waitFor } from "jest-matrix-react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import userEvent from "@testing-library/user-event";

import { stubClient } from "../../../../test-utils";
import { clearAllModals, stubClient } from "../../../../test-utils";
import DownloadActionButton from "../../../../../src/components/views/messages/DownloadActionButton";
import Modal from "../../../../../src/Modal";
import { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
import ErrorDialog from "../../../../../src/components/views/dialogs/ErrorDialog";

jest.mock("matrix-encrypt-attachment", () => ({
decryptAttachment: jest.fn().mockResolvedValue(new Blob(["TESTFILE"], { type: "application/octet-stream" })),
}));

describe("DownloadActionButton", () => {
const plainEvent = new MatrixEvent({
room_id: "!room:id",
sender: "@user:id",
type: "m.room.message",
content: {
body: "test",
msgtype: "m.image",
url: "mxc://matrix.org/1234",
},
});

beforeEach(() => {
jest.restoreAllMocks();
fetchMockJest.restore();
});

afterEach(() => {
clearAllModals();
});

it("should show error if media API returns one", async () => {
const cli = stubClient();
// eslint-disable-next-line no-restricted-properties
mocked(cli.mxcUrlToHttp).mockImplementation(
(mxc) => `https://matrix.org/_matrix/media/r0/download/${mxc.slice(6)}`,
);

fetchMockJest.get("https://matrix.org/_matrix/media/r0/download/matrix.org/1234", {
fetchMockJest.getOnce("https://matrix.org/_matrix/media/r0/download/matrix.org/1234", {
status: 404,
body: { errcode: "M_NOT_FOUND", error: "Not found" },
});

const event = new MatrixEvent({
room_id: "!room:id",
sender: "@user:id",
type: "m.room.message",
content: {
body: "test",
msgtype: "m.image",
url: "mxc://matrix.org/1234",
},
});
const mediaEventHelper = new MediaEventHelper(event);
const mediaEventHelper = new MediaEventHelper(plainEvent);

render(<DownloadActionButton mxEvent={event} mediaEventHelperGet={() => mediaEventHelper} />);
render(<DownloadActionButton mxEvent={plainEvent} mediaEventHelperGet={() => mediaEventHelper} />);

const spy = jest.spyOn(Modal, "createDialog");

Expand All @@ -57,4 +72,85 @@ describe("DownloadActionButton", () => {
),
);
});

it("should show download tooltip on hover", async () => {
stubClient();

const user = userEvent.setup();

fetchMockJest.getOnce("https://matrix.org/_matrix/media/r0/download/matrix.org/1234", "TESTFILE");

const event = new MatrixEvent({
room_id: "!room:id",
sender: "@user:id",
type: "m.room.message",
content: {
body: "test",
msgtype: "m.image",
url: "mxc://matrix.org/1234",
},
});

render(<DownloadActionButton mxEvent={event} mediaEventHelperGet={() => undefined} />);

const button = screen.getByRole("button");
await user.hover(button);

await waitFor(() => {
expect(screen.getByRole("tooltip")).toHaveTextContent("Download");
});
});

it("should show downloading tooltip while unencrypted files are downloading", async () => {
const user = userEvent.setup();

stubClient();

fetchMockJest.getOnce("http://this.is.a.url/matrix.org/1234", "TESTFILE");

const mediaEventHelper = new MediaEventHelper(plainEvent);

render(<DownloadActionButton mxEvent={plainEvent} mediaEventHelperGet={() => mediaEventHelper} />);

const button = screen.getByRole("button");
await user.hover(button);

await user.click(button);

await waitFor(() => {
expect(screen.getByRole("tooltip")).toHaveTextContent("Downloading");
});
});

it("should show decrypting tooltip while encrypted files are downloading", async () => {
const user = userEvent.setup();

stubClient();

fetchMockJest.getOnce("http://this.is.a.url/matrix.org/1234", "UFTUGJMF");

const e2eEvent = new MatrixEvent({
room_id: "!room:id",
sender: "@user:id",
type: "m.room.message",
content: {
body: "test",
msgtype: "m.image",
file: { url: "mxc://matrix.org/1234" },
},
});

const mediaEventHelper = new MediaEventHelper(e2eEvent);

render(<DownloadActionButton mxEvent={e2eEvent} mediaEventHelperGet={() => mediaEventHelper} />);

const button = screen.getByRole("button");
await user.hover(button);

await user.click(button);

await waitFor(() => {
expect(screen.getByRole("tooltip")).toHaveTextContent("Decrypting");
});
});
});
Loading