Prepare for repo merge
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
0f670b8dc0
commit
b084ff2313
807 changed files with 0 additions and 0 deletions
|
@ -0,0 +1,303 @@
|
|||
/*
|
||||
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 "@testing-library/jest-dom";
|
||||
import React from "react";
|
||||
import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { flushPromises, mkEvent } from "../../../../test-utils";
|
||||
import { EditWysiwygComposer } from "../../../../../src/components/views/rooms/wysiwyg_composer";
|
||||
import EditorStateTransfer from "../../../../../src/utils/EditorStateTransfer";
|
||||
import { Emoji } from "../../../../../src/components/views/rooms/wysiwyg_composer/components/Emoji";
|
||||
import { ChevronFace } from "../../../../../src/components/structures/ContextMenu";
|
||||
import { ComposerInsertPayload, ComposerType } from "../../../../../src/dispatcher/payloads/ComposerInsertPayload";
|
||||
import { ActionPayload } from "../../../../../src/dispatcher/payloads";
|
||||
import * as EmojiButton from "../../../../../src/components/views/rooms/EmojiButton";
|
||||
import { createMocks } from "./utils";
|
||||
|
||||
describe("EditWysiwygComposer", () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const { editorStateTransfer, defaultRoomContext, mockClient, mockEvent } = createMocks();
|
||||
|
||||
const customRender = (
|
||||
disabled = false,
|
||||
_editorStateTransfer = editorStateTransfer,
|
||||
client = mockClient,
|
||||
roomContext = defaultRoomContext,
|
||||
) => {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={roomContext}>
|
||||
<EditWysiwygComposer disabled={disabled} editorStateTransfer={_editorStateTransfer} />
|
||||
</RoomContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
beforeAll(
|
||||
async () => {
|
||||
// Load the dynamic import
|
||||
const component = customRender(false);
|
||||
await component.findByRole("textbox");
|
||||
component.unmount();
|
||||
},
|
||||
// it can take a while to load the wasm
|
||||
20000,
|
||||
);
|
||||
|
||||
it("Should not render the component when not ready", async () => {
|
||||
// When
|
||||
const { rerender } = customRender(false);
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveAttribute("contentEditable", "true"));
|
||||
|
||||
rerender(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={{ ...defaultRoomContext, room: undefined }}>
|
||||
<EditWysiwygComposer disabled={false} editorStateTransfer={editorStateTransfer} />
|
||||
</RoomContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
// Then
|
||||
await waitFor(() => expect(screen.queryByRole("textbox")).toBeNull());
|
||||
});
|
||||
|
||||
describe("Initialize with content", () => {
|
||||
it("Should initialize useWysiwyg with html content", async () => {
|
||||
// When
|
||||
customRender(false, editorStateTransfer);
|
||||
|
||||
// Then
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveAttribute("contentEditable", "true"), {
|
||||
timeout: 2000,
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole("textbox")).toContainHTML(mockEvent.getContent()["formatted_body"]),
|
||||
);
|
||||
});
|
||||
|
||||
it("Should initialize useWysiwyg with plain text content", async () => {
|
||||
// When
|
||||
const mockEvent = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: "myfakeroom",
|
||||
user: "myfakeuser",
|
||||
content: {
|
||||
msgtype: "m.text",
|
||||
body: "Replying to this",
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
const editorStateTransfer = new EditorStateTransfer(mockEvent);
|
||||
customRender(false, editorStateTransfer);
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveAttribute("contentEditable", "true"));
|
||||
|
||||
// Then
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toContainHTML(mockEvent.getContent()["body"]));
|
||||
});
|
||||
|
||||
it("Should ignore when formatted_body is not filled", async () => {
|
||||
// When
|
||||
const mockEvent = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: "myfakeroom",
|
||||
user: "myfakeuser",
|
||||
content: {
|
||||
msgtype: "m.text",
|
||||
body: "Replying to this",
|
||||
format: "org.matrix.custom.html",
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
|
||||
const editorStateTransfer = new EditorStateTransfer(mockEvent);
|
||||
customRender(false, editorStateTransfer);
|
||||
|
||||
// Then
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveAttribute("contentEditable", "true"));
|
||||
});
|
||||
|
||||
it("Should strip <mx-reply> tag from initial content", async () => {
|
||||
// When
|
||||
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: "<mx-reply>Reply</mx-reply>My content",
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
|
||||
const editorStateTransfer = new EditorStateTransfer(mockEvent);
|
||||
customRender(false, editorStateTransfer);
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveAttribute("contentEditable", "true"));
|
||||
|
||||
// Then
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("textbox")).not.toContainHTML("<mx-reply>Reply</mx-reply>");
|
||||
expect(screen.getByRole("textbox")).toContainHTML("My content");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edit and save actions", () => {
|
||||
let spyDispatcher: jest.SpyInstance<void, [payload: ActionPayload, sync?: boolean]>;
|
||||
beforeEach(async () => {
|
||||
spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch");
|
||||
customRender();
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveAttribute("contentEditable", "true"));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
spyDispatcher.mockRestore();
|
||||
});
|
||||
|
||||
it("Should cancel edit on cancel button click", async () => {
|
||||
// When
|
||||
screen.getByText("Cancel").click();
|
||||
|
||||
// Then
|
||||
expect(spyDispatcher).toHaveBeenCalledWith({
|
||||
action: Action.EditEvent,
|
||||
event: null,
|
||||
timelineRenderingType: defaultRoomContext.timelineRenderingType,
|
||||
});
|
||||
expect(spyDispatcher).toHaveBeenCalledWith({
|
||||
action: Action.FocusSendMessageComposer,
|
||||
context: defaultRoomContext.timelineRenderingType,
|
||||
});
|
||||
});
|
||||
|
||||
it("Should send message on save button click", async () => {
|
||||
// When
|
||||
fireEvent.input(screen.getByRole("textbox"), {
|
||||
data: "foo bar",
|
||||
inputType: "insertText",
|
||||
});
|
||||
await waitFor(() => expect(screen.getByText("Save")).not.toHaveAttribute("disabled"));
|
||||
|
||||
// Then
|
||||
screen.getByText("Save").click();
|
||||
const expectedContent = {
|
||||
"body": ` * foo bar`,
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": ` * foo bar`,
|
||||
"m.new_content": {
|
||||
body: "foo bar",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: "foo bar",
|
||||
msgtype: "m.text",
|
||||
},
|
||||
"m.relates_to": {
|
||||
event_id: mockEvent.getId(),
|
||||
rel_type: "m.replace",
|
||||
},
|
||||
"msgtype": "m.text",
|
||||
};
|
||||
await waitFor(() =>
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith(mockEvent.getRoomId(), null, expectedContent),
|
||||
);
|
||||
|
||||
expect(spyDispatcher).toHaveBeenCalledWith({ action: "message_sent" });
|
||||
});
|
||||
});
|
||||
|
||||
it("Should focus when receiving an Action.FocusEditMessageComposer action", async () => {
|
||||
// Given we don't have focus
|
||||
customRender();
|
||||
screen.getByLabelText("Bold").focus();
|
||||
expect(screen.getByRole("textbox")).not.toHaveFocus();
|
||||
|
||||
// When we send the right action
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.FocusEditMessageComposer,
|
||||
context: null,
|
||||
});
|
||||
|
||||
// Then the component gets the focus
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveFocus());
|
||||
});
|
||||
|
||||
it("Should not focus when disabled", async () => {
|
||||
// Given we don't have focus and we are disabled
|
||||
customRender(true);
|
||||
screen.getByLabelText("Bold").focus();
|
||||
expect(screen.getByRole("textbox")).not.toHaveFocus();
|
||||
|
||||
// When we send an action that would cause us to get focus
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.FocusEditMessageComposer,
|
||||
context: null,
|
||||
});
|
||||
// (Send a second event to exercise the clearTimeout logic)
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.FocusEditMessageComposer,
|
||||
context: null,
|
||||
});
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
// Then we don't get it because we are disabled
|
||||
expect(screen.getByRole("textbox")).not.toHaveFocus();
|
||||
});
|
||||
|
||||
it("Should add emoji", async () => {
|
||||
// When
|
||||
|
||||
// We are not testing here the emoji button (open modal, select emoji ...)
|
||||
// Instead we are directly firing an emoji to make the test easier to write
|
||||
jest.spyOn(EmojiButton, "EmojiButton").mockImplementation(
|
||||
({ addEmoji }: { addEmoji: (emoji: string) => void }) => {
|
||||
return (
|
||||
<button aria-label="Emoji" type="button" onClick={() => addEmoji("🦫")}>
|
||||
Emoji
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={defaultRoomContext}>
|
||||
<EditWysiwygComposer editorStateTransfer={editorStateTransfer} />
|
||||
<Emoji menuPosition={{ chevronFace: ChevronFace.Top }} />
|
||||
</RoomContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
// Same behavior as in RoomView.tsx
|
||||
// RoomView is re-dispatching the composer messages.
|
||||
// It adds the composerType fields where the value refers if the composer is in editing or not
|
||||
// The listeners in the RTE ignore the message if the composerType is missing in the payload
|
||||
const dispatcherRef = defaultDispatcher.register((payload: ActionPayload) => {
|
||||
defaultDispatcher.dispatch<ComposerInsertPayload>({
|
||||
...(payload as ComposerInsertPayload),
|
||||
composerType: ComposerType.Edit,
|
||||
});
|
||||
});
|
||||
|
||||
screen.getByLabelText("Emoji").click();
|
||||
|
||||
// Then
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(/🦫/));
|
||||
defaultDispatcher.unregister(dispatcherRef);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue