Merge pull request #9410 from matrix-org/feat/add-formating-buttons-to-wysiwyg

Add formatting buttons to the WysiwygComposer
pull/28788/head^2
Florian Duros 2022-10-14 11:19:32 +02:00 committed by GitHub
commit 77543b32d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 368 additions and 39 deletions

View File

@ -57,7 +57,7 @@
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/analytics-events": "^0.2.0",
"@matrix-org/matrix-wysiwyg": "^0.0.2",
"@matrix-org/matrix-wysiwyg": "^0.2.0",
"@matrix-org/react-sdk-module-api": "^0.0.3",
"@sentry/browser": "^6.11.0",
"@sentry/tracing": "^6.11.0",

View File

@ -297,6 +297,7 @@
@import "./views/rooms/_TopUnreadMessagesBar.pcss";
@import "./views/rooms/_VoiceRecordComposerTile.pcss";
@import "./views/rooms/_WhoIsTypingTile.pcss";
@import "./views/rooms/wysiwyg_composer/_FormattingButtons.pcss";
@import "./views/rooms/wysiwyg_composer/_WysiwygComposer.pcss";
@import "./views/settings/_AvatarSetting.pcss";
@import "./views/settings/_CrossSigningPanel.pcss";

View File

@ -233,6 +233,17 @@ limitations under the License.
}
}
/*
The wysisyg composer increase the size of the MessageComposer. We temporary move the buttons
Soon the dom structure of the MessageComposer will change with the next evolution of the wysiwyg composer
and this workaround will disappear
*/
.mx_MessageComposer_wysiwyg {
.mx_MessageComposer_e2eIcon.mx_E2EIcon,.mx_MessageComposer_button, .mx_MessageComposer_sendMessage {
margin-top: 22px;
}
}
.mx_MessageComposer_upload::before {
mask-image: url('$(res)/img/element-icons/room/composer/attach.svg');
}

View File

@ -0,0 +1,118 @@
/*
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.
*/
.mx_FormattingButtons {
display: flex;
justify-content: start;
.mx_FormattingButtons_Button {
--size: 28px;
position: relative;
cursor: pointer;
height: var(--size);
line-height: var(--size);
width: auto;
padding-left: 22px;
margin-right: 8px;
background-color: transparent;
border: none;
&:first-child {
margin-left: 12px;
}
&:last-child {
margin-right: auto;
}
&::before {
content: '';
position: absolute;
top: 6px;
left: 6px;
height: 16px;
width: 16px;
background-color: $icon-button-color;
mask-repeat: no-repeat;
mask-size: contain;
mask-position: center;
}
&::after {
content: '';
position: absolute;
left: 0;
top: 0;
z-index: 0;
width: var(--size);
height: var(--size);
border-radius: 5px;
}
&:hover {
&::after {
background: rgba($secondary-content, 0.1);
}
&::before {
background-color: $secondary-content;
}
}
}
.mx_FormattingButtons_active {
&::after {
background: rgba($accent, 0.1);
}
&::before {
background-color: $accent;
}
}
.mx_FormattingButtons_Button_bold::before {
mask-image: url('$(res)/img/element-icons/room/composer/bold.svg');
}
.mx_FormattingButtons_Button_italic::before {
mask-image: url('$(res)/img/element-icons/room/composer/italic.svg');
}
.mx_FormattingButtons_Button_underline::before {
mask-image: url('$(res)/img/element-icons/room/composer/underline.svg');
}
.mx_FormattingButtons_Button_strikethrough::before {
mask-image: url('$(res)/img/element-icons/room/composer/strikethrough.svg');
}
}
.mx_FormattingButtons_Tooltip {
padding: 0 2px 0 2px;
.mx_FormattingButtons_Tooltip_KeyboardShortcut {
color: $tertiary-content;
kbd {
margin-top: 2px;
text-align: center;
display: inline-block;
text-transform: capitalize;
font-size: 12px;
font-family: Inter, sans-serif;
}
}
}

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 3C4 2.44772 4.44772 2 5 2H8.19231C10.093 2 11.5 3.64388 11.5 5.5C11.5 6.25349 11.2681 6.97201 10.8655 7.55977C11.8435 8.1788 12.5 9.25485 12.5 10.5C12.5 12.4594 10.8743 14 8.92857 14H5C4.44772 14 4 13.5523 4 13V3ZM6 4V7H8.19231C8.84067 7 9.5 6.4053 9.5 5.5C9.5 4.5947 8.84067 4 8.19231 4H6ZM6 9V12H8.92857C9.82319 12 10.5 11.3021 10.5 10.5C10.5 9.69794 9.82319 9 8.92857 9H6Z" fill="#8D97A5"/>
</svg>

