Call Tile - Support declined call tile (#33371)
* Extract shared types and css * Add CallDeclinedTileView * Add storybook and view tests * Support declined event in view model * Render declined view from tile factory * Update snapshots * Add 10px padding to top and bottom * Distinguish between call declined by us and other users * Support `isCallDeclinedByUs` in view model * Update tests * Add better comments * Rename getInitial to generateSnapshot
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
M_POLL_START,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
CallDeclinedTileView,
|
||||
CallStartedTileView,
|
||||
EncryptionEventView,
|
||||
HiddenBodyView,
|
||||
@@ -56,7 +57,7 @@ import { TextualEventViewModel } from "../viewmodels/room/timeline/event-tile/Te
|
||||
import { HiddenBodyViewModel } from "../viewmodels/room/timeline/event-tile/body/HiddenBodyViewModel";
|
||||
import { ViewSourceEventViewModel } from "../viewmodels/room/timeline/event-tile/body/ViewSourceEventViewModel";
|
||||
import { ElementCallEventType } from "../call-types";
|
||||
import { CallStartedTileViewModel } from "../viewmodels/room/timeline/event-tile/call/CallStartedTileViewModel";
|
||||
import { CallTileViewModel } from "../viewmodels/room/timeline/event-tile/call/CallTileViewModel";
|
||||
|
||||
// Subset of EventTile's IProps plus some mixins
|
||||
export interface EventTileTypeProps extends Pick<
|
||||
@@ -187,9 +188,9 @@ function RoomAvatarEventWrappedView({ mxEvent, ref }: IBodyProps): JSX.Element {
|
||||
}
|
||||
const RoomAvatarEventFactory: Factory = (ref, props) => <RoomAvatarEventWrappedView ref={ref} {...props} />;
|
||||
|
||||
function CallStartedTileViewWrapped({ mxEvent }: IBodyProps): JSX.Element {
|
||||
const vm = useCreateAutoDisposedViewModel(() => new CallStartedTileViewModel({ mxEvent }));
|
||||
return <CallStartedTileView vm={vm} />;
|
||||
function CallStartedTileViewWrapped({ mxEvent, getRelationsForEvent }: IBodyProps): JSX.Element {
|
||||
const vm = useCreateAutoDisposedViewModel(() => new CallTileViewModel({ mxEvent, getRelationsForEvent }));
|
||||
return vm.isCallDeclined ? <CallDeclinedTileView vm={vm} /> : <CallStartedTileView vm={vm} />;
|
||||
}
|
||||
|
||||
export const CallStartedEventFactory: Factory = (ref, props) => {
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* 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 { BaseViewModel, CallType, type CallStartedTileViewSnapshot } from "@element-hq/web-shared-components";
|
||||
|
||||
import type { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import type { IRTCNotificationContent } from "matrix-js-sdk/src/matrixrtc";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
import { formatTime } from "../../../../../DateUtils";
|
||||
import defaultDispatcher from "../../../../../dispatcher/dispatcher";
|
||||
import type { SettingUpdatedPayload } from "../../../../../dispatcher/payloads/SettingUpdatedPayload";
|
||||
import type { ActionPayload } from "../../../../../dispatcher/payloads";
|
||||
import { Action } from "../../../../../dispatcher/actions";
|
||||
|
||||
export interface CallStartedTileViewModelProps {
|
||||
mxEvent: MatrixEvent;
|
||||
}
|
||||
|
||||
function getIntentFromEvent(event: MatrixEvent): CallStartedTileViewSnapshot["type"] {
|
||||
const content = event.getContent<IRTCNotificationContent>();
|
||||
const intentInContent = content["m.call.intent"];
|
||||
switch (intentInContent) {
|
||||
case "audio":
|
||||
return CallType.Voice;
|
||||
case "video":
|
||||
default:
|
||||
return CallType.Video;
|
||||
}
|
||||
}
|
||||
|
||||
function getTimeFromEvent(event: MatrixEvent, showTwelveHour: boolean): CallStartedTileViewSnapshot["timestamp"] {
|
||||
const content = event.getContent<IRTCNotificationContent>();
|
||||
const senderTs = content["sender_ts"];
|
||||
const originServerTs = event.getTs();
|
||||
const ts = Math.abs(senderTs - originServerTs) > 20000 ? originServerTs : senderTs;
|
||||
|
||||
const date = new Date(ts);
|
||||
const timestamp = formatTime(date, showTwelveHour);
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
function getInitialSnapshot(event: MatrixEvent): CallStartedTileViewSnapshot {
|
||||
const type = getIntentFromEvent(event);
|
||||
const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
|
||||
const timestamp = getTimeFromEvent(event, showTwelveHour);
|
||||
return { type, timestamp };
|
||||
}
|
||||
|
||||
function isSettingsChangedPayload(payload: ActionPayload): payload is SettingUpdatedPayload {
|
||||
return payload.action === Action.SettingUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewModel for a timeline tile that indicates the start of an element call.
|
||||
*/
|
||||
export class CallStartedTileViewModel extends BaseViewModel<
|
||||
CallStartedTileViewSnapshot,
|
||||
CallStartedTileViewModelProps
|
||||
> {
|
||||
public constructor(props: CallStartedTileViewModelProps) {
|
||||
super(props, getInitialSnapshot(props.mxEvent));
|
||||
SettingsStore.monitorSetting("showTwelveHourTimestamps", null);
|
||||
const token = defaultDispatcher.register(this.onAction);
|
||||
this.disposables.track(() => {
|
||||
defaultDispatcher.unregister(token);
|
||||
});
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload): void => {
|
||||
if (!isSettingsChangedPayload(payload) || payload.settingName !== "showTwelveHourTimestamps") return;
|
||||
const showTwelveHour = (payload.newValue as boolean) ?? false;
|
||||
const timestamp = getTimeFromEvent(this.props.mxEvent, showTwelveHour);
|
||||
this.snapshot.merge({ timestamp });
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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 { BaseViewModel, CallType, type CallTileViewSnapshot } from "@element-hq/web-shared-components";
|
||||
import { EventType, type MatrixEvent, MatrixEventEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import type { IRTCNotificationContent } from "matrix-js-sdk/src/matrixrtc";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
import { formatTime } from "../../../../../DateUtils";
|
||||
import defaultDispatcher from "../../../../../dispatcher/dispatcher";
|
||||
import type { SettingUpdatedPayload } from "../../../../../dispatcher/payloads/SettingUpdatedPayload";
|
||||
import type { ActionPayload } from "../../../../../dispatcher/payloads";
|
||||
import { Action } from "../../../../../dispatcher/actions";
|
||||
import type { GetRelationsForEvent } from "../../../../../components/views/rooms/EventTile";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
|
||||
export interface CallTileViewModelProps {
|
||||
/**
|
||||
* Event of type `org.matrix.msc4075.rtc.notification`.
|
||||
*/
|
||||
mxEvent: MatrixEvent;
|
||||
/**
|
||||
* Helper to fetch related events from a given event.
|
||||
*/
|
||||
getRelationsForEvent?: GetRelationsForEvent;
|
||||
}
|
||||
|
||||
function getIntentFromEvent(event: MatrixEvent): CallTileViewSnapshot["type"] {
|
||||
const content = event.getContent<IRTCNotificationContent>();
|
||||
const intentInContent = content["m.call.intent"];
|
||||
switch (intentInContent) {
|
||||
case "audio":
|
||||
return CallType.Voice;
|
||||
case "video":
|
||||
default:
|
||||
return CallType.Video;
|
||||
}
|
||||
}
|
||||
|
||||
function getTs(event: MatrixEvent): number {
|
||||
if (event.getType() === EventType.RTCNotification) {
|
||||
/**
|
||||
* According to the spec:
|
||||
* Receivers SHOULD use origin_server_ts if |sender_ts - origin_server_ts| > 20000 ms.
|
||||
*/
|
||||
const content = event.getContent<IRTCNotificationContent>();
|
||||
const senderTs = content["sender_ts"];
|
||||
const originServerTs = event.getTs();
|
||||
const ts = Math.abs(senderTs - originServerTs) > 20000 ? originServerTs : senderTs;
|
||||
return ts;
|
||||
} else return event.getTs();
|
||||
}
|
||||
|
||||
function getTimeFromEvent(event: MatrixEvent, showTwelveHour: boolean): CallTileViewSnapshot["timestamp"] {
|
||||
const ts = getTs(event);
|
||||
const date = new Date(ts);
|
||||
const timestamp = formatTime(date, showTwelveHour);
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
function generateSnapshot(
|
||||
event: MatrixEvent,
|
||||
getRelationsForEvent?: GetRelationsForEvent,
|
||||
): { snapshot: CallTileViewSnapshot; declineEvent: MatrixEvent | null } {
|
||||
const type = getIntentFromEvent(event);
|
||||
const declineEvent = getDeclinedEvent(event, getRelationsForEvent);
|
||||
let isCallDeclinedByUs: boolean | undefined;
|
||||
if (declineEvent) {
|
||||
isCallDeclinedByUs = declineEvent.getSender() === MatrixClientPeg.get()?.getUserId();
|
||||
}
|
||||
const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
|
||||
const timestamp = getTimeFromEvent(declineEvent ?? event, showTwelveHour);
|
||||
return { snapshot: { type, timestamp, isCallDeclinedByUs }, declineEvent };
|
||||
}
|
||||
|
||||
function isSettingsChangedPayload(payload: ActionPayload): payload is SettingUpdatedPayload {
|
||||
return payload.action === Action.SettingUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a declined event that is related to the given rtc notification event.
|
||||
* @param event rtc notification event
|
||||
*/
|
||||
function getDeclinedEvent(event: MatrixEvent, getRelationsForEvent?: GetRelationsForEvent): MatrixEvent | null {
|
||||
const eventId = event.getId();
|
||||
if (eventId && getRelationsForEvent) {
|
||||
const relations = getRelationsForEvent(eventId, RelationType.Reference, EventType.RTCDecline)?.getRelations();
|
||||
if (relations) return relations[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common view-model for call tiles; currently used to render:
|
||||
* 1. A tile that indicates that a call occurred (call tombstone).
|
||||
* 2. A tile that indicates that a call was declined.
|
||||
*/
|
||||
export class CallTileViewModel extends BaseViewModel<CallTileViewSnapshot, CallTileViewModelProps> {
|
||||
/**
|
||||
* The decline event associated with this call, if any.
|
||||
*/
|
||||
private declineEvent: MatrixEvent | null;
|
||||
|
||||
public constructor(props: CallTileViewModelProps) {
|
||||
const { declineEvent, snapshot } = generateSnapshot(props.mxEvent, props.getRelationsForEvent);
|
||||
super(props, snapshot);
|
||||
this.declineEvent = declineEvent;
|
||||
|
||||
// Listen to the changes on settings so that we can update the timestamp format (12H vs 24H).
|
||||
SettingsStore.monitorSetting("showTwelveHourTimestamps", null);
|
||||
const token = defaultDispatcher.register(this.onAction);
|
||||
this.disposables.track(() => {
|
||||
defaultDispatcher.unregister(token);
|
||||
});
|
||||
|
||||
// When a relation is added to the event, recompute the state.
|
||||
this.disposables.trackListener(props.mxEvent, MatrixEventEvent.RelationsCreated, () => {
|
||||
const { declineEvent, snapshot } = generateSnapshot(props.mxEvent, props.getRelationsForEvent);
|
||||
this.declineEvent = declineEvent;
|
||||
this.snapshot.set(snapshot);
|
||||
});
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload): void => {
|
||||
if (!isSettingsChangedPayload(payload) || payload.settingName !== "showTwelveHourTimestamps") return;
|
||||
const showTwelveHour = (payload.newValue as boolean) ?? false;
|
||||
const timestamp = getTimeFromEvent(this.declineEvent ?? this.props.mxEvent, showTwelveHour);
|
||||
this.snapshot.merge({ timestamp });
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the call associated with this vm has been declined.
|
||||
*/
|
||||
public get isCallDeclined(): boolean {
|
||||
return !!this.declineEvent;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* 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 { type MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { CallType } from "@element-hq/web-shared-components";
|
||||
import { waitFor } from "jest-matrix-react";
|
||||
|
||||
import { mkEvent } from "../../test-utils";
|
||||
import { CallStartedTileViewModel } from "../../../src/viewmodels/room/timeline/event-tile/call/CallStartedTileViewModel";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
|
||||
function getMockedRtcNotificationEvent(intent: string, senderTs: number, serverTs: number): MatrixEvent {
|
||||
const mockEvent = mkEvent({
|
||||
type: EventType.RTCNotification,
|
||||
user: "@foo:m.org",
|
||||
content: {
|
||||
"m.call.intent": intent,
|
||||
"sender_ts": senderTs,
|
||||
},
|
||||
ts: serverTs,
|
||||
event: true,
|
||||
});
|
||||
return mockEvent;
|
||||
}
|
||||
|
||||
describe("CallStartedTileViewModel", () => {
|
||||
it("should set voice intent in state", () => {
|
||||
const mxEvent = getMockedRtcNotificationEvent("audio", 1752583130365, 1752583130365);
|
||||
const vm = new CallStartedTileViewModel({ mxEvent });
|
||||
const { type } = vm.getSnapshot();
|
||||
expect(type).toStrictEqual(CallType.Voice);
|
||||
});
|
||||
|
||||
it("should set video intent in state", () => {
|
||||
const mxEvent = getMockedRtcNotificationEvent("video", 1752583130365, 1752583130365);
|
||||
const vm = new CallStartedTileViewModel({ mxEvent });
|
||||
const { type } = vm.getSnapshot();
|
||||
expect(type).toStrictEqual(CallType.Video);
|
||||
});
|
||||
|
||||
it("should calculate time string correctly", () => {
|
||||
const mxEvent = getMockedRtcNotificationEvent("video", 924285348000, 924285348000);
|
||||
const vm = new CallStartedTileViewModel({ mxEvent });
|
||||
const { timestamp } = vm.getSnapshot();
|
||||
expect(timestamp).toStrictEqual("17:55");
|
||||
});
|
||||
|
||||
it("should calculate time string correctly when configured to use 12 hour format", async () => {
|
||||
const mxEvent = getMockedRtcNotificationEvent("video", 924285348000, 924285348000);
|
||||
await SettingsStore.setValue("showTwelveHourTimestamps", null, SettingLevel.DEVICE, true);
|
||||
const vm = new CallStartedTileViewModel({ mxEvent });
|
||||
const { timestamp } = vm.getSnapshot();
|
||||
expect(timestamp).toStrictEqual("5:55 PM");
|
||||
SettingsStore.reset();
|
||||
});
|
||||
|
||||
it("should change timestamp format when setting is modified", async () => {
|
||||
const mxEvent = getMockedRtcNotificationEvent("video", 924285348000, 924285348000);
|
||||
const vm = new CallStartedTileViewModel({ mxEvent });
|
||||
expect(vm.getSnapshot().timestamp).toStrictEqual("17:55");
|
||||
await SettingsStore.setValue("showTwelveHourTimestamps", null, SettingLevel.DEVICE, true);
|
||||
await waitFor(() => {
|
||||
expect(vm.getSnapshot().timestamp).toStrictEqual("5:55 PM");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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, type MatrixEvent, MatrixEventEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { CallType } from "@element-hq/web-shared-components";
|
||||
import { waitFor } from "jest-matrix-react";
|
||||
|
||||
import { mkEvent, stubClient } from "../../test-utils";
|
||||
import { CallTileViewModel } from "../../../src/viewmodels/room/timeline/event-tile/call/CallTileViewModel";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
|
||||
function getMockedRtcNotificationEvent(intent: string, senderTs: number, serverTs: number): MatrixEvent {
|
||||
const mockEvent = mkEvent({
|
||||
type: EventType.RTCNotification,
|
||||
user: "@foo:m.org",
|
||||
content: {
|
||||
"m.call.intent": intent,
|
||||
"sender_ts": senderTs,
|
||||
},
|
||||
ts: serverTs,
|
||||
event: true,
|
||||
});
|
||||
return mockEvent;
|
||||
}
|
||||
|
||||
function getMockedRtcDeclineEvent(rtcNotificationEvent: MatrixEvent, sender = "@foo:m.org"): MatrixEvent {
|
||||
const mockEvent = mkEvent({
|
||||
type: EventType.RTCDecline,
|
||||
user: sender,
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: rtcNotificationEvent.getId(),
|
||||
},
|
||||
},
|
||||
ts: 924285416000,
|
||||
event: true,
|
||||
});
|
||||
return mockEvent;
|
||||
}
|
||||
|
||||
describe("CallTileViewModel", () => {
|
||||
it("should set voice intent in state", () => {
|
||||
const mxEvent = getMockedRtcNotificationEvent("audio", 1752583130365, 1752583130365);
|
||||
const vm = new CallTileViewModel({ mxEvent });
|
||||
const { type } = vm.getSnapshot();
|
||||
expect(type).toStrictEqual(CallType.Voice);
|
||||
});
|
||||
|
||||
it("should set video intent in state", () => {
|
||||
const mxEvent = getMockedRtcNotificationEvent("video", 1752583130365, 1752583130365);
|
||||
const vm = new CallTileViewModel({ mxEvent });
|
||||
const { type } = vm.getSnapshot();
|
||||
expect(type).toStrictEqual(CallType.Video);
|
||||
});
|
||||
|
||||
it("should calculate time string correctly", () => {
|
||||
const mxEvent = getMockedRtcNotificationEvent("video", 924285348000, 924285348000);
|
||||
const vm = new CallTileViewModel({ mxEvent });
|
||||
const { timestamp } = vm.getSnapshot();
|
||||
expect(timestamp).toStrictEqual("17:55");
|
||||
});
|
||||
|
||||
it("should calculate time string correctly when configured to use 12 hour format", async () => {
|
||||
const mxEvent = getMockedRtcNotificationEvent("video", 924285348000, 924285348000);
|
||||
await SettingsStore.setValue("showTwelveHourTimestamps", null, SettingLevel.DEVICE, true);
|
||||
const vm = new CallTileViewModel({ mxEvent });
|
||||
const { timestamp } = vm.getSnapshot();
|
||||
expect(timestamp).toStrictEqual("5:55 PM");
|
||||
SettingsStore.reset();
|
||||
});
|
||||
|
||||
it("should change timestamp format when setting is modified", async () => {
|
||||
const mxEvent = getMockedRtcNotificationEvent("video", 924285348000, 924285348000);
|
||||
const vm = new CallTileViewModel({ mxEvent });
|
||||
expect(vm.getSnapshot().timestamp).toStrictEqual("17:55");
|
||||
await SettingsStore.setValue("showTwelveHourTimestamps", null, SettingLevel.DEVICE, true);
|
||||
await waitFor(() => {
|
||||
expect(vm.getSnapshot().timestamp).toStrictEqual("5:55 PM");
|
||||
});
|
||||
SettingsStore.reset();
|
||||
});
|
||||
|
||||
describe("On call declined", () => {
|
||||
it("should calculate isCallDeclined correctly", () => {
|
||||
const mxEvent = getMockedRtcNotificationEvent("audio", 1752583130365, 1752583130365);
|
||||
// When there's no decline event, isCallDeclined = false
|
||||
const vm1 = new CallTileViewModel({ mxEvent, getRelationsForEvent: jest.fn() });
|
||||
expect(vm1.isCallDeclined).toStrictEqual(false);
|
||||
|
||||
// When there's a decline event, isCallDeclined = true
|
||||
const declineEvent = getMockedRtcDeclineEvent(mxEvent);
|
||||
const getRelationsForEvent = jest.fn().mockReturnValue({
|
||||
getRelations: () => [declineEvent],
|
||||
});
|
||||
const vm2 = new CallTileViewModel({ mxEvent, getRelationsForEvent });
|
||||
expect(vm2.isCallDeclined).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it("should calculate isCallDeclinedByUs correctly", () => {
|
||||
const cli = stubClient();
|
||||
cli.getUserId = jest.fn().mockReturnValue("@bar:m.org");
|
||||
|
||||
const mxEvent = getMockedRtcNotificationEvent("audio", 924285348000, 924285348000);
|
||||
const declineEvent: MatrixEvent[] = [];
|
||||
const getRelationsForEvent = jest.fn().mockReturnValue({
|
||||
getRelations: () => declineEvent,
|
||||
});
|
||||
|
||||
// Decline event sent by somebody else
|
||||
declineEvent.push(getMockedRtcDeclineEvent(mxEvent));
|
||||
const vm = new CallTileViewModel({ mxEvent, getRelationsForEvent });
|
||||
expect(vm.getSnapshot().isCallDeclinedByUs).toStrictEqual(false);
|
||||
|
||||
// Decline event sent by us
|
||||
declineEvent.pop();
|
||||
declineEvent.push(getMockedRtcDeclineEvent(mxEvent, MatrixClientPeg.get()!.getUserId()!));
|
||||
const vm2 = new CallTileViewModel({ mxEvent, getRelationsForEvent });
|
||||
expect(vm2.getSnapshot().isCallDeclinedByUs).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it("should recompute state when call is declined", () => {
|
||||
const mxEvent = getMockedRtcNotificationEvent("audio", 924285348000, 924285348000);
|
||||
const declineEvent: MatrixEvent[] = [];
|
||||
const getRelationsForEvent = jest.fn().mockReturnValue({
|
||||
getRelations: () => declineEvent,
|
||||
});
|
||||
|
||||
// No decline event yet, so timestamp should be based on rtc notification event.
|
||||
const vm = new CallTileViewModel({ mxEvent, getRelationsForEvent });
|
||||
expect(vm.getSnapshot().timestamp).toStrictEqual("17:55");
|
||||
|
||||
// Decline event arrives, timestamp should update to be that of the decline event.
|
||||
declineEvent.push(getMockedRtcDeclineEvent(mxEvent));
|
||||
mxEvent.emit(MatrixEventEvent.RelationsCreated, RelationType.Reference, EventType.RTCDecline);
|
||||
expect(vm.getSnapshot().timestamp).toStrictEqual("17:56");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user