Migrate more jest tests to vitest (#33922)
* Migrate more jest tests to vitest * Fix jest config * Fix jest config * Make remaining jest tests type-happy * Iterate * Fix Notifier import cycle * Fix tests * Delint * Iterate * Handle SDKContextClass `client` initialisation internally Rather than via MatrixChat - this is predominantly for Lifecycle tests as they don't use a MatrixChat and it doesn't make much sense for this component to own this state. * Fix tests * Iterate * Simplify diff * Improve coverage * Improve coverage * Iterate
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { type ReactElement } from "react";
|
||||
import { render } from "test-utils-rtl";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { MatrixError, ConnectionError } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
adminContactStrings,
|
||||
messageForConnectionError,
|
||||
messageForLoginError,
|
||||
messageForResourceLimitError,
|
||||
messageForSyncError,
|
||||
resourceLimitStrings,
|
||||
} from "./ErrorUtils";
|
||||
|
||||
describe("messageForResourceLimitError", () => {
|
||||
it("should match snapshot for monthly_active_user", () => {
|
||||
const { asFragment } = render(
|
||||
messageForResourceLimitError("monthly_active_user", "some@email", resourceLimitStrings) as ReactElement,
|
||||
);
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should match snapshot for admin contact links", () => {
|
||||
const { asFragment } = render(
|
||||
messageForResourceLimitError("", "some@email", adminContactStrings) as ReactElement,
|
||||
);
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("messageForSyncError", () => {
|
||||
it("should match snapshot for M_RESOURCE_LIMIT_EXCEEDED", () => {
|
||||
const err = new MatrixError({
|
||||
errcode: "M_RESOURCE_LIMIT_EXCEEDED",
|
||||
data: {
|
||||
limit_type: "monthly_active_user",
|
||||
admin_contact: "some@email",
|
||||
},
|
||||
});
|
||||
const { asFragment } = render(messageForSyncError(err) as ReactElement);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should match snapshot for other errors", () => {
|
||||
const err = new MatrixError({
|
||||
errcode: "OTHER_ERROR",
|
||||
});
|
||||
const { asFragment } = render(messageForSyncError(err) as ReactElement);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("messageForLoginError", () => {
|
||||
it("should match snapshot for M_RESOURCE_LIMIT_EXCEEDED", () => {
|
||||
const err = new MatrixError({
|
||||
errcode: "M_RESOURCE_LIMIT_EXCEEDED",
|
||||
data: {
|
||||
limit_type: "monthly_active_user",
|
||||
admin_contact: "some@email",
|
||||
},
|
||||
});
|
||||
const { asFragment } = render(
|
||||
messageForLoginError(err, {
|
||||
hsUrl: "hsUrl",
|
||||
hsName: "hsName",
|
||||
}) as ReactElement,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should match snapshot for M_USER_DEACTIVATED", () => {
|
||||
const err = new MatrixError(
|
||||
{
|
||||
errcode: "M_USER_DEACTIVATED",
|
||||
},
|
||||
403,
|
||||
);
|
||||
const { asFragment } = render(
|
||||
messageForLoginError(err, {
|
||||
hsUrl: "hsUrl",
|
||||
hsName: "hsName",
|
||||
}) as ReactElement,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should match snapshot for 401", () => {
|
||||
const err = new MatrixError(
|
||||
{
|
||||
errcode: "UNKNOWN",
|
||||
},
|
||||
401,
|
||||
);
|
||||
const { asFragment } = render(
|
||||
messageForLoginError(err, {
|
||||
hsUrl: "hsUrl",
|
||||
hsName: "hsName",
|
||||
}) as ReactElement,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should match snapshot for unknown error", () => {
|
||||
const err = new MatrixError({}, 400);
|
||||
const { asFragment } = render(
|
||||
messageForLoginError(err, {
|
||||
hsUrl: "hsUrl",
|
||||
hsName: "hsName",
|
||||
}) as ReactElement,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("messageForConnectionError", () => {
|
||||
it("should match snapshot for ConnectionError", () => {
|
||||
const err = new ConnectionError("Internal Server Error", new MatrixError({}, 500));
|
||||
const { asFragment } = render(
|
||||
messageForConnectionError(err, {
|
||||
hsUrl: "hsUrl",
|
||||
hsName: "hsName",
|
||||
}) as ReactElement,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should match snapshot for MatrixError M_NOT_FOUND", () => {
|
||||
const err = new MatrixError(
|
||||
{
|
||||
errcode: "M_NOT_FOUND",
|
||||
},
|
||||
404,
|
||||
);
|
||||
const { asFragment } = render(
|
||||
messageForConnectionError(err, {
|
||||
hsUrl: "hsUrl",
|
||||
hsName: "hsName",
|
||||
}) as ReactElement,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should match snapshot for unknown error", () => {
|
||||
const err = new Error("What even");
|
||||
const { asFragment } = render(
|
||||
messageForConnectionError(err, {
|
||||
hsUrl: "hsUrl",
|
||||
hsName: "hsName",
|
||||
}) as ReactElement,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should match snapshot for mixed content error", () => {
|
||||
const err = new ConnectionError("Mixed content maybe?");
|
||||
Object.defineProperty(window, "location", { value: { protocol: "https:" } });
|
||||
const { asFragment } = render(
|
||||
messageForConnectionError(err, {
|
||||
hsUrl: "http://server.com",
|
||||
hsName: "hsName",
|
||||
}) as ReactElement,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,468 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import {
|
||||
M_LOCATION,
|
||||
EventStatus,
|
||||
EventType,
|
||||
type IEvent,
|
||||
type MatrixClient,
|
||||
MatrixEvent,
|
||||
MsgType,
|
||||
PendingEventOrdering,
|
||||
RelationType,
|
||||
Room,
|
||||
Thread,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { vi, describe, it, expect, beforeEach, afterAll } from "vitest";
|
||||
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import {
|
||||
canCancel,
|
||||
canEditContent,
|
||||
canEditOwnEvent,
|
||||
fetchInitialEvent,
|
||||
findEditableEvent,
|
||||
highlightEvent,
|
||||
isContentActionable,
|
||||
isLocationEvent,
|
||||
isVoiceMessage,
|
||||
} from "./EventUtils";
|
||||
import {
|
||||
getMockClientWithEventEmitter,
|
||||
makeBeaconInfoEvent,
|
||||
makePollStartEvent,
|
||||
stubClient,
|
||||
} from "../../test/test-utils";
|
||||
import dis from "../dispatcher/dispatcher";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
|
||||
vi.mock("../dispatcher/dispatcher");
|
||||
|
||||
describe("EventUtils", () => {
|
||||
const userId = "@user:server";
|
||||
const roomId = "!room:server";
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
getUserId: vi.fn().mockReturnValue(userId),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient.getUserId.mockClear().mockReturnValue(userId);
|
||||
});
|
||||
afterAll(() => {
|
||||
vi.spyOn(MatrixClientPeg, "get").mockRestore();
|
||||
});
|
||||
|
||||
// setup events
|
||||
const unsentEvent = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: userId,
|
||||
});
|
||||
unsentEvent.status = EventStatus.ENCRYPTING;
|
||||
|
||||
const redactedEvent = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: userId,
|
||||
});
|
||||
redactedEvent.makeRedacted(
|
||||
redactedEvent,
|
||||
new Room(redactedEvent.getRoomId()!, mockClient, mockClient.getUserId()!),
|
||||
);
|
||||
|
||||
const stateEvent = new MatrixEvent({
|
||||
type: EventType.RoomTopic,
|
||||
state_key: "",
|
||||
});
|
||||
const beaconInfoEvent = makeBeaconInfoEvent(userId, roomId);
|
||||
|
||||
const roomMemberEvent = new MatrixEvent({
|
||||
type: EventType.RoomMember,
|
||||
sender: userId,
|
||||
});
|
||||
|
||||
const stickerEvent = new MatrixEvent({
|
||||
type: EventType.Sticker,
|
||||
sender: userId,
|
||||
});
|
||||
|
||||
const pollStartEvent = makePollStartEvent("What?", userId);
|
||||
|
||||
const notDecryptedEvent = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: userId,
|
||||
content: {
|
||||
msgtype: "m.bad.encrypted",
|
||||
},
|
||||
});
|
||||
|
||||
const noMsgType = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: userId,
|
||||
content: {
|
||||
msgtype: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const noContentBody = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: userId,
|
||||
content: {
|
||||
msgtype: MsgType.Image,
|
||||
},
|
||||
});
|
||||
|
||||
const emptyContentBody = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: userId,
|
||||
content: {
|
||||
msgtype: MsgType.Text,
|
||||
body: "",
|
||||
},
|
||||
});
|
||||
|
||||
const objectContentBody = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: userId,
|
||||
content: {
|
||||
msgtype: MsgType.File,
|
||||
body: {},
|
||||
},
|
||||
});
|
||||
|
||||
const niceTextMessage = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: userId,
|
||||
content: {
|
||||
msgtype: MsgType.Text,
|
||||
body: "Hello",
|
||||
},
|
||||
});
|
||||
|
||||
const bobsTextMessage = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: "@bob:server",
|
||||
content: {
|
||||
msgtype: MsgType.Text,
|
||||
body: "Hello from Bob",
|
||||
},
|
||||
});
|
||||
|
||||
describe("isContentActionable()", () => {
|
||||
type TestCase = [string, MatrixEvent];
|
||||
it.each<TestCase>([
|
||||
["unsent event", unsentEvent],
|
||||
["redacted event", redactedEvent],
|
||||
["state event", stateEvent],
|
||||
["undecrypted event", notDecryptedEvent],
|
||||
["room member event", roomMemberEvent],
|
||||
["event without msgtype", noMsgType],
|
||||
["event without content body property", noContentBody],
|
||||
])("returns false for %s", (_description, event) => {
|
||||
expect(isContentActionable(event)).toBe(false);
|
||||
});
|
||||
|
||||
it.each<TestCase>([
|
||||
["sticker event", stickerEvent],
|
||||
["poll start event", pollStartEvent],
|
||||
["event with empty content body", emptyContentBody],
|
||||
["event with a content body", niceTextMessage],
|
||||
["beacon_info event", beaconInfoEvent],
|
||||
])("returns true for %s", (_description, event) => {
|
||||
expect(isContentActionable(event)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("editable content helpers", () => {
|
||||
const replaceRelationEvent = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: userId,
|
||||
content: {
|
||||
"msgtype": MsgType.Text,
|
||||
"body": "Hello",
|
||||
["m.relates_to"]: {
|
||||
rel_type: RelationType.Replace,
|
||||
event_id: "1",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const referenceRelationEvent = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: userId,
|
||||
content: {
|
||||
"msgtype": MsgType.Text,
|
||||
"body": "Hello",
|
||||
["m.relates_to"]: {
|
||||
rel_type: RelationType.Reference,
|
||||
event_id: "1",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const emoteEvent = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: userId,
|
||||
content: {
|
||||
msgtype: MsgType.Emote,
|
||||
body: "🧪",
|
||||
},
|
||||
});
|
||||
|
||||
type TestCase = [string, MatrixEvent];
|
||||
|
||||
const uneditableCases: TestCase[] = [
|
||||
["redacted event", redactedEvent],
|
||||
["state event", stateEvent],
|
||||
["event that is not room message", roomMemberEvent],
|
||||
["event without msgtype", noMsgType],
|
||||
["event without content body property", noContentBody],
|
||||
["event with empty content body property", emptyContentBody],
|
||||
["event with non-string body", objectContentBody],
|
||||
["event not sent by current user", bobsTextMessage],
|
||||
["event with a replace relation", replaceRelationEvent],
|
||||
];
|
||||
|
||||
const editableCases: TestCase[] = [
|
||||
["event with reference relation", referenceRelationEvent],
|
||||
["emote event", emoteEvent],
|
||||
["poll start event", pollStartEvent],
|
||||
["event with a content body", niceTextMessage],
|
||||
];
|
||||
|
||||
describe("canEditContent()", () => {
|
||||
it.each<TestCase>(uneditableCases)("returns false for %s", (_description, event) => {
|
||||
expect(canEditContent(mockClient, event)).toBe(false);
|
||||
});
|
||||
|
||||
it.each<TestCase>(editableCases)("returns true for %s", (_description, event) => {
|
||||
expect(canEditContent(mockClient, event)).toBe(true);
|
||||
});
|
||||
});
|
||||
describe("canEditOwnContent()", () => {
|
||||
it.each<TestCase>(uneditableCases)("returns false for %s", (_description, event) => {
|
||||
expect(canEditOwnEvent(mockClient, event)).toBe(false);
|
||||
});
|
||||
|
||||
it.each<TestCase>(editableCases)("returns true for %s", (_description, event) => {
|
||||
expect(canEditOwnEvent(mockClient, event)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("isVoiceMessage()", () => {
|
||||
it("returns true for an event with msc2516.voice content", () => {
|
||||
const event = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
["org.matrix.msc2516.voice"]: {},
|
||||
},
|
||||
});
|
||||
|
||||
expect(isVoiceMessage(event)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for an event with msc3245.voice content", () => {
|
||||
const event = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
["org.matrix.msc3245.voice"]: {},
|
||||
},
|
||||
});
|
||||
|
||||
expect(isVoiceMessage(event)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for an event with voice content", () => {
|
||||
const event = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
body: "hello",
|
||||
},
|
||||
});
|
||||
|
||||
expect(isVoiceMessage(event)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isLocationEvent()", () => {
|
||||
it("returns true for an event with m.location stable type", () => {
|
||||
const event = new MatrixEvent({
|
||||
type: M_LOCATION.altName,
|
||||
});
|
||||
expect(isLocationEvent(event)).toBe(true);
|
||||
});
|
||||
it("returns true for an event with m.location unstable prefixed type", () => {
|
||||
const event = new MatrixEvent({
|
||||
type: M_LOCATION.name,
|
||||
});
|
||||
expect(isLocationEvent(event)).toBe(true);
|
||||
});
|
||||
it("returns true for a room message with stable m.location msgtype", () => {
|
||||
const event = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
msgtype: M_LOCATION.altName,
|
||||
},
|
||||
});
|
||||
expect(isLocationEvent(event)).toBe(true);
|
||||
});
|
||||
it("returns true for a room message with unstable m.location msgtype", () => {
|
||||
const event = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
msgtype: M_LOCATION.name,
|
||||
},
|
||||
});
|
||||
expect(isLocationEvent(event)).toBe(true);
|
||||
});
|
||||
it("returns false for a non location event", () => {
|
||||
const event = new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
body: "Hello",
|
||||
},
|
||||
});
|
||||
expect(isLocationEvent(event)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("canCancel()", () => {
|
||||
it.each([[EventStatus.QUEUED], [EventStatus.NOT_SENT], [EventStatus.ENCRYPTING]])(
|
||||
"return true for status %s",
|
||||
(status) => {
|
||||
expect(canCancel(status)).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
[EventStatus.SENDING],
|
||||
[EventStatus.CANCELLED],
|
||||
[EventStatus.SENT],
|
||||
["invalid-status" as unknown as EventStatus],
|
||||
])("return false for status %s", (status) => {
|
||||
expect(canCancel(status)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetchInitialEvent", () => {
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
let room: Room;
|
||||
let client: MatrixClient;
|
||||
|
||||
const NORMAL_EVENT = "$normalEvent";
|
||||
const THREAD_ROOT = "$threadRoot";
|
||||
const THREAD_REPLY = "$threadReply";
|
||||
|
||||
const events: Record<string, Partial<IEvent>> = {
|
||||
[NORMAL_EVENT]: {
|
||||
event_id: NORMAL_EVENT,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
body: "Classic event",
|
||||
msgtype: MsgType.Text,
|
||||
},
|
||||
},
|
||||
[THREAD_ROOT]: {
|
||||
event_id: THREAD_ROOT,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
body: "Thread root",
|
||||
msgtype: "m.text",
|
||||
},
|
||||
unsigned: {
|
||||
"m.relations": {
|
||||
[RelationType.Thread]: {
|
||||
latest_event: {
|
||||
event_id: THREAD_REPLY,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
"body": "Thread reply",
|
||||
"msgtype": MsgType.Text,
|
||||
"m.relates_to": {
|
||||
event_id: "$threadRoot",
|
||||
rel_type: RelationType.Thread,
|
||||
},
|
||||
},
|
||||
},
|
||||
count: 1,
|
||||
current_user_participated: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[THREAD_REPLY]: {
|
||||
event_id: THREAD_REPLY,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
"body": "Thread reply",
|
||||
"msgtype": MsgType.Text,
|
||||
"m.relates_to": {
|
||||
event_id: THREAD_ROOT,
|
||||
rel_type: RelationType.Thread,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
stubClient();
|
||||
client = MatrixClientPeg.safeGet();
|
||||
|
||||
room = new Room(ROOM_ID, client, client.getUserId()!, {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
|
||||
vi.spyOn(client, "supportsThreads").mockReturnValue(true);
|
||||
vi.spyOn(client, "getRoom").mockReturnValue(room);
|
||||
vi.spyOn(client, "fetchRoomEvent").mockImplementation(async (roomId, eventId) => {
|
||||
return events[eventId] ?? Promise.reject();
|
||||
});
|
||||
});
|
||||
|
||||
it("returns null for unknown events", async () => {
|
||||
expect(await fetchInitialEvent(client, room.roomId, "$UNKNOWN")).toBeNull();
|
||||
expect(await fetchInitialEvent(client, room.roomId, NORMAL_EVENT)).toBeInstanceOf(MatrixEvent);
|
||||
});
|
||||
|
||||
it("creates a thread when needed", async () => {
|
||||
await fetchInitialEvent(client, room.roomId, THREAD_REPLY);
|
||||
expect(room.getThread(THREAD_ROOT)).toBeInstanceOf(Thread);
|
||||
});
|
||||
});
|
||||
|
||||
describe("findEditableEvent", () => {
|
||||
it("should not explode when given empty events array", () => {
|
||||
expect(
|
||||
findEditableEvent({
|
||||
events: [],
|
||||
isForward: true,
|
||||
matrixClient: mockClient,
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("highlightEvent", () => {
|
||||
const eventId = "$zLg9jResFQmMO_UKFeWpgLgOgyWrL8qIgLgZ5VywrCQ";
|
||||
|
||||
it("should dispatch an action to view the event", () => {
|
||||
highlightEvent(roomId, eventId);
|
||||
expect(dis.dispatch).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
event_id: eventId,
|
||||
highlighted: true,
|
||||
room_id: roomId,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type MediaEventContent } from "matrix-js-sdk/src/types";
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import { downloadLabelForFile } from "./FileUtils.ts";
|
||||
|
||||
describe("FileUtils", () => {
|
||||
describe("downloadLabelForFile", () => {
|
||||
it.each([
|
||||
[
|
||||
"File with size",
|
||||
{
|
||||
input: {
|
||||
msgtype: "m.file",
|
||||
body: "Test",
|
||||
info: {
|
||||
size: 102434566,
|
||||
},
|
||||
} as MediaEventContent,
|
||||
output: "Download (97.69 MB)",
|
||||
},
|
||||
],
|
||||
[
|
||||
"Image",
|
||||
{
|
||||
input: {
|
||||
msgtype: "m.image",
|
||||
body: "Test",
|
||||
} as MediaEventContent,
|
||||
output: "Download",
|
||||
},
|
||||
],
|
||||
[
|
||||
"Video",
|
||||
{
|
||||
input: {
|
||||
msgtype: "m.video",
|
||||
body: "Test",
|
||||
} as MediaEventContent,
|
||||
output: "Download",
|
||||
},
|
||||
],
|
||||
[
|
||||
"Audio",
|
||||
{
|
||||
input: {
|
||||
msgtype: "m.audio",
|
||||
body: "Test",
|
||||
} as MediaEventContent,
|
||||
output: "Download",
|
||||
},
|
||||
],
|
||||
])("should correctly label %s", (_d, { input, output }) => expect(downloadLabelForFile(input)).toBe(output));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,141 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`messageForConnectionError > should match snapshot for ConnectionError 1`] = `
|
||||
<DocumentFragment>
|
||||
<span>
|
||||
<span>
|
||||
Can't connect to homeserver - please check your connectivity, ensure your
|
||||
<a
|
||||
class="mx_ExternalLink"
|
||||
href="hsUrl"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
homeserver's SSL certificate
|
||||
<svg
|
||||
class="mx_ExternalLink_icon"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5 3h6a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2"
|
||||
/>
|
||||
<path
|
||||
d="M15 3h5a1 1 0 0 1 1 1v5a1 1 0 1 1-2 0V6.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L17.586 5H15a1 1 0 1 1 0-2"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
is trusted, and that a browser extension is not blocking requests.
|
||||
</span>
|
||||
</span>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`messageForConnectionError > should match snapshot for MatrixError M_NOT_FOUND 1`] = `
|
||||
<DocumentFragment>
|
||||
There was a problem communicating with the homeserver, please try again later.(M_NOT_FOUND)
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`messageForConnectionError > should match snapshot for mixed content error 1`] = `
|
||||
<DocumentFragment>
|
||||
<span>
|
||||
<span>
|
||||
Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or
|
||||
<a
|
||||
href="https://www.google.com/search?&q=enable%20unsafe%20scripts"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
enable unsafe scripts
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
</span>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`messageForConnectionError > should match snapshot for unknown error 1`] = `
|
||||
<DocumentFragment>
|
||||
There was a problem communicating with the homeserver, please try again later.
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`messageForLoginError > should match snapshot for 401 1`] = `
|
||||
<DocumentFragment>
|
||||
Incorrect username and/or password.
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`messageForLoginError > should match snapshot for M_RESOURCE_LIMIT_EXCEEDED 1`] = `
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
<div>
|
||||
This homeserver has exceeded one of its resource limits.
|
||||
</div>
|
||||
<div
|
||||
class="mx_Login_smallError"
|
||||
>
|
||||
Please contact your service administrator to continue using this service.
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`messageForLoginError > should match snapshot for M_USER_DEACTIVATED 1`] = `
|
||||
<DocumentFragment>
|
||||
This account has been deactivated.
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`messageForLoginError > should match snapshot for unknown error 1`] = `
|
||||
<DocumentFragment>
|
||||
There was a problem communicating with the homeserver, please try again later. (HTTP 400)
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`messageForResourceLimitError > should match snapshot for admin contact links 1`] = `
|
||||
<DocumentFragment>
|
||||
<span>
|
||||
Please
|
||||
<a
|
||||
href="some@email"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
contact your service administrator
|
||||
</a>
|
||||
to continue using this service.
|
||||
</span>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`messageForResourceLimitError > should match snapshot for monthly_active_user 1`] = `
|
||||
<DocumentFragment>
|
||||
This homeserver has hit its Monthly Active User limit.
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`messageForSyncError > should match snapshot for M_RESOURCE_LIMIT_EXCEEDED 1`] = `
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
<div>
|
||||
This homeserver has exceeded one of its resource limits.
|
||||
</div>
|
||||
<div>
|
||||
Please contact your service administrator to continue using this service.
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`messageForSyncError > should match snapshot for other errors 1`] = `
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
Unable to connect to Homeserver. Retrying…
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { vi, describe, it, expect, beforeEach, afterEach, type Mocked } from "vitest";
|
||||
import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import { mkRoom, resetAsyncStoreWithClient, setupAsyncStoreWithClient, stubClient } from "test-utils/test-utils";
|
||||
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||
import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
import { leaveRoomBehaviour } from "./leave-behaviour";
|
||||
import { SDKContextClass } from "../contexts/SDKContextClass";
|
||||
import DMRoomMap from "../utils/DMRoomMap";
|
||||
import SpaceStore from "../stores/spaces/SpaceStore";
|
||||
import { MetaSpace } from "../stores/spaces";
|
||||
import { type ActionPayload } from "../dispatcher/payloads";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import { CallStore } from "../stores/CallStore";
|
||||
import { type Call } from "../models/Call";
|
||||
import LegacyCallHandler from "../LegacyCallHandler";
|
||||
|
||||
vi.mock("../Modal.tsx");
|
||||
|
||||
describe("leaveRoomBehaviour", () => {
|
||||
SDKContextClass.instance.constructEagerStores(); // Initialize RoomViewStore
|
||||
|
||||
let client: Mocked<MatrixClient>;
|
||||
let room: Mocked<Room>;
|
||||
let space: Mocked<Room>;
|
||||
|
||||
beforeEach(async () => {
|
||||
stubClient();
|
||||
client = vi.mocked(MatrixClientPeg.safeGet());
|
||||
DMRoomMap.makeShared(client);
|
||||
|
||||
room = mkRoom(client, "!1:example.org");
|
||||
space = mkRoom(client, "!2:example.org");
|
||||
space.isSpaceRoom.mockReturnValue(true);
|
||||
client.getRoom.mockImplementation((roomId) => {
|
||||
switch (roomId) {
|
||||
case room.roomId:
|
||||
return room;
|
||||
case space.roomId:
|
||||
return space;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
await setupAsyncStoreWithClient(SpaceStore.instance, client);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
||||
await resetAsyncStoreWithClient(SpaceStore.instance);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
const viewRoom = (room: Room) =>
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>(
|
||||
{
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
metricsTrigger: undefined,
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
const expectDispatch = async <T extends ActionPayload>(payload: T) => {
|
||||
const dispatcherSpy = vi.fn();
|
||||
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
|
||||
await sleep(0);
|
||||
expect(dispatcherSpy).toHaveBeenCalledWith(payload);
|
||||
defaultDispatcher.unregister(dispatcherRef);
|
||||
};
|
||||
|
||||
it("hangs up legacy calls when leaving a room", async () => {
|
||||
const hangupSpy = vi.spyOn(LegacyCallHandler.instance, "hangupOrReject").mockImplementation(() => {});
|
||||
|
||||
viewRoom(room);
|
||||
await leaveRoomBehaviour(client, room.roomId);
|
||||
|
||||
expect(hangupSpy).toHaveBeenCalledWith(room.roomId);
|
||||
});
|
||||
|
||||
it("disconnects widget-based calls when leaving a room", async () => {
|
||||
const mockCall = {
|
||||
disconnect: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as Call;
|
||||
|
||||
vi.spyOn(CallStore.instance, "getActiveCall").mockReturnValue(mockCall);
|
||||
|
||||
viewRoom(room);
|
||||
await leaveRoomBehaviour(client, room.roomId);
|
||||
|
||||
expect(mockCall.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns to the home page after leaving a room outside of a space that was being viewed", async () => {
|
||||
viewRoom(room);
|
||||
|
||||
await leaveRoomBehaviour(client, room.roomId);
|
||||
await expectDispatch({ action: Action.ViewHomePage });
|
||||
});
|
||||
|
||||
it("returns to the parent space after leaving a room inside of a space that was being viewed", async () => {
|
||||
vi.spyOn(SpaceStore.instance, "getCanonicalParent").mockImplementation((roomId) =>
|
||||
roomId === room.roomId ? space : null,
|
||||
);
|
||||
viewRoom(room);
|
||||
SpaceStore.instance.setActiveSpace(space.roomId, false);
|
||||
|
||||
await leaveRoomBehaviour(client, room.roomId);
|
||||
await expectDispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: space.roomId,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns to the home page after leaving a top-level space that was being viewed", async () => {
|
||||
viewRoom(space);
|
||||
SpaceStore.instance.setActiveSpace(space.roomId, false);
|
||||
|
||||
await leaveRoomBehaviour(client, space.roomId);
|
||||
await expectDispatch({ action: Action.ViewHomePage });
|
||||
});
|
||||
|
||||
it("returns to the parent space after leaving a subspace that was being viewed", async () => {
|
||||
room.isSpaceRoom.mockReturnValue(true);
|
||||
vi.spyOn(SpaceStore.instance, "getCanonicalParent").mockImplementation((roomId) =>
|
||||
roomId === room.roomId ? space : null,
|
||||
);
|
||||
viewRoom(room);
|
||||
SpaceStore.instance.setActiveSpace(room.roomId, false);
|
||||
|
||||
await leaveRoomBehaviour(client, room.roomId);
|
||||
await expectDispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: space.roomId,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
describe("If the feature_dynamic_room_predecessors is not enabled", () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
});
|
||||
|
||||
it("Passes through the dynamic predecessor setting", async () => {
|
||||
await leaveRoomBehaviour(client, room.roomId);
|
||||
expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(room.roomId, true, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("If the feature_dynamic_room_predecessors is enabled", () => {
|
||||
beforeEach(() => {
|
||||
// Turn on feature_dynamic_room_predecessors setting
|
||||
vi.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(settingName) => settingName === "feature_dynamic_room_predecessors",
|
||||
);
|
||||
});
|
||||
|
||||
it("Passes through the dynamic predecessor setting", async () => {
|
||||
await leaveRoomBehaviour(client, room.roomId);
|
||||
expect(client.getRoomUpgradeHistory).toHaveBeenCalledWith(room.roomId, true, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user