mirror of https://github.com/vector-im/riot-web
Add test for plain mode
parent
f1610dae3d
commit
fb751c3c7b
|
@ -43,7 +43,13 @@ export function PlainTextComposer({
|
|||
usePlainTextInitialization(initialContent, 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} />
|
||||
{ children?.(ref, composerFunctions) }
|
||||
</div>;
|
||||
|
|
|
@ -52,7 +52,7 @@ export const WysiwygComposer = memo(function WysiwygComposer(
|
|||
useSetCursorPosition(!isReady, ref);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div data-testid="WysiwygComposer" className={className}>
|
||||
<FormattingButtons composer={wysiwyg} formattingStates={formattingStates} />
|
||||
<Editor ref={ref} disabled={!isReady} />
|
||||
{ children?.(ref, wysiwyg) }
|
||||
|
|
|
@ -16,13 +16,10 @@ limitations under the License.
|
|||
|
||||
import { RefObject, useEffect } from "react";
|
||||
|
||||
import { setCursorPositionAtTheEnd } from "./utils";
|
||||
|
||||
export function usePlainTextInitialization(initialContent: string, ref: RefObject<HTMLElement>) {
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
ref.current.innerText = initialContent;
|
||||
setCursorPositionAtTheEnd(ref.current);
|
||||
}
|
||||
}, [ref, initialContent]);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
import { KeyboardEvent, SyntheticEvent, useCallback, useRef } from "react";
|
||||
|
||||
import { useInputEventProcessor } from "./useInputEventProcessor";
|
||||
import { useSettingValue } from "../../../../../hooks/useSettings";
|
||||
|
||||
function isDivElement(target: EventTarget): target is HTMLDivElement {
|
||||
return target instanceof HTMLDivElement;
|
||||
|
@ -26,25 +26,25 @@ export function usePlainTextListeners(onChange: (content: string) => void, onSen
|
|||
const ref = useRef<HTMLDivElement>();
|
||||
const send = useCallback((() => {
|
||||
if (ref.current) {
|
||||
ref.current.innerText = '';
|
||||
ref.current.innerHTML = '';
|
||||
}
|
||||
onSend();
|
||||
}), [ref, onSend]);
|
||||
|
||||
const inputEventProcessor = useInputEventProcessor(send);
|
||||
|
||||
const onInput = useCallback((event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>) => {
|
||||
if (isDivElement(event.target)) {
|
||||
onChange(event.target.innerText);
|
||||
onChange(event.target.innerHTML);
|
||||
}
|
||||
inputEventProcessor(event.nativeEvent);
|
||||
}, [onChange, inputEventProcessor]);
|
||||
}, [onChange]);
|
||||
|
||||
const isCtrlEnter = useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
|
||||
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]);
|
||||
}, [isCtrlEnter, send]);
|
||||
|
||||
return { ref, onInput, onPaste: onInput, onKeyDown };
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ import { Action } from "../../../../../src/dispatcher/actions";
|
|||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import { createTestClient, getRoomContext, mkEvent, mkStubRoom } from "../../../../test-utils";
|
||||
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();
|
||||
|
||||
|
@ -68,86 +70,119 @@ describe('SendWysiwygComposer', () => {
|
|||
|
||||
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(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={defaultRoomContext}>
|
||||
<SendWysiwygComposer onChange={onChange} onSend={onSend} disabled={disabled} isRichTextEnabled={true} />
|
||||
<SendWysiwygComposer onChange={onChange} onSend={onSend} disabled={disabled} isRichTextEnabled={isRichTextEnabled} />
|
||||
</RoomContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
it('Should focus when receiving an Action.FocusSendMessageComposer action', async () => {
|
||||
// Given we don't have focus
|
||||
customRender(jest.fn(), jest.fn());
|
||||
screen.getByLabelText('Bold').focus();
|
||||
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||
it('Should render WysiwygComposer when isRichTextEnabled is at true', () => {
|
||||
// When
|
||||
customRender(jest.fn(), jest.fn(), false, true);
|
||||
|
||||
// When we send the right action
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.FocusSendMessageComposer,
|
||||
context: null,
|
||||
});
|
||||
|
||||
// Then the component gets the focus
|
||||
await waitFor(() => expect(screen.getByRole('textbox')).toHaveFocus());
|
||||
// Then
|
||||
expect(screen.getByTestId('WysiwygComposer')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Should focus and clear when receiving an Action.ClearAndFocusSendMessageComposer', async () => {
|
||||
// Given we don't have focus
|
||||
customRender(jest.fn(), jest.fn());
|
||||
screen.getByLabelText('Bold').focus();
|
||||
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||
it('Should render PlainTextComposer when isRichTextEnabled is at false', () => {
|
||||
// When
|
||||
customRender(jest.fn(), jest.fn(), false, false);
|
||||
|
||||
// When we send the right action
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.ClearAndFocusSendMessageComposer,
|
||||
context: null,
|
||||
});
|
||||
|
||||
// Then the component gets the focus
|
||||
await waitFor(() => expect(screen.getByRole('textbox')).toHaveFocus());
|
||||
expect(mockClear).toBeCalledTimes(1);
|
||||
// Then
|
||||
expect(screen.getByTestId('PlainTextComposer')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Should focus when receiving a reply_to_event action', async () => {
|
||||
// Given we don't have focus
|
||||
customRender(jest.fn(), jest.fn());
|
||||
screen.getByLabelText('Bold').focus();
|
||||
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||
describe.each([{ isRichTextEnabled: true }, { isRichTextEnabled: false }])(
|
||||
'Should focus when receiving an Action.FocusSendMessageComposer action',
|
||||
({ isRichTextEnabled }) => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
// When we send the right action
|
||||
defaultDispatcher.dispatch({
|
||||
action: "reply_to_event",
|
||||
context: null,
|
||||
it('Should focus when receiving an Action.FocusSendMessageComposer action', async () => {
|
||||
// Given we don't have focus
|
||||
customRender(jest.fn(), jest.fn(), false, isRichTextEnabled);
|
||||
document.head.focus();
|
||||
// screen.getByLabelText('Bold').focus();
|
||||
// expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||
|
||||
// When we send the right action
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.FocusSendMessageComposer,
|
||||
context: null,
|
||||
});
|
||||
|
||||
// Then the component gets the focus
|
||||
await waitFor(() => expect(screen.getByRole('textbox')).toHaveFocus());
|
||||
});
|
||||
|
||||
it('Should focus and clear when receiving an Action.ClearAndFocusSendMessageComposer', async () => {
|
||||
// Given we don't have focus
|
||||
const mock = jest.spyOn(useComposerFunctions, 'useComposerFunctions');
|
||||
mock.mockReturnValue({ clear: mockClear });
|
||||
customRender(jest.fn(), jest.fn(), false, isRichTextEnabled);
|
||||
// screen.getByLabelText('Bold').focus();
|
||||
// expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||
|
||||
// When we send the right action
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.ClearAndFocusSendMessageComposer,
|
||||
context: null,
|
||||
});
|
||||
|
||||
// Then the component gets the focus
|
||||
await waitFor(() => expect(screen.getByRole('textbox')).toHaveFocus());
|
||||
expect(mockClear).toBeCalledTimes(1);
|
||||
|
||||
mock.mockRestore();
|
||||
});
|
||||
|
||||
it('Should focus when receiving a reply_to_event action', async () => {
|
||||
// Given we don't have focus
|
||||
customRender(jest.fn(), jest.fn(), false, isRichTextEnabled);
|
||||
// screen.getByLabelText('Bold').focus();
|
||||
// expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||
|
||||
// When we send the right action
|
||||
defaultDispatcher.dispatch({
|
||||
action: "reply_to_event",
|
||||
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(jest.fn(), jest.fn(), true, isRichTextEnabled);
|
||||
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||
|
||||
// When we send an action that would cause us to get focus
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.FocusSendMessageComposer,
|
||||
context: null,
|
||||
});
|
||||
// (Send a second event to exercise the clearTimeout logic)
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.FocusSendMessageComposer,
|
||||
context: null,
|
||||
});
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
|
||||
// Then we don't get it because we are disabled
|
||||
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||
});
|
||||
});
|
||||
|
||||
// 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(jest.fn(), jest.fn(), true);
|
||||
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
||||
|
||||
// When we send an action that would cause us to get focus
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.FocusSendMessageComposer,
|
||||
context: null,
|
||||
});
|
||||
// (Send a second event to exercise the clearTimeout logic)
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.FocusSendMessageComposer,
|
||||
context: null,
|
||||
});
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
|
||||
// Then we don't get it because we are disabled
|
||||
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 { 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 }
|
||||
from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer";
|
||||
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||
|
@ -54,36 +50,14 @@ jest.mock("@matrix-org/matrix-wysiwyg", () => ({
|
|||
}));
|
||||
|
||||
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 = (
|
||||
onChange = (_content: string) => void 0,
|
||||
onSend = () => void 0,
|
||||
disabled = false,
|
||||
initialContent?: string) => {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={defaultRoomContext}>
|
||||
<WysiwygComposer onChange={onChange} onSend={onSend} disabled={disabled} initialContent={initialContent} />
|
||||
</RoomContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
<WysiwygComposer onChange={onChange} onSend={onSend} disabled={disabled} initialContent={initialContent} />,
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue