Merge branch 'develop' into florianduros/fix-white-black-theme-switch
This commit is contained in:
commit
f85685a0fb
26 changed files with 824 additions and 119 deletions
|
@ -162,7 +162,7 @@ describe("Spotlight", () => {
|
||||||
cy.window({ log: false }).then(({ matrixcs: { Visibility } }) => {
|
cy.window({ log: false }).then(({ matrixcs: { Visibility } }) => {
|
||||||
cy.createRoom({ name: room1Name, visibility: Visibility.Public }).then(_room1Id => {
|
cy.createRoom({ name: room1Name, visibility: Visibility.Public }).then(_room1Id => {
|
||||||
room1Id = _room1Id;
|
room1Id = _room1Id;
|
||||||
cy.inviteUser(room1Id, bot1.getUserId());
|
bot1.joinRoom(room1Id);
|
||||||
cy.visit("/#/room/" + room1Id);
|
cy.visit("/#/room/" + room1Id);
|
||||||
});
|
});
|
||||||
bot2.createRoom({ name: room2Name, visibility: Visibility.Public })
|
bot2.createRoom({ name: room2Name, visibility: Visibility.Public })
|
||||||
|
|
|
@ -73,7 +73,10 @@ describe("Threads", () => {
|
||||||
|
|
||||||
it("should be usable for a conversation", () => {
|
it("should be usable for a conversation", () => {
|
||||||
let bot: MatrixClient;
|
let bot: MatrixClient;
|
||||||
cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => {
|
cy.getBot(synapse, {
|
||||||
|
displayName: "BotBob",
|
||||||
|
autoAcceptInvites: false,
|
||||||
|
}).then(_bot => {
|
||||||
bot = _bot;
|
bot = _bot;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -81,6 +84,7 @@ describe("Threads", () => {
|
||||||
cy.createRoom({}).then(_roomId => {
|
cy.createRoom({}).then(_roomId => {
|
||||||
roomId = _roomId;
|
roomId = _roomId;
|
||||||
cy.inviteUser(roomId, bot.getUserId());
|
cy.inviteUser(roomId, bot.getUserId());
|
||||||
|
bot.joinRoom(roomId);
|
||||||
cy.visit("/#/room/" + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -337,6 +337,7 @@
|
||||||
@import "./views/spaces/_SpacePublicShare.pcss";
|
@import "./views/spaces/_SpacePublicShare.pcss";
|
||||||
@import "./views/terms/_InlineTermsAgreement.pcss";
|
@import "./views/terms/_InlineTermsAgreement.pcss";
|
||||||
@import "./views/toasts/_AnalyticsToast.pcss";
|
@import "./views/toasts/_AnalyticsToast.pcss";
|
||||||
|
@import "./views/toasts/_IncomingCallToast.pcss";
|
||||||
@import "./views/toasts/_IncomingLegacyCallToast.pcss";
|
@import "./views/toasts/_IncomingLegacyCallToast.pcss";
|
||||||
@import "./views/toasts/_NonUrgentEchoFailureToast.pcss";
|
@import "./views/toasts/_NonUrgentEchoFailureToast.pcss";
|
||||||
@import "./views/typography/_Heading.pcss";
|
@import "./views/typography/_Heading.pcss";
|
||||||
|
|
|
@ -22,7 +22,7 @@ limitations under the License.
|
||||||
padding: 0 $spacing-8 $spacing-8 0;
|
padding: 0 $spacing-8 $spacing-8 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DeviceTypeIcon_deviceIcon {
|
.mx_DeviceTypeIcon_deviceIconWrapper {
|
||||||
--background-color: $system;
|
--background-color: $system;
|
||||||
--icon-color: $secondary-content;
|
--icon-color: $secondary-content;
|
||||||
|
|
||||||
|
@ -36,11 +36,16 @@ limitations under the License.
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DeviceTypeIcon_selected .mx_DeviceTypeIcon_deviceIcon {
|
.mx_DeviceTypeIcon_selected .mx_DeviceTypeIcon_deviceIconWrapper {
|
||||||
--background-color: $primary-content;
|
--background-color: $primary-content;
|
||||||
--icon-color: $background;
|
--icon-color: $background;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_DeviceTypeIcon_deviceIcon {
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_DeviceTypeIcon_verificationIcon {
|
.mx_DeviceTypeIcon_verificationIcon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
105
res/css/views/toasts/_IncomingCallToast.pcss
Normal file
105
res/css/views/toasts/_IncomingCallToast.pcss
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_IncomingCallToast {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
pointer-events: initial; /* restore pointer events so the user can accept/decline */
|
||||||
|
width: 250px;
|
||||||
|
|
||||||
|
.mx_IncomingCallToast_content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 8px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.mx_IncomingCallToast_info {
|
||||||
|
margin-bottom: $spacing-16;
|
||||||
|
|
||||||
|
.mx_IncomingCallToast_room {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
margin-bottom: $spacing-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_IncomingCallToast_message {
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
|
||||||
|
margin-bottom: $spacing-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LiveContentSummary {
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
|
||||||
|
.mx_LiveContentSummary_participants::before {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_IncomingCallToast_joinButton {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
bottom: $spacing-4;
|
||||||
|
right: $spacing-4;
|
||||||
|
|
||||||
|
align-self: flex-end;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 120px;
|
||||||
|
|
||||||
|
padding: $spacing-4 0;
|
||||||
|
|
||||||
|
line-height: $font-24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_IncomingCallToast_closeButton {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
top: $spacing-4;
|
||||||
|
right: $spacing-4;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
|
||||||
|
mask-image: url('$(res)/img/cancel.svg');
|
||||||
|
|
||||||
|
height: inherit;
|
||||||
|
width: inherit;
|
||||||
|
background-color: $secondary-content;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-position: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
res/img/element-icons/settings/desktop.svg
Normal file
3
res/img/element-icons/settings/desktop.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="22" height="19" viewBox="0 0 22 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3 15.5C2.45 15.5 1.97933 15.3043 1.588 14.913C1.196 14.521 1 14.05 1 13.5V2.5C1 1.95 1.196 1.479 1.588 1.087C1.97933 0.695667 2.45 0.5 3 0.5H19C19.55 0.5 20.021 0.695667 20.413 1.087C20.8043 1.479 21 1.95 21 2.5V13.5C21 14.05 20.8043 14.521 20.413 14.913C20.021 15.3043 19.55 15.5 19 15.5H3ZM3 13.5H19V2.5H3V13.5ZM1 18.5C0.716667 18.5 0.479333 18.404 0.288 18.212C0.096 18.0207 0 17.7833 0 17.5C0 17.2167 0.096 16.9793 0.288 16.788C0.479333 16.596 0.716667 16.5 1 16.5H21C21.2833 16.5 21.5207 16.596 21.712 16.788C21.904 16.9793 22 17.2167 22 17.5C22 17.7833 21.904 18.0207 21.712 18.212C21.5207 18.404 21.2833 18.5 21 18.5H1ZM3 13.5V2.5V13.5Z" fill="currentColor"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 780 B |
3
res/img/element-icons/settings/mobile.svg
Normal file
3
res/img/element-icons/settings/mobile.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="14" height="23" viewBox="0 0 14 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 0.51L2 0.5C0.9 0.5 0 1.4 0 2.5V20.5C0 21.6 0.9 22.5 2 22.5H12C13.1 22.5 14 21.6 14 20.5V2.5C14 1.4 13.1 0.51 12 0.51ZM12 18.5H2V4.5H12V18.5Z" fill="currentColor"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 280 B |
3
res/img/element-icons/settings/web.svg
Normal file
3
res/img/element-icons/settings/web.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="20" height="17" viewBox="0 0 20 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18 16.5H2C1.45 16.5 0.979333 16.3043 0.588 15.913C0.196 15.521 0 15.05 0 14.5V2.5C0 1.95 0.196 1.47933 0.588 1.088C0.979333 0.696 1.45 0.5 2 0.5H18C18.55 0.5 19.021 0.696 19.413 1.088C19.8043 1.47933 20 1.95 20 2.5V14.5C20 15.05 19.8043 15.521 19.413 15.913C19.021 16.3043 18.55 16.5 18 16.5ZM2 4.5V14.5H18V4.5H2Z" fill="currentColor"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 450 B |
|
@ -47,6 +47,9 @@ import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
||||||
import LegacyCallHandler from "./LegacyCallHandler";
|
import LegacyCallHandler from "./LegacyCallHandler";
|
||||||
import VoipUserMapper from "./VoipUserMapper";
|
import VoipUserMapper from "./VoipUserMapper";
|
||||||
import { localNotificationsAreSilenced } from "./utils/notifications";
|
import { localNotificationsAreSilenced } from "./utils/notifications";
|
||||||
|
import { getIncomingCallToastKey, IncomingCallToast } from "./toasts/IncomingCallToast";
|
||||||
|
import ToastStore from "./stores/ToastStore";
|
||||||
|
import { ElementCall } from "./models/Call";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dispatches:
|
* Dispatches:
|
||||||
|
@ -358,7 +361,7 @@ export const Notifier = {
|
||||||
|
|
||||||
onEvent: function(ev: MatrixEvent) {
|
onEvent: function(ev: MatrixEvent) {
|
||||||
if (!this.isSyncing) return; // don't alert for any messages initially
|
if (!this.isSyncing) return; // don't alert for any messages initially
|
||||||
if (ev.getSender() === MatrixClientPeg.get().credentials.userId) return;
|
if (ev.getSender() === MatrixClientPeg.get().getUserId()) return;
|
||||||
|
|
||||||
MatrixClientPeg.get().decryptEventIfNeeded(ev);
|
MatrixClientPeg.get().decryptEventIfNeeded(ev);
|
||||||
|
|
||||||
|
@ -419,6 +422,8 @@ export const Notifier = {
|
||||||
|
|
||||||
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
||||||
if (actions?.notify) {
|
if (actions?.notify) {
|
||||||
|
this._performCustomEventHandling(ev);
|
||||||
|
|
||||||
if (RoomViewStore.instance.getRoomId() === room.roomId &&
|
if (RoomViewStore.instance.getRoomId() === room.roomId &&
|
||||||
UserActivity.sharedInstance().userActiveRecently() &&
|
UserActivity.sharedInstance().userActiveRecently() &&
|
||||||
!Modal.hasDialogs()
|
!Modal.hasDialogs()
|
||||||
|
@ -436,6 +441,24 @@ export const Notifier = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some events require special handling such as showing in-app toasts
|
||||||
|
*/
|
||||||
|
_performCustomEventHandling: function(ev: MatrixEvent) {
|
||||||
|
if (
|
||||||
|
ElementCall.CALL_EVENT_TYPE.names.includes(ev.getType())
|
||||||
|
&& SettingsStore.getValue("feature_group_calls")
|
||||||
|
) {
|
||||||
|
ToastStore.sharedInstance().addOrReplaceToast({
|
||||||
|
key: getIncomingCallToastKey(ev.getStateKey()),
|
||||||
|
priority: 100,
|
||||||
|
component: IncomingCallToast,
|
||||||
|
bodyClassName: "mx_IncomingCallToast",
|
||||||
|
props: { callEvent: ev },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!window.mxNotifier) {
|
if (!window.mxNotifier) {
|
||||||
|
|
|
@ -45,6 +45,7 @@ import AccessibleButton from './components/views/elements/AccessibleButton';
|
||||||
import RightPanelStore from './stores/right-panel/RightPanelStore';
|
import RightPanelStore from './stores/right-panel/RightPanelStore';
|
||||||
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
|
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
|
||||||
import { isLocationEvent } from './utils/EventUtils';
|
import { isLocationEvent } from './utils/EventUtils';
|
||||||
|
import { ElementCall } from "./models/Call";
|
||||||
|
|
||||||
export function getSenderName(event: MatrixEvent): string {
|
export function getSenderName(event: MatrixEvent): string {
|
||||||
return event.sender?.name ?? event.getSender() ?? _t("Someone");
|
return event.sender?.name ?? event.getSender() ?? _t("Someone");
|
||||||
|
@ -57,6 +58,15 @@ function getRoomMemberDisplayname(event: MatrixEvent, userId = event.getSender()
|
||||||
return member?.name || member?.rawDisplayName || userId || _t("Someone");
|
return member?.name || member?.rawDisplayName || userId || _t("Someone");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function textForCallEvent(event: MatrixEvent): () => string {
|
||||||
|
const roomName = MatrixClientPeg.get().getRoom(event.getRoomId()!).name;
|
||||||
|
const isSupported = MatrixClientPeg.get().supportsVoip();
|
||||||
|
|
||||||
|
return isSupported
|
||||||
|
? () => _t("Video call started in %(roomName)s.", { roomName })
|
||||||
|
: () => _t("Video call started in %(roomName)s. (not supported by this browser)", { roomName });
|
||||||
|
}
|
||||||
|
|
||||||
// These functions are frequently used just to check whether an event has
|
// These functions are frequently used just to check whether an event has
|
||||||
// any text to display at all. For this reason they return deferred values
|
// any text to display at all. For this reason they return deferred values
|
||||||
// to avoid the expense of looking up translations when they're not needed.
|
// to avoid the expense of looking up translations when they're not needed.
|
||||||
|
@ -798,6 +808,11 @@ for (const evType of ALL_RULE_TYPES) {
|
||||||
stateHandlers[evType] = textForMjolnirEvent;
|
stateHandlers[evType] = textForMjolnirEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add both stable and unstable m.call events
|
||||||
|
for (const evType of ElementCall.CALL_EVENT_TYPE.names) {
|
||||||
|
stateHandlers[evType] = textForCallEvent;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the given event has text to display.
|
* Determines whether the given event has text to display.
|
||||||
* @param ev The event
|
* @param ev The event
|
||||||
|
|
|
@ -18,6 +18,8 @@ import React, { FC } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
|
import { Call } from "../../../models/Call";
|
||||||
|
import { useParticipants } from "../../../hooks/useCall";
|
||||||
|
|
||||||
export enum LiveContentType {
|
export enum LiveContentType {
|
||||||
Video,
|
Video,
|
||||||
|
@ -55,3 +57,18 @@ export const LiveContentSummary: FC<Props> = ({ type, text, active, participantC
|
||||||
</> }
|
</> }
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
interface LiveContentSummaryWithCallProps {
|
||||||
|
call: Call;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LiveContentSummaryWithCall({ call }: LiveContentSummaryWithCallProps) {
|
||||||
|
const participants = useParticipants(call);
|
||||||
|
|
||||||
|
return <LiveContentSummary
|
||||||
|
type={LiveContentType.Video}
|
||||||
|
text={_t("Video")}
|
||||||
|
active={false}
|
||||||
|
participantCount={participants.size}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { Icon as UnknownDeviceIcon } from '../../../../../res/img/element-icons/settings/unknown-device.svg';
|
import { Icon as UnknownDeviceIcon } from '../../../../../res/img/element-icons/settings/unknown-device.svg';
|
||||||
|
import { Icon as DesktopIcon } from '../../../../../res/img/element-icons/settings/desktop.svg';
|
||||||
|
import { Icon as WebIcon } from '../../../../../res/img/element-icons/settings/web.svg';
|
||||||
|
import { Icon as MobileIcon } from '../../../../../res/img/element-icons/settings/mobile.svg';
|
||||||
import { Icon as VerifiedIcon } from '../../../../../res/img/e2e/verified.svg';
|
import { Icon as VerifiedIcon } from '../../../../../res/img/e2e/verified.svg';
|
||||||
import { Icon as UnverifiedIcon } from '../../../../../res/img/e2e/warning.svg';
|
import { Icon as UnverifiedIcon } from '../../../../../res/img/e2e/warning.svg';
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
|
@ -30,21 +33,38 @@ interface Props {
|
||||||
deviceType?: DeviceType;
|
deviceType?: DeviceType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deviceTypeIcon: Record<DeviceType, React.FC<React.SVGProps<SVGSVGElement>>> = {
|
||||||
|
[DeviceType.Desktop]: DesktopIcon,
|
||||||
|
[DeviceType.Mobile]: MobileIcon,
|
||||||
|
[DeviceType.Web]: WebIcon,
|
||||||
|
[DeviceType.Unknown]: UnknownDeviceIcon,
|
||||||
|
};
|
||||||
|
const deviceTypeLabel: Record<DeviceType, string> = {
|
||||||
|
[DeviceType.Desktop]: _t('Desktop session'),
|
||||||
|
[DeviceType.Mobile]: _t('Mobile session'),
|
||||||
|
[DeviceType.Web]: _t('Web session'),
|
||||||
|
[DeviceType.Unknown]: _t('Unknown session type'),
|
||||||
|
};
|
||||||
|
|
||||||
export const DeviceTypeIcon: React.FC<Props> = ({
|
export const DeviceTypeIcon: React.FC<Props> = ({
|
||||||
isVerified,
|
isVerified,
|
||||||
isSelected,
|
isSelected,
|
||||||
deviceType,
|
deviceType,
|
||||||
}) => (
|
}) => {
|
||||||
|
const Icon = deviceTypeIcon[deviceType] || deviceTypeIcon[DeviceType.Unknown];
|
||||||
|
const label = deviceTypeLabel[deviceType] || deviceTypeLabel[DeviceType.Unknown];
|
||||||
|
return (
|
||||||
<div className={classNames('mx_DeviceTypeIcon', {
|
<div className={classNames('mx_DeviceTypeIcon', {
|
||||||
mx_DeviceTypeIcon_selected: isSelected,
|
mx_DeviceTypeIcon_selected: isSelected,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{ /* TODO(kerrya) all devices have an unknown type until PSG-650 */ }
|
<div className='mx_DeviceTypeIcon_deviceIconWrapper'>
|
||||||
<UnknownDeviceIcon
|
<Icon
|
||||||
className='mx_DeviceTypeIcon_deviceIcon'
|
className='mx_DeviceTypeIcon_deviceIcon'
|
||||||
role='img'
|
role='img'
|
||||||
aria-label={_t('Unknown device type')}
|
aria-label={label}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
{
|
{
|
||||||
isVerified
|
isVerified
|
||||||
? <VerifiedIcon
|
? <VerifiedIcon
|
||||||
|
@ -59,4 +79,5 @@ export const DeviceTypeIcon: React.FC<Props> = ({
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>);
|
</div>);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -470,6 +470,8 @@
|
||||||
"Converts the DM to a room": "Converts the DM to a room",
|
"Converts the DM to a room": "Converts the DM to a room",
|
||||||
"Displays action": "Displays action",
|
"Displays action": "Displays action",
|
||||||
"Someone": "Someone",
|
"Someone": "Someone",
|
||||||
|
"Video call started in %(roomName)s.": "Video call started in %(roomName)s.",
|
||||||
|
"Video call started in %(roomName)s. (not supported by this browser)": "Video call started in %(roomName)s. (not supported by this browser)",
|
||||||
"%(senderName)s placed a voice call.": "%(senderName)s placed a voice call.",
|
"%(senderName)s placed a voice call.": "%(senderName)s placed a voice call.",
|
||||||
"%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s placed a voice call. (not supported by this browser)",
|
"%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s placed a voice call. (not supported by this browser)",
|
||||||
"%(senderName)s placed a video call.": "%(senderName)s placed a video call.",
|
"%(senderName)s placed a video call.": "%(senderName)s placed a video call.",
|
||||||
|
@ -795,6 +797,11 @@
|
||||||
"Don't miss a reply": "Don't miss a reply",
|
"Don't miss a reply": "Don't miss a reply",
|
||||||
"Notifications": "Notifications",
|
"Notifications": "Notifications",
|
||||||
"Enable desktop notifications": "Enable desktop notifications",
|
"Enable desktop notifications": "Enable desktop notifications",
|
||||||
|
"Unknown room": "Unknown room",
|
||||||
|
"Video call started": "Video call started",
|
||||||
|
"Video": "Video",
|
||||||
|
"Join": "Join",
|
||||||
|
"Close": "Close",
|
||||||
"Unknown caller": "Unknown caller",
|
"Unknown caller": "Unknown caller",
|
||||||
"Voice call": "Voice call",
|
"Voice call": "Voice call",
|
||||||
"Video call": "Video call",
|
"Video call": "Video call",
|
||||||
|
@ -1051,7 +1058,6 @@
|
||||||
"Video devices": "Video devices",
|
"Video devices": "Video devices",
|
||||||
"Turn off camera": "Turn off camera",
|
"Turn off camera": "Turn off camera",
|
||||||
"Turn on camera": "Turn on camera",
|
"Turn on camera": "Turn on camera",
|
||||||
"Join": "Join",
|
|
||||||
"%(count)s people joined|other": "%(count)s people joined",
|
"%(count)s people joined|other": "%(count)s people joined",
|
||||||
"%(count)s people joined|one": "%(count)s person joined",
|
"%(count)s people joined|one": "%(count)s person joined",
|
||||||
"Dial": "Dial",
|
"Dial": "Dial",
|
||||||
|
@ -1519,7 +1525,6 @@
|
||||||
"Ban list rules - %(roomName)s": "Ban list rules - %(roomName)s",
|
"Ban list rules - %(roomName)s": "Ban list rules - %(roomName)s",
|
||||||
"Server rules": "Server rules",
|
"Server rules": "Server rules",
|
||||||
"User rules": "User rules",
|
"User rules": "User rules",
|
||||||
"Close": "Close",
|
|
||||||
"You have not ignored anyone.": "You have not ignored anyone.",
|
"You have not ignored anyone.": "You have not ignored anyone.",
|
||||||
"You are currently ignoring:": "You are currently ignoring:",
|
"You are currently ignoring:": "You are currently ignoring:",
|
||||||
"You are not subscribed to any lists": "You are not subscribed to any lists",
|
"You are not subscribed to any lists": "You are not subscribed to any lists",
|
||||||
|
@ -1729,7 +1734,10 @@
|
||||||
"Inactive for %(inactiveAgeDays)s+ days": "Inactive for %(inactiveAgeDays)s+ days",
|
"Inactive for %(inactiveAgeDays)s+ days": "Inactive for %(inactiveAgeDays)s+ days",
|
||||||
"Verified": "Verified",
|
"Verified": "Verified",
|
||||||
"Unverified": "Unverified",
|
"Unverified": "Unverified",
|
||||||
"Unknown device type": "Unknown device type",
|
"Desktop session": "Desktop session",
|
||||||
|
"Mobile session": "Mobile session",
|
||||||
|
"Web session": "Web session",
|
||||||
|
"Unknown session type": "Unknown session type",
|
||||||
"Verified session": "Verified session",
|
"Verified session": "Verified session",
|
||||||
"This session is ready for secure messaging.": "This session is ready for secure messaging.",
|
"This session is ready for secure messaging.": "This session is ready for secure messaging.",
|
||||||
"Unverified session": "Unverified session",
|
"Unverified session": "Unverified session",
|
||||||
|
@ -2002,7 +2010,6 @@
|
||||||
"%(count)s unread messages.|other": "%(count)s unread messages.",
|
"%(count)s unread messages.|other": "%(count)s unread messages.",
|
||||||
"%(count)s unread messages.|one": "1 unread message.",
|
"%(count)s unread messages.|one": "1 unread message.",
|
||||||
"Unread messages.": "Unread messages.",
|
"Unread messages.": "Unread messages.",
|
||||||
"Video": "Video",
|
|
||||||
"Joining…": "Joining…",
|
"Joining…": "Joining…",
|
||||||
"Joined": "Joined",
|
"Joined": "Joined",
|
||||||
"Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.",
|
"Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.",
|
||||||
|
|
119
src/toasts/IncomingCallToast.tsx
Normal file
119
src/toasts/IncomingCallToast.tsx
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useCallback, useEffect } from 'react';
|
||||||
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
|
import { _t } from '../languageHandler';
|
||||||
|
import RoomAvatar from '../components/views/avatars/RoomAvatar';
|
||||||
|
import AccessibleButton from '../components/views/elements/AccessibleButton';
|
||||||
|
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||||
|
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||||
|
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
|
||||||
|
import { Action } from "../dispatcher/actions";
|
||||||
|
import ToastStore from "../stores/ToastStore";
|
||||||
|
import AccessibleTooltipButton from "../components/views/elements/AccessibleTooltipButton";
|
||||||
|
import {
|
||||||
|
LiveContentSummary,
|
||||||
|
LiveContentSummaryWithCall,
|
||||||
|
LiveContentType,
|
||||||
|
} from "../components/views/rooms/LiveContentSummary";
|
||||||
|
import { useCall } from "../hooks/useCall";
|
||||||
|
import { useRoomState } from "../hooks/useRoomState";
|
||||||
|
import { ButtonEvent } from "../components/views/elements/AccessibleButton";
|
||||||
|
|
||||||
|
export const getIncomingCallToastKey = (stateKey: string) => `call_${stateKey}`;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
callEvent: MatrixEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IncomingCallToast({ callEvent }: Props) {
|
||||||
|
const roomId = callEvent.getRoomId()!;
|
||||||
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
|
const call = useCall(roomId);
|
||||||
|
|
||||||
|
const dismissToast = useCallback((): void => {
|
||||||
|
ToastStore.sharedInstance().dismissToast(getIncomingCallToastKey(callEvent.getStateKey()!));
|
||||||
|
}, [callEvent]);
|
||||||
|
|
||||||
|
const latestEvent = useRoomState(room, useCallback((state) => {
|
||||||
|
return state.getStateEvents(callEvent.getType(), callEvent.getStateKey()!);
|
||||||
|
}, [callEvent]));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if ("m.terminated" in latestEvent.getContent()) {
|
||||||
|
dismissToast();
|
||||||
|
}
|
||||||
|
}, [latestEvent, dismissToast]);
|
||||||
|
|
||||||
|
const onJoinClick = useCallback((e: ButtonEvent): void => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||||
|
action: Action.ViewRoom,
|
||||||
|
room_id: room.roomId,
|
||||||
|
view_call: true,
|
||||||
|
metricsTrigger: undefined,
|
||||||
|
});
|
||||||
|
dismissToast();
|
||||||
|
}, [room, dismissToast]);
|
||||||
|
|
||||||
|
const onCloseClick = useCallback((e: ButtonEvent): void => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
dismissToast();
|
||||||
|
}, [dismissToast]);
|
||||||
|
|
||||||
|
return <React.Fragment>
|
||||||
|
<RoomAvatar
|
||||||
|
room={room ?? undefined}
|
||||||
|
height={24}
|
||||||
|
width={24}
|
||||||
|
/>
|
||||||
|
<div className="mx_IncomingCallToast_content">
|
||||||
|
<div className="mx_IncomingCallToast_info">
|
||||||
|
<span className="mx_IncomingCallToast_room">
|
||||||
|
{ room ? room.name : _t("Unknown room") }
|
||||||
|
</span>
|
||||||
|
<div className="mx_IncomingCallToast_message">
|
||||||
|
{ _t("Video call started") }
|
||||||
|
</div>
|
||||||
|
{ call
|
||||||
|
? <LiveContentSummaryWithCall call={call} />
|
||||||
|
: <LiveContentSummary
|
||||||
|
type={LiveContentType.Video}
|
||||||
|
text={_t("Video")}
|
||||||
|
active={false}
|
||||||
|
participantCount={0}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_IncomingCallToast_joinButton"
|
||||||
|
onClick={onJoinClick}
|
||||||
|
kind="primary"
|
||||||
|
>
|
||||||
|
{ _t("Join") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
<AccessibleTooltipButton
|
||||||
|
className="mx_IncomingCallToast_closeButton"
|
||||||
|
onClick={onCloseClick}
|
||||||
|
title={_t("Close")}
|
||||||
|
/>
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
|
@ -14,17 +14,39 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { MockedObject } from "jest-mock";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
|
import BasePlatform from "../src/BasePlatform";
|
||||||
|
import { ElementCall } from "../src/models/Call";
|
||||||
import Notifier from "../src/Notifier";
|
import Notifier from "../src/Notifier";
|
||||||
|
import SettingsStore from "../src/settings/SettingsStore";
|
||||||
|
import ToastStore from "../src/stores/ToastStore";
|
||||||
import { getLocalNotificationAccountDataEventType } from "../src/utils/notifications";
|
import { getLocalNotificationAccountDataEventType } from "../src/utils/notifications";
|
||||||
import { getMockClientWithEventEmitter, mkEvent, mkRoom, mockPlatformPeg } from "./test-utils";
|
import { getMockClientWithEventEmitter, mkEvent, mkRoom, mockPlatformPeg } from "./test-utils";
|
||||||
|
import { IncomingCallToast } from "../src/toasts/IncomingCallToast";
|
||||||
|
|
||||||
describe("Notifier", () => {
|
describe("Notifier", () => {
|
||||||
let MockPlatform;
|
const roomId = "!room1:server";
|
||||||
|
const testEvent = mkEvent({
|
||||||
|
event: true,
|
||||||
|
type: "m.room.message",
|
||||||
|
user: "@user1:server",
|
||||||
|
room: roomId,
|
||||||
|
content: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
let MockPlatform: MockedObject<BasePlatform>;
|
||||||
|
let mockClient: MockedObject<MatrixClient>;
|
||||||
|
let testRoom: MockedObject<Room>;
|
||||||
|
let accountDataEventKey: string;
|
||||||
let accountDataStore = {};
|
let accountDataStore = {};
|
||||||
|
|
||||||
const mockClient = getMockClientWithEventEmitter({
|
beforeEach(() => {
|
||||||
|
accountDataStore = {};
|
||||||
|
mockClient = getMockClientWithEventEmitter({
|
||||||
getUserId: jest.fn().mockReturnValue("@bob:example.org"),
|
getUserId: jest.fn().mockReturnValue("@bob:example.org"),
|
||||||
isGuest: jest.fn().mockReturnValue(false),
|
isGuest: jest.fn().mockReturnValue(false),
|
||||||
getAccountData: jest.fn().mockImplementation(eventType => accountDataStore[eventType]),
|
getAccountData: jest.fn().mockImplementation(eventType => accountDataStore[eventType]),
|
||||||
|
@ -34,20 +56,14 @@ describe("Notifier", () => {
|
||||||
content,
|
content,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
decryptEventIfNeeded: jest.fn(),
|
||||||
|
getRoom: jest.fn(),
|
||||||
|
getPushActionsForEvent: jest.fn(),
|
||||||
});
|
});
|
||||||
const accountDataEventKey = getLocalNotificationAccountDataEventType(mockClient.deviceId);
|
accountDataEventKey = getLocalNotificationAccountDataEventType(mockClient.deviceId);
|
||||||
const roomId = "!room1:server";
|
|
||||||
const testEvent = mkEvent({
|
testRoom = mkRoom(mockClient, roomId);
|
||||||
event: true,
|
|
||||||
type: "m.room.message",
|
|
||||||
user: "@user1:server",
|
|
||||||
room: roomId,
|
|
||||||
content: {},
|
|
||||||
});
|
|
||||||
const testRoom = mkRoom(mockClient, roomId);
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
accountDataStore = {};
|
|
||||||
MockPlatform = mockPlatformPeg({
|
MockPlatform = mockPlatformPeg({
|
||||||
supportsNotifications: jest.fn().mockReturnValue(true),
|
supportsNotifications: jest.fn().mockReturnValue(true),
|
||||||
maySendNotifications: jest.fn().mockReturnValue(true),
|
maySendNotifications: jest.fn().mockReturnValue(true),
|
||||||
|
@ -55,6 +71,8 @@ describe("Notifier", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Notifier.isBodyEnabled = jest.fn().mockReturnValue(true);
|
Notifier.isBodyEnabled = jest.fn().mockReturnValue(true);
|
||||||
|
|
||||||
|
mockClient.getRoom.mockReturnValue(testRoom);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("_displayPopupNotification", () => {
|
describe("_displayPopupNotification", () => {
|
||||||
|
@ -82,4 +100,73 @@ describe("Notifier", () => {
|
||||||
expect(Notifier.getSoundForRoom).toHaveBeenCalledTimes(count);
|
expect(Notifier.getSoundForRoom).toHaveBeenCalledTimes(count);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("group call notifications", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
|
||||||
|
jest.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast");
|
||||||
|
|
||||||
|
mockClient.getPushActionsForEvent.mockReturnValue({
|
||||||
|
notify: true,
|
||||||
|
tweaks: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
Notifier.onSyncStateChange("SYNCING");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const callOnEvent = (type?: string) => {
|
||||||
|
const callEvent = {
|
||||||
|
getContent: () => { },
|
||||||
|
getRoomId: () => roomId,
|
||||||
|
isBeingDecrypted: () => false,
|
||||||
|
isDecryptionFailure: () => false,
|
||||||
|
getSender: () => "@alice:foo",
|
||||||
|
getType: () => type ?? ElementCall.CALL_EVENT_TYPE.name,
|
||||||
|
getStateKey: () => "state_key",
|
||||||
|
} as unknown as MatrixEvent;
|
||||||
|
|
||||||
|
Notifier.onEvent(callEvent);
|
||||||
|
return callEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setGroupCallsEnabled = (val: boolean) => {
|
||||||
|
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||||
|
if (name === "feature_group_calls") return val;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
it("should show toast when group calls are supported", () => {
|
||||||
|
setGroupCallsEnabled(true);
|
||||||
|
|
||||||
|
const callEvent = callOnEvent();
|
||||||
|
|
||||||
|
expect(ToastStore.sharedInstance().addOrReplaceToast).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
key: `call_${callEvent.getStateKey()}`,
|
||||||
|
priority: 100,
|
||||||
|
component: IncomingCallToast,
|
||||||
|
bodyClassName: "mx_IncomingCallToast",
|
||||||
|
props: { callEvent },
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show toast when group calls are not supported", () => {
|
||||||
|
setGroupCallsEnabled(false);
|
||||||
|
|
||||||
|
callOnEvent();
|
||||||
|
|
||||||
|
expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show toast when calling with non-group call event", () => {
|
||||||
|
setGroupCallsEnabled(true);
|
||||||
|
|
||||||
|
callOnEvent("event_type");
|
||||||
|
|
||||||
|
expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,15 +14,17 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventType, MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix";
|
import { EventType, MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||||
import TestRenderer from 'react-test-renderer';
|
import TestRenderer from 'react-test-renderer';
|
||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
|
import { mocked } from "jest-mock";
|
||||||
|
|
||||||
import { getSenderName, textForEvent } from "../src/TextForEvent";
|
import { getSenderName, textForEvent } from "../src/TextForEvent";
|
||||||
import SettingsStore from "../src/settings/SettingsStore";
|
import SettingsStore from "../src/settings/SettingsStore";
|
||||||
import { createTestClient } from './test-utils';
|
import { createTestClient, stubClient } from './test-utils';
|
||||||
import { MatrixClientPeg } from '../src/MatrixClientPeg';
|
import { MatrixClientPeg } from '../src/MatrixClientPeg';
|
||||||
import UserIdentifierCustomisations from '../src/customisations/UserIdentifier';
|
import UserIdentifierCustomisations from '../src/customisations/UserIdentifier';
|
||||||
|
import { ElementCall } from "../src/models/Call";
|
||||||
|
|
||||||
jest.mock("../src/settings/SettingsStore");
|
jest.mock("../src/settings/SettingsStore");
|
||||||
jest.mock('../src/customisations/UserIdentifier', () => ({
|
jest.mock('../src/customisations/UserIdentifier', () => ({
|
||||||
|
@ -444,4 +446,42 @@ describe('TextForEvent', () => {
|
||||||
expect(textForEvent(messageEvent)).toEqual('@a: test message');
|
expect(textForEvent(messageEvent)).toEqual('@a: test message');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("textForCallEvent()", () => {
|
||||||
|
let mockClient: MatrixClient;
|
||||||
|
let callEvent: MatrixEvent;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
stubClient();
|
||||||
|
mockClient = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
mocked(mockClient.getRoom).mockReturnValue({
|
||||||
|
name: "Test room",
|
||||||
|
} as unknown as Room);
|
||||||
|
|
||||||
|
callEvent = {
|
||||||
|
getRoomId: jest.fn(),
|
||||||
|
getType: jest.fn(),
|
||||||
|
isState: jest.fn().mockReturnValue(true),
|
||||||
|
} as unknown as MatrixEvent;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each(ElementCall.CALL_EVENT_TYPE.names)("eventType=%s", (eventType: string) => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocked(callEvent).getType.mockReturnValue(eventType);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns correct message for call event when supported", () => {
|
||||||
|
expect(textForEvent(callEvent)).toEqual('Video call started in Test room.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns correct message for call event when supported", () => {
|
||||||
|
mocked(mockClient).supportsVoip.mockReturnValue(false);
|
||||||
|
|
||||||
|
expect(textForEvent(callEvent)).toEqual(
|
||||||
|
'Video call started in Test room. (not supported by this browser)',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,7 +27,7 @@ import {
|
||||||
EventType,
|
EventType,
|
||||||
} from 'matrix-js-sdk/src/matrix';
|
} from 'matrix-js-sdk/src/matrix';
|
||||||
import { ExtensibleEvent, MessageEvent, M_POLL_KIND_DISCLOSED, PollStartEvent } from 'matrix-events-sdk';
|
import { ExtensibleEvent, MessageEvent, M_POLL_KIND_DISCLOSED, PollStartEvent } from 'matrix-events-sdk';
|
||||||
import { Thread } from "matrix-js-sdk/src/models/thread";
|
import { FeatureSupport, Thread } from "matrix-js-sdk/src/models/thread";
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
import { act } from '@testing-library/react';
|
import { act } from '@testing-library/react';
|
||||||
|
|
||||||
|
@ -469,7 +469,7 @@ describe('MessageContextMenu', () => {
|
||||||
const eventContent = MessageEvent.from("hello");
|
const eventContent = MessageEvent.from("hello");
|
||||||
const mxEvent = new MatrixEvent(eventContent.serialize());
|
const mxEvent = new MatrixEvent(eventContent.serialize());
|
||||||
|
|
||||||
Thread.hasServerSideSupport = true;
|
Thread.hasServerSideSupport = FeatureSupport.Stable;
|
||||||
const context = {
|
const context = {
|
||||||
canSendMessages: true,
|
canSendMessages: true,
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,7 +25,7 @@ import {
|
||||||
MsgType,
|
MsgType,
|
||||||
Room,
|
Room,
|
||||||
} from 'matrix-js-sdk/src/matrix';
|
} from 'matrix-js-sdk/src/matrix';
|
||||||
import { Thread } from 'matrix-js-sdk/src/models/thread';
|
import { FeatureSupport, Thread } from 'matrix-js-sdk/src/models/thread';
|
||||||
|
|
||||||
import MessageActionBar from '../../../../src/components/views/messages/MessageActionBar';
|
import MessageActionBar from '../../../../src/components/views/messages/MessageActionBar';
|
||||||
import {
|
import {
|
||||||
|
@ -388,13 +388,13 @@ describe('<MessageActionBar />', () => {
|
||||||
|
|
||||||
describe('thread button', () => {
|
describe('thread button', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
Thread.setServerSideSupport(true, false);
|
Thread.setServerSideSupport(FeatureSupport.Stable);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when threads feature is not enabled', () => {
|
describe('when threads feature is not enabled', () => {
|
||||||
it('does not render thread button when threads does not have server support', () => {
|
it('does not render thread button when threads does not have server support', () => {
|
||||||
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
|
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
|
||||||
Thread.setServerSideSupport(false, false);
|
Thread.setServerSideSupport(FeatureSupport.None);
|
||||||
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
|
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
|
||||||
expect(queryByLabelText('Reply in thread')).toBeFalsy();
|
expect(queryByLabelText('Reply in thread')).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
|
@ -115,10 +115,14 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
|
@ -238,10 +242,14 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
|
@ -320,10 +328,14 @@ exports[`<DevicesPanel /> renders device panel with devices 1`] = `
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { render } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { DeviceTypeIcon } from '../../../../../src/components/views/settings/devices/DeviceTypeIcon';
|
import { DeviceTypeIcon } from '../../../../../src/components/views/settings/devices/DeviceTypeIcon';
|
||||||
|
import { DeviceType } from '../../../../../src/utils/device/parseUserAgent';
|
||||||
|
|
||||||
describe('<DeviceTypeIcon />', () => {
|
describe('<DeviceTypeIcon />', () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
|
@ -41,4 +42,33 @@ describe('<DeviceTypeIcon />', () => {
|
||||||
const { container } = render(getComponent({ isSelected: true }));
|
const { container } = render(getComponent({ isSelected: true }));
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders an unknown device icon when no device type given', () => {
|
||||||
|
const { getByLabelText } = render(getComponent());
|
||||||
|
expect(getByLabelText('Unknown session type')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a desktop device type', () => {
|
||||||
|
const deviceType = DeviceType.Desktop;
|
||||||
|
const { getByLabelText } = render(getComponent({ deviceType }));
|
||||||
|
expect(getByLabelText('Desktop session')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a web device type', () => {
|
||||||
|
const deviceType = DeviceType.Web;
|
||||||
|
const { getByLabelText } = render(getComponent({ deviceType }));
|
||||||
|
expect(getByLabelText('Web session')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a mobile device type', () => {
|
||||||
|
const deviceType = DeviceType.Mobile;
|
||||||
|
const { getByLabelText } = render(getComponent({ deviceType }));
|
||||||
|
expect(getByLabelText('Mobile session')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders an unknown device type', () => {
|
||||||
|
const deviceType = DeviceType.Unknown;
|
||||||
|
const { getByLabelText } = render(getComponent({ deviceType }));
|
||||||
|
expect(getByLabelText('Unknown session type')).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -154,10 +154,14 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
|
@ -270,10 +274,14 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
|
|
|
@ -10,10 +10,14 @@ exports[`<DeviceTile /> renders a device with no metadata 1`] = `
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
|
@ -61,10 +65,14 @@ exports[`<DeviceTile /> renders a verified device with no metadata 1`] = `
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
|
@ -112,10 +120,14 @@ exports[`<DeviceTile /> renders display name with a tooltip 1`] = `
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
|
@ -163,10 +175,14 @@ exports[`<DeviceTile /> separates metadata with a dot 1`] = `
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
|
|
|
@ -6,10 +6,14 @@ exports[`<DeviceTypeIcon /> renders a verified device 1`] = `
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Verified"
|
aria-label="Verified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon verified"
|
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||||
|
@ -25,10 +29,14 @@ exports[`<DeviceTypeIcon /> renders an unverified device 1`] = `
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
|
@ -44,10 +52,14 @@ exports[`<DeviceTypeIcon /> renders correctly when selected 1`] = `
|
||||||
class="mx_DeviceTypeIcon mx_DeviceTypeIcon_selected"
|
class="mx_DeviceTypeIcon mx_DeviceTypeIcon_selected"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
|
|
|
@ -42,10 +42,14 @@ exports[`<SelectableDeviceTile /> renders unselected device tile with checkbox 1
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
|
|
|
@ -97,10 +97,14 @@ exports[`<SessionManagerTab /> renders current session section with a verified s
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Verified"
|
aria-label="Verified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon verified"
|
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||||
|
@ -199,10 +203,14 @@ exports[`<SessionManagerTab /> renders current session section with an unverifie
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Unverified"
|
aria-label="Unverified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
class="mx_DeviceTypeIcon_verificationIcon unverified"
|
||||||
|
@ -301,10 +309,14 @@ exports[`<SessionManagerTab /> sets device verification status correctly 1`] = `
|
||||||
class="mx_DeviceTypeIcon"
|
class="mx_DeviceTypeIcon"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Unknown device type"
|
class="mx_DeviceTypeIcon_deviceIconWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Unknown session type"
|
||||||
class="mx_DeviceTypeIcon_deviceIcon"
|
class="mx_DeviceTypeIcon_deviceIcon"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-label="Verified"
|
aria-label="Verified"
|
||||||
class="mx_DeviceTypeIcon_verificationIcon verified"
|
class="mx_DeviceTypeIcon_verificationIcon verified"
|
||||||
|
|
158
test/toasts/IncomingCallToast-test.tsx
Normal file
158
test/toasts/IncomingCallToast-test.tsx
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { render, screen, cleanup, fireEvent, waitFor } from "@testing-library/react";
|
||||||
|
import "@testing-library/jest-dom";
|
||||||
|
import { mocked, Mocked } from "jest-mock";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||||
|
import { ClientWidgetApi, Widget } from "matrix-widget-api";
|
||||||
|
|
||||||
|
import type { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
|
import {
|
||||||
|
useMockedCalls,
|
||||||
|
MockedCall,
|
||||||
|
stubClient,
|
||||||
|
mkRoomMember,
|
||||||
|
setupAsyncStoreWithClient,
|
||||||
|
resetAsyncStoreWithClient,
|
||||||
|
} from "../test-utils";
|
||||||
|
import defaultDispatcher from "../../src/dispatcher/dispatcher";
|
||||||
|
import { Action } from "../../src/dispatcher/actions";
|
||||||
|
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
|
||||||
|
import { CallStore } from "../../src/stores/CallStore";
|
||||||
|
import { WidgetMessagingStore } from "../../src/stores/widgets/WidgetMessagingStore";
|
||||||
|
import DMRoomMap from "../../src/utils/DMRoomMap";
|
||||||
|
import ToastStore from "../../src/stores/ToastStore";
|
||||||
|
import { getIncomingCallToastKey, IncomingCallToast } from "../../src/toasts/IncomingCallToast";
|
||||||
|
|
||||||
|
describe("IncomingCallEvent", () => {
|
||||||
|
useMockedCalls();
|
||||||
|
Object.defineProperty(navigator, "mediaDevices", { value: { enumerateDevices: () => [] } });
|
||||||
|
jest.spyOn(HTMLMediaElement.prototype, "play").mockImplementation(async () => { });
|
||||||
|
|
||||||
|
let client: Mocked<MatrixClient>;
|
||||||
|
let room: Room;
|
||||||
|
let alice: RoomMember;
|
||||||
|
let bob: RoomMember;
|
||||||
|
let call: MockedCall;
|
||||||
|
let widget: Widget;
|
||||||
|
const dmRoomMap = {
|
||||||
|
getUserIdForRoomId: jest.fn(),
|
||||||
|
} as unknown as DMRoomMap;
|
||||||
|
const toastStore = {
|
||||||
|
dismissToast: jest.fn(),
|
||||||
|
} as unknown as ToastStore;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
stubClient();
|
||||||
|
client = mocked(MatrixClientPeg.get());
|
||||||
|
|
||||||
|
room = new Room("!1:example.org", client, "@alice:example.org");
|
||||||
|
|
||||||
|
alice = mkRoomMember(room.roomId, "@alice:example.org");
|
||||||
|
bob = mkRoomMember(room.roomId, "@bob:example.org");
|
||||||
|
|
||||||
|
client.getRoom.mockImplementation(roomId => roomId === room.roomId ? room : null);
|
||||||
|
client.getRooms.mockReturnValue([room]);
|
||||||
|
client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
|
||||||
|
|
||||||
|
await Promise.all([CallStore.instance, WidgetMessagingStore.instance].map(
|
||||||
|
store => setupAsyncStoreWithClient(store, client),
|
||||||
|
));
|
||||||
|
|
||||||
|
MockedCall.create(room, "1");
|
||||||
|
const maybeCall = CallStore.instance.get(room.roomId);
|
||||||
|
if (!(maybeCall instanceof MockedCall)) throw new Error("Failed to create call");
|
||||||
|
call = maybeCall;
|
||||||
|
|
||||||
|
widget = new Widget(call.widget);
|
||||||
|
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
|
||||||
|
stop: () => { },
|
||||||
|
} as unknown as ClientWidgetApi);
|
||||||
|
|
||||||
|
jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
|
||||||
|
jest.spyOn(ToastStore, "sharedInstance").mockReturnValue(toastStore);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
cleanup(); // Unmount before we do any cleanup that might update the component
|
||||||
|
call.destroy();
|
||||||
|
WidgetMessagingStore.instance.stopMessaging(widget, room.roomId);
|
||||||
|
await Promise.all([CallStore.instance, WidgetMessagingStore.instance].map(resetAsyncStoreWithClient));
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderToast = () => { render(<IncomingCallToast callEvent={call.event} />); };
|
||||||
|
|
||||||
|
it("correctly shows all the information", () => {
|
||||||
|
call.participants = new Set([alice, bob]);
|
||||||
|
renderToast();
|
||||||
|
|
||||||
|
screen.getByText("Video call started");
|
||||||
|
screen.getByText("Video");
|
||||||
|
screen.getByLabelText("2 participants");
|
||||||
|
|
||||||
|
screen.getByRole("button", { name: "Join" });
|
||||||
|
screen.getByRole("button", { name: "Close" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("correctly renders toast without a call", () => {
|
||||||
|
call.destroy();
|
||||||
|
renderToast();
|
||||||
|
|
||||||
|
screen.getByText("Video call started");
|
||||||
|
screen.getByText("Video");
|
||||||
|
|
||||||
|
screen.getByRole("button", { name: "Join" });
|
||||||
|
screen.getByRole("button", { name: "Close" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("joins the call and closes the toast", async () => {
|
||||||
|
renderToast();
|
||||||
|
|
||||||
|
const dispatcherSpy = jest.fn();
|
||||||
|
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Join" }));
|
||||||
|
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith({
|
||||||
|
action: Action.ViewRoom,
|
||||||
|
room_id: room.roomId,
|
||||||
|
view_call: true,
|
||||||
|
}));
|
||||||
|
await waitFor(() => expect(toastStore.dismissToast).toHaveBeenCalledWith(
|
||||||
|
getIncomingCallToastKey(call.event.getStateKey()!),
|
||||||
|
));
|
||||||
|
|
||||||
|
defaultDispatcher.unregister(dispatcherRef);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("closes the toast", async () => {
|
||||||
|
renderToast();
|
||||||
|
|
||||||
|
const dispatcherSpy = jest.fn();
|
||||||
|
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Close" }));
|
||||||
|
await waitFor(() => expect(toastStore.dismissToast).toHaveBeenCalledWith(
|
||||||
|
getIncomingCallToastKey(call.event.getStateKey()!),
|
||||||
|
));
|
||||||
|
|
||||||
|
defaultDispatcher.unregister(dispatcherRef);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue