mirror of
https://github.com/vector-im/element-web.git
synced 2026-03-03 20:42:28 +01:00
Cleanup
This commit is contained in:
parent
b90820c6c2
commit
ba91e16201
@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type MouseEventHandler, type JSX, useCallback, useId } from "react";
|
||||
import React, { type MouseEventHandler, type JSX, useCallback } from "react";
|
||||
import { IconButton, Tooltip } from "@vector-im/compound-web";
|
||||
import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
|
||||
import classNames from "classnames";
|
||||
@ -15,7 +15,7 @@ import styles from "./LinkPreview.module.css";
|
||||
import type { UrlPreviewViewSnapshotPreview } from "./types";
|
||||
|
||||
export interface LinkPreviewActions {
|
||||
onHideClick?: () => void;
|
||||
onHideClick?: () => Promise<void>;
|
||||
onImageClick: () => void;
|
||||
}
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ export interface UrlPreviewGroupViewProps {
|
||||
|
||||
export interface UrlPreviewGroupViewActions {
|
||||
onTogglePreviewLimit: () => void;
|
||||
onHideClick: () => void;
|
||||
onHideClick: () => Promise<void>;
|
||||
onImageClick: (preview: UrlPreviewViewSnapshotPreview) => void;
|
||||
}
|
||||
|
||||
|
||||
@ -95,7 +95,7 @@ class InnerTextualBody extends React.Component<IBodyProps & { urlPreviewViewMode
|
||||
},
|
||||
|
||||
unhideWidget: () => {
|
||||
this.props.urlPreviewViewModel.onShowClick();
|
||||
void this.props.urlPreviewViewModel.onShowClick();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -172,7 +172,7 @@ export class UrlPreviewViewModel
|
||||
}
|
||||
|
||||
const media =
|
||||
typeof preview["og:image"] === "string" && this.visibility >= PreviewVisibility.MediaHidden
|
||||
typeof preview["og:image"] === "string" && this.visibility > PreviewVisibility.MediaHidden
|
||||
? mediaFromMxc(preview["og:image"], this.client)
|
||||
: undefined;
|
||||
const needsTooltip = link !== title && PlatformPeg.get()?.needsUrlTooltips();
|
||||
@ -319,7 +319,6 @@ export class UrlPreviewViewModel
|
||||
previewsLimited: this.limitPreviews,
|
||||
overPreviewLimit: this.links.length > MAX_PREVIEWS_WHEN_LIMITED,
|
||||
});
|
||||
console.log("SNAPSHOT", this.visibility, previews, this.snapshot.current);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -350,22 +349,22 @@ export class UrlPreviewViewModel
|
||||
* Called when the user has requsted previews be visible. The provided
|
||||
* props `urlPreviewVisible` state will always override this.
|
||||
*/
|
||||
public readonly onShowClick = (): void => {
|
||||
public readonly onShowClick = (): Promise<void> => {
|
||||
// FIXME: persist this somewhere smarter than local storage
|
||||
this.urlPreviewEnabledByUser = true;
|
||||
global.localStorage?.removeItem(this.storageKey);
|
||||
void this.computeSnapshot();
|
||||
return this.computeSnapshot();
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the user has requsted previews be hidden. Will take precedence
|
||||
* over other settings.
|
||||
*/
|
||||
public readonly onHideClick = (): void => {
|
||||
public readonly onHideClick = (): Promise<void> => {
|
||||
// FIXME: persist this somewhere smarter than local storage
|
||||
global.localStorage?.setItem(this.storageKey, "1");
|
||||
this.urlPreviewEnabledByUser = false;
|
||||
void this.computeSnapshot();
|
||||
return this.computeSnapshot();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -5,20 +5,38 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { expect } from "@jest/globals";
|
||||
|
||||
import type { MockedObject } from "jest-mock";
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { UrlPreviewViewModel } from "../../../src/viewmodels/message-body/UrlPreviewViewModel";
|
||||
import type { UrlPreviewViewSnapshotPreview } from "@element-hq/web-shared-components";
|
||||
import { getMockClientWithEventEmitter, mkEvent } from "../../test-utils";
|
||||
|
||||
function getViewModel(): { vm: UrlPreviewViewModel; client: MockedObject<MatrixClient> } {
|
||||
const IMAGE_MXC = "mxc://example.org/abc";
|
||||
const BASIC_PREVIEW_OGDATA = {
|
||||
"og:title": "This is an example!",
|
||||
"og:description": "This is a description",
|
||||
"og:type": "document",
|
||||
"og:url": "https://example.org",
|
||||
"og:site_name": "Example.org",
|
||||
};
|
||||
|
||||
function getViewModel({ mediaVisible, visible } = { mediaVisible: true, visible: true }): {
|
||||
vm: UrlPreviewViewModel;
|
||||
client: MockedObject<MatrixClient>;
|
||||
onImageClicked: jest.Mock<void, [UrlPreviewViewSnapshotPreview]>;
|
||||
} {
|
||||
const client = getMockClientWithEventEmitter({
|
||||
getUrlPreview: jest.fn(),
|
||||
mxcUrlToHttp: jest.fn(),
|
||||
});
|
||||
const onImageClicked = jest.fn<void, [UrlPreviewViewSnapshotPreview]>();
|
||||
const vm = new UrlPreviewViewModel({
|
||||
client,
|
||||
mediaVisible: true,
|
||||
visible: true,
|
||||
onImageClicked: jest.fn(),
|
||||
mediaVisible,
|
||||
visible,
|
||||
onImageClicked,
|
||||
mxEvent: mkEvent({
|
||||
event: true,
|
||||
user: "@foo:bar",
|
||||
@ -27,7 +45,7 @@ function getViewModel(): { vm: UrlPreviewViewModel; client: MockedObject<MatrixC
|
||||
id: "$id",
|
||||
}),
|
||||
});
|
||||
return { vm, client };
|
||||
return { vm, client, onImageClicked };
|
||||
}
|
||||
|
||||
describe("UrlPreviewViewModel", () => {
|
||||
@ -41,11 +59,97 @@ describe("UrlPreviewViewModel", () => {
|
||||
});
|
||||
});
|
||||
it("should preview a single valid URL", async () => {
|
||||
const { vm, client } = getViewModel();
|
||||
client.getUrlPreview.mockResolvedValueOnce(BASIC_PREVIEW_OGDATA);
|
||||
const msg = document.createElement("div");
|
||||
msg.innerHTML = '<a href="https://example.org">Test</a>';
|
||||
await vm.updateEventElement(msg);
|
||||
expect(vm.getSnapshot()).toEqual({
|
||||
previews: [
|
||||
{
|
||||
link: "https://example.org",
|
||||
title: "This is an example!",
|
||||
siteName: "Example.org",
|
||||
showTooltipOnLink: undefined,
|
||||
description: "This is a description",
|
||||
image: undefined,
|
||||
},
|
||||
],
|
||||
compactLayout: false,
|
||||
overPreviewLimit: false,
|
||||
previewsLimited: true,
|
||||
totalPreviewCount: 1,
|
||||
});
|
||||
});
|
||||
it("should hide preview when invisible", async () => {
|
||||
const { vm, client } = getViewModel({ visible: false, mediaVisible: true });
|
||||
const msg = document.createElement("div");
|
||||
msg.innerHTML = '<a href="https://example.org">Test</a>';
|
||||
await vm.updateEventElement(msg);
|
||||
expect(vm.getSnapshot()).toEqual({
|
||||
previews: [],
|
||||
compactLayout: false,
|
||||
overPreviewLimit: false,
|
||||
previewsLimited: true,
|
||||
totalPreviewCount: 1,
|
||||
});
|
||||
expect(client.getUrlPreview).not.toHaveBeenCalled();
|
||||
});
|
||||
it("should preview a URL with media", async () => {
|
||||
const { vm, client } = getViewModel();
|
||||
client.getUrlPreview.mockResolvedValueOnce({
|
||||
"og:title": "This is an example!",
|
||||
"og:type": "document",
|
||||
"og:url": "https://example.org",
|
||||
"og:image": IMAGE_MXC,
|
||||
"og:image:height": 128,
|
||||
"og:image:width": 128,
|
||||
"matrix:image:size": 10000,
|
||||
});
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
client.mxcUrlToHttp.mockImplementation((url, width) => {
|
||||
expect(url).toEqual(IMAGE_MXC);
|
||||
if (width) {
|
||||
return "https://example.org/image/thumb";
|
||||
}
|
||||
return "https://example.org/image/src";
|
||||
});
|
||||
const msg = document.createElement("div");
|
||||
msg.innerHTML = '<a href="https://example.org">Test</a>';
|
||||
await vm.updateEventElement(msg);
|
||||
expect(vm.getSnapshot()).toEqual({
|
||||
previews: [
|
||||
{
|
||||
link: "https://example.org",
|
||||
title: "This is an example!",
|
||||
siteName: undefined,
|
||||
showTooltipOnLink: undefined,
|
||||
description: undefined,
|
||||
image: {
|
||||
height: 100,
|
||||
width: 100,
|
||||
fileSize: 10000,
|
||||
imageThumb: "https://example.org/image/thumb",
|
||||
imageFull: "https://example.org/image/src",
|
||||
},
|
||||
},
|
||||
],
|
||||
compactLayout: false,
|
||||
overPreviewLimit: false,
|
||||
previewsLimited: true,
|
||||
totalPreviewCount: 1,
|
||||
});
|
||||
});
|
||||
it("should ignore media when mediaVisible is false", async () => {
|
||||
const { vm, client } = getViewModel({ mediaVisible: false, visible: true });
|
||||
client.getUrlPreview.mockResolvedValueOnce({
|
||||
"og:title": "This is an example!",
|
||||
"og:type": "document",
|
||||
"og:url": "https://example.org",
|
||||
"og:image": IMAGE_MXC,
|
||||
"og:image:height": 128,
|
||||
"og:image:width": 128,
|
||||
"matrix:image:size": 10000,
|
||||
});
|
||||
const msg = document.createElement("div");
|
||||
msg.innerHTML = '<a href="https://example.org">Test</a>';
|
||||
@ -66,12 +170,120 @@ describe("UrlPreviewViewModel", () => {
|
||||
previewsLimited: true,
|
||||
totalPreviewCount: 1,
|
||||
});
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
expect(client.mxcUrlToHttp).not.toHaveBeenCalled();
|
||||
});
|
||||
it("should deduplicate multiple versions of the same URL", async () => {
|
||||
const { vm, client } = getViewModel();
|
||||
client.getUrlPreview.mockResolvedValueOnce(BASIC_PREVIEW_OGDATA);
|
||||
const msg = document.createElement("div");
|
||||
msg.innerHTML =
|
||||
'<a href="https://example.org">Test</a><a href="https://example.org">Test</a><a href="https://example.org">Test</a>';
|
||||
await vm.updateEventElement(msg);
|
||||
expect(vm.getSnapshot()).toEqual({
|
||||
previews: [
|
||||
{
|
||||
link: "https://example.org",
|
||||
title: "This is an example!",
|
||||
siteName: "Example.org",
|
||||
showTooltipOnLink: undefined,
|
||||
description: "This is a description",
|
||||
image: undefined,
|
||||
},
|
||||
],
|
||||
compactLayout: false,
|
||||
overPreviewLimit: false,
|
||||
previewsLimited: true,
|
||||
totalPreviewCount: 1,
|
||||
});
|
||||
expect(client.getUrlPreview).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it("should ignore failed previews", async () => {
|
||||
const { vm, client } = getViewModel();
|
||||
client.getUrlPreview.mockRejectedValue(new Error("Forced test failure"));
|
||||
const msg = document.createElement("div");
|
||||
msg.innerHTML = '<a href="https://example.org">Test</a>';
|
||||
await vm.updateEventElement(msg);
|
||||
expect(vm.getSnapshot()).toEqual({
|
||||
previews: [],
|
||||
compactLayout: false,
|
||||
overPreviewLimit: false,
|
||||
previewsLimited: true,
|
||||
totalPreviewCount: 1,
|
||||
});
|
||||
});
|
||||
it("should handle image clicks", async () => {
|
||||
const { vm, client, onImageClicked } = getViewModel();
|
||||
client.getUrlPreview.mockResolvedValueOnce({
|
||||
"og:title": "This is an example!",
|
||||
"og:type": "document",
|
||||
"og:url": "https://example.org",
|
||||
"og:image": IMAGE_MXC,
|
||||
"og:image:height": 128,
|
||||
"og:image:width": 128,
|
||||
"matrix:image:size": 10000,
|
||||
});
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
client.mxcUrlToHttp.mockImplementation((url, width) => {
|
||||
expect(url).toEqual(IMAGE_MXC);
|
||||
if (width) {
|
||||
return "https://example.org/image/thumb";
|
||||
}
|
||||
return "https://example.org/image/src";
|
||||
});
|
||||
const msg = document.createElement("div");
|
||||
msg.innerHTML = '<a href="https://example.org">Test</a>';
|
||||
await vm.updateEventElement(msg);
|
||||
const { previews } = vm.getSnapshot();
|
||||
vm.onImageClick(previews[0]);
|
||||
expect(onImageClicked).toHaveBeenCalled();
|
||||
});
|
||||
it("should handle being hidden and shown by the user", async () => {
|
||||
const { vm, client } = getViewModel();
|
||||
client.getUrlPreview.mockResolvedValueOnce(BASIC_PREVIEW_OGDATA);
|
||||
const msg = document.createElement("div");
|
||||
msg.innerHTML = '<a href="https://example.org">Test</a>';
|
||||
await vm.updateEventElement(msg);
|
||||
await vm.onHideClick();
|
||||
expect(vm.getSnapshot()).toEqual({
|
||||
previews: [],
|
||||
compactLayout: false,
|
||||
overPreviewLimit: false,
|
||||
previewsLimited: true,
|
||||
totalPreviewCount: 1,
|
||||
});
|
||||
|
||||
await vm.onShowClick();
|
||||
expect(vm.getSnapshot()).toEqual({
|
||||
previews: [
|
||||
{
|
||||
link: "https://example.org",
|
||||
title: "This is an example!",
|
||||
siteName: "Example.org",
|
||||
showTooltipOnLink: undefined,
|
||||
description: "This is a description",
|
||||
image: undefined,
|
||||
},
|
||||
],
|
||||
compactLayout: false,
|
||||
overPreviewLimit: false,
|
||||
previewsLimited: true,
|
||||
totalPreviewCount: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ text: "", href: "", hasPreview: false },
|
||||
{ text: "test", href: "noprotocol.example.org", hasPreview: false },
|
||||
{ text: "matrix link", href: "https://matrix.to", hasPreview: false },
|
||||
{ text: "email", href: "mailto:example.org", hasPreview: false },
|
||||
{ text: "", href: "https://example.org", hasPreview: true },
|
||||
])("handles different kinds of links %s", async (item) => {
|
||||
const { vm, client } = getViewModel();
|
||||
client.getUrlPreview.mockResolvedValueOnce(BASIC_PREVIEW_OGDATA);
|
||||
const msg = document.createElement("div");
|
||||
msg.innerHTML = `<a href="${item.href}">${item.text}</a>`;
|
||||
await vm.updateEventElement(msg);
|
||||
expect(vm.getSnapshot().previews).toHaveLength(item.hasPreview ? 1 : 0);
|
||||
});
|
||||
it.todo("should preview a URL with media");
|
||||
it.todo("should ignore media when mediaVisible is false");
|
||||
it.todo("should deduplicate multiple versions of the same URL");
|
||||
it.todo("should ignore failed previews");
|
||||
it.todo("should handle image clicks");
|
||||
it.todo("should handle being hidden by the user");
|
||||
it.todo("should handle being shown by the user");
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user