Merge branch 'develop' into michaelk/continue_if_trci_upload_fails
This commit is contained in:
commit
e652519cc6
20 changed files with 300 additions and 115 deletions
6
.github/workflows/cypress.yaml
vendored
6
.github/workflows/cypress.yaml
vendored
|
@ -92,6 +92,12 @@ jobs:
|
||||||
# # Run 4 instances in Parallel
|
# # Run 4 instances in Parallel
|
||||||
# runner: [1, 2, 3, 4]
|
# runner: [1, 2, 3, 4]
|
||||||
steps:
|
steps:
|
||||||
|
- uses: tecolicom/actions-use-apt-tools@v1
|
||||||
|
with:
|
||||||
|
# Our test suite includes some screenshot tests with unusual diacritics, which are
|
||||||
|
# supposed to be covered by STIXGeneral.
|
||||||
|
tools: fonts-stix
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
# XXX: We're checking out untrusted code in a secure context
|
# XXX: We're checking out untrusted code in a secure context
|
||||||
|
|
|
@ -115,7 +115,10 @@ describe("Polls", () => {
|
||||||
|
|
||||||
const pollParams = {
|
const pollParams = {
|
||||||
title: "Does the polls feature work?",
|
title: "Does the polls feature work?",
|
||||||
options: ["Yes", "No", "Maybe"],
|
// Since we're going to take a screenshot anyways, we include some
|
||||||
|
// non-ASCII characters here to stress test the app's font config
|
||||||
|
// while we're at it.
|
||||||
|
options: ["Yes", "Noo⃐o⃑o⃩o⃪o⃫o⃬o⃭o⃮o⃯", "のらねこ Maybe?"],
|
||||||
};
|
};
|
||||||
createPoll(pollParams);
|
createPoll(pollParams);
|
||||||
|
|
||||||
|
|
|
@ -4,17 +4,20 @@
|
||||||
Arial empirically gets it right, hence prioritising Arial here.
|
Arial empirically gets it right, hence prioritising Arial here.
|
||||||
We also include STIXGeneral explicitly to support a wider range
|
We also include STIXGeneral explicitly to support a wider range
|
||||||
of combining diacritics (Chrome fails without it, as per
|
of combining diacritics (Chrome fails without it, as per
|
||||||
https://bugs.chromium.org/p/chromium/issues/detail?id=1328898) */
|
https://bugs.chromium.org/p/chromium/issues/detail?id=1328898).
|
||||||
|
We should never actively *prefer* STIXGeneral over the default font though,
|
||||||
|
since it looks pretty rough and implements some non-LGC scripts only
|
||||||
|
partially, making, for example, Japanese text look patchy and sad. */
|
||||||
/* We fall through to Twemoji for emoji rather than falling through
|
/* We fall through to Twemoji for emoji rather than falling through
|
||||||
to native Emoji fonts (if any) to ensure cross-browser consistency */
|
to native Emoji fonts (if any) to ensure cross-browser consistency */
|
||||||
/* Noto Color Emoji contains digits, in fixed-width, therefore causing
|
/* Noto Color Emoji contains digits, in fixed-width, therefore causing
|
||||||
digits in flowed text to stand out.
|
digits in flowed text to stand out.
|
||||||
TODO: Consider putting all emoji fonts to the end rather than the front. */
|
TODO: Consider putting all emoji fonts to the end rather than the front. */
|
||||||
$font-family: "Nunito", "Twemoji", "Apple Color Emoji", "Segoe UI Emoji", "STIXGeneral", "Arial", "Helvetica",
|
$font-family: "Nunito", "Twemoji", "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", sans-serif,
|
||||||
sans-serif, "Noto Color Emoji";
|
"STIXGeneral", "Noto Color Emoji";
|
||||||
|
|
||||||
$monospace-font-family: "Inconsolata", "Twemoji", "Apple Color Emoji", "Segoe UI Emoji", "STIXGeneral", "Courier",
|
$monospace-font-family: "Inconsolata", "Twemoji", "Apple Color Emoji", "Segoe UI Emoji", "Courier", monospace,
|
||||||
monospace, "Noto Color Emoji";
|
"STIXGeneral", "Noto Color Emoji";
|
||||||
|
|
||||||
/* unified palette */
|
/* unified palette */
|
||||||
/* try to use these colors when possible */
|
/* try to use these colors when possible */
|
||||||
|
|
|
@ -4,17 +4,20 @@
|
||||||
Arial empirically gets it right, hence prioritising Arial here.
|
Arial empirically gets it right, hence prioritising Arial here.
|
||||||
We also include STIXGeneral explicitly to support a wider range
|
We also include STIXGeneral explicitly to support a wider range
|
||||||
of combining diacritics (Chrome fails without it, as per
|
of combining diacritics (Chrome fails without it, as per
|
||||||
https://bugs.chromium.org/p/chromium/issues/detail?id=1328898) */
|
https://bugs.chromium.org/p/chromium/issues/detail?id=1328898).
|
||||||
|
We should never actively *prefer* STIXGeneral over the default font though,
|
||||||
|
since it looks pretty rough and implements some non-LGC scripts only
|
||||||
|
partially, making, for example, Japanese text look patchy and sad. */
|
||||||
/* We fall through to Twemoji for emoji rather than falling through
|
/* We fall through to Twemoji for emoji rather than falling through
|
||||||
to native Emoji fonts (if any) to ensure cross-browser consistency */
|
to native Emoji fonts (if any) to ensure cross-browser consistency */
|
||||||
/* Noto Color Emoji contains digits, in fixed-width, therefore causing
|
/* Noto Color Emoji contains digits, in fixed-width, therefore causing
|
||||||
digits in flowed text to stand out.
|
digits in flowed text to stand out.
|
||||||
TODO: Consider putting all emoji fonts to the end rather than the front. */
|
TODO: Consider putting all emoji fonts to the end rather than the front. */
|
||||||
$font-family: "Inter", "Twemoji", "Apple Color Emoji", "Segoe UI Emoji", "STIXGeneral", "Arial", "Helvetica", sans-serif,
|
$font-family: "Inter", "Twemoji", "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", sans-serif, "STIXGeneral",
|
||||||
"Noto Color Emoji";
|
"Noto Color Emoji";
|
||||||
|
|
||||||
$monospace-font-family: "Inconsolata", "Twemoji", "Apple Color Emoji", "Segoe UI Emoji", "STIXGeneral", "Courier",
|
$monospace-font-family: "Inconsolata", "Twemoji", "Apple Color Emoji", "Segoe UI Emoji", "Courier", monospace,
|
||||||
monospace, "Noto Color Emoji";
|
"STIXGeneral", "Noto Color Emoji";
|
||||||
|
|
||||||
/* Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A120 */
|
/* Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A120 */
|
||||||
/* ******************** */
|
/* ******************** */
|
||||||
|
|
|
@ -50,6 +50,7 @@ import { localNotificationsAreSilenced, createLocalNotificationSettingsIfNeeded
|
||||||
import { getIncomingCallToastKey, IncomingCallToast } from "./toasts/IncomingCallToast";
|
import { getIncomingCallToastKey, IncomingCallToast } from "./toasts/IncomingCallToast";
|
||||||
import ToastStore from "./stores/ToastStore";
|
import ToastStore from "./stores/ToastStore";
|
||||||
import { ElementCall } from "./models/Call";
|
import { ElementCall } from "./models/Call";
|
||||||
|
import { VoiceBroadcastChunkEventType } from "./voice-broadcast";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dispatches:
|
* Dispatches:
|
||||||
|
@ -77,6 +78,13 @@ const msgTypeHandlers = {
|
||||||
[M_LOCATION.altName]: (event: MatrixEvent) => {
|
[M_LOCATION.altName]: (event: MatrixEvent) => {
|
||||||
return TextForEvent.textForLocationEvent(event)();
|
return TextForEvent.textForLocationEvent(event)();
|
||||||
},
|
},
|
||||||
|
[MsgType.Audio]: (event: MatrixEvent): string | null => {
|
||||||
|
if (event.getContent()?.[VoiceBroadcastChunkEventType]) {
|
||||||
|
// mute broadcast chunks
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return TextForEvent.textForEvent(event);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Notifier = {
|
export const Notifier = {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import * as Avatar from "../../../Avatar";
|
||||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||||
import { mediaFromMxc } from "../../../customisations/Media";
|
import { mediaFromMxc } from "../../../customisations/Media";
|
||||||
import { IOOBData } from "../../../stores/ThreepidInviteStore";
|
import { IOOBData } from "../../../stores/ThreepidInviteStore";
|
||||||
|
import { LocalRoom } from "../../../models/LocalRoom";
|
||||||
|
|
||||||
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
|
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
|
||||||
// Room may be left unset here, but if it is,
|
// Room may be left unset here, but if it is,
|
||||||
|
@ -117,13 +118,26 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
|
||||||
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true);
|
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
public render() {
|
private get roomIdName(): string | undefined {
|
||||||
const { room, oobData, viewAvatarOnClick, onClick, className, ...otherProps } = this.props;
|
const room = this.props.room;
|
||||||
|
|
||||||
const roomName = room?.name ?? oobData.name;
|
if (room) {
|
||||||
|
const dmMapUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
|
||||||
// If the room is a DM, we use the other user's ID for the color hash
|
// If the room is a DM, we use the other user's ID for the color hash
|
||||||
// in order to match the room avatar with their avatar
|
// in order to match the room avatar with their avatar
|
||||||
const idName = room ? DMRoomMap.shared().getUserIdForRoomId(room.roomId) ?? room.roomId : oobData.roomId;
|
if (dmMapUserId) return dmMapUserId;
|
||||||
|
|
||||||
|
if (room instanceof LocalRoom && room.targets.length === 1) {
|
||||||
|
return room.targets[0].userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.room?.roomId || this.props.oobData?.roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { room, oobData, viewAvatarOnClick, onClick, className, ...otherProps } = this.props;
|
||||||
|
const roomName = room?.name ?? oobData.name;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
|
@ -132,7 +146,7 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
|
||||||
mx_RoomAvatar_isSpaceRoom: (room?.getType() ?? this.props.oobData?.roomType) === RoomType.Space,
|
mx_RoomAvatar_isSpaceRoom: (room?.getType() ?? this.props.oobData?.roomType) === RoomType.Space,
|
||||||
})}
|
})}
|
||||||
name={roomName}
|
name={roomName}
|
||||||
idName={idName}
|
idName={this.roomIdName}
|
||||||
urls={this.state.urls}
|
urls={this.state.urls}
|
||||||
onClick={viewAvatarOnClick && this.state.urls[0] ? this.onRoomAvatarClick : onClick}
|
onClick={viewAvatarOnClick && this.state.urls[0] ? this.onRoomAvatarClick : onClick}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -642,7 +642,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||||
className="mx_UserNotifSettings_clearNotifsButton"
|
className="mx_UserNotifSettings_clearNotifsButton"
|
||||||
data-testid="clear-notifications"
|
data-testid="clear-notifications"
|
||||||
>
|
>
|
||||||
{_t("Clear notifications")}
|
{_t("Mark all as read")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
/*
|
|
||||||
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 { EventType, MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
|
||||||
import { Relations } from "matrix-js-sdk/src/models/relations";
|
|
||||||
|
|
||||||
import { VoiceBroadcastInfoEventType } from "../voice-broadcast";
|
|
||||||
|
|
||||||
export const getReferenceRelationsForEvent = (
|
|
||||||
event: MatrixEvent,
|
|
||||||
messageType: EventType | typeof VoiceBroadcastInfoEventType,
|
|
||||||
client: MatrixClient,
|
|
||||||
): Relations | undefined => {
|
|
||||||
const room = client.getRoom(event.getRoomId());
|
|
||||||
return room
|
|
||||||
?.getUnfilteredTimelineSet()
|
|
||||||
?.relations?.getChildEventsForEvent(event.getId(), RelationType.Reference, messageType);
|
|
||||||
};
|
|
|
@ -16,4 +16,3 @@ limitations under the License.
|
||||||
|
|
||||||
export { getForwardableEvent } from "./forward/getForwardableEvent";
|
export { getForwardableEvent } from "./forward/getForwardableEvent";
|
||||||
export { getShareableLocationEvent } from "./location/getShareableLocationEvent";
|
export { getShareableLocationEvent } from "./location/getShareableLocationEvent";
|
||||||
export * from "./getReferenceRelationsForEvent";
|
|
||||||
|
|
|
@ -1419,7 +1419,7 @@
|
||||||
"Enable desktop notifications for this session": "Enable desktop notifications for this session",
|
"Enable desktop notifications for this session": "Enable desktop notifications for this session",
|
||||||
"Show message in desktop notification": "Show message in desktop notification",
|
"Show message in desktop notification": "Show message in desktop notification",
|
||||||
"Enable audible notifications for this session": "Enable audible notifications for this session",
|
"Enable audible notifications for this session": "Enable audible notifications for this session",
|
||||||
"Clear notifications": "Clear notifications",
|
"Mark all as read": "Mark all as read",
|
||||||
"Keyword": "Keyword",
|
"Keyword": "Keyword",
|
||||||
"New keyword": "New keyword",
|
"New keyword": "New keyword",
|
||||||
"On": "On",
|
"On": "On",
|
||||||
|
@ -2126,7 +2126,6 @@
|
||||||
"%(count)s reply|one": "%(count)s reply",
|
"%(count)s reply|one": "%(count)s reply",
|
||||||
"Open thread": "Open thread",
|
"Open thread": "Open thread",
|
||||||
"Jump to first unread message.": "Jump to first unread message.",
|
"Jump to first unread message.": "Jump to first unread message.",
|
||||||
"Mark all as read": "Mark all as read",
|
|
||||||
"Unable to access your microphone": "Unable to access your microphone",
|
"Unable to access your microphone": "Unable to access your microphone",
|
||||||
"We were unable to access your microphone. Please check your browser settings and try again.": "We were unable to access your microphone. Please check your browser settings and try again.",
|
"We were unable to access your microphone. Please check your browser settings and try again.": "We were unable to access your microphone. Please check your browser settings and try again.",
|
||||||
"No microphone found": "No microphone found",
|
"No microphone found": "No microphone found",
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
||||||
import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { SyncState } from "matrix-js-sdk/src/sync";
|
import { SyncState } from "matrix-js-sdk/src/sync";
|
||||||
import { waitFor } from "@testing-library/react";
|
import { waitFor } from "@testing-library/react";
|
||||||
|
import { EventType, MsgType } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import BasePlatform from "../src/BasePlatform";
|
import BasePlatform from "../src/BasePlatform";
|
||||||
import { ElementCall } from "../src/models/Call";
|
import { ElementCall } from "../src/models/Call";
|
||||||
|
@ -39,6 +40,7 @@ import { mkThread } from "./test-utils/threads";
|
||||||
import dis from "../src/dispatcher/dispatcher";
|
import dis from "../src/dispatcher/dispatcher";
|
||||||
import { ThreadPayload } from "../src/dispatcher/payloads/ThreadPayload";
|
import { ThreadPayload } from "../src/dispatcher/payloads/ThreadPayload";
|
||||||
import { Action } from "../src/dispatcher/actions";
|
import { Action } from "../src/dispatcher/actions";
|
||||||
|
import { VoiceBroadcastChunkEventType } from "../src/voice-broadcast";
|
||||||
|
|
||||||
jest.mock("../src/utils/notifications", () => ({
|
jest.mock("../src/utils/notifications", () => ({
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -73,6 +75,22 @@ describe("Notifier", () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mkAudioEvent = (broadcastChunk = false): MatrixEvent => {
|
||||||
|
const chunkContent = broadcastChunk ? { [VoiceBroadcastChunkEventType]: {} } : {};
|
||||||
|
|
||||||
|
return mkEvent({
|
||||||
|
event: true,
|
||||||
|
type: EventType.RoomMessage,
|
||||||
|
user: "@user:example.com",
|
||||||
|
room: "!room:example.com",
|
||||||
|
content: {
|
||||||
|
...chunkContent,
|
||||||
|
msgtype: MsgType.Audio,
|
||||||
|
body: "test audio message",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
accountDataStore = {};
|
accountDataStore = {};
|
||||||
mockClient = getMockClientWithEventEmitter({
|
mockClient = getMockClientWithEventEmitter({
|
||||||
|
@ -94,11 +112,11 @@ describe("Notifier", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
mockClient.pushRules = {
|
mockClient.pushRules = {
|
||||||
global: undefined,
|
global: {},
|
||||||
};
|
};
|
||||||
accountDataEventKey = getLocalNotificationAccountDataEventType(mockClient.deviceId);
|
accountDataEventKey = getLocalNotificationAccountDataEventType(mockClient.deviceId!);
|
||||||
|
|
||||||
testRoom = new Room(roomId, mockClient, mockClient.getUserId());
|
testRoom = new Room(roomId, mockClient, mockClient.getSafeUserId());
|
||||||
|
|
||||||
MockPlatform = mockPlatformPeg({
|
MockPlatform = mockPlatformPeg({
|
||||||
supportsNotifications: jest.fn().mockReturnValue(true),
|
supportsNotifications: jest.fn().mockReturnValue(true),
|
||||||
|
@ -109,8 +127,10 @@ describe("Notifier", () => {
|
||||||
|
|
||||||
Notifier.isBodyEnabled = jest.fn().mockReturnValue(true);
|
Notifier.isBodyEnabled = jest.fn().mockReturnValue(true);
|
||||||
|
|
||||||
mockClient.getRoom.mockImplementation((id) => {
|
mockClient.getRoom.mockImplementation((id: string | undefined): Room | null => {
|
||||||
return id === roomId ? testRoom : new Room(id, mockClient, mockClient.getUserId());
|
if (id === roomId) return testRoom;
|
||||||
|
if (id) return new Room(id, mockClient, mockClient.getSafeUserId());
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -256,6 +276,24 @@ describe("Notifier", () => {
|
||||||
Notifier._displayPopupNotification(testEvent, testRoom);
|
Notifier._displayPopupNotification(testEvent, testRoom);
|
||||||
expect(MockPlatform.displayNotification).toHaveBeenCalledTimes(count);
|
expect(MockPlatform.displayNotification).toHaveBeenCalledTimes(count);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should display a notification for a voice message", () => {
|
||||||
|
const audioEvent = mkAudioEvent();
|
||||||
|
Notifier._displayPopupNotification(audioEvent, testRoom);
|
||||||
|
expect(MockPlatform.displayNotification).toHaveBeenCalledWith(
|
||||||
|
"@user:example.com (!room1:server)",
|
||||||
|
"@user:example.com: test audio message",
|
||||||
|
"data:image/png;base64,00",
|
||||||
|
testRoom,
|
||||||
|
audioEvent,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not display a notification for a broadcast chunk", () => {
|
||||||
|
const audioEvent = mkAudioEvent(true);
|
||||||
|
Notifier._displayPopupNotification(audioEvent, testRoom);
|
||||||
|
expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getSoundForRoom", () => {
|
describe("getSoundForRoom", () => {
|
||||||
|
@ -448,7 +486,7 @@ describe("Notifier", () => {
|
||||||
|
|
||||||
dis.dispatch<ThreadPayload>({
|
dis.dispatch<ThreadPayload>({
|
||||||
action: Action.ViewThread,
|
action: Action.ViewThread,
|
||||||
thread_id: rootEvent.getId(),
|
thread_id: rootEvent.getId()!,
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBe(rootEvent.getId()));
|
await waitFor(() => expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBe(rootEvent.getId()));
|
||||||
|
@ -456,6 +494,11 @@ describe("Notifier", () => {
|
||||||
Notifier._evaluateEvent(events[1]);
|
Notifier._evaluateEvent(events[1]);
|
||||||
expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1);
|
expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should show a pop-up for an audio message", () => {
|
||||||
|
Notifier._evaluateEvent(mkAudioEvent());
|
||||||
|
expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("setPromptHidden", () => {
|
describe("setPromptHidden", () => {
|
||||||
|
|
|
@ -40,7 +40,6 @@ describe("<ForgotPassword>", () => {
|
||||||
let onComplete: () => void;
|
let onComplete: () => void;
|
||||||
let onLoginClick: () => void;
|
let onLoginClick: () => void;
|
||||||
let renderResult: RenderResult;
|
let renderResult: RenderResult;
|
||||||
let restoreConsole: () => void;
|
|
||||||
|
|
||||||
const typeIntoField = async (label: string, value: string): Promise<void> => {
|
const typeIntoField = async (label: string, value: string): Promise<void> => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
@ -63,14 +62,14 @@ describe("<ForgotPassword>", () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
filterConsole(
|
||||||
restoreConsole = filterConsole(
|
|
||||||
// not implemented by js-dom https://github.com/jsdom/jsdom/issues/1937
|
// not implemented by js-dom https://github.com/jsdom/jsdom/issues/1937
|
||||||
"Not implemented: HTMLFormElement.prototype.requestSubmit",
|
"Not implemented: HTMLFormElement.prototype.requestSubmit",
|
||||||
// not of interested for this test
|
// not of interested for this test
|
||||||
"Starting load of AsyncWrapper for modal",
|
"Starting load of AsyncWrapper for modal",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
client = stubClient();
|
client = stubClient();
|
||||||
mocked(createClient).mockReturnValue(client);
|
mocked(createClient).mockReturnValue(client);
|
||||||
|
|
||||||
|
@ -87,7 +86,6 @@ describe("<ForgotPassword>", () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
// clean up modals
|
// clean up modals
|
||||||
Modal.closeCurrentModal("force");
|
Modal.closeCurrentModal("force");
|
||||||
restoreConsole?.();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
|
78
test/components/views/avatars/RoomAvatar-test.tsx
Normal file
78
test/components/views/avatars/RoomAvatar-test.tsx
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
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 } from "@testing-library/react";
|
||||||
|
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { mocked } from "jest-mock";
|
||||||
|
|
||||||
|
import RoomAvatar from "../../../../src/components/views/avatars/RoomAvatar";
|
||||||
|
import { filterConsole, stubClient } from "../../../test-utils";
|
||||||
|
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||||
|
import { LocalRoom } from "../../../../src/models/LocalRoom";
|
||||||
|
import * as AvatarModule from "../../../../src/Avatar";
|
||||||
|
import { DirectoryMember } from "../../../../src/utils/direct-messages";
|
||||||
|
|
||||||
|
describe("RoomAvatar", () => {
|
||||||
|
let client: MatrixClient;
|
||||||
|
|
||||||
|
filterConsole(
|
||||||
|
// unrelated for this test
|
||||||
|
"Room !room:example.com does not have an m.room.create event",
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
client = stubClient();
|
||||||
|
const dmRoomMap = new DMRoomMap(client);
|
||||||
|
jest.spyOn(dmRoomMap, "getUserIdForRoomId");
|
||||||
|
jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
|
||||||
|
jest.spyOn(AvatarModule, "defaultAvatarUrlForString");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mocked(DMRoomMap.shared().getUserIdForRoomId).mockReset();
|
||||||
|
mocked(AvatarModule.defaultAvatarUrlForString).mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render as expected for a Room", () => {
|
||||||
|
const room = new Room("!room:example.com", client, client.getSafeUserId());
|
||||||
|
room.name = "test room";
|
||||||
|
expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
|
||||||
|
expect(AvatarModule.defaultAvatarUrlForString).toHaveBeenCalledWith(room.roomId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render as expected for a DM room", () => {
|
||||||
|
const userId = "@dm_user@example.com";
|
||||||
|
const room = new Room("!room:example.com", client, client.getSafeUserId());
|
||||||
|
room.name = "DM room";
|
||||||
|
mocked(DMRoomMap.shared().getUserIdForRoomId).mockReturnValue(userId);
|
||||||
|
expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
|
||||||
|
expect(AvatarModule.defaultAvatarUrlForString).toHaveBeenCalledWith(userId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render as expected for a LocalRoom", () => {
|
||||||
|
const userId = "@local_room_user@example.com";
|
||||||
|
const localRoom = new LocalRoom("!room:example.com", client, client.getSafeUserId());
|
||||||
|
localRoom.name = "local test room";
|
||||||
|
localRoom.targets.push(new DirectoryMember({ user_id: userId }));
|
||||||
|
expect(render(<RoomAvatar room={localRoom} />).container).toMatchSnapshot();
|
||||||
|
expect(AvatarModule.defaultAvatarUrlForString).toHaveBeenCalledWith(userId);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,76 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`RoomAvatar should render as expected for a DM room 1`] = `
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
class="mx_BaseAvatar"
|
||||||
|
role="presentation"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="mx_BaseAvatar_initial"
|
||||||
|
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
|
||||||
|
>
|
||||||
|
D
|
||||||
|
</span>
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
class="mx_BaseAvatar_image"
|
||||||
|
data-testid="avatar-img"
|
||||||
|
src="data:image/png;base64,00"
|
||||||
|
style="width: 36px; height: 36px;"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`RoomAvatar should render as expected for a LocalRoom 1`] = `
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
class="mx_BaseAvatar"
|
||||||
|
role="presentation"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="mx_BaseAvatar_initial"
|
||||||
|
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
|
||||||
|
>
|
||||||
|
L
|
||||||
|
</span>
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
class="mx_BaseAvatar_image"
|
||||||
|
data-testid="avatar-img"
|
||||||
|
src="data:image/png;base64,00"
|
||||||
|
style="width: 36px; height: 36px;"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`RoomAvatar should render as expected for a Room 1`] = `
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
class="mx_BaseAvatar"
|
||||||
|
role="presentation"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="mx_BaseAvatar_initial"
|
||||||
|
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
|
||||||
|
>
|
||||||
|
T
|
||||||
|
</span>
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
class="mx_BaseAvatar_image"
|
||||||
|
data-testid="avatar-img"
|
||||||
|
src="data:image/png;base64,00"
|
||||||
|
style="width: 36px; height: 36px;"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -75,20 +75,19 @@ describe("RoomTile", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
let client: Mocked<MatrixClient>;
|
let client: Mocked<MatrixClient>;
|
||||||
let restoreConsole: () => void;
|
|
||||||
let voiceBroadcastInfoEvent: MatrixEvent;
|
let voiceBroadcastInfoEvent: MatrixEvent;
|
||||||
let room: Room;
|
let room: Room;
|
||||||
let renderResult: RenderResult;
|
let renderResult: RenderResult;
|
||||||
let sdkContext: TestSdkContext;
|
let sdkContext: TestSdkContext;
|
||||||
|
|
||||||
beforeEach(() => {
|
filterConsole(
|
||||||
sdkContext = new TestSdkContext();
|
|
||||||
|
|
||||||
restoreConsole = filterConsole(
|
|
||||||
// irrelevant for this test
|
// irrelevant for this test
|
||||||
"Room !1:example.org does not have an m.room.create event",
|
"Room !1:example.org does not have an m.room.create event",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sdkContext = new TestSdkContext();
|
||||||
|
|
||||||
client = mocked(stubClient());
|
client = mocked(stubClient());
|
||||||
sdkContext.client = client;
|
sdkContext.client = client;
|
||||||
DMRoomMap.makeShared();
|
DMRoomMap.makeShared();
|
||||||
|
@ -105,7 +104,6 @@ describe("RoomTile", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
restoreConsole();
|
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -33,8 +33,6 @@ jest.mock("../../../../src/components/structures/HomePage", () => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe("UserOnboardingPage", () => {
|
describe("UserOnboardingPage", () => {
|
||||||
let restoreConsole: () => void;
|
|
||||||
|
|
||||||
const renderComponent = async (): Promise<RenderResult> => {
|
const renderComponent = async (): Promise<RenderResult> => {
|
||||||
const renderResult = render(<UserOnboardingPage />);
|
const renderResult = render(<UserOnboardingPage />);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
@ -43,12 +41,10 @@ describe("UserOnboardingPage", () => {
|
||||||
return renderResult;
|
return renderResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeAll(() => {
|
filterConsole(
|
||||||
restoreConsole = filterConsole(
|
|
||||||
// unrelated for this test
|
// unrelated for this test
|
||||||
"could not update user onboarding context",
|
"could not update user onboarding context",
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stubClient();
|
stubClient();
|
||||||
|
@ -60,10 +56,6 @@ describe("UserOnboardingPage", () => {
|
||||||
jest.restoreAllMocks();
|
jest.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
restoreConsole();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when the user registered before the cutoff date", () => {
|
describe("when the user registered before the cutoff date", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.spyOn(MatrixClientPeg, "userRegisteredAfter").mockReturnValue(false);
|
jest.spyOn(MatrixClientPeg, "userRegisteredAfter").mockReturnValue(false);
|
||||||
|
|
|
@ -92,6 +92,7 @@ export const unmockClientPeg = () => jest.spyOn(MatrixClientPeg, "get").mockRest
|
||||||
*/
|
*/
|
||||||
export const mockClientMethodsUser = (userId = "@alice:domain") => ({
|
export const mockClientMethodsUser = (userId = "@alice:domain") => ({
|
||||||
getUserId: jest.fn().mockReturnValue(userId),
|
getUserId: jest.fn().mockReturnValue(userId),
|
||||||
|
getSafeUserId: jest.fn().mockReturnValue(userId),
|
||||||
getUser: jest.fn().mockReturnValue(new User(userId)),
|
getUser: jest.fn().mockReturnValue(new User(userId)),
|
||||||
isGuest: jest.fn().mockReturnValue(false),
|
isGuest: jest.fn().mockReturnValue(false),
|
||||||
mxcUrlToHttp: jest.fn().mockReturnValue("mock-mxcUrlToHttp"),
|
mxcUrlToHttp: jest.fn().mockReturnValue("mock-mxcUrlToHttp"),
|
||||||
|
|
|
@ -16,21 +16,23 @@ limitations under the License.
|
||||||
|
|
||||||
type FilteredConsole = Pick<Console, "log" | "error" | "info" | "debug" | "warn">;
|
type FilteredConsole = Pick<Console, "log" | "error" | "info" | "debug" | "warn">;
|
||||||
|
|
||||||
const originalFunctions: FilteredConsole = {
|
/**
|
||||||
|
* Allows to filter out specific messages in console.*.
|
||||||
|
* Call this from any describe block.
|
||||||
|
* Automagically restores the original function by implementing an afterAll hook.
|
||||||
|
*
|
||||||
|
* @param ignoreList Messages to be filtered
|
||||||
|
*/
|
||||||
|
export const filterConsole = (...ignoreList: string[]): void => {
|
||||||
|
const originalFunctions: FilteredConsole = {
|
||||||
log: console.log,
|
log: console.log,
|
||||||
error: console.error,
|
error: console.error,
|
||||||
info: console.info,
|
info: console.info,
|
||||||
debug: console.debug,
|
debug: console.debug,
|
||||||
warn: console.warn,
|
warn: console.warn,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
beforeAll(() => {
|
||||||
* Allows to filter out specific messages in console.*.
|
|
||||||
*
|
|
||||||
* @param ignoreList Messages to be filtered
|
|
||||||
* @returns function to restore the console
|
|
||||||
*/
|
|
||||||
export const filterConsole = (...ignoreList: string[]): (() => void) => {
|
|
||||||
for (const [key, originalFunction] of Object.entries(originalFunctions)) {
|
for (const [key, originalFunction] of Object.entries(originalFunctions)) {
|
||||||
window.console[key as keyof FilteredConsole] = (...data: any[]) => {
|
window.console[key as keyof FilteredConsole] = (...data: any[]) => {
|
||||||
const message = data?.[0]?.message || data?.[0];
|
const message = data?.[0]?.message || data?.[0];
|
||||||
|
@ -42,10 +44,11 @@ export const filterConsole = (...ignoreList: string[]): (() => void) => {
|
||||||
originalFunction(...data);
|
originalFunction(...data);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
afterAll(() => {
|
||||||
for (const [key, originalFunction] of Object.entries(originalFunctions)) {
|
for (const [key, originalFunction] of Object.entries(originalFunctions)) {
|
||||||
window.console[key as keyof FilteredConsole] = originalFunction;
|
window.console[key as keyof FilteredConsole] = originalFunction;
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -62,7 +62,6 @@ describe("VoiceBroadcastRecordingPip", () => {
|
||||||
let infoEvent: MatrixEvent;
|
let infoEvent: MatrixEvent;
|
||||||
let recording: VoiceBroadcastRecording;
|
let recording: VoiceBroadcastRecording;
|
||||||
let renderResult: RenderResult;
|
let renderResult: RenderResult;
|
||||||
let restoreConsole: () => void;
|
|
||||||
|
|
||||||
const renderPip = async (state: VoiceBroadcastInfoState) => {
|
const renderPip = async (state: VoiceBroadcastInfoState) => {
|
||||||
infoEvent = mkVoiceBroadcastInfoStateEvent(roomId, state, client.getUserId() || "", client.getDeviceId() || "");
|
infoEvent = mkVoiceBroadcastInfoStateEvent(roomId, state, client.getUserId() || "", client.getDeviceId() || "");
|
||||||
|
@ -85,6 +84,8 @@ describe("VoiceBroadcastRecordingPip", () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
filterConsole("Starting load of AsyncWrapper for modal");
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
client = stubClient();
|
client = stubClient();
|
||||||
mocked(requestMediaPermissions).mockResolvedValue({
|
mocked(requestMediaPermissions).mockResolvedValue({
|
||||||
|
@ -105,11 +106,6 @@ describe("VoiceBroadcastRecordingPip", () => {
|
||||||
[MediaDeviceKindEnum.VideoInput]: [],
|
[MediaDeviceKindEnum.VideoInput]: [],
|
||||||
});
|
});
|
||||||
jest.spyOn(MediaDeviceHandler.instance, "setDevice").mockImplementation();
|
jest.spyOn(MediaDeviceHandler.instance, "setDevice").mockImplementation();
|
||||||
restoreConsole = filterConsole("Starting load of AsyncWrapper for modal");
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
restoreConsole();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when rendering a started recording", () => {
|
describe("when rendering a started recording", () => {
|
||||||
|
|
|
@ -35,10 +35,6 @@ import { flushPromises, stubClient } from "../../test-utils";
|
||||||
import { createTestPlayback } from "../../test-utils/audio";
|
import { createTestPlayback } from "../../test-utils/audio";
|
||||||
import { mkVoiceBroadcastChunkEvent, mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils";
|
import { mkVoiceBroadcastChunkEvent, mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils";
|
||||||
|
|
||||||
jest.mock("../../../src/events/getReferenceRelationsForEvent", () => ({
|
|
||||||
getReferenceRelationsForEvent: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock("../../../src/utils/MediaEventHelper", () => ({
|
jest.mock("../../../src/utils/MediaEventHelper", () => ({
|
||||||
MediaEventHelper: jest.fn(),
|
MediaEventHelper: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
Loading…
Reference in a new issue