Skip to content
Draft
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
28 changes: 24 additions & 4 deletions apps/web/src/shouldHideEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@

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 { type MatrixEvent, EventType, RelationType, M_POLL_END } from "matrix-js-sdk/src/matrix";
*/

import {
type MatrixEvent,
type MatrixClient,
EventType,
RelationType,
JoinRule,
M_POLL_END,
} from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";

import SettingsStore from "./settings/SettingsStore";
import { MatrixClientPeg } from "./MatrixClientPeg";
import { type IRoomState } from "./components/structures/RoomView";
import { type SettingKey } from "./settings/Settings.tsx";

Expand Down Expand Up @@ -43,16 +51,26 @@
return diff;
}

function isPublicRoom(ev: MatrixEvent, client?: MatrixClient): boolean {
const mxClient = client ?? MatrixClientPeg.get();
const room = mxClient?.getRoom(ev.getRoomId());
return room?.getJoinRule() === JoinRule.Public;
}

/**
* Determines whether the given event should be hidden from timelines.
* @param ev The event
* @param ctx An optional RoomContext to pull cached settings values from to avoid
* hitting the settings store
* @param client An optional MatrixClient to use instead of MatrixClientPeg
* @returns True if the event should be hidden
*/
export default function shouldHideEvent(ev: MatrixEvent, ctx?: IRoomState): boolean {
export default function shouldHideEvent(ev: MatrixEvent, ctx?: IRoomState, client?: MatrixClient): boolean {

Check failure on line 68 in apps/web/src/shouldHideEvent.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 33 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=element-web&issues=AZ2p4DuSWXwdTZN19akW&open=AZ2p4DuSWXwdTZN19akW&pullRequest=33192
// Hide all poll end events
if (M_POLL_END.matches(ev.getType())) return true;

if (ev.getType() === EventType.RoomTopic && isPublicRoom(ev, client)) return true;

// Accessing the settings store directly can be expensive if done frequently,
// so we should prefer using cached values if a RoomContext is available
const isEnabled = ctx
Expand All @@ -70,6 +88,8 @@
const eventDiff = memberEventDiff(ev);

if (eventDiff.isMemberEvent) {
if (isPublicRoom(ev, client)) return true;

if ((eventDiff.isJoin || eventDiff.isPart) && !isEnabled("showJoinLeaves")) return true;
if (eventDiff.isAvatarChange && !isEnabled("showAvatarChanges")) return true;
if (eventDiff.isDisplaynameChange && !isEnabled("showDisplaynameChanges")) return true;
Expand Down
109 changes: 109 additions & 0 deletions apps/web/test/unit-tests/shouldHideEvent-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
Copyright 2026 Element Creations 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 { EventType, JoinRule, type MatrixClient, type Room, Room as SDKRoom } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";

import shouldHideEvent from "../../src/shouldHideEvent";
import { mkEvent } from "../test-utils/test-utils";

describe("shouldHideEvent", () => {
let client: MatrixClient;
let publicRoom: Room;
let privateRoom: Room;

beforeEach(() => {
client = {
getRoom: jest.fn().mockImplementation((roomId: string) => {
if (roomId === "!public:server") return publicRoom;
if (roomId === "!private:server") return privateRoom;
return null;
}),
} as unknown as MatrixClient;

publicRoom = new SDKRoom("!public:server", client, "@user:server");
privateRoom = new SDKRoom("!private:server", client, "@user:server");

(publicRoom as unknown as { getJoinRule: jest.Mock }).getJoinRule = jest.fn().mockReturnValue(JoinRule.Public);
(privateRoom as unknown as { getJoinRule: jest.Mock }).getJoinRule = jest
.fn()
.mockReturnValue(JoinRule.Private);
});

function makeMemberEvent(

Check warning on line 37 in apps/web/test/unit-tests/shouldHideEvent-test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move function 'makeMemberEvent' to the outer scope.

See more on https://sonarcloud.io/project/issues?id=element-web&issues=AZ2p4Dj0WXwdTZN19akT&open=AZ2p4Dj0WXwdTZN19akT&pullRequest=33192
roomId: string,
membership: KnownMembership,
prevMembership?: KnownMembership,
): ReturnType<typeof mkEvent> {
return mkEvent({
type: EventType.RoomMember,
room: roomId,
user: "@user:server",
content: {
membership,
displayname: "User",
avatar_url: "mxc://avatar",
},
prev_content:
prevMembership !== undefined

Check warning on line 52 in apps/web/test/unit-tests/shouldHideEvent-test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=element-web&issues=AZ2p4Dj0WXwdTZN19akU&open=AZ2p4Dj0WXwdTZN19akU&pullRequest=33192
? {
membership: prevMembership,
displayname: "User",
avatar_url: "mxc://avatar",
}
: undefined,
event: true,
});
}

function makeTopicEvent(roomId: string): ReturnType<typeof mkEvent> {

Check warning on line 63 in apps/web/test/unit-tests/shouldHideEvent-test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move function 'makeTopicEvent' to the outer scope.

See more on https://sonarcloud.io/project/issues?id=element-web&issues=AZ2p4Dj0WXwdTZN19akV&open=AZ2p4Dj0WXwdTZN19akV&pullRequest=33192
return mkEvent({
type: EventType.RoomTopic,
room: roomId,
user: "@user:server",
skey: "",
content: {
topic: "Test Topic",
},
event: true,
});
}

describe("public room events", () => {
it("should hide member join events in public rooms", () => {
const event = makeMemberEvent("!public:server", KnownMembership.Join, KnownMembership.Invite);
expect(shouldHideEvent(event, undefined, client)).toBe(true);
});

it("should hide member leave events in public rooms", () => {
const event = makeMemberEvent("!public:server", KnownMembership.Leave, KnownMembership.Join);
expect(shouldHideEvent(event, undefined, client)).toBe(true);
});

it("should hide topic events in public rooms", () => {
const event = makeTopicEvent("!public:server");
expect(shouldHideEvent(event, undefined, client)).toBe(true);
});
});

describe("non-public room events", () => {
it("should show member join events in private rooms", () => {
const event = makeMemberEvent("!private:server", KnownMembership.Join, KnownMembership.Invite);
expect(shouldHideEvent(event, undefined, client)).toBe(false);
});

it("should show member leave events in private rooms", () => {
const event = makeMemberEvent("!private:server", KnownMembership.Leave, KnownMembership.Join);
expect(shouldHideEvent(event, undefined, client)).toBe(false);
});

it("should show topic events in private rooms", () => {
const event = makeTopicEvent("!private:server");
expect(shouldHideEvent(event, undefined, client)).toBe(false);
});
});
});
Loading