* Add pure sender render inputs to EventTileViewModel * Move sender/avatar normalization to EventTile * Split sender and avatar adapters into focused leaf components * Add tests for the sender and avatar adapters * Fix sonar issues * Make sender profile consume pure member info * Collapse sender adapter onto snapshot-driven props * Share current-member resolution for event and reply tiles * Fix Prettier issues * Keep settings preview on historical sender profiles * Update the proprty name to reflect the use of the event-time sender data instead of resolving the current room member * Fix Sonar issue * Tighten member to MemberInfo | null in the view model
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
RelationType,
|
||||
type Relations,
|
||||
Room,
|
||||
RoomMember,
|
||||
TweakName,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
@@ -518,7 +519,7 @@ describe("EventTile", () => {
|
||||
});
|
||||
|
||||
describe("sender and avatar rendering", () => {
|
||||
it("shows sender and avatar in room timelines", () => {
|
||||
it("keeps the sender/avatar composition in room timelines", () => {
|
||||
const { container } = getComponent();
|
||||
|
||||
expect(container.querySelector(".mx_DisambiguatedProfile")).not.toBeNull();
|
||||
@@ -1060,6 +1061,25 @@ describe("EventTile", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it("uses the current room member when current profiles are enabled", async () => {
|
||||
const senderId = mxEvent.getSender()!;
|
||||
const currentMember = new RoomMember(room.roomId, senderId);
|
||||
currentMember.rawDisplayName = "Alan (away)";
|
||||
|
||||
jest.spyOn(room, "getMember").mockImplementation((userId) => (userId === senderId ? currentMember : null));
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(settingName) => settingName === "useOnlyCurrentProfiles",
|
||||
);
|
||||
|
||||
const { container } = getComponent();
|
||||
|
||||
await waitFor(() =>
|
||||
expect(container.querySelector(".mx_DisambiguatedProfile_displayName")).toHaveTextContent(
|
||||
"Alan (away)",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("renders the tile error fallback when tile rendering throws", async () => {
|
||||
jest.spyOn(console, "error").mockImplementation(() => {});
|
||||
jest.spyOn(EventTileFactory, "renderTile").mockImplementation(() => {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
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 React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import MemberAvatar from "../../../../../src/components/views/avatars/MemberAvatar";
|
||||
import { EventTileAvatarAdapter } from "../../../../../src/components/views/rooms/EventTile/EventTileAvatarAdapter";
|
||||
import { type EventTileSenderSnapshot } from "../../../../../src/viewmodels/room/timeline/event-tile/EventTileViewModel";
|
||||
|
||||
jest.mock("../../../../../src/components/views/avatars/MemberAvatar", () =>
|
||||
jest.fn(() => <div data-testid="member-avatar" />),
|
||||
);
|
||||
|
||||
function makeSenderSnapshot(overrides: Partial<EventTileSenderSnapshot> = {}): EventTileSenderSnapshot {
|
||||
return {
|
||||
senderId: "@alice:example.org",
|
||||
member: null,
|
||||
viewUserOnClick: true,
|
||||
profileMode: "clickable",
|
||||
forceHistoricalAvatar: false,
|
||||
isEmote: false,
|
||||
...overrides,
|
||||
profileState: {
|
||||
avatarSize: "30px",
|
||||
needsSenderProfile: true,
|
||||
...overrides.profileState,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("EventTileAvatarAdapter", () => {
|
||||
const mockedMemberAvatar = mocked(MemberAvatar);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("passes render data through to MemberAvatar", () => {
|
||||
const avatarMember = new RoomMember("!room:example.org", "@alice:example.org");
|
||||
const senderSnapshot = makeSenderSnapshot({
|
||||
profileState: {
|
||||
avatarSize: "24px",
|
||||
needsSenderProfile: true,
|
||||
},
|
||||
viewUserOnClick: false,
|
||||
forceHistoricalAvatar: true,
|
||||
});
|
||||
|
||||
const { container } = render(
|
||||
<EventTileAvatarAdapter avatarMember={avatarMember} senderSnapshot={senderSnapshot} />,
|
||||
);
|
||||
|
||||
expect(container.querySelector(".mx_EventTile_avatar")).not.toBeNull();
|
||||
expect(mockedMemberAvatar.mock.calls[0][0]).toMatchObject({
|
||||
member: avatarMember,
|
||||
size: "24px",
|
||||
viewUserOnClick: false,
|
||||
forceHistorical: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("renders nothing when the avatar is not available", () => {
|
||||
const { container } = render(
|
||||
<EventTileAvatarAdapter avatarMember={null} senderSnapshot={makeSenderSnapshot()} />,
|
||||
);
|
||||
|
||||
expect(container.querySelector(".mx_EventTile_avatar")).toBeNull();
|
||||
expect(mockedMemberAvatar).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
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 React from "react";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import SenderProfile from "../../../../../src/components/views/messages/SenderProfile";
|
||||
import { EventTileSenderAdapter } from "../../../../../src/components/views/rooms/EventTile/EventTileSenderAdapter";
|
||||
import { type MemberInfo } from "../../../../../src/viewmodels/room/timeline/event-tile/DisambiguatedProfileViewModel";
|
||||
import { type EventTileSenderSnapshot } from "../../../../../src/viewmodels/room/timeline/event-tile/EventTileViewModel";
|
||||
|
||||
jest.mock("../../../../../src/components/views/messages/SenderProfile", () =>
|
||||
jest.fn(() => <div data-testid="sender-profile" />),
|
||||
);
|
||||
|
||||
function makeSenderSnapshot(overrides: Partial<EventTileSenderSnapshot> = {}): EventTileSenderSnapshot {
|
||||
return {
|
||||
senderId: "@alice:example.org",
|
||||
member: {
|
||||
userId: "@alice:example.org",
|
||||
roomId: "!room:example.org",
|
||||
rawDisplayName: "Alice",
|
||||
disambiguate: false,
|
||||
},
|
||||
viewUserOnClick: true,
|
||||
profileMode: "clickable",
|
||||
forceHistoricalAvatar: false,
|
||||
isEmote: false,
|
||||
...overrides,
|
||||
profileState: {
|
||||
avatarSize: "30px",
|
||||
needsSenderProfile: true,
|
||||
...overrides.profileState,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("EventTileSenderAdapter", () => {
|
||||
const mockedSenderProfile = mocked(SenderProfile);
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("passes clickable sender state to SenderProfile", () => {
|
||||
const onSenderProfileClick = jest.fn();
|
||||
const senderSnapshot = makeSenderSnapshot({ profileMode: "clickable" });
|
||||
const memberInfo: MemberInfo = senderSnapshot.member!;
|
||||
|
||||
render(<EventTileSenderAdapter sender={senderSnapshot} onSenderProfileClick={onSenderProfileClick} />);
|
||||
|
||||
expect(screen.getByTestId("sender-profile")).toBeInTheDocument();
|
||||
expect(mockedSenderProfile.mock.calls[0][0]).toMatchObject({
|
||||
senderId: "@alice:example.org",
|
||||
member: memberInfo,
|
||||
isEmote: false,
|
||||
onClick: onSenderProfileClick,
|
||||
});
|
||||
});
|
||||
|
||||
it("enables tooltip rendering for tooltip mode", () => {
|
||||
const senderSnapshot = makeSenderSnapshot({ profileMode: "tooltip" });
|
||||
|
||||
render(<EventTileSenderAdapter sender={senderSnapshot} onSenderProfileClick={jest.fn()} />);
|
||||
|
||||
expect(screen.getByTestId("sender-profile")).toBeInTheDocument();
|
||||
expect(mockedSenderProfile.mock.calls[0][0]).toMatchObject({
|
||||
senderId: "@alice:example.org",
|
||||
isEmote: false,
|
||||
withTooltip: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns nothing when the sender should be hidden", () => {
|
||||
const senderSnapshot = makeSenderSnapshot({ profileMode: "hidden" });
|
||||
|
||||
const { container } = render(
|
||||
<EventTileSenderAdapter sender={senderSnapshot} onSenderProfileClick={jest.fn()} />,
|
||||
);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
expect(mockedSenderProfile).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -30,6 +30,10 @@ describe("ReplyTile", () => {
|
||||
jest.mocked(renderReplyTile).mockClear().mockReturnValue(null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("renders video replies with the video body", () => {
|
||||
const mxEvent = mkEvent({
|
||||
event: true,
|
||||
|
||||
Reference in New Issue
Block a user