After

Width:  |  Height:  |  Size: 549 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.08596 3.60016L6.28068 12.4045H4.79997C4.35814 12.4045 3.99997 12.7627 3.99997 13.2045C3.99997 13.6464 4.35814 14.0045 4.79997 14.0045H6.92104C6.92979 14.0047 6.93852 14.0047 6.94722 14.0045H9.06664C9.50846 14.0045 9.86664 13.6464 9.86664 13.2045C9.86664 12.7627 9.50846 12.4045 9.06664 12.4045H7.91397L9.71924 3.60016H11.2C11.6418 3.60016 12 3.24199 12 2.80016C12 2.35833 11.6418 2.00016 11.2 2.00016H9.08168C9.07107 1.99995 9.06048 1.99995 9.04993 2.00016H6.9333C6.49147 2.00016 6.1333 2.35833 6.1333 2.80016C6.1333 3.24199 6.49147 3.60016 6.9333 3.60016H8.08596Z" fill="#8D97A5"/>
</svg>

After

Width:  |  Height:  |  Size: 738 B

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.93167 4.76958C9.48979 3.88101 8.58375 3.47362 7.58232 3.58148C6.03349 3.74829 5.62648 4.94831 5.81497 5.72822C6.02118 6.58143 6.69765 6.91317 7.89252 7.21717H13.28C13.6776 7.21717 14 7.56757 14 7.99981C14 8.43204 13.6776 8.78244 13.28 8.78244H2.72C2.32235 8.78244 2 8.43204 2 7.99981C2 7.56757 2.32235 7.21717 2.72 7.21717H4.90308C4.69392 6.91824 4.52701 6.55948 4.42223 6.12592C4.0021 4.38757 5.03605 2.28279 7.44033 2.02384C8.85635 1.87133 10.4103 2.44045 11.1984 4.02524C11.3875 4.40548 11.2572 4.88035 10.9074 5.08589C10.5576 5.29143 10.1208 5.14981 9.93167 4.76958Z" fill="#8D97A5"/>
<path d="M10.2846 10.0868H11.7797C11.9254 10.8705 11.8142 11.7067 11.3665 12.4212C10.7426 13.4169 9.57984 14 7.98987 14C5.38435 14 4.18628 12.3895 3.94151 11.324C3.84516 10.9047 4.07981 10.4798 4.4656 10.3751C4.8514 10.2704 5.24225 10.5254 5.3386 10.9448C5.41285 11.268 6.00136 12.4347 7.98987 12.4347C9.27118 12.4347 9.90296 11.9764 10.1763 11.5401C10.4284 11.1378 10.472 10.6062 10.2846 10.0868Z" fill="#8D97A5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.19333 11.3C10.2133 11.04 11.6667 9.22667 11.6667 7.19333V2.83333C11.6667 2.37333 11.2933 2 10.8333 2C10.3733 2 10 2.37333 10 2.83333V7.26667C10 8.38 9.24667 9.39333 8.15333 9.61333C6.65333 9.92667 5.33333 8.78 5.33333 7.33333V2.83333C5.33333 2.37333 4.96 2 4.5 2C4.04 2 3.66667 2.37333 3.66667 2.83333V7.33333C3.66667 9.71333 5.75333 11.6133 8.19333 11.3ZM3 13.3333C3 13.7 3.3 14 3.66667 14H11.6667C12.0333 14 12.3333 13.7 12.3333 13.3333C12.3333 12.9667 12.0333 12.6667 11.6667 12.6667H3.66667C3.3 12.6667 3 12.9667 3 13.3333Z" fill="#8D97A5"/>
</svg>

After

Width:  |  Height:  |  Size: 661 B

View File

@ -389,6 +389,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
}
public render() {
const isWysiwygComposerEnabled = SettingsStore.getValue("feature_wysiwyg_composer");
const controls = [
this.props.e2eStatus ?
<E2EIcon key="e2eIcon" status={this.props.e2eStatus} className="mx_MessageComposer_e2eIcon" /> :
@ -403,8 +404,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
const canSendMessages = this.context.canSendMessages && !this.context.tombstone;
if (canSendMessages) {
const isWysiwygComposerEnabled = SettingsStore.getValue("feature_wysiwyg_composer");
if (isWysiwygComposerEnabled) {
controls.push(
<WysiwygComposer key="controls_input"
@ -503,6 +502,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
"mx_MessageComposer": true,
"mx_MessageComposer--compact": this.props.compact,
"mx_MessageComposer_e2eStatus": this.props.e2eStatus != undefined,
"mx_MessageComposer_wysiwyg": isWysiwygComposerEnabled,
});
return (

View File

@ -0,0 +1,41 @@
/*
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, { forwardRef, memo } from 'react';
interface EditorProps {
disabled: boolean;
}
export const Editor = memo(
forwardRef<HTMLDivElement, EditorProps>(
function Editor({ disabled }: EditorProps, ref,
) {
return <div className="mx_WysiwygComposer_container">
<div className="mx_WysiwygComposer_content"
ref={ref!}
contentEditable={!disabled}
role="textbox"
aria-multiline="true"
aria-autocomplete="list"
aria-haspopup="listbox"
dir="auto"
aria-disabled={disabled}
/>
</div>;
},
),
);

View File

@ -0,0 +1,69 @@
/*
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, { MouseEventHandler } from "react";
import { useWysiwyg } from "@matrix-org/matrix-wysiwyg";
import classNames from "classnames";
import AccessibleTooltipButton from "../../elements/AccessibleTooltipButton";
import { Alignment } from "../../elements/Tooltip";
import { KeyboardShortcut } from "../../settings/KeyboardShortcut";
import { KeyCombo } from "../../../../KeyBindingsManager";
import { _td } from "../../../../languageHandler";
interface TooltipProps {
label: string;
keyCombo?: KeyCombo;
}
function Tooltip({ label, keyCombo }: TooltipProps) {
return <div className="mx_FormattingButtons_Tooltip">
{ label }
{ keyCombo && <KeyboardShortcut value={keyCombo} className="mx_FormattingButtons_Tooltip_KeyboardShortcut" /> }
</div>;
}
interface ButtonProps extends TooltipProps {
className: string;
isActive: boolean;
onClick: MouseEventHandler<HTMLButtonElement>;
}
function Button({ label, keyCombo, onClick, isActive, className }: ButtonProps) {
return <AccessibleTooltipButton
element="button"
onClick={onClick}
title={label}
className={
classNames('mx_FormattingButtons_Button', className, { 'mx_FormattingButtons_active': isActive })}
tooltip={keyCombo && <Tooltip label={label} keyCombo={keyCombo} />}
alignment={Alignment.Top}
/>;
}
interface FormattingButtonsProps {
composer: ReturnType<typeof useWysiwyg>['wysiwyg'];
formattingStates: ReturnType<typeof useWysiwyg>['formattingStates'];
}
export function FormattingButtons({ composer, formattingStates }: FormattingButtonsProps) {
return <div className="mx_FormattingButtons">
<Button isActive={formattingStates.bold === 'reversed'} label={_td("Bold")} keyCombo={{ ctrlOrCmdKey: true, key: 'b' }} onClick={() => composer.bold()} className="mx_FormattingButtons_Button_bold" />
<Button isActive={formattingStates.italic === 'reversed'} label={_td('Italic')} keyCombo={{ ctrlOrCmdKey: true, key: 'i' }} onClick={() => composer.italic()} className="mx_FormattingButtons_Button_italic" />
<Button isActive={formattingStates.underline === 'reversed'} label={_td('Underline')} keyCombo={{ ctrlOrCmdKey: true, key: 'u' }} onClick={() => composer.underline()} className="mx_FormattingButtons_Button_underline" />
<Button isActive={formattingStates.strikeThrough === 'reversed'} label={_td('Strikethrough')} onClick={() => composer.strikeThrough()} className="mx_FormattingButtons_Button_strikethrough" />
</div>;
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useState } from 'react';
import React, { useCallback, useEffect } from 'react';
import { useWysiwyg } from "@matrix-org/matrix-wysiwyg";
import { IEventRelation, MatrixEvent } from 'matrix-js-sdk/src/models/event';
@ -22,6 +22,8 @@ import { useRoomContext } from '../../../../contexts/RoomContext';
import { sendMessage } from './message';
import { RoomPermalinkCreator } from '../../../../utils/permalinks/Permalinks';
import { useMatrixClientContext } from '../../../../contexts/MatrixClientContext';
import { FormattingButtons } from './FormattingButtons';
import { Editor } from './Editor';
interface WysiwygProps {
disabled?: boolean;
@ -39,11 +41,13 @@ export function WysiwygComposer(
const roomContext = useRoomContext();
const mxClient = useMatrixClientContext();
const [content, setContent] = useState<string>();
const { ref, isWysiwygReady, wysiwyg } = useWysiwyg({ onChange: (_content) => {
setContent(_content);
onChange(_content);
} });
const { ref, isWysiwygReady, content, formattingStates, wysiwyg } = useWysiwyg();
useEffect(() => {
if (!disabled && content !== null) {
onChange(content);
}
}, [onChange, content, disabled]);
const memoizedSendMessage = useCallback(() => {
sendMessage(content, { mxClient, roomContext, ...props });
@ -53,18 +57,8 @@ export function WysiwygComposer(
return (
<div className="mx_WysiwygComposer">
<div className="mx_WysiwygComposer_container">
<div className="mx_WysiwygComposer_content"
ref={ref}
contentEditable={!disabled && isWysiwygReady}
role="textbox"
aria-multiline="true"
aria-autocomplete="list"
aria-haspopup="listbox"
dir="auto"
aria-disabled={disabled || !isWysiwygReady}
/>
</div>
<FormattingButtons composer={wysiwyg} formattingStates={formattingStates} />
<Editor ref={ref} disabled={!isWysiwygReady || disabled} />
{ children?.(memoizedSendMessage) }
</div>
);

View File

@ -38,9 +38,10 @@ export const KeyboardKey: React.FC<IKeyboardKeyProps> = ({ name, last }) => {
interface IKeyboardShortcutProps {
value: KeyCombo;
className?: string;
}
export const KeyboardShortcut: React.FC<IKeyboardShortcutProps> = ({ value }) => {
export const KeyboardShortcut: React.FC<IKeyboardShortcutProps> = ({ value, className = 'mx_KeyboardShortcut' }) => {
if (!value) return null;
const modifiersElement = [];
@ -58,7 +59,7 @@ export const KeyboardShortcut: React.FC<IKeyboardShortcutProps> = ({ value }) =>
modifiersElement.push(<KeyboardKey key="shiftKey" name={Key.SHIFT} />);
}
return <div className="mx_KeyboardShortcut">
return <div className={className}>
{ modifiersElement }
<KeyboardKey name={value.key} last />
</div>;

View File

@ -904,7 +904,7 @@
"How can I leave the beta?": "How can I leave the beta?",
"To leave, return to this page and use the “%(leaveTheBeta)s” button.": "To leave, return to this page and use the “%(leaveTheBeta)s” button.",
"Leave the beta": "Leave the beta",
"Wysiwyg composer (plain text mode coming soon) (under active development)": "Wysiwyg composer (plain text mode coming soon) (under active development)",
"Try out the rich text editor (plain text mode coming soon)": "Try out the rich text editor (plain text mode coming soon)",
"Render simple counters in room header": "Render simple counters in room header",
"Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
"Support adding custom themes": "Support adding custom themes",
@ -2060,6 +2060,8 @@
"No microphone found": "No microphone found",
"We didn't find a microphone on your device. Please check your settings and try again.": "We didn't find a microphone on your device. Please check your settings and try again.",
"Stop recording": "Stop recording",
"Italic": "Italic",
"Underline": "Underline",
"Error updating main address": "Error updating main address",
"There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.",
"There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.",

View File

@ -306,7 +306,7 @@ export const SETTINGS: {[setting: string]: ISetting} = {
"feature_wysiwyg_composer": {
isFeature: true,
labsGroup: LabGroup.Messaging,
displayName: _td("Wysiwyg composer (plain text mode coming soon) (under active development)"),
displayName: _td("Try out the rich text editor (plain text mode coming soon)"),
supportedLevels: LEVELS_FEATURE,
default: false,
},

View File

@ -44,8 +44,9 @@ import { WysiwygComposer } from "../../../../src/components/views/rooms/wysiwyg_
// The wysiwyg fetch wasm bytes and a specific workaround is needed to make it works in a node (jest) environnement
// See https://github.com/matrix-org/matrix-wysiwyg/blob/main/platforms/web/test.setup.ts
jest.mock("@matrix-org/matrix-wysiwyg", () => ({
useWysiwyg: ({ onChange }) => {
return { ref: { current: null }, isWysiwygReady: true, wysiwyg: { clear: () => void 0 } };
useWysiwyg: () => {
return { ref: { current: null }, isWysiwygReady: true, wysiwyg: { clear: () => void 0 },
formattingStates: { bold: 'enabled', italic: 'enabled', underline: 'enabled', strikeThrough: 'enabled' } };
},
}));

View File

@ -0,0 +1,77 @@
/*
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 { FormattingButtons } from "../../../../../src/components/views/rooms/wysiwyg_composer/FormattingButtons";
describe('FormattingButtons', () => {
const wysiwyg = {
bold: jest.fn(),
italic: jest.fn(),
underline: jest.fn(),
strikeThrough: jest.fn(),
} as any;
const formattingStates = {
bold: 'reversed',
italic: 'reversed',
underline: 'enabled',
strikeThrough: 'enabled',
} as any;
afterEach(() => {
jest.resetAllMocks();
});
it('Should have the correspond CSS classes', () => {
// When
render(<FormattingButtons composer={wysiwyg} formattingStates={formattingStates} />);
// Then
expect(screen.getByLabelText('Bold')).toHaveClass('mx_FormattingButtons_active');
expect(screen.getByLabelText('Italic')).toHaveClass('mx_FormattingButtons_active');
expect(screen.getByLabelText('Underline')).not.toHaveClass('mx_FormattingButtons_active');
expect(screen.getByLabelText('Strikethrough')).not.toHaveClass('mx_FormattingButtons_active');
});
it('Should call wysiwyg function on button click', () => {
// When
render(<FormattingButtons composer={wysiwyg} formattingStates={formattingStates} />);
screen.getByLabelText('Bold').click();
screen.getByLabelText('Italic').click();
screen.getByLabelText('Underline').click();
screen.getByLabelText('Strikethrough').click();
// Then
expect(wysiwyg.bold).toHaveBeenCalledTimes(1);
expect(wysiwyg.italic).toHaveBeenCalledTimes(1);
expect(wysiwyg.underline).toHaveBeenCalledTimes(1);
expect(wysiwyg.strikeThrough).toHaveBeenCalledTimes(1);
});
it('Should display the tooltip on mouse over', async () => {
// When
const user = userEvent.setup();
render(<FormattingButtons composer={wysiwyg} formattingStates={formattingStates} />);
await user.hover(screen.getByLabelText('Bold'));
// Then
expect(await screen.findByText('Bold')).toBeTruthy();
});
});

View File

@ -16,7 +16,6 @@ limitations under the License.
import React from "react";
import { act, render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import { IRoomState } from "../../../../../src/components/structures/RoomView";
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
@ -25,14 +24,12 @@ import { createTestClient, mkEvent, mkStubRoom } from "../../../../test-utils";
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
import { WysiwygComposer } from "../../../../../src/components/views/rooms/wysiwyg_composer/WysiwygComposer";
let callOnChange: (content: string) => void;
// The wysiwyg fetch wasm bytes and a specific workaround is needed to make it works in a node (jest) environnement
// See https://github.com/matrix-org/matrix-wysiwyg/blob/main/platforms/web/test.setup.ts
jest.mock("@matrix-org/matrix-wysiwyg", () => ({
useWysiwyg: ({ onChange }) => {
callOnChange = onChange;
return { ref: { current: null }, isWysiwygReady: true, wysiwyg: { clear: () => void 0 } };
useWysiwyg: () => {
return { ref: { current: null }, content: '<b>html</b>', isWysiwygReady: true, wysiwyg: { clear: () => void 0 },
formattingStates: { bold: 'enabled', italic: 'enabled', underline: 'enabled', strikeThrough: 'enabled' } };
},
}));
@ -122,7 +119,7 @@ describe('WysiwygComposer', () => {
expect(content).toBe((html));
done();
});
act(() => callOnChange(html));
// act(() => callOnChange(html));
});
it('Should send message, call clear and focus the textbox', async () => {
@ -130,7 +127,6 @@ describe('WysiwygComposer', () => {
const html = '<b>html</b>';
await new Promise((resolve) => {
customRender(() => resolve(null));
act(() => callOnChange(html));
});
act(() => sendMessage());

View File

@ -1660,10 +1660,10 @@
resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.2.0.tgz#453925c939ecdd5ca6c797d293deb8cf0933f1b8"
integrity sha512-+0/Sydm4MNOcqd8iySJmojVPB74Axba4BXlwTsiKmL5fgYqdUkwmqkO39K7Pn8i+a+8pg11oNvBPkpWs3O5Qww==
"@matrix-org/matrix-wysiwyg@^0.0.2":
version "0.0.2"
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-0.0.2.tgz#c1a18f5f9ac061c4147a0fbbf9303a3c82e626e6"
integrity sha512-AY4sbmgcaFZhNxJfn3Va1SiKH4/gIdvWV9c/iehcIi3/xFB7lKCIwe7NNxzPpFOp+b+fEIbdHf3fhS5vJBi7xg==
"@matrix-org/matrix-wysiwyg@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-0.2.0.tgz#651002ad67be3004698d4a89806cf344283a4ca3"
integrity sha512-m9R1NOd0ogkhrjqFNg159TMXL5dpME90G9RDrZrO106263Qtoj0TazyBaLhNjgvPkogbzbCJUULQWPFiLQfTjw==
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz":
version "3.2.8"
@ -3198,6 +3198,11 @@ browser-process-hrtime@^1.0.0:
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
browser-request@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/browser-request/-/browser-request-0.3.3.tgz#9ece5b5aca89a29932242e18bf933def9876cc17"
integrity sha512-YyNI4qJJ+piQG6MMEuo7J3Bzaqssufx04zpEKYfSrl/1Op59HWali9zMtBpXnkmqMcOuWJPZvudrm9wISmnCbg==
browserslist@^4.20.2, browserslist@^4.21.3:
version "4.21.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a"