Handle command completions in RTE (#10521)
* pass handleCommand prop down and use it in WysiwygAutocomplete * allow a command to generate a query from buildQuery * port command functionality into the sendMessage util * tidy up comments * remove use of shouldSend and update comments * remove console log * make logic more explicit and amend comment * uncomment replyToEvent block * update util test * remove commented out test * use local text over import from current composer * expand tests * expand tests * handle the FocusAComposer action for the wysiwyg composer * remove TODO comment * remove TODO * test for action dispatch * fix failing tests * tidy up tests * fix TS error and improve typing * fix TS error * amend return types for sendMessage, editMessage * fix null content TS error * fix another null content TS error * use as to correct final TS error * remove undefined argument * try to fix TS errors for editMessage function usage * tidy up * add TODO * improve comments * update comment
This commit is contained in:
parent
7ef7ccb55f
commit
3fa6f8cbf0
10 changed files with 268 additions and 31 deletions
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventStatus } from "matrix-js-sdk/src/matrix";
|
||||
import { EventStatus, IEventRelation } 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";
|
||||
|
@ -25,6 +25,11 @@ 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 = {
|
||||
|
@ -47,6 +52,9 @@ describe("message", () => {
|
|||
});
|
||||
|
||||
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;
|
||||
|
@ -56,8 +64,13 @@ describe("message", () => {
|
|||
|
||||
const spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch");
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
beforeEach(() => {
|
||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("sendMessage", () => {
|
||||
|
@ -226,6 +239,153 @@ describe("message", () => {
|
|||
// 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([{ content: "mock content" }, 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", () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue