Make EventTileViewModel consume pure render data (#33805)
This commit is contained in:
@@ -17,7 +17,8 @@ import React, {
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import {
|
||||
type EventStatus,
|
||||
EventStatus,
|
||||
EventType,
|
||||
type MatrixEvent,
|
||||
MatrixEventEvent,
|
||||
type Relations,
|
||||
@@ -690,6 +691,14 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
});
|
||||
};
|
||||
|
||||
private readonly getAvatarMember = (): RoomMember | null => {
|
||||
if (this.props.mxEvent.getContent().third_party_invite) {
|
||||
return this.props.mxEvent.target;
|
||||
}
|
||||
|
||||
return this.props.mxEvent.sender;
|
||||
};
|
||||
|
||||
private readonly onReactionsCreated = (relationType: string, eventType: string): void => {
|
||||
if (!isEventTileReactionRelation(relationType, eventType)) {
|
||||
return;
|
||||
@@ -852,11 +861,25 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
const isProbablyMedia = MediaEventHelper.isEligible(this.props.mxEvent);
|
||||
const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();
|
||||
const isEditing = !!this.props.editState;
|
||||
const eventType = this.props.mxEvent.getType();
|
||||
const msgtype = this.props.mxEvent.getContent().msgtype;
|
||||
const isSending =
|
||||
this.props.eventSendStatus === EventStatus.SENDING ||
|
||||
this.props.eventSendStatus === EventStatus.QUEUED ||
|
||||
this.props.eventSendStatus === EventStatus.ENCRYPTING;
|
||||
|
||||
return {
|
||||
event: {
|
||||
mxEvent: this.props.mxEvent,
|
||||
eventSendStatus: this.props.eventSendStatus,
|
||||
eventType,
|
||||
msgtype,
|
||||
eventTs: this.props.mxEvent.getTs(),
|
||||
eventId: this.props.mxEvent.getId() ?? undefined,
|
||||
isLocalEcho: !!this.props.mxEvent.status,
|
||||
isSending,
|
||||
ariaLive: this.props.eventSendStatus === null ? undefined : "off",
|
||||
isRoomCreate: eventType === EventType.RoomCreate,
|
||||
isCallInvite: eventType === EventType.CallInvite,
|
||||
isRtcNotification: eventType === EventType.RTCNotification,
|
||||
isEditing,
|
||||
isEncryptionFailure,
|
||||
forExport: this.props.forExport,
|
||||
@@ -974,9 +997,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
const scrollToken = eventTileRenderState.root.scrollToken;
|
||||
const rootRenderState = { tileClasses, tileAriaLive, scrollToken };
|
||||
|
||||
const avatar = (
|
||||
<EventTileAvatarAdapter mxEvent={this.props.mxEvent} senderSnapshot={eventTileSnapshot.sender} />
|
||||
);
|
||||
const avatarMember = this.getAvatarMember();
|
||||
const avatar = <EventTileAvatarAdapter avatarMember={avatarMember} senderSnapshot={eventTileSnapshot.sender} />;
|
||||
const sender = (
|
||||
<EventTileSenderAdapter
|
||||
mxEvent={this.props.mxEvent}
|
||||
|
||||
@@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX } from "react";
|
||||
import { EventType, type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { type MatrixEvent, type RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import MemberAvatar from "../../avatars/MemberAvatar";
|
||||
import SenderProfile from "../../messages/SenderProfile";
|
||||
@@ -16,8 +16,8 @@ import { type EventTileSenderSnapshot } from "../../../../viewmodels/room/timeli
|
||||
* Props for the {@link EventTileAvatarAdapter} component.
|
||||
*/
|
||||
interface EventTileAvatarAdapterProps {
|
||||
/** Matrix event whose sender avatar is being rendered. */
|
||||
mxEvent: MatrixEvent;
|
||||
/** Room member whose avatar is being rendered. */
|
||||
avatarMember: RoomMember | null;
|
||||
/** Snapshot of the sender identity state for this tile. */
|
||||
senderSnapshot: EventTileSenderSnapshot;
|
||||
}
|
||||
@@ -26,22 +26,22 @@ interface EventTileAvatarAdapterProps {
|
||||
* Renders the sender avatar for an event tile.
|
||||
*/
|
||||
export function EventTileAvatarAdapter({
|
||||
mxEvent,
|
||||
avatarMember,
|
||||
senderSnapshot,
|
||||
}: Readonly<EventTileAvatarAdapterProps>): JSX.Element | null {
|
||||
const { avatarSize } = senderSnapshot.profileState;
|
||||
|
||||
if (!mxEvent.sender || avatarSize === null) {
|
||||
if (!avatarMember || avatarSize === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_EventTile_avatar">
|
||||
<MemberAvatar
|
||||
member={senderSnapshot.avatarMember}
|
||||
member={avatarMember}
|
||||
size={avatarSize}
|
||||
viewUserOnClick={senderSnapshot.viewUserOnClick}
|
||||
forceHistorical={mxEvent.getType() === EventType.RoomMember}
|
||||
forceHistorical={senderSnapshot.forceHistoricalAvatar}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,8 +5,6 @@ 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 { EventStatus, EventType, type MatrixEvent, MsgType, type RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { ElementCallEventType } from "../../../../call-types";
|
||||
import { TimelineRenderingType } from "../../../../contexts/RoomContext";
|
||||
import { Layout } from "../../../../settings/enums/Layout";
|
||||
@@ -16,19 +14,17 @@ import { Layout } from "../../../../settings/enums/Layout";
|
||||
* Keep this module free of React lifecycle, DOM access, dispatch, and MatrixClientPeg lookups.
|
||||
*/
|
||||
|
||||
/** Whether the event send status represents a pending send state. */
|
||||
export function isSendingStatus(eventSendStatus?: EventStatus): boolean {
|
||||
return [EventStatus.SENDING, EventStatus.QUEUED, EventStatus.ENCRYPTING].includes(eventSendStatus!);
|
||||
}
|
||||
|
||||
/** The aria-live setting used by EventTile for the current send status. */
|
||||
export function getAriaLive(eventSendStatus?: EventStatus | null): "off" | undefined {
|
||||
return eventSendStatus === null ? undefined : "off";
|
||||
/** Inputs for the stable scroll token derivation. */
|
||||
export interface ScrollTokenInput {
|
||||
/** The event identifier, when available. */
|
||||
eventId?: string;
|
||||
/** Whether the event is a local echo. */
|
||||
isLocalEcho: boolean;
|
||||
}
|
||||
|
||||
/** The stable scroll token for a non-local-echo event. */
|
||||
export function getScrollToken(mxEvent: MatrixEvent): string | undefined {
|
||||
return mxEvent.status ? undefined : mxEvent.getId();
|
||||
export function getScrollToken({ eventId, isLocalEcho }: ScrollTokenInput): string | undefined {
|
||||
return isLocalEcho ? undefined : eventId;
|
||||
}
|
||||
|
||||
/** Whether EventTile should render as a continuation in the current layout/rendering mode. */
|
||||
@@ -65,11 +61,16 @@ export function getEventTileLineClassState({
|
||||
eventType,
|
||||
msgtype,
|
||||
}: EventTileLineClassState): Record<string, boolean> {
|
||||
const roomMessageEventType = "m.room.message";
|
||||
const stickerEventType = "m.sticker";
|
||||
const imageMsgtype = "m.image";
|
||||
const emoteMsgtype = "m.emote";
|
||||
|
||||
return {
|
||||
mx_EventTile_mediaLine: isProbablyMedia,
|
||||
mx_EventTile_image: eventType === EventType.RoomMessage && msgtype === MsgType.Image,
|
||||
mx_EventTile_sticker: eventType === EventType.Sticker,
|
||||
mx_EventTile_emote: eventType === EventType.RoomMessage && msgtype === MsgType.Emote,
|
||||
mx_EventTile_image: eventType === roomMessageEventType && msgtype === imageMsgtype,
|
||||
mx_EventTile_sticker: eventType === stickerEventType,
|
||||
mx_EventTile_emote: eventType === roomMessageEventType && msgtype === emoteMsgtype,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -89,6 +90,12 @@ export interface EventTileSenderProfileStateInput {
|
||||
isBubbleMessage: boolean;
|
||||
/** The current timeline layout. */
|
||||
layout?: Layout;
|
||||
/** Whether the event is a room create event. */
|
||||
isRoomCreate: boolean;
|
||||
/** Whether the event is a call invite. */
|
||||
isCallInvite: boolean;
|
||||
/** Whether the event is an RTC notification. */
|
||||
isRtcNotification: boolean;
|
||||
}
|
||||
|
||||
/** EventTile avatar and sender profile display state. */
|
||||
@@ -108,6 +115,9 @@ export function getEventTileSenderProfileState({
|
||||
eventType,
|
||||
isBubbleMessage,
|
||||
layout,
|
||||
isRoomCreate,
|
||||
isCallInvite,
|
||||
isRtcNotification,
|
||||
}: EventTileSenderProfileStateInput): EventTileSenderProfileState {
|
||||
if (isRenderingNotification) {
|
||||
return { avatarSize: "24px", needsSenderProfile: true };
|
||||
@@ -124,7 +134,7 @@ export function getEventTileSenderProfileState({
|
||||
return { avatarSize: "32px", needsSenderProfile: true };
|
||||
}
|
||||
|
||||
if (eventType === EventType.RoomCreate || isBubbleMessage) {
|
||||
if (isRoomCreate || isBubbleMessage) {
|
||||
return { avatarSize: null, needsSenderProfile: false };
|
||||
}
|
||||
|
||||
@@ -134,9 +144,9 @@ export function getEventTileSenderProfileState({
|
||||
|
||||
if (
|
||||
(continuation && timelineRenderingType !== TimelineRenderingType.File) ||
|
||||
eventType === EventType.CallInvite ||
|
||||
isCallInvite ||
|
||||
ElementCallEventType.matches(eventType) ||
|
||||
eventType === EventType.RTCNotification
|
||||
isRtcNotification
|
||||
) {
|
||||
return { avatarSize: null, needsSenderProfile: false };
|
||||
}
|
||||
@@ -148,15 +158,6 @@ export function getEventTileSenderProfileState({
|
||||
return { avatarSize: "30px", needsSenderProfile: true };
|
||||
}
|
||||
|
||||
/** The room member whose avatar should render for the EventTile. */
|
||||
export function getEventTileAvatarMember(mxEvent: MatrixEvent): RoomMember | null {
|
||||
if (mxEvent.getContent().third_party_invite) {
|
||||
return mxEvent.target;
|
||||
}
|
||||
|
||||
return mxEvent.sender;
|
||||
}
|
||||
|
||||
/** Whether clicking the avatar should open the user profile. */
|
||||
export function getShouldViewUserOnClick(
|
||||
inhibitInteraction: boolean | undefined,
|
||||
@@ -239,8 +240,8 @@ export function getShouldShowMessageActionBar({
|
||||
export interface ShouldShowTimestampInput {
|
||||
/** The event origin timestamp. */
|
||||
eventTs: number;
|
||||
/** The Matrix event type for event-type timestamp derivation. */
|
||||
eventType: string;
|
||||
/** Whether the event is an RTC notification. */
|
||||
isRtcNotification: boolean;
|
||||
/** Whether timestamp rendering is disabled. */
|
||||
hideTimestamp?: boolean;
|
||||
/** Whether timestamps should always show. */
|
||||
@@ -260,7 +261,7 @@ export interface ShouldShowTimestampInput {
|
||||
/** Whether EventTile should render the message timestamp. */
|
||||
export function getShouldShowTimestamp({
|
||||
eventTs,
|
||||
eventType,
|
||||
isRtcNotification,
|
||||
hideTimestamp,
|
||||
alwaysShowTimestamps,
|
||||
last,
|
||||
@@ -271,7 +272,7 @@ export function getShouldShowTimestamp({
|
||||
}: ShouldShowTimestampInput): boolean {
|
||||
return (
|
||||
!!eventTs &&
|
||||
eventType !== EventType.RTCNotification &&
|
||||
!isRtcNotification &&
|
||||
!hideTimestamp &&
|
||||
(alwaysShowTimestamps || last || hover || focusWithin || actionBarFocused || hasContextMenu)
|
||||
);
|
||||
@@ -420,6 +421,8 @@ export interface EventTileClassState {
|
||||
isContinuation?: boolean;
|
||||
/** The Matrix event type for event-type class derivation. */
|
||||
eventType: string;
|
||||
/** Whether the event is a call invite. */
|
||||
isCallInvite: boolean;
|
||||
/** Whether the tile is the last event in the timeline. */
|
||||
isLast?: boolean;
|
||||
/** Whether the tile is the last event in its section. */
|
||||
@@ -455,6 +458,7 @@ export function getEventTileClassState({
|
||||
isSelected,
|
||||
isContinuation,
|
||||
eventType,
|
||||
isCallInvite,
|
||||
isLast,
|
||||
isLastInSection,
|
||||
isContextual,
|
||||
@@ -478,14 +482,13 @@ export function getEventTileClassState({
|
||||
mx_EventTile_sending: !isEditing && isSending,
|
||||
mx_EventTile_highlight: isHighlighted,
|
||||
mx_EventTile_selected: isSelected,
|
||||
mx_EventTile_continuation:
|
||||
isContinuation || eventType === EventType.CallInvite || ElementCallEventType.matches(eventType),
|
||||
mx_EventTile_continuation: isContinuation || isCallInvite || ElementCallEventType.matches(eventType),
|
||||
mx_EventTile_last: isLast,
|
||||
mx_EventTile_lastInSection: isLastInSection,
|
||||
mx_EventTile_contextual: isContextual,
|
||||
mx_EventTile_actionBarFocused: isActionBarFocused,
|
||||
mx_EventTile_bad: isEncryptionFailure,
|
||||
mx_EventTile_emote: msgtype === MsgType.Emote,
|
||||
mx_EventTile_emote: msgtype === "m.emote",
|
||||
mx_EventTile_noSender: hideSender,
|
||||
mx_EventTile_clamp: timelineRenderingType === TimelineRenderingType.ThreadsList || isRenderingNotification,
|
||||
mx_EventTile_noBubble: noBubbleEvent,
|
||||
|
||||
@@ -5,15 +5,12 @@ 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 { type EventStatus, type MatrixEvent, type RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import classNames from "classnames";
|
||||
import { BaseViewModel } from "@element-hq/web-shared-components";
|
||||
|
||||
import {
|
||||
type EventTileSenderProfileState,
|
||||
type FooterDisplayState,
|
||||
getAriaLive,
|
||||
getEventTileAvatarMember,
|
||||
getEventTileClassState,
|
||||
getEventTileLineClassState,
|
||||
getEventTileSenderProfileState,
|
||||
@@ -27,7 +24,6 @@ import {
|
||||
getShouldShowTimestamp,
|
||||
getShouldViewUserOnClick,
|
||||
getTimestampDisplayState,
|
||||
isSendingStatus,
|
||||
type SenderProfileMode,
|
||||
type TimestampDisplayState,
|
||||
} from "./EventTileDerivedState";
|
||||
@@ -54,10 +50,26 @@ import { ReactionsRowViewModel, type ReactionsRowViewModelProps } from "./reacti
|
||||
|
||||
/** Event-level inputs for deriving the EventTile snapshot. */
|
||||
export interface EventTileEventInput {
|
||||
/** The Matrix event rendered by the tile. */
|
||||
mxEvent: MatrixEvent;
|
||||
/** The event send status supplied by EventTile. */
|
||||
eventSendStatus?: EventStatus | null;
|
||||
/** The event type rendered by the tile. */
|
||||
eventType: string;
|
||||
/** The Matrix message type rendered by the tile. */
|
||||
msgtype?: string;
|
||||
/** The event origin timestamp. */
|
||||
eventTs: number;
|
||||
/** The stable event identifier, when available. */
|
||||
eventId?: string;
|
||||
/** Whether the event is a local echo. */
|
||||
isLocalEcho: boolean;
|
||||
/** Whether the event is in a pending send state. */
|
||||
isSending: boolean;
|
||||
/** Whether EventTile should announce updates in an aria-live region. */
|
||||
ariaLive?: "off";
|
||||
/** Whether the event is a room create event. */
|
||||
isRoomCreate: boolean;
|
||||
/** Whether the event is a call invite. */
|
||||
isCallInvite: boolean;
|
||||
/** Whether the event is an RTC notification. */
|
||||
isRtcNotification: boolean;
|
||||
/** Whether the event is currently being edited. */
|
||||
isEditing: boolean;
|
||||
/** Whether the event failed decryption. */
|
||||
@@ -196,12 +208,12 @@ export interface EventTileLineSnapshot {
|
||||
export interface EventTileSenderSnapshot {
|
||||
/** EventTile avatar and sender profile display state. */
|
||||
profileState: EventTileSenderProfileState;
|
||||
/** The room member whose avatar should render. */
|
||||
avatarMember: RoomMember | null;
|
||||
/** Whether clicking the avatar should open the user profile. */
|
||||
viewUserOnClick: boolean;
|
||||
/** SenderProfile rendering mode. */
|
||||
profileMode: SenderProfileMode;
|
||||
/** Whether the avatar should use historical room member details. */
|
||||
forceHistoricalAvatar: boolean;
|
||||
}
|
||||
|
||||
/** Action bar state derived for the EventTile snapshot. */
|
||||
@@ -459,15 +471,12 @@ export class EventTileViewModel extends BaseViewModel<EventTileRenderState, Even
|
||||
/** Creates an EventTile view model snapshot. */
|
||||
public static createSnapshot(props: EventTileViewModelProps): EventTileViewModelSnapshot {
|
||||
const { event, display, interaction, sender, timestamp, footer } = props;
|
||||
const eventType = event.mxEvent.getType();
|
||||
const msgtype = event.mxEvent.getContent().msgtype;
|
||||
const isSending = isSendingStatus(event.eventSendStatus ?? undefined);
|
||||
const isContinuation = getIsContinuation(display.continuation, display.timelineRenderingType, display.layout);
|
||||
const isRenderingNotification = display.timelineRenderingType === TimelineRenderingType.Notification;
|
||||
const eventSnapshot: EventTileEventSnapshot = {
|
||||
eventType,
|
||||
msgtype,
|
||||
isSending,
|
||||
eventType: event.eventType,
|
||||
msgtype: event.msgtype,
|
||||
isSending: event.isSending,
|
||||
isEditing: event.isEditing,
|
||||
isContinuation,
|
||||
isRenderingNotification,
|
||||
@@ -477,13 +486,16 @@ export class EventTileViewModel extends BaseViewModel<EventTileRenderState, Even
|
||||
isInfoMessage: display.isInfoMessage,
|
||||
timelineRenderingType: display.timelineRenderingType,
|
||||
continuation: display.continuation,
|
||||
eventType,
|
||||
eventType: event.eventType,
|
||||
isBubbleMessage: display.isBubbleMessage,
|
||||
layout: display.layout,
|
||||
isRoomCreate: event.isRoomCreate,
|
||||
isCallInvite: event.isCallInvite,
|
||||
isRtcNotification: event.isRtcNotification,
|
||||
});
|
||||
const showTimestamp = getShouldShowTimestamp({
|
||||
eventTs: event.mxEvent.getTs(),
|
||||
eventType,
|
||||
eventTs: event.eventTs,
|
||||
isRtcNotification: event.isRtcNotification,
|
||||
hideTimestamp: timestamp.hideTimestamp,
|
||||
alwaysShowTimestamps: timestamp.alwaysShowTimestamps,
|
||||
last: display.isLast,
|
||||
@@ -494,23 +506,27 @@ export class EventTileViewModel extends BaseViewModel<EventTileRenderState, Even
|
||||
});
|
||||
const timestampValue = getEventTileTimestamp({
|
||||
timelineRenderingType: display.timelineRenderingType,
|
||||
eventTs: event.mxEvent.getTs(),
|
||||
eventTs: event.eventTs,
|
||||
threadReplyEventTs: timestamp.threadReplyEventTs,
|
||||
});
|
||||
|
||||
return {
|
||||
event: eventSnapshot,
|
||||
root: {
|
||||
ariaLive: getAriaLive(event.eventSendStatus),
|
||||
scrollToken: getScrollToken(event.mxEvent),
|
||||
ariaLive: event.ariaLive,
|
||||
scrollToken: getScrollToken({
|
||||
eventId: event.eventId,
|
||||
isLocalEcho: event.isLocalEcho,
|
||||
}),
|
||||
classState: EventTileViewModel.getClassState({
|
||||
event,
|
||||
display,
|
||||
interaction,
|
||||
sender,
|
||||
eventType,
|
||||
msgtype,
|
||||
isSending,
|
||||
eventType: event.eventType,
|
||||
msgtype: event.msgtype,
|
||||
isSending: event.isSending,
|
||||
isCallInvite: event.isCallInvite,
|
||||
isContinuation,
|
||||
isRenderingNotification,
|
||||
}),
|
||||
@@ -518,13 +534,12 @@ export class EventTileViewModel extends BaseViewModel<EventTileRenderState, Even
|
||||
line: {
|
||||
classState: getEventTileLineClassState({
|
||||
isProbablyMedia: display.isProbablyMedia,
|
||||
eventType,
|
||||
msgtype,
|
||||
eventType: event.eventType,
|
||||
msgtype: event.msgtype,
|
||||
}),
|
||||
},
|
||||
sender: {
|
||||
profileState: senderProfileState,
|
||||
avatarMember: getEventTileAvatarMember(event.mxEvent),
|
||||
viewUserOnClick: getShouldViewUserOnClick(
|
||||
interaction.inhibitInteraction,
|
||||
display.timelineRenderingType,
|
||||
@@ -534,6 +549,7 @@ export class EventTileViewModel extends BaseViewModel<EventTileRenderState, Even
|
||||
hideSender: sender.hideSender,
|
||||
timelineRenderingType: display.timelineRenderingType,
|
||||
}),
|
||||
forceHistoricalAvatar: event.eventType === "m.room.member",
|
||||
},
|
||||
actionBar: {
|
||||
show: getShouldShowMessageActionBar({
|
||||
@@ -580,6 +596,7 @@ export class EventTileViewModel extends BaseViewModel<EventTileRenderState, Even
|
||||
eventType,
|
||||
msgtype,
|
||||
isSending,
|
||||
isCallInvite,
|
||||
isContinuation,
|
||||
isRenderingNotification,
|
||||
}: {
|
||||
@@ -590,6 +607,7 @@ export class EventTileViewModel extends BaseViewModel<EventTileRenderState, Even
|
||||
eventType: string;
|
||||
msgtype?: string;
|
||||
isSending: boolean;
|
||||
isCallInvite: boolean;
|
||||
isContinuation?: boolean;
|
||||
isRenderingNotification: boolean;
|
||||
}): ReturnType<typeof getEventTileClassState> {
|
||||
@@ -605,6 +623,7 @@ export class EventTileViewModel extends BaseViewModel<EventTileRenderState, Even
|
||||
isSelected: display.isSelected,
|
||||
isContinuation,
|
||||
eventType,
|
||||
isCallInvite,
|
||||
isLast: display.isLast,
|
||||
isLastInSection: display.isLastInSection,
|
||||
isContextual: display.isContextual,
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { EventStatus, EventType, MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { mkEvent } from "../../test-utils";
|
||||
import { TimelineRenderingType } from "../../../src/contexts/RoomContext";
|
||||
import { Layout } from "../../../src/settings/enums/Layout";
|
||||
import {
|
||||
@@ -16,8 +13,6 @@ import {
|
||||
} from "../../../src/viewmodels/room/timeline/event-tile/EventTileViewModel";
|
||||
|
||||
describe("EventTileViewModel", () => {
|
||||
const roomId = "!room:example.org";
|
||||
const userId = "@alice:example.org";
|
||||
type EventTileViewModelPropsOverrides = {
|
||||
event?: Partial<EventTileViewModelProps["event"]>;
|
||||
display?: Partial<EventTileViewModelProps["display"]>;
|
||||
@@ -27,34 +22,22 @@ describe("EventTileViewModel", () => {
|
||||
footer?: Partial<EventTileViewModelProps["footer"]>;
|
||||
};
|
||||
|
||||
function makeMessageEvent({
|
||||
type = EventType.RoomMessage,
|
||||
content = { msgtype: MsgType.Text, body: "Hello" },
|
||||
ts = 123,
|
||||
status,
|
||||
}: {
|
||||
type?: string;
|
||||
content?: Record<string, unknown>;
|
||||
ts?: number;
|
||||
status?: EventStatus;
|
||||
} = {}): MatrixEvent {
|
||||
return mkEvent({
|
||||
event: true,
|
||||
type,
|
||||
room: roomId,
|
||||
user: userId,
|
||||
content,
|
||||
ts,
|
||||
status,
|
||||
});
|
||||
}
|
||||
|
||||
function makeProps(overrides: EventTileViewModelPropsOverrides = {}): EventTileViewModelProps {
|
||||
return {
|
||||
event: {
|
||||
mxEvent: makeMessageEvent(),
|
||||
eventType: "m.room.message",
|
||||
msgtype: "m.text",
|
||||
eventTs: 123,
|
||||
eventId: "$event",
|
||||
isLocalEcho: false,
|
||||
isSending: false,
|
||||
ariaLive: "off",
|
||||
isRoomCreate: false,
|
||||
isCallInvite: false,
|
||||
isRtcNotification: false,
|
||||
isEditing: false,
|
||||
isEncryptionFailure: false,
|
||||
forExport: false,
|
||||
...overrides.event,
|
||||
},
|
||||
display: {
|
||||
@@ -94,14 +77,12 @@ describe("EventTileViewModel", () => {
|
||||
};
|
||||
}
|
||||
|
||||
it("derives sending, aria-live, and local echo scroll state", () => {
|
||||
const mxEvent = makeMessageEvent({ status: EventStatus.SENDING });
|
||||
|
||||
it("derives sending, aria-live, and scroll state from plain event data", () => {
|
||||
const snapshot = EventTileViewModel.createSnapshot(
|
||||
makeProps({
|
||||
event: {
|
||||
mxEvent,
|
||||
eventSendStatus: EventStatus.SENDING,
|
||||
isSending: true,
|
||||
isLocalEcho: true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -112,14 +93,25 @@ describe("EventTileViewModel", () => {
|
||||
expect(snapshot.root.classState.mx_EventTile_sending).toBe(true);
|
||||
});
|
||||
|
||||
it("derives render-ready root and line state", () => {
|
||||
const mxEvent = makeMessageEvent({ status: EventStatus.SENDING });
|
||||
it("derives a scroll token for non-local-echo events", () => {
|
||||
const snapshot = EventTileViewModel.createSnapshot(
|
||||
makeProps({
|
||||
event: {
|
||||
eventId: "$remote-event",
|
||||
isLocalEcho: false,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(snapshot.root.scrollToken).toBe("$remote-event");
|
||||
});
|
||||
|
||||
it("derives render-ready root and line state", () => {
|
||||
const renderState = EventTileViewModel.createRenderState(
|
||||
makeProps({
|
||||
event: {
|
||||
mxEvent,
|
||||
eventSendStatus: EventStatus.SENDING,
|
||||
isSending: true,
|
||||
isLocalEcho: true,
|
||||
},
|
||||
display: {
|
||||
isHighlighted: true,
|
||||
@@ -269,9 +261,7 @@ describe("EventTileViewModel", () => {
|
||||
const snapshot = EventTileViewModel.createSnapshot(
|
||||
makeProps({
|
||||
event: {
|
||||
mxEvent: makeMessageEvent({
|
||||
content: { msgtype: MsgType.Image, body: "image" },
|
||||
}),
|
||||
msgtype: "m.image",
|
||||
},
|
||||
display: {
|
||||
isProbablyMedia: true,
|
||||
@@ -317,6 +307,18 @@ describe("EventTileViewModel", () => {
|
||||
expect(snapshot.sender.viewUserOnClick).toBe(true);
|
||||
});
|
||||
|
||||
it("marks room member avatars as historical", () => {
|
||||
const snapshot = EventTileViewModel.createSnapshot(
|
||||
makeProps({
|
||||
event: {
|
||||
eventType: "m.room.member",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(snapshot.sender.forceHistoricalAvatar).toBe(true);
|
||||
});
|
||||
|
||||
it("derives action bar visibility from interaction state", () => {
|
||||
const hoverSnapshot = EventTileViewModel.createSnapshot(makeProps({ interaction: { hover: true } }));
|
||||
const contextMenuSnapshot = EventTileViewModel.createSnapshot(
|
||||
@@ -356,10 +358,7 @@ describe("EventTileViewModel", () => {
|
||||
const snapshot = EventTileViewModel.createSnapshot(
|
||||
makeProps({
|
||||
event: {
|
||||
mxEvent: makeMessageEvent({
|
||||
type: EventType.RTCNotification,
|
||||
content: {},
|
||||
}),
|
||||
isRtcNotification: true,
|
||||
},
|
||||
interaction: {
|
||||
hover: true,
|
||||
@@ -479,17 +478,10 @@ describe("EventTileViewModel", () => {
|
||||
});
|
||||
|
||||
it("does not initialize timestamp child view models for events without an origin timestamp", () => {
|
||||
const mxEvent = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
room_id: roomId,
|
||||
sender: userId,
|
||||
content: { msgtype: MsgType.Text, body: "Hello" },
|
||||
event_id: "$event",
|
||||
});
|
||||
const vm = new EventTileViewModel(
|
||||
makeProps({
|
||||
event: {
|
||||
mxEvent,
|
||||
eventTs: 0,
|
||||
},
|
||||
timestamp: {
|
||||
hideTimestamp: true,
|
||||
|
||||
Reference in New Issue
Block a user