Add support for hiding videos (#29496)
* start hide * Move useSettingsValueWithSetter to useSettings * Add new setting showMediaEventIds * Add a migration path * Add an action button to hide settings. * Tweaks to MImageBody to support new setting. * Fixup and add tests * add description for migration * docs fixes * add type * i18n * appese prettier * Add tests for HideActionButton * lint * lint * First pass at support for previewing/hiding images. * Add a test for video files. * First pass at supporting hiding video files. * Use a hook for media visibility. * Drop setting hook usage. * Fixup MImageBody test * Fixup tests * Support functional components for message body rendering. * Add a comment * Move props into IProps * Use new wrapping logic * lint * fixup * allow for a delay for the image to render * remove .only * lint * Fix jest test * Fixup tests. * make tests happy * Improve comments * review fixes * unbreak test
This commit is contained in:
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { act } from "react";
|
||||
import { fireEvent, render, screen, waitForElementToBeRemoved } from "jest-matrix-react";
|
||||
import { fireEvent, render, screen, waitFor, waitForElementToBeRemoved } from "jest-matrix-react";
|
||||
import { EventType, getHttpUriForMxc, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
import encrypt from "matrix-encrypt-attachment";
|
||||
@@ -85,6 +85,10 @@ describe("<MImageBody/>", () => {
|
||||
fetchMock.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mocked(encrypt.decryptAttachment).mockReset();
|
||||
});
|
||||
|
||||
it("should show a thumbnail while image is being downloaded", async () => {
|
||||
fetchMock.getOnce(url, { status: 200 });
|
||||
|
||||
@@ -166,6 +170,8 @@ describe("<MImageBody/>", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Show image")).toBeInTheDocument();
|
||||
|
||||
expect(fetchMock).not.toHaveFetched(url);
|
||||
});
|
||||
|
||||
@@ -186,8 +192,12 @@ describe("<MImageBody/>", () => {
|
||||
|
||||
expect(fetchMock).toHaveFetched(url);
|
||||
|
||||
// spinner while downloading image
|
||||
expect(screen.getByRole("progressbar")).toBeInTheDocument();
|
||||
// Show image is asynchronous since it applies through a settings watcher hook, so
|
||||
// be sure to wait here.
|
||||
await waitFor(() => {
|
||||
// spinner while downloading image
|
||||
expect(screen.getByRole("progressbar")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { act } from "react";
|
||||
import { EventType, getHttpUriForMxc, type IContent, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { render, type RenderResult } from "jest-matrix-react";
|
||||
import { fireEvent, render, screen, type RenderResult } from "jest-matrix-react";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
@@ -22,19 +22,22 @@ import {
|
||||
mockClientMethodsUser,
|
||||
} from "../../../../test-utils";
|
||||
import MVideoBody from "../../../../../src/components/views/messages/MVideoBody";
|
||||
import type { IBodyProps } from "../../../../../src/components/views/messages/IBodyProps";
|
||||
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
|
||||
// Needed so we don't throw an error about failing to decrypt.
|
||||
jest.mock("matrix-encrypt-attachment", () => ({
|
||||
decryptAttachment: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("MVideoBody", () => {
|
||||
it("does not crash when given a portrait image", () => {
|
||||
// Check for an unreliable crash caused by a fractional-sized
|
||||
// image dimension being used for a CanvasImageData.
|
||||
const { asFragment } = makeMVideoBody(720, 1280);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
// If we get here, we did not crash.
|
||||
});
|
||||
const userId = "@user:server";
|
||||
const deviceId = "DEADB33F";
|
||||
|
||||
it("should show poster for encrypted media before downloading it", async () => {
|
||||
const userId = "@user:server";
|
||||
const deviceId = "DEADB33F";
|
||||
const thumbUrl = "https://server/_matrix/media/v3/download/server/encrypted-poster";
|
||||
|
||||
beforeEach(() => {
|
||||
const cli = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
...mockClientMethodsServer(),
|
||||
@@ -49,40 +52,108 @@ describe("MVideoBody", () => {
|
||||
},
|
||||
}),
|
||||
});
|
||||
const thumbUrl = "https://server/_matrix/media/v3/download/server/encrypted-poster";
|
||||
fetchMock.getOnce(thumbUrl, { status: 200 });
|
||||
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
cli.mxcUrlToHttp.mockImplementation(
|
||||
(mxcUrl: string, width?: number, height?: number, resizeMethod?: string, allowDirectLinks?: boolean) => {
|
||||
return getHttpUriForMxc("https://server", mxcUrl, width, height, resizeMethod, allowDirectLinks);
|
||||
},
|
||||
);
|
||||
const encryptedMediaEvent = new MatrixEvent({
|
||||
room_id: "!room:server",
|
||||
sender: userId,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
body: "alt for a test video",
|
||||
info: {
|
||||
duration: 420,
|
||||
w: 40,
|
||||
h: 50,
|
||||
thumbnail_file: {
|
||||
url: "mxc://server/encrypted-poster",
|
||||
},
|
||||
},
|
||||
file: {
|
||||
url: "mxc://server/encrypted-image",
|
||||
fetchMock.mockReset();
|
||||
});
|
||||
|
||||
const encryptedMediaEvent = new MatrixEvent({
|
||||
room_id: "!room:server",
|
||||
sender: userId,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
body: "alt for a test video",
|
||||
info: {
|
||||
duration: 420,
|
||||
w: 40,
|
||||
h: 50,
|
||||
thumbnail_file: {
|
||||
url: "mxc://server/encrypted-poster",
|
||||
},
|
||||
},
|
||||
});
|
||||
file: {
|
||||
url: "mxc://server/encrypted-image",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
it("does not crash when given a portrait image", () => {
|
||||
// Check for an unreliable crash caused by a fractional-sized
|
||||
// image dimension being used for a CanvasImageData.
|
||||
const { asFragment } = makeMVideoBody(720, 1280);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
// If we get here, we did not crash.
|
||||
});
|
||||
|
||||
it("should show poster for encrypted media before downloading it", async () => {
|
||||
fetchMock.getOnce(thumbUrl, { status: 200 });
|
||||
const { asFragment } = render(
|
||||
<MVideoBody mxEvent={encryptedMediaEvent} mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)} />,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("with video previews/thumbnails disabled", () => {
|
||||
beforeEach(() => {
|
||||
act(() => {
|
||||
SettingsStore.setValue("showImages", null, SettingLevel.DEVICE, false);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
act(() => {
|
||||
SettingsStore.setValue(
|
||||
"showImages",
|
||||
null,
|
||||
SettingLevel.DEVICE,
|
||||
SettingsStore.getDefaultValue("showImages"),
|
||||
);
|
||||
SettingsStore.setValue(
|
||||
"showMediaEventIds",
|
||||
null,
|
||||
SettingLevel.DEVICE,
|
||||
SettingsStore.getDefaultValue("showMediaEventIds"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not download video", async () => {
|
||||
fetchMock.getOnce(thumbUrl, { status: 200 });
|
||||
|
||||
render(
|
||||
<MVideoBody
|
||||
mxEvent={encryptedMediaEvent}
|
||||
mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Show video")).toBeInTheDocument();
|
||||
|
||||
expect(fetchMock).not.toHaveFetched(thumbUrl);
|
||||
});
|
||||
|
||||
it("should render video poster after user consent", async () => {
|
||||
fetchMock.getOnce(thumbUrl, { status: 200 });
|
||||
|
||||
render(
|
||||
<MVideoBody
|
||||
mxEvent={encryptedMediaEvent}
|
||||
mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)}
|
||||
/>,
|
||||
);
|
||||
|
||||
const placeholderButton = screen.getByRole("button", { name: "Show video" });
|
||||
|
||||
expect(placeholderButton).toBeInTheDocument();
|
||||
fireEvent.click(placeholderButton);
|
||||
|
||||
expect(fetchMock).toHaveFetched(thumbUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function makeMVideoBody(w: number, h: number): RenderResult {
|
||||
@@ -109,7 +180,7 @@ function makeMVideoBody(w: number, h: number): RenderResult {
|
||||
content,
|
||||
});
|
||||
|
||||
const defaultProps: MVideoBody["props"] = {
|
||||
const defaultProps: IBodyProps = {
|
||||
mxEvent: event,
|
||||
highlights: [],
|
||||
highlightLink: "",
|
||||
|
||||
@@ -41,9 +41,6 @@ exports[`<MImageBody/> should generate a thumbnail if one isn't included for ani
|
||||
GIF
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
style="height: 50px; width: 40px;"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -77,9 +74,6 @@ exports[`<MImageBody/> should show a thumbnail while image is being downloaded 1
|
||||
<div
|
||||
style="max-height: 50px; max-width: 40px;"
|
||||
/>
|
||||
<div
|
||||
style="height: 50px; width: 40px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user