Rewrite tons of tests
This commit is contained in:
129
playwright/e2e/timeline/media-preview-settings.spec.ts
Normal file
129
playwright/e2e/timeline/media-preview-settings.spec.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024, 2025 New Vector Ltd.
|
||||||
|
Copyright 2022, 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 * as fs from "node:fs";
|
||||||
|
import { type EventType, type MsgType } from "matrix-js-sdk/src/types";
|
||||||
|
import { JoinRule } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import type { Locator, Page } from "@playwright/test";
|
||||||
|
import { test, expect } from "../../element-web-test";
|
||||||
|
import { Bot } from "../../pages/bot";
|
||||||
|
|
||||||
|
const ROOM_NAME = "Test room";
|
||||||
|
const OLD_NAME = "Alan";
|
||||||
|
|
||||||
|
const MEDIA_FILE = fs.readFileSync("playwright/sample-files/riot.png");
|
||||||
|
|
||||||
|
test.describe("Media preview settings", () => {
|
||||||
|
test.use({
|
||||||
|
displayName: OLD_NAME,
|
||||||
|
room: async ({ app, page, homeserver, bot, user }, use) => {
|
||||||
|
const mxc = (await bot.uploadContent(MEDIA_FILE, { name: "image.png", type: "image/png" })).content_uri;
|
||||||
|
const roomId = await bot.createRoom({
|
||||||
|
name: ROOM_NAME,
|
||||||
|
invite: [user.userId],
|
||||||
|
initial_state: [{ type: "m.room.avatar", content: { url: mxc }, state_key: "" }],
|
||||||
|
});
|
||||||
|
await bot.sendEvent(roomId, null, "m.room.message" as EventType, {
|
||||||
|
msgtype: "m.image" as MsgType,
|
||||||
|
body: "image.png",
|
||||||
|
url: mxc,
|
||||||
|
});
|
||||||
|
|
||||||
|
await use({ roomId });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should be able to hide avatars of inviters", { tag: "@screenshot" }, async ({ page, app, room, user }) => {
|
||||||
|
let settings = await app.settings.openUserSettings("Preferences");
|
||||||
|
await settings.getByLabel("Hide avatars of room and inviter").click();
|
||||||
|
await app.closeDialog();
|
||||||
|
await app.viewRoomById(room.roomId);
|
||||||
|
expect(page.getByRole("complementary").filter({ hasText: "Do you want to join Test room" })).toMatchScreenshot(
|
||||||
|
"invite-no-avatar.png",
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Test room" }),
|
||||||
|
).toMatchScreenshot("invite-room-tree-no-avatar.png");
|
||||||
|
|
||||||
|
// And then go back to being visible
|
||||||
|
settings = await app.settings.openUserSettings("Preferences");
|
||||||
|
await settings.getByLabel("Hide avatars of room and inviter").click();
|
||||||
|
await app.closeDialog();
|
||||||
|
await page.goto("#/home");
|
||||||
|
await app.viewRoomById(room.roomId);
|
||||||
|
expect(page.getByRole("complementary").filter({ hasText: "Do you want to join Test room" })).toMatchScreenshot(
|
||||||
|
"invite-with-avatar.png",
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Test room" }),
|
||||||
|
).toMatchScreenshot("invite-room-tree-with-avatar.png");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should be able to hide media in rooms globally", async ({ page, app, room, user }) => {
|
||||||
|
const settings = await app.settings.openUserSettings("Preferences");
|
||||||
|
await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always hide" }).click();
|
||||||
|
await app.closeDialog();
|
||||||
|
await app.viewRoomById(room.roomId);
|
||||||
|
await page.getByRole("button", { name: "Accept" }).click();
|
||||||
|
await expect(page.getByText("Show image")).toBeVisible();
|
||||||
|
});
|
||||||
|
test("should be able to hide media in non-private rooms globally", async ({ page, app, room, user, bot }) => {
|
||||||
|
await bot.sendStateEvent(room.roomId, "m.room.join_rules", {
|
||||||
|
join_rule: "public",
|
||||||
|
});
|
||||||
|
const settings = await app.settings.openUserSettings("Preferences");
|
||||||
|
await settings.getByLabel("Show media in timeline").getByLabel("In private rooms").click();
|
||||||
|
await app.closeDialog();
|
||||||
|
await app.viewRoomById(room.roomId);
|
||||||
|
await page.getByRole("button", { name: "Accept" }).click();
|
||||||
|
await expect(page.getByText("Show image")).toBeVisible();
|
||||||
|
for (const joinRule of [JoinRule.Invite, JoinRule.Knock, JoinRule.Restricted]) {
|
||||||
|
await bot.sendStateEvent(room.roomId, "m.room.join_rules", {
|
||||||
|
join_rule: joinRule,
|
||||||
|
});
|
||||||
|
await expect(page.getByText("Show image")).not.toBeVisible();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
test("should be able to show media in rooms globally", async ({ page, app, room, user }) => {
|
||||||
|
const settings = await app.settings.openUserSettings("Preferences");
|
||||||
|
await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always show" }).click();
|
||||||
|
await app.closeDialog();
|
||||||
|
await app.viewRoomById(room.roomId);
|
||||||
|
await page.getByRole("button", { name: "Accept" }).click();
|
||||||
|
await expect(page.getByText("Show image")).not.toBeVisible();
|
||||||
|
});
|
||||||
|
test("should be able to hide media in an individual room", async ({ page, app, room, user }) => {
|
||||||
|
const settings = await app.settings.openUserSettings("Preferences");
|
||||||
|
await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always show" }).click();
|
||||||
|
await app.closeDialog();
|
||||||
|
|
||||||
|
await app.viewRoomById(room.roomId);
|
||||||
|
await page.getByRole("button", { name: "Accept" }).click();
|
||||||
|
|
||||||
|
const roomSettings = await app.settings.openRoomSettings("General");
|
||||||
|
await roomSettings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always hide" }).click();
|
||||||
|
await app.closeDialog();
|
||||||
|
|
||||||
|
await expect(page.getByText("Show image")).toBeVisible();
|
||||||
|
});
|
||||||
|
test("should be able to show media in an individual room", async ({ page, app, room, user }) => {
|
||||||
|
const settings = await app.settings.openUserSettings("Preferences");
|
||||||
|
await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always hide" }).click();
|
||||||
|
await app.closeDialog();
|
||||||
|
|
||||||
|
await app.viewRoomById(room.roomId);
|
||||||
|
await page.getByRole("button", { name: "Accept" }).click();
|
||||||
|
|
||||||
|
const roomSettings = await app.settings.openRoomSettings("General");
|
||||||
|
await roomSettings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always show" }).click();
|
||||||
|
await app.closeDialog();
|
||||||
|
|
||||||
|
await expect(page.getByText("Show image")).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -22,6 +22,7 @@ import { SettingsSubsection } from "../../shared/SettingsSubsection";
|
|||||||
import SettingsTab from "../SettingsTab";
|
import SettingsTab from "../SettingsTab";
|
||||||
import { SettingsSection } from "../../shared/SettingsSection";
|
import { SettingsSection } from "../../shared/SettingsSection";
|
||||||
import { UrlPreviewSettings } from "../../../room_settings/UrlPreviewSettings";
|
import { UrlPreviewSettings } from "../../../room_settings/UrlPreviewSettings";
|
||||||
|
import { MediaPreviewAccountSettings } from "../user/MediaPreviewSetting";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
@@ -92,6 +93,7 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
|
|||||||
|
|
||||||
<SettingsSection heading={_t("room_settings|general|other_section")}>
|
<SettingsSection heading={_t("room_settings|general|other_section")}>
|
||||||
{urlPreviewSettings}
|
{urlPreviewSettings}
|
||||||
|
<MediaPreviewAccountSettings roomId={room.roomId} />
|
||||||
{leaveSection}
|
{leaveSection}
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
</SettingsTab>
|
</SettingsTab>
|
||||||
|
|||||||
@@ -15,19 +15,30 @@ import { useSettingValue } from "../../../../../hooks/useSettings";
|
|||||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||||
|
|
||||||
export const MediaPreviewAccountSettings: React.FC = () => {
|
export const MediaPreviewAccountSettings: React.FC<{ roomId?: string }> = ({ roomId }) => {
|
||||||
const currentMediaPreview = useSettingValue("mediaPreviewConfig");
|
const currentMediaPreview = useSettingValue("mediaPreviewConfig", roomId);
|
||||||
|
|
||||||
|
const changeSetting = useCallback(
|
||||||
|
(newValue: MediaPreviewConfig) => {
|
||||||
|
SettingsStore.setValue(
|
||||||
|
"mediaPreviewConfig",
|
||||||
|
roomId ?? null,
|
||||||
|
roomId ? SettingLevel.ROOM_ACCOUNT : SettingLevel.ACCOUNT,
|
||||||
|
newValue,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[roomId],
|
||||||
|
);
|
||||||
|
|
||||||
const avatarOnChange = useCallback(
|
const avatarOnChange = useCallback(
|
||||||
(c: boolean) => {
|
(c: boolean) => {
|
||||||
const newValue = {
|
changeSetting({
|
||||||
...currentMediaPreview,
|
...currentMediaPreview,
|
||||||
// Switch is inverted. "Hide avatars..."
|
// Switch is inverted. "Hide avatars..."
|
||||||
invite_avatars: c ? MediaPreviewValue.Off : MediaPreviewValue.On,
|
invite_avatars: c ? MediaPreviewValue.Off : MediaPreviewValue.On,
|
||||||
} satisfies MediaPreviewConfig;
|
});
|
||||||
SettingsStore.setValue("mediaPreviewConfig", null, SettingLevel.ACCOUNT, newValue);
|
|
||||||
},
|
},
|
||||||
[currentMediaPreview],
|
[changeSetting, currentMediaPreview],
|
||||||
);
|
);
|
||||||
|
|
||||||
const mediaPreviewOnChangeOff = useCallback<ChangeEventHandler<HTMLInputElement>>(
|
const mediaPreviewOnChangeOff = useCallback<ChangeEventHandler<HTMLInputElement>>(
|
||||||
@@ -35,12 +46,12 @@ export const MediaPreviewAccountSettings: React.FC = () => {
|
|||||||
if (!event.target.checked) {
|
if (!event.target.checked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SettingsStore.setValue("mediaPreviewConfig", null, SettingLevel.ACCOUNT, {
|
changeSetting({
|
||||||
...currentMediaPreview,
|
...currentMediaPreview,
|
||||||
media_previews: MediaPreviewValue.Off,
|
media_previews: MediaPreviewValue.Off,
|
||||||
} satisfies MediaPreviewConfig);
|
});
|
||||||
},
|
},
|
||||||
[currentMediaPreview],
|
[changeSetting, currentMediaPreview],
|
||||||
);
|
);
|
||||||
|
|
||||||
const mediaPreviewOnChangePrivate = useCallback<ChangeEventHandler<HTMLInputElement>>(
|
const mediaPreviewOnChangePrivate = useCallback<ChangeEventHandler<HTMLInputElement>>(
|
||||||
@@ -48,12 +59,12 @@ export const MediaPreviewAccountSettings: React.FC = () => {
|
|||||||
if (!event.target.checked) {
|
if (!event.target.checked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SettingsStore.setValue("mediaPreviewConfig", null, SettingLevel.ACCOUNT, {
|
changeSetting({
|
||||||
...currentMediaPreview,
|
...currentMediaPreview,
|
||||||
media_previews: MediaPreviewValue.Private,
|
media_previews: MediaPreviewValue.Private,
|
||||||
} satisfies MediaPreviewConfig);
|
});
|
||||||
},
|
},
|
||||||
[currentMediaPreview],
|
[changeSetting, currentMediaPreview],
|
||||||
);
|
);
|
||||||
|
|
||||||
const mediaPreviewOnChangeOn = useCallback<ChangeEventHandler<HTMLInputElement>>(
|
const mediaPreviewOnChangeOn = useCallback<ChangeEventHandler<HTMLInputElement>>(
|
||||||
@@ -61,56 +72,71 @@ export const MediaPreviewAccountSettings: React.FC = () => {
|
|||||||
if (!event.target.checked) {
|
if (!event.target.checked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SettingsStore.setValue("mediaPreviewConfig", null, SettingLevel.ACCOUNT, {
|
changeSetting({
|
||||||
...currentMediaPreview,
|
...currentMediaPreview,
|
||||||
media_previews: MediaPreviewValue.On,
|
media_previews: MediaPreviewValue.On,
|
||||||
} satisfies MediaPreviewConfig);
|
});
|
||||||
},
|
},
|
||||||
[currentMediaPreview],
|
[changeSetting, currentMediaPreview],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
<LabelledToggleSwitch
|
{!roomId && (
|
||||||
label={_t("settings|media_preview|hide_avatars")}
|
<LabelledToggleSwitch
|
||||||
value={currentMediaPreview.invite_avatars === MediaPreviewValue.Off}
|
label={_t("settings|media_preview|hide_avatars")}
|
||||||
onChange={avatarOnChange}
|
value={currentMediaPreview.invite_avatars === MediaPreviewValue.Off}
|
||||||
/>
|
onChange={avatarOnChange}
|
||||||
<Field role="radiogroup" name="media_previews">
|
/>
|
||||||
|
)}
|
||||||
|
{/* Explict label here because htmlFor is not supported for linking to radiogroups */}
|
||||||
|
<Field
|
||||||
|
id="mx_media_previews"
|
||||||
|
role="radiogroup"
|
||||||
|
name="media_previews"
|
||||||
|
aria-label={_t("settings|media_preview|media_preview_label")}
|
||||||
|
>
|
||||||
<Label>{_t("settings|media_preview|media_preview_label")}</Label>
|
<Label>{_t("settings|media_preview|media_preview_label")}</Label>
|
||||||
<HelpMessage>{_t("settings|media_preview|media_preview_description")}</HelpMessage>
|
<HelpMessage>{_t("settings|media_preview|media_preview_description")}</HelpMessage>
|
||||||
<InlineField
|
<InlineField
|
||||||
name="media_preview_off"
|
name="media_preview_off"
|
||||||
control={
|
control={
|
||||||
<RadioInput
|
<RadioInput
|
||||||
|
id="mx_media_previews_off"
|
||||||
checked={currentMediaPreview.media_previews === MediaPreviewValue.Off}
|
checked={currentMediaPreview.media_previews === MediaPreviewValue.Off}
|
||||||
onChange={mediaPreviewOnChangeOff}
|
onChange={mediaPreviewOnChangeOff}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Label>{_t("settings|media_preview|hide_media")}</Label>
|
<Label htmlFor="mx_media_previews_off">{_t("settings|media_preview|hide_media")}</Label>
|
||||||
</InlineField>
|
|
||||||
<InlineField
|
|
||||||
name="media_preview_private"
|
|
||||||
control={
|
|
||||||
<RadioInput
|
|
||||||
checked={currentMediaPreview.media_previews === MediaPreviewValue.Private}
|
|
||||||
onChange={mediaPreviewOnChangePrivate}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Label>{_t("settings|media_preview|show_in_private")}</Label>
|
|
||||||
</InlineField>
|
</InlineField>
|
||||||
|
{!roomId && (
|
||||||
|
<InlineField
|
||||||
|
name="mx_media_previews_private"
|
||||||
|
control={
|
||||||
|
<RadioInput
|
||||||
|
id="mx_media_previews_private"
|
||||||
|
checked={currentMediaPreview.media_previews === MediaPreviewValue.Private}
|
||||||
|
onChange={mediaPreviewOnChangePrivate}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Label htmlFor="mx_media_previews_private">
|
||||||
|
{_t("settings|media_preview|show_in_private")}
|
||||||
|
</Label>
|
||||||
|
</InlineField>
|
||||||
|
)}
|
||||||
<InlineField
|
<InlineField
|
||||||
name="media_preview_on"
|
name="media_preview_on"
|
||||||
control={
|
control={
|
||||||
<RadioInput
|
<RadioInput
|
||||||
|
id="mx_media_previews_on"
|
||||||
checked={currentMediaPreview.media_previews === MediaPreviewValue.On}
|
checked={currentMediaPreview.media_previews === MediaPreviewValue.On}
|
||||||
onChange={mediaPreviewOnChangeOn}
|
onChange={mediaPreviewOnChangeOn}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Label>{_t("settings|media_preview|show_media")}</Label>
|
<Label htmlFor="mx_media_previews_on">{_t("settings|media_preview|show_media")}</Label>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</Field>
|
</Field>
|
||||||
</Root>
|
</Root>
|
||||||
|
|||||||
30
src/hooks/room/useJoinRule.ts
Normal file
30
src/hooks/room/useJoinRule.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
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 { useEffect, useState } from "react";
|
||||||
|
import { EventType, type MatrixEvent, type Room, RoomStateEvent, type JoinRule } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { type Optional } from "matrix-events-sdk";
|
||||||
|
|
||||||
|
import { useTypedEventEmitter } from "../useEventEmitter";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to retrieve the join rules for given room
|
||||||
|
* @param room
|
||||||
|
* @returns the current join rule
|
||||||
|
*/
|
||||||
|
export function useJoinRule(room?: Room): Optional<JoinRule> {
|
||||||
|
const [topic, setJoinRule] = useState(room?.getJoinRule());
|
||||||
|
useTypedEventEmitter(room?.currentState, RoomStateEvent.Events, (ev: MatrixEvent) => {
|
||||||
|
if (ev.getType() !== EventType.RoomJoinRules) return;
|
||||||
|
setJoinRule(room?.getJoinRule());
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
setJoinRule(room?.getJoinRule());
|
||||||
|
}, [room]);
|
||||||
|
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
30
src/hooks/room/useRoomAvatar.ts
Normal file
30
src/hooks/room/useRoomAvatar.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
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 { useEffect, useState } from "react";
|
||||||
|
import { EventType, type MatrixEvent, type Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { type Optional } from "matrix-events-sdk";
|
||||||
|
|
||||||
|
import { useTypedEventEmitter } from "../useEventEmitter";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to retrieve the avatar for given room
|
||||||
|
* @param room
|
||||||
|
* @returns the current avatar
|
||||||
|
*/
|
||||||
|
export function useRoomAvatar(room?: Room): Optional<string> {
|
||||||
|
const [topic, setAvatar] = useState(room?.getMxcAvatarUrl());
|
||||||
|
useTypedEventEmitter(room?.currentState, RoomStateEvent.Events, (ev: MatrixEvent) => {
|
||||||
|
if (ev.getType() !== EventType.RoomAvatar) return;
|
||||||
|
setAvatar(room?.getMxcAvatarUrl());
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
setAvatar(room?.getMxcAvatarUrl());
|
||||||
|
}, [room]);
|
||||||
|
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ 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.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback } from "react";
|
||||||
import { JoinRule } from "matrix-js-sdk/src/matrix";
|
import { JoinRule } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { SettingLevel } from "../settings/SettingLevel";
|
import { SettingLevel } from "../settings/SettingLevel";
|
||||||
@@ -13,6 +13,7 @@ import { useSettingValue } from "./useSettings";
|
|||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
import { useMatrixClientContext } from "../contexts/MatrixClientContext";
|
import { useMatrixClientContext } from "../contexts/MatrixClientContext";
|
||||||
import { MediaPreviewValue } from "../@types/media_preview";
|
import { MediaPreviewValue } from "../@types/media_preview";
|
||||||
|
import { useJoinRule } from "./room/useJoinRule";
|
||||||
|
|
||||||
const PRIVATE_JOIN_RULES: JoinRule[] = [JoinRule.Invite, JoinRule.Knock, JoinRule.Restricted];
|
const PRIVATE_JOIN_RULES: JoinRule[] = [JoinRule.Invite, JoinRule.Knock, JoinRule.Restricted];
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ export function useMediaVisible(eventId: string, roomId: string): [boolean, (vis
|
|||||||
const mediaPreviewSetting = useSettingValue("mediaPreviewConfig", roomId);
|
const mediaPreviewSetting = useSettingValue("mediaPreviewConfig", roomId);
|
||||||
const client = useMatrixClientContext();
|
const client = useMatrixClientContext();
|
||||||
const eventVisibility = useSettingValue("showMediaEventIds");
|
const eventVisibility = useSettingValue("showMediaEventIds");
|
||||||
|
const joinRule = useJoinRule(client.getRoom(roomId) ?? undefined);
|
||||||
const setMediaVisible = useCallback(
|
const setMediaVisible = useCallback(
|
||||||
(visible: boolean) => {
|
(visible: boolean) => {
|
||||||
SettingsStore.setValue("showMediaEventIds", null, SettingLevel.DEVICE, {
|
SettingsStore.setValue("showMediaEventIds", null, SettingLevel.DEVICE, {
|
||||||
@@ -35,15 +37,7 @@ export function useMediaVisible(eventId: string, roomId: string): [boolean, (vis
|
|||||||
[eventId, eventVisibility],
|
[eventId, eventVisibility],
|
||||||
);
|
);
|
||||||
|
|
||||||
const roomIsPrivate = useMemo(() => {
|
const roomIsPrivate = joinRule ? PRIVATE_JOIN_RULES.includes(joinRule) : false;
|
||||||
const joinRule = client?.getRoom(roomId)?.getJoinRule();
|
|
||||||
if (PRIVATE_JOIN_RULES.includes(joinRule as JoinRule)) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// All other join rules, and unknown will default to hiding.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}, [client, roomId]);
|
|
||||||
|
|
||||||
const explicitEventVisiblity = eventVisibility[eventId];
|
const explicitEventVisiblity = eventVisibility[eventId];
|
||||||
// Always prefer the explicit per-event user preference here.
|
// Always prefer the explicit per-event user preference here.
|
||||||
|
|||||||
@@ -374,10 +374,6 @@ export default class SettingsStore {
|
|||||||
roomId: string | null = null,
|
roomId: string | null = null,
|
||||||
excludeDefault = false,
|
excludeDefault = false,
|
||||||
): Settings[S]["default"] | undefined {
|
): Settings[S]["default"] | undefined {
|
||||||
if (settingName === "mediaPreviewConfig") {
|
|
||||||
console.log("GET VALUE", SETTINGS[settingName]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the setting is actually a setting
|
// Verify that the setting is actually a setting
|
||||||
if (!SETTINGS[settingName]) {
|
if (!SETTINGS[settingName]) {
|
||||||
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
|
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ClientEvent, type MatrixEvent, type MatrixClient, IContent } from "matrix-js-sdk/src/matrix";
|
import { type IContent } from "matrix-js-sdk/src/matrix";
|
||||||
import { type AccountDataEvents } from "matrix-js-sdk/src/types";
|
import { type AccountDataEvents } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -31,17 +31,23 @@ export default class MediaPreviewConfigController extends MatrixClientBackedCont
|
|||||||
const inviteAvatars: MediaPreviewValue = content.invite_avatars;
|
const inviteAvatars: MediaPreviewValue = content.invite_avatars;
|
||||||
const validValues = Object.values(MediaPreviewValue);
|
const validValues = Object.values(MediaPreviewValue);
|
||||||
return {
|
return {
|
||||||
invite_avatars: validValues.includes(inviteAvatars) ? inviteAvatars : MediaPreviewConfigController.default.invite_avatars,
|
invite_avatars: validValues.includes(inviteAvatars)
|
||||||
media_previews: validValues.includes(mediaPreviews) ? mediaPreviews : MediaPreviewConfigController.default.media_previews,
|
? inviteAvatars
|
||||||
|
: MediaPreviewConfigController.default.invite_avatars,
|
||||||
|
media_previews: validValues.includes(mediaPreviews)
|
||||||
|
? mediaPreviews
|
||||||
|
: MediaPreviewConfigController.default.media_previews,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
private getValue = (roomId?: string): MediaPreviewConfig | null => {
|
private getValue = (roomId?: string): MediaPreviewConfig | null => {
|
||||||
const source = roomId ? this.client?.getRoom(roomId) : this.client;
|
const source = roomId ? this.client?.getRoom(roomId) : this.client;
|
||||||
const value = source
|
const value = source?.getAccountData(MEDIA_PREVIEW_ACCOUNT_DATA_TYPE)?.getContent<MediaPreviewConfig>();
|
||||||
?.getAccountData(MEDIA_PREVIEW_ACCOUNT_DATA_TYPE)
|
|
||||||
?.getContent<MediaPreviewConfig>();
|
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
@@ -49,20 +55,17 @@ export default class MediaPreviewConfigController extends MatrixClientBackedCont
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protected async initMatrixClient(): Promise<void> {
|
||||||
protected async initMatrixClient(newClient: MatrixClient, oldClient?: MatrixClient): Promise<void> {
|
// Unused
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getValueOverride(_level: SettingLevel, roomId: string | null): MediaPreviewConfig {
|
public getValueOverride(_level: SettingLevel, roomId: string | null): MediaPreviewConfig {
|
||||||
if (roomId) {
|
const roomConfig = roomId && this.getValue(roomId);
|
||||||
// Use globals for any undefined setting
|
if (roomConfig) {
|
||||||
return {
|
return roomConfig;
|
||||||
...this.getRoomValue(roomId),
|
|
||||||
...this.globalSetting,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return this.globalSetting;
|
// If no room config, or global settings request then return global.
|
||||||
|
return this.getValue() ?? MediaPreviewConfigController.default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get settingDisabled(): false {
|
public get settingDisabled(): false {
|
||||||
@@ -79,9 +82,7 @@ export default class MediaPreviewConfigController extends MatrixClientBackedCont
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (roomId) {
|
if (roomId) {
|
||||||
await this.client.setRoomAccountData(roomId, MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, {
|
await this.client.setRoomAccountData(roomId, MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, newValue);
|
||||||
value: newValue,
|
|
||||||
});
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
await this.client.setAccountData(MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, newValue);
|
await this.client.setAccountData(MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, newValue);
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
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 { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController";
|
||||||
|
import MediaPreviewConfigController from "../../../../src/settings/controllers/MediaPreviewConfigController";
|
||||||
|
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||||
|
import { getMockClientWithEventEmitter, mockClientMethodsServer } from "../../../test-utils";
|
||||||
|
import { MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, MediaPreviewValue } from "../../../../src/@types/media_preview";
|
||||||
|
|
||||||
|
describe("MediaPreviewConfigController", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const ROOM_ID = "!room:example.org";
|
||||||
|
|
||||||
|
it("gets the default settings when none are specified.", () => {
|
||||||
|
const controller = new MediaPreviewConfigController();
|
||||||
|
|
||||||
|
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||||
|
...mockClientMethodsServer(),
|
||||||
|
getAccountData: jest.fn().mockReturnValue(null),
|
||||||
|
});
|
||||||
|
|
||||||
|
const value = controller.getValueOverride(SettingLevel.ACCOUNT, null);
|
||||||
|
expect(value).toEqual(MediaPreviewConfigController.default);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gets the default settings when the setting is empty.", () => {
|
||||||
|
const controller = new MediaPreviewConfigController();
|
||||||
|
|
||||||
|
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||||
|
...mockClientMethodsServer(),
|
||||||
|
getAccountData: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(new MatrixEvent({ type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE, content: {} })),
|
||||||
|
});
|
||||||
|
|
||||||
|
const value = controller.getValueOverride(SettingLevel.ACCOUNT, null);
|
||||||
|
expect(value).toEqual(MediaPreviewConfigController.default);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([["media_previews"], ["invite_avatars"]])("gets the correct value for %s at the global level", (key) => {
|
||||||
|
const controller = new MediaPreviewConfigController();
|
||||||
|
|
||||||
|
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||||
|
...mockClientMethodsServer(),
|
||||||
|
getAccountData: jest.fn().mockReturnValue(
|
||||||
|
new MatrixEvent({
|
||||||
|
type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
|
||||||
|
content: {
|
||||||
|
[key]: MediaPreviewValue.Private,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
getRoom: jest.fn().mockReturnValue({
|
||||||
|
getAccountData: jest.fn().mockReturnValue(null),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT, null);
|
||||||
|
expect(globalValue[key]).toEqual(MediaPreviewValue.Private);
|
||||||
|
|
||||||
|
// Should follow the global value.
|
||||||
|
const roomValue = controller.getValueOverride(SettingLevel.ROOM_ACCOUNT, ROOM_ID);
|
||||||
|
expect(roomValue[key]).toEqual(MediaPreviewValue.Private);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([["media_previews"], ["invite_avatars"]])("gets the correct value for %s at the room level", (key) => {
|
||||||
|
const controller = new MediaPreviewConfigController();
|
||||||
|
|
||||||
|
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||||
|
...mockClientMethodsServer(),
|
||||||
|
getAccountData: jest.fn().mockReturnValue(null),
|
||||||
|
getRoom: jest.fn().mockReturnValue({
|
||||||
|
getAccountData: jest.fn().mockReturnValue(
|
||||||
|
new MatrixEvent({
|
||||||
|
type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
|
||||||
|
content: {
|
||||||
|
[key]: MediaPreviewValue.Private,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT, null);
|
||||||
|
expect(globalValue[key]).toEqual(MediaPreviewValue.On);
|
||||||
|
|
||||||
|
// Should follow the global value.
|
||||||
|
const roomValue = controller.getValueOverride(SettingLevel.ROOM_ACCOUNT, ROOM_ID);
|
||||||
|
expect(roomValue[key]).toEqual(MediaPreviewValue.Private);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([["media_previews"], ["invite_avatars"]])(
|
||||||
|
"uses defaults when an invalid value is set on the global level",
|
||||||
|
(key) => {
|
||||||
|
const controller = new MediaPreviewConfigController();
|
||||||
|
|
||||||
|
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||||
|
...mockClientMethodsServer(),
|
||||||
|
getAccountData: jest.fn().mockReturnValue(
|
||||||
|
new MatrixEvent({
|
||||||
|
type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
|
||||||
|
content: {
|
||||||
|
[key]: "bibble",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
getRoom: jest.fn().mockReturnValue({
|
||||||
|
getAccountData: jest.fn().mockReturnValue(null),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT, null);
|
||||||
|
expect(globalValue[key]).toEqual(MediaPreviewValue.On);
|
||||||
|
|
||||||
|
// Should follow the global value.
|
||||||
|
const roomValue = controller.getValueOverride(SettingLevel.ROOM_ACCOUNT, ROOM_ID);
|
||||||
|
expect(roomValue[key]).toEqual(MediaPreviewValue.On);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
it.each([["media_previews"], ["invite_avatars"]])(
|
||||||
|
"uses global value when an invalid value is set on the room level",
|
||||||
|
(key) => {
|
||||||
|
const controller = new MediaPreviewConfigController();
|
||||||
|
|
||||||
|
MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({
|
||||||
|
...mockClientMethodsServer(),
|
||||||
|
getAccountData: jest.fn().mockReturnValue(
|
||||||
|
new MatrixEvent({
|
||||||
|
type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
|
||||||
|
content: {
|
||||||
|
[key]: MediaPreviewValue.Private,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
getRoom: jest.fn().mockReturnValue({
|
||||||
|
getAccountData: jest.fn().mockReturnValue(
|
||||||
|
new MatrixEvent({
|
||||||
|
type: MEDIA_PREVIEW_ACCOUNT_DATA_TYPE,
|
||||||
|
content: {
|
||||||
|
[key]: "bibble",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT, null);
|
||||||
|
expect(globalValue[key]).toEqual(MediaPreviewValue.Private);
|
||||||
|
|
||||||
|
// Should follow the global value.
|
||||||
|
const roomValue = controller.getValueOverride(SettingLevel.ROOM_ACCOUNT, ROOM_ID);
|
||||||
|
expect(roomValue[key]).toEqual(MediaPreviewValue.On);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user