Merge matrix-react-sdk into element-web
Merge remote-tracking branch 'repomerge/t3chguy/repomerge' into t3chguy/repo-merge Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
commit
f0ee7f7905
3265 changed files with 484599 additions and 699 deletions
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import React from "react";
|
||||
|
||||
import { ICompletion } from "../../../../../../../src/autocomplete/Autocompleter";
|
||||
import {
|
||||
buildQuery,
|
||||
getRoomFromCompletion,
|
||||
getMentionDisplayText,
|
||||
getMentionAttributes,
|
||||
} from "../../../../../../../src/components/views/rooms/wysiwyg_composer/utils/autocomplete";
|
||||
import { createTestClient, mkRoom } from "../../../../../../test-utils";
|
||||
import * as _mockAvatar from "../../../../../../../src/Avatar";
|
||||
|
||||
const mockClient = createTestClient();
|
||||
const mockRoomId = "mockRoomId";
|
||||
const mockRoom = mkRoom(mockClient, mockRoomId);
|
||||
|
||||
const createMockCompletion = (props: Partial<ICompletion>): ICompletion => {
|
||||
return {
|
||||
completion: "mock",
|
||||
range: { beginning: true, start: 0, end: 0 },
|
||||
component: React.createElement("div"),
|
||||
...props,
|
||||
};
|
||||
};
|
||||
|
||||
jest.mock("../../../../../../../src/Avatar");
|
||||
jest.mock("../../../../../../../src/stores/WidgetStore");
|
||||
jest.mock("../../../../../../../src/stores/widgets/WidgetLayoutStore");
|
||||
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
afterAll(() => jest.restoreAllMocks());
|
||||
|
||||
describe("buildQuery", () => {
|
||||
it("returns an empty string for a falsy argument", () => {
|
||||
expect(buildQuery(null)).toBe("");
|
||||
});
|
||||
|
||||
it("returns an empty string when keyChar is falsy", () => {
|
||||
const noKeyCharSuggestion = { keyChar: "" as const, text: "test", type: "unknown" as const };
|
||||
expect(buildQuery(noKeyCharSuggestion)).toBe("");
|
||||
});
|
||||
|
||||
it("combines the keyChar and text of the suggestion in the query", () => {
|
||||
const handledSuggestion = { keyChar: "@" as const, text: "alice", type: "mention" as const };
|
||||
expect(buildQuery(handledSuggestion)).toBe("@alice");
|
||||
|
||||
const handledCommand = { keyChar: "/" as const, text: "spoiler", type: "mention" as const };
|
||||
expect(buildQuery(handledCommand)).toBe("/spoiler");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getRoomFromCompletion", () => {
|
||||
const createMockRoomCompletion = (props: Partial<ICompletion>): ICompletion => {
|
||||
return createMockCompletion({ ...props, type: "room" });
|
||||
};
|
||||
|
||||
it("calls getRoom with completionId if present in the completion", () => {
|
||||
const testId = "arbitraryId";
|
||||
const completionWithId = createMockRoomCompletion({ completionId: testId });
|
||||
|
||||
getRoomFromCompletion(completionWithId, mockClient);
|
||||
|
||||
expect(mockClient.getRoom).toHaveBeenCalledWith(testId);
|
||||
});
|
||||
|
||||
it("calls getRoom with completion if present and correct format", () => {
|
||||
const testCompletion = "arbitraryCompletion";
|
||||
const completionWithId = createMockRoomCompletion({ completionId: testCompletion });
|
||||
|
||||
getRoomFromCompletion(completionWithId, mockClient);
|
||||
|
||||
expect(mockClient.getRoom).toHaveBeenCalledWith(testCompletion);
|
||||
});
|
||||
|
||||
it("calls getRooms if no completionId is present and completion starts with #", () => {
|
||||
const completionWithId = createMockRoomCompletion({ completion: "#hash" });
|
||||
|
||||
const result = getRoomFromCompletion(completionWithId, mockClient);
|
||||
|
||||
expect(mockClient.getRoom).not.toHaveBeenCalled();
|
||||
expect(mockClient.getRooms).toHaveBeenCalled();
|
||||
|
||||
// in this case, because the mock client returns an empty array of rooms
|
||||
// from the call to get rooms, we'd expect the result to be null
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMentionDisplayText", () => {
|
||||
it("returns an empty string if we are not handling a user, room or at-room type", () => {
|
||||
const nonHandledCompletionTypes = ["community", "command"] as const;
|
||||
const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type }));
|
||||
|
||||
nonHandledCompletions.forEach((completion) => {
|
||||
expect(getMentionDisplayText(completion, mockClient)).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
it("returns the completion if we are handling a user", () => {
|
||||
const testCompletion = "display this";
|
||||
const userCompletion = createMockCompletion({ type: "user", completion: testCompletion });
|
||||
|
||||
expect(getMentionDisplayText(userCompletion, mockClient)).toBe(testCompletion);
|
||||
});
|
||||
|
||||
it("returns the room name when the room has a valid completionId", () => {
|
||||
const testCompletionId = "testId";
|
||||
const userCompletion = createMockCompletion({ type: "room", completionId: testCompletionId });
|
||||
|
||||
// as this uses the mockClient, the name will be the mock room name returned from there
|
||||
expect(getMentionDisplayText(userCompletion, mockClient)).toBe(mockClient.getRoom("")?.name);
|
||||
});
|
||||
|
||||
it("falls back to the completion for a room if completion starts with #", () => {
|
||||
const testCompletion = "#hash";
|
||||
const userCompletion = createMockCompletion({ type: "room", completion: testCompletion });
|
||||
|
||||
// as this uses the mockClient, the name will be the mock room name returned from there
|
||||
expect(getMentionDisplayText(userCompletion, mockClient)).toBe(testCompletion);
|
||||
});
|
||||
|
||||
it("returns the completion if we are handling an at-room completion", () => {
|
||||
const testCompletion = "display this";
|
||||
const atRoomCompletion = createMockCompletion({ type: "at-room", completion: testCompletion });
|
||||
|
||||
expect(getMentionDisplayText(atRoomCompletion, mockClient)).toBe(testCompletion);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMentionAttributes", () => {
|
||||
it("returns an empty map for completion types other than room, user or at-room", () => {
|
||||
const nonHandledCompletionTypes = ["community", "command"] as const;
|
||||
const nonHandledCompletions = nonHandledCompletionTypes.map((type) => createMockCompletion({ type }));
|
||||
|
||||
nonHandledCompletions.forEach((completion) => {
|
||||
expect(getMentionAttributes(completion, mockClient, mockRoom)).toEqual(new Map());
|
||||
});
|
||||
});
|
||||
|
||||
const testAvatarUrlForString = "www.stringUrl.com";
|
||||
const testAvatarUrlForMember = "www.memberUrl.com";
|
||||
const testAvatarUrlForRoom = "www.roomUrl.com";
|
||||
const testInitialLetter = "z";
|
||||
|
||||
const mockAvatar = mocked(_mockAvatar);
|
||||
mockAvatar.defaultAvatarUrlForString.mockReturnValue(testAvatarUrlForString);
|
||||
mockAvatar.avatarUrlForMember.mockReturnValue(testAvatarUrlForMember);
|
||||
mockAvatar.avatarUrlForRoom.mockReturnValue(testAvatarUrlForRoom);
|
||||
mockAvatar.getInitialLetter.mockReturnValue(testInitialLetter);
|
||||
|
||||
describe("user mentions", () => {
|
||||
it("returns an empty map when no member can be found", () => {
|
||||
const userCompletion = createMockCompletion({ type: "user" });
|
||||
|
||||
// mock not being able to find a member
|
||||
mockRoom.getMember.mockImplementationOnce(() => null);
|
||||
|
||||
const result = getMentionAttributes(userCompletion, mockClient, mockRoom);
|
||||
expect(result).toEqual(new Map());
|
||||
});
|
||||
|
||||
it("returns expected attributes when avatar url is not default", () => {
|
||||
const userCompletion = createMockCompletion({ type: "user" });
|
||||
|
||||
const result = getMentionAttributes(userCompletion, mockClient, mockRoom);
|
||||
|
||||
expect(result).toEqual(
|
||||
new Map([
|
||||
["data-mention-type", "user"],
|
||||
["style", `--avatar-background: url(${testAvatarUrlForMember}); --avatar-letter: '\u200b'`],
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns expected style attributes when avatar url matches default", () => {
|
||||
const userCompletion = createMockCompletion({ type: "user" });
|
||||
|
||||
// mock a single implementation of avatarUrlForMember to make it match the default
|
||||
mockAvatar.avatarUrlForMember.mockReturnValueOnce(testAvatarUrlForString);
|
||||
|
||||
const result = getMentionAttributes(userCompletion, mockClient, mockRoom);
|
||||
|
||||
expect(result).toEqual(
|
||||
new Map([
|
||||
["data-mention-type", "user"],
|
||||
[
|
||||
"style",
|
||||
`--avatar-background: url(${testAvatarUrlForString}); --avatar-letter: '${testInitialLetter}'`,
|
||||
],
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("room mentions", () => {
|
||||
it("returns expected attributes when avatar url for room is truthy", () => {
|
||||
const userCompletion = createMockCompletion({ type: "room" });
|
||||
|
||||
const result = getMentionAttributes(userCompletion, mockClient, mockRoom);
|
||||
|
||||
expect(result).toEqual(
|
||||
new Map([
|
||||
["data-mention-type", "room"],
|
||||
["style", `--avatar-background: url(${testAvatarUrlForRoom}); --avatar-letter: '\u200b'`],
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns expected style attributes when avatar url for room is falsy", () => {
|
||||
const userCompletion = createMockCompletion({ type: "room" });
|
||||
|
||||
// mock a single implementation of avatarUrlForRoom to make it falsy
|
||||
mockAvatar.avatarUrlForRoom.mockReturnValueOnce(null);
|
||||
|
||||
const result = getMentionAttributes(userCompletion, mockClient, mockRoom);
|
||||
|
||||
expect(result).toEqual(
|
||||
new Map([
|
||||
["data-mention-type", "room"],
|
||||
[
|
||||
"style",
|
||||
`--avatar-background: url(${testAvatarUrlForString}); --avatar-letter: '${testInitialLetter}'`,
|
||||
],
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("at-room mentions", () => {
|
||||
it("returns expected attributes when avatar url for room is truthyf", () => {
|
||||
const atRoomCompletion = createMockCompletion({ type: "at-room" });
|
||||
|
||||
const result = getMentionAttributes(atRoomCompletion, mockClient, mockRoom);
|
||||
|
||||
expect(result).toEqual(
|
||||
new Map([
|
||||
["data-mention-type", "at-room"],
|
||||
["style", `--avatar-background: url(${testAvatarUrlForRoom}); --avatar-letter: '\u200b'`],
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns expected style attributes when avatar url for room is falsy", () => {
|
||||
const atRoomCompletion = createMockCompletion({ type: "at-room" });
|
||||
|
||||
// mock a single implementation of avatarUrlForRoom to make it falsy
|
||||
mockAvatar.avatarUrlForRoom.mockReturnValueOnce(null);
|
||||
|
||||
const result = getMentionAttributes(atRoomCompletion, mockClient, mockRoom);
|
||||
|
||||
expect(result).toEqual(
|
||||
new Map([
|
||||
["data-mention-type", "at-room"],
|
||||
[
|
||||
"style",
|
||||
`--avatar-background: url(${testAvatarUrlForString}); --avatar-letter: '${testInitialLetter}'`,
|
||||
],
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
import { MsgType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { filterConsole, mkEvent } from "../../../../../../test-utils";
|
||||
import { RoomPermalinkCreator } from "../../../../../../../src/utils/permalinks/Permalinks";
|
||||
import {
|
||||
createMessageContent,
|
||||
EMOTE_PREFIX,
|
||||
} from "../../../../../../../src/components/views/rooms/wysiwyg_composer/utils/createMessageContent";
|
||||
|
||||
describe("createMessageContent", () => {
|
||||
const permalinkCreator = {
|
||||
forEvent(eventId: string): string {
|
||||
return "$$permalink$$";
|
||||
},
|
||||
} as RoomPermalinkCreator;
|
||||
const message = "<em><b>hello</b> world</em>";
|
||||
const mockEvent = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: "myfakeroom",
|
||||
user: "myfakeuser",
|
||||
content: { msgtype: "m.text", body: "Replying to this" },
|
||||
event: true,
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("Richtext composer input", () => {
|
||||
filterConsole(
|
||||
"WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm`",
|
||||
);
|
||||
|
||||
beforeAll(async () => {
|
||||
// Warm up by creating the component once, with a long timeout.
|
||||
// This prevents tests timing out because of the time spent loading
|
||||
// the WASM component.
|
||||
await createMessageContent(message, true, { permalinkCreator });
|
||||
}, 10000);
|
||||
|
||||
it("Should create html message", async () => {
|
||||
// When
|
||||
const content = await createMessageContent(message, true, { permalinkCreator });
|
||||
|
||||
// Then
|
||||
expect(content).toEqual({
|
||||
body: "*__hello__ world*",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: message,
|
||||
msgtype: "m.text",
|
||||
});
|
||||
});
|
||||
|
||||
it("Should add reply to message content", async () => {
|
||||
// When
|
||||
const content = await createMessageContent(message, true, { permalinkCreator, replyToEvent: mockEvent });
|
||||
|
||||
// Then
|
||||
expect(content).toEqual({
|
||||
"body": "> <myfakeuser> Replying to this\n\n*__hello__ world*",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body":
|
||||
'<mx-reply><blockquote><a href="$$permalink$$">In reply to</a>' +
|
||||
' <a href="https://matrix.to/#/myfakeuser">myfakeuser</a>' +
|
||||
"<br>Replying to this</blockquote></mx-reply><em><b>hello</b> world</em>",
|
||||
"msgtype": "m.text",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
event_id: mockEvent.getId(),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("Should add relation to message", async () => {
|
||||
// When
|
||||
const relation = {
|
||||
rel_type: "m.thread",
|
||||
event_id: "myFakeThreadId",
|
||||
};
|
||||
const content = await createMessageContent(message, true, { permalinkCreator, relation });
|
||||
|
||||
// Then
|
||||
expect(content).toEqual({
|
||||
"body": "*__hello__ world*",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": message,
|
||||
"msgtype": "m.text",
|
||||
"m.relates_to": {
|
||||
event_id: "myFakeThreadId",
|
||||
rel_type: "m.thread",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("Should add fields related to edition", async () => {
|
||||
// When
|
||||
const editedEvent = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: "myfakeroom",
|
||||
user: "myfakeuser2",
|
||||
content: {
|
||||
"msgtype": "m.text",
|
||||
"body": "First message",
|
||||
"formatted_body": "<b>First Message</b>",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
event_id: "eventId",
|
||||
},
|
||||
},
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
const content = await createMessageContent(message, true, { permalinkCreator, editedEvent });
|
||||
|
||||
// Then
|
||||
expect(content).toEqual({
|
||||
"body": " * *__hello__ world*",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": ` * ${message}`,
|
||||
"msgtype": "m.text",
|
||||
"m.new_content": {
|
||||
body: "*__hello__ world*",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: message,
|
||||
msgtype: "m.text",
|
||||
},
|
||||
"m.relates_to": {
|
||||
event_id: editedEvent.getId(),
|
||||
rel_type: "m.replace",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("Should strip the /me prefix from a message", async () => {
|
||||
const textBody = "some body text";
|
||||
const content = await createMessageContent(EMOTE_PREFIX + textBody, true, { permalinkCreator });
|
||||
|
||||
expect(content).toMatchObject({ body: textBody, formatted_body: textBody });
|
||||
});
|
||||
|
||||
it("Should strip single / from message prefixed with //", async () => {
|
||||
const content = await createMessageContent("//twoSlashes", true, { permalinkCreator });
|
||||
|
||||
expect(content).toMatchObject({ body: "/twoSlashes", formatted_body: "/twoSlashes" });
|
||||
});
|
||||
|
||||
it("Should set the content type to MsgType.Emote when /me prefix is used", async () => {
|
||||
const textBody = "some body text";
|
||||
const content = await createMessageContent(EMOTE_PREFIX + textBody, true, { permalinkCreator });
|
||||
|
||||
expect(content).toMatchObject({ msgtype: MsgType.Emote });
|
||||
});
|
||||
});
|
||||
|
||||
describe("Plaintext composer input", () => {
|
||||
it("Should replace at-room mentions with `@room` in body", async () => {
|
||||
const messageComposerState = `<a href="#" contenteditable="false" data-mention-type="at-room" style="some styling">@room</a> `;
|
||||
|
||||
const content = await createMessageContent(messageComposerState, false, { permalinkCreator });
|
||||
expect(content).toMatchObject({ body: "@room " });
|
||||
});
|
||||
|
||||
it("Should replace user mentions with user name in body", async () => {
|
||||
const messageComposerState = `<a href="https://matrix.to/#/@test_user:element.io" contenteditable="false" data-mention-type="user" style="some styling">a test user</a> `;
|
||||
|
||||
const content = await createMessageContent(messageComposerState, false, { permalinkCreator });
|
||||
|
||||
expect(content).toMatchObject({ body: "a test user " });
|
||||
});
|
||||
|
||||
it("Should replace room mentions with room mxid in body", async () => {
|
||||
const messageComposerState = `<a href="https://matrix.to/#/#test_room:element.io" contenteditable="false" data-mention-type="room" style="some styling">a test room</a> `;
|
||||
|
||||
const content = await createMessageContent(messageComposerState, false, { permalinkCreator });
|
||||
|
||||
expect(content).toMatchObject({
|
||||
body: "#test_room:element.io ",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,466 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { EventStatus, IEventRelation, MsgType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { IRoomState } from "../../../../../../../src/components/structures/RoomView";
|
||||
import {
|
||||
editMessage,
|
||||
sendMessage,
|
||||
} from "../../../../../../../src/components/views/rooms/wysiwyg_composer/utils/message";
|
||||
import { createTestClient, getRoomContext, mkEvent, mkStubRoom } from "../../../../../../test-utils";
|
||||
import defaultDispatcher from "../../../../../../../src/dispatcher/dispatcher";
|
||||
import SettingsStore from "../../../../../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../../../../../src/settings/SettingLevel";
|
||||
import { RoomPermalinkCreator } from "../../../../../../../src/utils/permalinks/Permalinks";
|
||||
import EditorStateTransfer from "../../../../../../../src/utils/EditorStateTransfer";
|
||||
import * as ConfirmRedactDialog from "../../../../../../../src/components/views/dialogs/ConfirmRedactDialog";
|
||||
import * as SlashCommands from "../../../../../../../src/SlashCommands";
|
||||
import * as Commands from "../../../../../../../src/editor/commands";
|
||||
import * as Reply from "../../../../../../../src/utils/Reply";
|
||||
import { MatrixClientPeg } from "../../../../../../../src/MatrixClientPeg";
|
||||
import { Action } from "../../../../../../../src/dispatcher/actions";
|
||||
|
||||
describe("message", () => {
|
||||
const permalinkCreator = {
|
||||
forEvent(eventId: string): string {
|
||||
return "$$permalink$$";
|
||||
},
|
||||
} as RoomPermalinkCreator;
|
||||
const message = "<i><b>hello</b> world</i>";
|
||||
const mockEvent = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: "myfakeroom",
|
||||
user: "myfakeuser",
|
||||
content: {
|
||||
msgtype: "m.text",
|
||||
body: "Replying to this",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: "Replying to this",
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
|
||||
const mockClient = createTestClient();
|
||||
mockClient.setDisplayName = jest.fn().mockResolvedValue({});
|
||||
mockClient.setRoomName = jest.fn().mockResolvedValue({});
|
||||
|
||||
const mockRoom = mkStubRoom("myfakeroom", "myfakeroom", mockClient) as any;
|
||||
mockRoom.findEventById = jest.fn((eventId) => {
|
||||
return eventId === mockEvent.getId() ? mockEvent : null;
|
||||
});
|
||||
|
||||
const defaultRoomContext: IRoomState = getRoomContext(mockRoom, {});
|
||||
|
||||
const spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch");
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("sendMessage", () => {
|
||||
it("Should not send empty html message", async () => {
|
||||
// When
|
||||
await sendMessage("", true, { roomContext: defaultRoomContext, mxClient: mockClient, permalinkCreator });
|
||||
|
||||
// Then
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledTimes(0);
|
||||
expect(spyDispatcher).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("Should not send message when there is no roomId", async () => {
|
||||
// When
|
||||
const mockRoomWithoutId = mkStubRoom("", "room without id", mockClient) as any;
|
||||
const mockRoomContextWithoutId: IRoomState = getRoomContext(mockRoomWithoutId, {});
|
||||
|
||||
await sendMessage(message, true, {
|
||||
roomContext: mockRoomContextWithoutId,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledTimes(0);
|
||||
expect(spyDispatcher).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
describe("calls client.sendMessage with", () => {
|
||||
it("a null argument if SendMessageParams is missing relation", async () => {
|
||||
// When
|
||||
await sendMessage(message, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith(expect.anything(), null, expect.anything());
|
||||
});
|
||||
it("a null argument if SendMessageParams has relation but relation is missing event_id", async () => {
|
||||
// When
|
||||
await sendMessage(message, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
relation: {},
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith(expect.anything(), null, expect.anything());
|
||||
});
|
||||
it("a null argument if SendMessageParams has relation but rel_type does not match THREAD_RELATION_TYPE.name", async () => {
|
||||
// When
|
||||
await sendMessage(message, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
relation: {
|
||||
event_id: "valid_id",
|
||||
rel_type: "m.does_not_match",
|
||||
},
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith(expect.anything(), null, expect.anything());
|
||||
});
|
||||
|
||||
it("the event_id if SendMessageParams has relation and rel_type matches THREAD_RELATION_TYPE.name", async () => {
|
||||
// When
|
||||
await sendMessage(message, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
relation: {
|
||||
event_id: "valid_id",
|
||||
rel_type: "m.thread",
|
||||
},
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith(expect.anything(), "valid_id", expect.anything());
|
||||
});
|
||||
});
|
||||
|
||||
it("Should send html message", async () => {
|
||||
// When
|
||||
await sendMessage(message, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
});
|
||||
|
||||
// Then
|
||||
const expectedContent = {
|
||||
body: "*__hello__ world*",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: "<i><b>hello</b> world</i>",
|
||||
msgtype: "m.text",
|
||||
};
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith("myfakeroom", null, expectedContent);
|
||||
expect(spyDispatcher).toHaveBeenCalledWith({ action: "message_sent" });
|
||||
});
|
||||
|
||||
it("Should send reply to html message", async () => {
|
||||
const mockReplyEvent = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: "myfakeroom",
|
||||
user: "myfakeuser2",
|
||||
content: { msgtype: "m.text", body: "My reply" },
|
||||
event: true,
|
||||
});
|
||||
|
||||
// When
|
||||
await sendMessage(message, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
replyToEvent: mockReplyEvent,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(spyDispatcher).toHaveBeenCalledWith({
|
||||
action: "reply_to_event",
|
||||
event: null,
|
||||
context: defaultRoomContext.timelineRenderingType,
|
||||
});
|
||||
|
||||
const expectedContent = {
|
||||
"body": "> <myfakeuser2> My reply\n\n*__hello__ world*",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body":
|
||||
'<mx-reply><blockquote><a href="$$permalink$$">In reply to</a>' +
|
||||
' <a href="https://matrix.to/#/myfakeuser2">myfakeuser2</a>' +
|
||||
"<br>My reply</blockquote></mx-reply><i><b>hello</b> world</i>",
|
||||
"msgtype": "m.text",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
event_id: mockReplyEvent.getId(),
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith("myfakeroom", null, expectedContent);
|
||||
});
|
||||
|
||||
it("Should scroll to bottom after sending a html message", async () => {
|
||||
// When
|
||||
SettingsStore.setValue("scrollToBottomOnMessageSent", null, SettingLevel.DEVICE, true);
|
||||
await sendMessage(message, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(spyDispatcher).toHaveBeenCalledWith({
|
||||
action: "scroll_to_bottom",
|
||||
timelineRenderingType: defaultRoomContext.timelineRenderingType,
|
||||
});
|
||||
});
|
||||
|
||||
it("Should handle emojis", async () => {
|
||||
// When
|
||||
await sendMessage("🎉", false, { roomContext: defaultRoomContext, mxClient: mockClient, permalinkCreator });
|
||||
|
||||
// Then
|
||||
expect(spyDispatcher).toHaveBeenCalledWith({ action: "effects.confetti" });
|
||||
});
|
||||
|
||||
describe("slash commands", () => {
|
||||
const getCommandSpy = jest.spyOn(SlashCommands, "getCommand");
|
||||
|
||||
it("calls getCommand for a message starting with a valid command", async () => {
|
||||
// When
|
||||
const validCommand = "/spoiler";
|
||||
await sendMessage(validCommand, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(getCommandSpy).toHaveBeenCalledWith(validCommand);
|
||||
});
|
||||
|
||||
it("does not call getCommand for valid command with invalid prefix", async () => {
|
||||
// When
|
||||
const invalidPrefixCommand = "//spoiler";
|
||||
await sendMessage(invalidPrefixCommand, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(getCommandSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("returns undefined when the command is not successful", async () => {
|
||||
// When
|
||||
const validCommand = "/spoiler";
|
||||
jest.spyOn(Commands, "runSlashCommand").mockResolvedValueOnce([
|
||||
{ body: "mock content", msgtype: MsgType.Text },
|
||||
false,
|
||||
]);
|
||||
|
||||
const result = await sendMessage(validCommand, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
// /spoiler is a .messages category command, /fireworks is an .effect category command
|
||||
const messagesAndEffectCategoryTestCases = ["/spoiler text", "/fireworks"];
|
||||
it.each(messagesAndEffectCategoryTestCases)(
|
||||
"does not add relations for a .messages or .effects category command if there is no relation to add",
|
||||
async (inputText) => {
|
||||
await sendMessage(inputText, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
});
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith(
|
||||
"myfakeroom",
|
||||
null,
|
||||
expect.not.objectContaining({ "m.relates_to": expect.any }),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(messagesAndEffectCategoryTestCases)(
|
||||
"adds relations for a .messages or .effects category command if there is a relation",
|
||||
async (inputText) => {
|
||||
const mockRelation: IEventRelation = {
|
||||
rel_type: "mock relation type",
|
||||
};
|
||||
await sendMessage(inputText, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
relation: mockRelation,
|
||||
});
|
||||
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith(
|
||||
"myfakeroom",
|
||||
null,
|
||||
expect.objectContaining({ "m.relates_to": expect.objectContaining(mockRelation) }),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("calls addReplyToMessageContent when there is an event to reply to", async () => {
|
||||
const addReplySpy = jest.spyOn(Reply, "addReplyToMessageContent");
|
||||
await sendMessage("input", true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
replyToEvent: mockEvent,
|
||||
});
|
||||
|
||||
expect(addReplySpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// these test cases are .action and .admin categories
|
||||
const otherCategoryTestCases = ["/nick new_nickname", "/roomname new_room_name"];
|
||||
it.each(otherCategoryTestCases)(
|
||||
"returns undefined when the command category is not .messages or .effects",
|
||||
async (input) => {
|
||||
const result = await sendMessage(input, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
replyToEvent: mockEvent,
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
},
|
||||
);
|
||||
|
||||
it("if user enters invalid command and then sends it anyway", async () => {
|
||||
// mock out returning a true value for `shouldSendAnyway` to avoid rendering the modal
|
||||
jest.spyOn(Commands, "shouldSendAnyway").mockResolvedValueOnce(true);
|
||||
const invalidCommandInput = "/badCommand";
|
||||
|
||||
await sendMessage(invalidCommandInput, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
});
|
||||
|
||||
// we expect the message to have been sent
|
||||
// and a composer focus action to have been dispatched
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith(
|
||||
"myfakeroom",
|
||||
null,
|
||||
expect.objectContaining({ body: invalidCommandInput }),
|
||||
);
|
||||
expect(spyDispatcher).toHaveBeenCalledWith(expect.objectContaining({ action: Action.FocusAComposer }));
|
||||
});
|
||||
|
||||
it("if user enters invalid command and then does not send, return undefined", async () => {
|
||||
// mock out returning a false value for `shouldSendAnyway` to avoid rendering the modal
|
||||
jest.spyOn(Commands, "shouldSendAnyway").mockResolvedValueOnce(false);
|
||||
const invalidCommandInput = "/badCommand";
|
||||
|
||||
const result = await sendMessage(invalidCommandInput, true, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
permalinkCreator,
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("editMessage", () => {
|
||||
const editorStateTransfer = new EditorStateTransfer(mockEvent);
|
||||
|
||||
it("Should cancel editing and ask for event removal when message is empty", async () => {
|
||||
// When
|
||||
const mockCreateRedactEventDialog = jest.spyOn(ConfirmRedactDialog, "createRedactEventDialog");
|
||||
|
||||
const mockEvent = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: "myfakeroom",
|
||||
user: "myfakeuser",
|
||||
content: { msgtype: "m.text", body: "Replying to this" },
|
||||
event: true,
|
||||
});
|
||||
const replacingEvent = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: "myfakeroom",
|
||||
user: "myfakeuser",
|
||||
content: { msgtype: "m.text", body: "ReplacingEvent" },
|
||||
event: true,
|
||||
});
|
||||
replacingEvent.setStatus(EventStatus.QUEUED);
|
||||
mockEvent.makeReplaced(replacingEvent);
|
||||
const editorStateTransfer = new EditorStateTransfer(mockEvent);
|
||||
|
||||
await editMessage("", { roomContext: defaultRoomContext, mxClient: mockClient, editorStateTransfer });
|
||||
|
||||
// Then
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledTimes(0);
|
||||
expect(mockClient.cancelPendingEvent).toHaveBeenCalledTimes(1);
|
||||
expect(mockCreateRedactEventDialog).toHaveBeenCalledTimes(1);
|
||||
expect(spyDispatcher).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("Should do nothing if the content is unmodified", async () => {
|
||||
// When
|
||||
await editMessage(mockEvent.getContent().body, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
editorStateTransfer,
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("Should send a message when the content is modified", async () => {
|
||||
// When
|
||||
const newMessage = `${mockEvent.getContent().body} new content`;
|
||||
await editMessage(newMessage, {
|
||||
roomContext: defaultRoomContext,
|
||||
mxClient: mockClient,
|
||||
editorStateTransfer,
|
||||
});
|
||||
|
||||
// Then
|
||||
const { msgtype, format } = mockEvent.getContent();
|
||||
const expectedContent = {
|
||||
"body": ` * ${newMessage}`,
|
||||
"formatted_body": ` * ${newMessage}`,
|
||||
"m.new_content": {
|
||||
body: "Replying to this new content",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: "Replying to this new content",
|
||||
msgtype: "m.text",
|
||||
},
|
||||
"m.relates_to": {
|
||||
event_id: mockEvent.getId(),
|
||||
rel_type: "m.replace",
|
||||
},
|
||||
msgtype,
|
||||
format,
|
||||
};
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith(mockEvent.getRoomId(), null, expectedContent);
|
||||
expect(spyDispatcher).toHaveBeenCalledWith({ action: "message_sent" });
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue