Add test for plain mode
This commit is contained in:
parent
f1610dae3d
commit
fb751c3c7b
7 changed files with 213 additions and 107 deletions
|
@ -43,7 +43,13 @@ export function PlainTextComposer({
|
||||||
usePlainTextInitialization(initialContent, ref);
|
usePlainTextInitialization(initialContent, ref);
|
||||||
useSetCursorPosition(disabled, ref);
|
useSetCursorPosition(disabled, ref);
|
||||||
|
|
||||||
return <div className={className} onInput={onInput} onPaste={onPaste} onKeyDown={onKeyDown}>
|
return <div
|
||||||
|
data-testid="PlainTextComposer"
|
||||||
|
className={className}
|
||||||
|
onInput={onInput}
|
||||||
|
onPaste={onPaste}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
>
|
||||||
<Editor ref={ref} disabled={disabled} />
|
<Editor ref={ref} disabled={disabled} />
|
||||||
{ children?.(ref, composerFunctions) }
|
{ children?.(ref, composerFunctions) }
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -52,7 +52,7 @@ export const WysiwygComposer = memo(function WysiwygComposer(
|
||||||
useSetCursorPosition(!isReady, ref);
|
useSetCursorPosition(!isReady, ref);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div data-testid="WysiwygComposer" className={className}>
|
||||||
<FormattingButtons composer={wysiwyg} formattingStates={formattingStates} />
|
<FormattingButtons composer={wysiwyg} formattingStates={formattingStates} />
|
||||||
<Editor ref={ref} disabled={!isReady} />
|
<Editor ref={ref} disabled={!isReady} />
|
||||||
{ children?.(ref, wysiwyg) }
|
{ children?.(ref, wysiwyg) }
|
||||||
|
|
|
@ -16,13 +16,10 @@ limitations under the License.
|
||||||
|
|
||||||
import { RefObject, useEffect } from "react";
|
import { RefObject, useEffect } from "react";
|
||||||
|
|
||||||
import { setCursorPositionAtTheEnd } from "./utils";
|
|
||||||
|
|
||||||
export function usePlainTextInitialization(initialContent: string, ref: RefObject<HTMLElement>) {
|
export function usePlainTextInitialization(initialContent: string, ref: RefObject<HTMLElement>) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
ref.current.innerText = initialContent;
|
ref.current.innerText = initialContent;
|
||||||
setCursorPositionAtTheEnd(ref.current);
|
|
||||||
}
|
}
|
||||||
}, [ref, initialContent]);
|
}, [ref, initialContent]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import { KeyboardEvent, SyntheticEvent, useCallback, useRef } from "react";
|
import { KeyboardEvent, SyntheticEvent, useCallback, useRef } from "react";
|
||||||
|
|
||||||
import { useInputEventProcessor } from "./useInputEventProcessor";
|
import { useSettingValue } from "../../../../../hooks/useSettings";
|
||||||
|
|
||||||
function isDivElement(target: EventTarget): target is HTMLDivElement {
|
function isDivElement(target: EventTarget): target is HTMLDivElement {
|
||||||
return target instanceof HTMLDivElement;
|
return target instanceof HTMLDivElement;
|
||||||
|
@ -26,25 +26,25 @@ export function usePlainTextListeners(onChange: (content: string) => void, onSen
|
||||||
const ref = useRef<HTMLDivElement>();
|
const ref = useRef<HTMLDivElement>();
|
||||||
const send = useCallback((() => {
|
const send = useCallback((() => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
ref.current.innerText = '';
|
ref.current.innerHTML = '';
|
||||||
}
|
}
|
||||||
onSend();
|
onSend();
|
||||||
}), [ref, onSend]);
|
}), [ref, onSend]);
|
||||||
|
|
||||||
const inputEventProcessor = useInputEventProcessor(send);
|
|
||||||
|
|
||||||
const onInput = useCallback((event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>) => {
|
const onInput = useCallback((event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>) => {
|
||||||
if (isDivElement(event.target)) {
|
if (isDivElement(event.target)) {
|
||||||
onChange(event.target.innerText);
|
onChange(event.target.innerHTML);
|
||||||
}
|
}
|
||||||
inputEventProcessor(event.nativeEvent);
|
}, [onChange]);
|
||||||
}, [onChange, inputEventProcessor]);
|
|
||||||
|
|
||||||
|
const isCtrlEnter = useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
|
||||||
const onKeyDown = useCallback((event: KeyboardEvent<HTMLDivElement>) => {
|
const onKeyDown = useCallback((event: KeyboardEvent<HTMLDivElement>) => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter' && !event.shiftKey && (!isCtrlEnter || (isCtrlEnter && event.ctrlKey))) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
send();
|
send();
|
||||||
}
|
}
|
||||||
}, [send]);
|
}, [isCtrlEnter, send]);
|
||||||
|
|
||||||
return { ref, onInput, onPaste: onInput, onKeyDown };
|
return { ref, onInput, onPaste: onInput, onKeyDown };
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ import { Action } from "../../../../../src/dispatcher/actions";
|
||||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||||
import { createTestClient, getRoomContext, mkEvent, mkStubRoom } from "../../../../test-utils";
|
import { createTestClient, getRoomContext, mkEvent, mkStubRoom } from "../../../../test-utils";
|
||||||
import { SendWysiwygComposer } from "../../../../../src/components/views/rooms/wysiwyg_composer";
|
import { SendWysiwygComposer } from "../../../../../src/components/views/rooms/wysiwyg_composer";
|
||||||
|
import * as useComposerFunctions
|
||||||
|
from "../../../../../src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions";
|
||||||
|
|
||||||
const mockClear = jest.fn();
|
const mockClear = jest.fn();
|
||||||
|
|
||||||
|
@ -68,21 +70,49 @@ describe('SendWysiwygComposer', () => {
|
||||||
|
|
||||||
const defaultRoomContext: IRoomState = getRoomContext(mockRoom, {});
|
const defaultRoomContext: IRoomState = getRoomContext(mockRoom, {});
|
||||||
|
|
||||||
const customRender = (onChange = (_content: string) => void 0, onSend = () => void 0, disabled = false) => {
|
const customRender = (
|
||||||
|
onChange = (_content: string) => void 0,
|
||||||
|
onSend = () => void 0,
|
||||||
|
disabled = false,
|
||||||
|
isRichTextEnabled = true) => {
|
||||||
return render(
|
return render(
|
||||||
<MatrixClientContext.Provider value={mockClient}>
|
<MatrixClientContext.Provider value={mockClient}>
|
||||||
<RoomContext.Provider value={defaultRoomContext}>
|
<RoomContext.Provider value={defaultRoomContext}>
|
||||||
<SendWysiwygComposer onChange={onChange} onSend={onSend} disabled={disabled} isRichTextEnabled={true} />
|
<SendWysiwygComposer onChange={onChange} onSend={onSend} disabled={disabled} isRichTextEnabled={isRichTextEnabled} />
|
||||||
</RoomContext.Provider>
|
</RoomContext.Provider>
|
||||||
</MatrixClientContext.Provider>,
|
</MatrixClientContext.Provider>,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
it('Should render WysiwygComposer when isRichTextEnabled is at true', () => {
|
||||||
|
// When
|
||||||
|
customRender(jest.fn(), jest.fn(), false, true);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(screen.getByTestId('WysiwygComposer')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should render PlainTextComposer when isRichTextEnabled is at false', () => {
|
||||||
|
// When
|
||||||
|
customRender(jest.fn(), jest.fn(), false, false);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(screen.getByTestId('PlainTextComposer')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each([{ isRichTextEnabled: true }, { isRichTextEnabled: false }])(
|
||||||
|
'Should focus when receiving an Action.FocusSendMessageComposer action',
|
||||||
|
({ isRichTextEnabled }) => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
it('Should focus when receiving an Action.FocusSendMessageComposer action', async () => {
|
it('Should focus when receiving an Action.FocusSendMessageComposer action', async () => {
|
||||||
// Given we don't have focus
|
// Given we don't have focus
|
||||||
customRender(jest.fn(), jest.fn());
|
customRender(jest.fn(), jest.fn(), false, isRichTextEnabled);
|
||||||
screen.getByLabelText('Bold').focus();
|
document.head.focus();
|
||||||
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
// screen.getByLabelText('Bold').focus();
|
||||||
|
// expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||||
|
|
||||||
// When we send the right action
|
// When we send the right action
|
||||||
defaultDispatcher.dispatch({
|
defaultDispatcher.dispatch({
|
||||||
|
@ -96,9 +126,11 @@ describe('SendWysiwygComposer', () => {
|
||||||
|
|
||||||
it('Should focus and clear when receiving an Action.ClearAndFocusSendMessageComposer', async () => {
|
it('Should focus and clear when receiving an Action.ClearAndFocusSendMessageComposer', async () => {
|
||||||
// Given we don't have focus
|
// Given we don't have focus
|
||||||
customRender(jest.fn(), jest.fn());
|
const mock = jest.spyOn(useComposerFunctions, 'useComposerFunctions');
|
||||||
screen.getByLabelText('Bold').focus();
|
mock.mockReturnValue({ clear: mockClear });
|
||||||
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
customRender(jest.fn(), jest.fn(), false, isRichTextEnabled);
|
||||||
|
// screen.getByLabelText('Bold').focus();
|
||||||
|
// expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||||
|
|
||||||
// When we send the right action
|
// When we send the right action
|
||||||
defaultDispatcher.dispatch({
|
defaultDispatcher.dispatch({
|
||||||
|
@ -109,13 +141,15 @@ describe('SendWysiwygComposer', () => {
|
||||||
// Then the component gets the focus
|
// Then the component gets the focus
|
||||||
await waitFor(() => expect(screen.getByRole('textbox')).toHaveFocus());
|
await waitFor(() => expect(screen.getByRole('textbox')).toHaveFocus());
|
||||||
expect(mockClear).toBeCalledTimes(1);
|
expect(mockClear).toBeCalledTimes(1);
|
||||||
|
|
||||||
|
mock.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should focus when receiving a reply_to_event action', async () => {
|
it('Should focus when receiving a reply_to_event action', async () => {
|
||||||
// Given we don't have focus
|
// Given we don't have focus
|
||||||
customRender(jest.fn(), jest.fn());
|
customRender(jest.fn(), jest.fn(), false, isRichTextEnabled);
|
||||||
screen.getByLabelText('Bold').focus();
|
// screen.getByLabelText('Bold').focus();
|
||||||
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
// expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||||
|
|
||||||
// When we send the right action
|
// When we send the right action
|
||||||
defaultDispatcher.dispatch({
|
defaultDispatcher.dispatch({
|
||||||
|
@ -129,7 +163,7 @@ describe('SendWysiwygComposer', () => {
|
||||||
|
|
||||||
it('Should not focus when disabled', async () => {
|
it('Should not focus when disabled', async () => {
|
||||||
// Given we don't have focus and we are disabled
|
// Given we don't have focus and we are disabled
|
||||||
customRender(jest.fn(), jest.fn(), true);
|
customRender(jest.fn(), jest.fn(), true, isRichTextEnabled);
|
||||||
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||||
|
|
||||||
// When we send an action that would cause us to get focus
|
// When we send an action that would cause us to get focus
|
||||||
|
@ -149,5 +183,6 @@ describe('SendWysiwygComposer', () => {
|
||||||
// Then we don't get it because we are disabled
|
// Then we don't get it because we are disabled
|
||||||
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
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, screen } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
|
||||||
|
import { PlainTextComposer }
|
||||||
|
from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/PlainTextComposer";
|
||||||
|
|
||||||
|
// Work around missing ClipboardEvent type
|
||||||
|
class MyClipboardEvent {}
|
||||||
|
window.ClipboardEvent = MyClipboardEvent as any;
|
||||||
|
|
||||||
|
describe('PlainTextComposer', () => {
|
||||||
|
const customRender = (
|
||||||
|
onChange = (_content: string) => void 0,
|
||||||
|
onSend = () => void 0,
|
||||||
|
disabled = false,
|
||||||
|
initialContent?: string) => {
|
||||||
|
return render(
|
||||||
|
<PlainTextComposer onChange={onChange} onSend={onSend} disabled={disabled} initialContent={initialContent} />,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('Should have contentEditable at false when disabled', () => {
|
||||||
|
// When
|
||||||
|
customRender(jest.fn(), jest.fn(), true);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(screen.getByRole('textbox')).toHaveAttribute('contentEditable', "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should have focus', () => {
|
||||||
|
// When
|
||||||
|
customRender(jest.fn(), jest.fn(), false);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(screen.getByRole('textbox')).toHaveFocus();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should call onChange handler', async () => {
|
||||||
|
// When
|
||||||
|
const content = 'content';
|
||||||
|
const onChange = jest.fn();
|
||||||
|
customRender(onChange, jest.fn());
|
||||||
|
await userEvent.type(screen.getByRole('textbox'), content);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(onChange).toBeCalledWith(content);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should call onSend when Enter is pressed', async () => {
|
||||||
|
//When
|
||||||
|
const onSend = jest.fn();
|
||||||
|
customRender(jest.fn(), onSend);
|
||||||
|
await userEvent.type(screen.getByRole('textbox'), '{enter}');
|
||||||
|
|
||||||
|
// Then it sends a message
|
||||||
|
expect(onSend).toBeCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should clear textbox content when clear is called', async () => {
|
||||||
|
//When
|
||||||
|
let composer;
|
||||||
|
render(
|
||||||
|
<PlainTextComposer onChange={jest.fn()} onSend={jest.fn()}>
|
||||||
|
{ (ref, composerFunctions) => {
|
||||||
|
composer = composerFunctions;
|
||||||
|
return null;
|
||||||
|
} }
|
||||||
|
</PlainTextComposer>,
|
||||||
|
);
|
||||||
|
await userEvent.type(screen.getByRole('textbox'), 'content');
|
||||||
|
expect(screen.getByRole('textbox').innerHTML).toBe('content');
|
||||||
|
composer.clear();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(screen.getByRole('textbox').innerHTML).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -19,10 +19,6 @@ import React from "react";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import { InputEventProcessor, Wysiwyg, WysiwygProps } from "@matrix-org/matrix-wysiwyg";
|
import { InputEventProcessor, Wysiwyg, WysiwygProps } from "@matrix-org/matrix-wysiwyg";
|
||||||
|
|
||||||
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
|
||||||
import { IRoomState } from "../../../../../../src/components/structures/RoomView";
|
|
||||||
import { createTestClient, getRoomContext, mkEvent, mkStubRoom } from "../../../../../test-utils";
|
|
||||||
import RoomContext from "../../../../../../src/contexts/RoomContext";
|
|
||||||
import { WysiwygComposer }
|
import { WysiwygComposer }
|
||||||
from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer";
|
from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer";
|
||||||
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||||
|
@ -54,36 +50,14 @@ jest.mock("@matrix-org/matrix-wysiwyg", () => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('WysiwygComposer', () => {
|
describe('WysiwygComposer', () => {
|
||||||
afterEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
const mockClient = createTestClient();
|
|
||||||
const mockEvent = mkEvent({
|
|
||||||
type: "m.room.message",
|
|
||||||
room: 'myfakeroom',
|
|
||||||
user: 'myfakeuser',
|
|
||||||
content: { "msgtype": "m.text", "body": "Replying to this" },
|
|
||||||
event: true,
|
|
||||||
});
|
|
||||||
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 customRender = (
|
const customRender = (
|
||||||
onChange = (_content: string) => void 0,
|
onChange = (_content: string) => void 0,
|
||||||
onSend = () => void 0,
|
onSend = () => void 0,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
initialContent?: string) => {
|
initialContent?: string) => {
|
||||||
return render(
|
return render(
|
||||||
<MatrixClientContext.Provider value={mockClient}>
|
<WysiwygComposer onChange={onChange} onSend={onSend} disabled={disabled} initialContent={initialContent} />,
|
||||||
<RoomContext.Provider value={defaultRoomContext}>
|
|
||||||
<WysiwygComposer onChange={onChange} onSend={onSend} disabled={disabled} initialContent={initialContent} />
|
|
||||||
</RoomContext.Provider>
|
|
||||||
</MatrixClientContext.Provider>,
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue