Merge branch 'develop' into kegan/lists-as-keys

This commit is contained in:
kegsay 2023-01-19 16:52:06 +00:00 committed by GitHub
commit fcde4b7880
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 236 additions and 50 deletions

View file

@ -1,3 +1,9 @@
Changes in [3.64.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.64.1) (2023-01-18)
=====================================================================================================
## 🐛 Bug Fixes
* Fix crash in older browsers (replace .at() with array.length-1) ([\#9933](https://github.com/matrix-org/matrix-react-sdk/pull/9933)). Fixes matrix-org/element-web-rageshakes#19281.
Changes in [3.64.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.64.0) (2023-01-18)
=====================================================================================================

View file

@ -20,6 +20,7 @@ import type { ISasEvent } from "matrix-js-sdk/src/crypto/verification/SAS";
import type { CypressBot } from "../../support/bot";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
import { UserCredentials } from "../../support/login";
type EmojiMapping = [emoji: string, name: string];
interface CryptoTestContext extends Mocha.Context {
@ -154,11 +155,15 @@ const verify = function (this: CryptoTestContext) {
};
describe("Cryptography", function () {
let aliceCredentials: UserCredentials;
beforeEach(function () {
cy.startHomeserver("default")
.as("homeserver")
.then((homeserver: HomeserverInstance) => {
cy.initTestUser(homeserver, "Alice", undefined, "alice_");
cy.initTestUser(homeserver, "Alice", undefined, "alice_").then((credentials) => {
aliceCredentials = credentials;
});
cy.getBot(homeserver, { displayName: "Bob", autoAcceptInvites: false, userIdPrefix: "bob_" }).as("bob");
});
});
@ -183,7 +188,7 @@ describe("Cryptography", function () {
});
it("creating a DM should work, being e2e-encrypted / user verification", function (this: CryptoTestContext) {
cy.bootstrapCrossSigning();
cy.bootstrapCrossSigning(aliceCredentials);
startDMWithBob.call(this);
// send first message
cy.get(".mx_BasicMessageComposer_input").click().should("have.focus").type("Hey!{enter}");
@ -194,7 +199,7 @@ describe("Cryptography", function () {
});
it("should allow verification when there is no existing DM", function (this: CryptoTestContext) {
cy.bootstrapCrossSigning();
cy.bootstrapCrossSigning(aliceCredentials);
autoJoin(this.bob);
// we need to have a room with the other user present, so we can open the verification panel
@ -212,7 +217,7 @@ describe("Cryptography", function () {
});
it("should show the correct shield on edited e2e events", function (this: CryptoTestContext) {
cy.bootstrapCrossSigning();
cy.bootstrapCrossSigning(aliceCredentials);
// bob has a second, not cross-signed, device
cy.loginBot(this.homeserver, this.bob.getUserId(), this.bob.__cypress_password, {}).as("bobSecondDevice");

View file

@ -105,15 +105,9 @@ describe("Decryption Failure Bar", () => {
"and there are other verified devices or backups",
() => {
let otherDevice: MatrixClient | undefined;
cy.loginBot(homeserver, testUser.username, testUser.password, {})
cy.loginBot(homeserver, testUser.username, testUser.password, { bootstrapCrossSigning: true })
.then(async (cli) => {
otherDevice = cli;
await otherDevice.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest) => {
await makeRequest({});
},
setupNewCrossSigning: true,
});
})
.then(() => {
cy.botSendMessage(bot, roomId, "test");
@ -169,15 +163,11 @@ describe("Decryption Failure Bar", () => {
"should prompt the user to reset keys, if this device isn't verified " +
"and there are no other verified devices or backups",
() => {
cy.loginBot(homeserver, testUser.username, testUser.password, {}).then(async (cli) => {
await cli.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest) => {
await makeRequest({});
},
setupNewCrossSigning: true,
});
await cli.logout(true);
});
cy.loginBot(homeserver, testUser.username, testUser.password, { bootstrapCrossSigning: true }).then(
async (cli) => {
await cli.logout(true);
},
);
cy.botSendMessage(bot, roomId, "test");
cy.wait(5000);

View file

@ -150,7 +150,14 @@ function setupBotClient(
if (opts.bootstrapCrossSigning) {
await cli.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (func) => {
await func({});
await func({
type: "m.login.password",
identifier: {
type: "m.id.user",
user: credentials.userId,
},
password: credentials.password,
});
},
});
}

View file

@ -22,6 +22,7 @@ import type { MatrixClient } from "matrix-js-sdk/src/client";
import type { Room } from "matrix-js-sdk/src/models/room";
import type { IContent } from "matrix-js-sdk/src/models/event";
import Chainable = Cypress.Chainable;
import { UserCredentials } from "./login";
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
@ -119,7 +120,7 @@ declare global {
/**
* Boostraps cross-signing.
*/
bootstrapCrossSigning(): Chainable<void>;
bootstrapCrossSigning(credendtials: UserCredentials): Chainable<void>;
/**
* Joins the given room by alias or ID
* @param roomIdOrAlias the id or alias of the room to join
@ -210,11 +211,18 @@ Cypress.Commands.add("setAvatarUrl", (url: string): Chainable<{}> => {
});
});
Cypress.Commands.add("bootstrapCrossSigning", () => {
Cypress.Commands.add("bootstrapCrossSigning", (credentials: UserCredentials) => {
cy.window({ log: false }).then((win) => {
win.mxMatrixClientPeg.matrixClient.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (func) => {
await func({});
await func({
type: "m.login.password",
identifier: {
type: "m.id.user",
user: credentials.userId,
},
password: credentials.password,
});
},
});
});

View file

@ -76,6 +76,7 @@ export interface Credentials {
userId: string;
deviceId: string;
homeServer: string;
password: string;
}
function registerUser(
@ -120,6 +121,7 @@ function registerUser(
accessToken: response.body.access_token,
userId: response.body.user_id,
deviceId: response.body.device_id,
password: password,
}));
}

View file

@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
"version": "3.64.0",
"version": "3.64.1",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {

View file

@ -16,6 +16,7 @@ limitations under the License.
/* 1rem :: 10px */
$spacing-2: 2px;
$spacing-4: 4px;
$spacing-8: 8px;
$spacing-12: 12px;

View file

@ -548,7 +548,19 @@ $left-gutter: 64px;
pre,
code {
font-family: $monospace-font-family !important;
background-color: $codeblock-background-color;
background-color: $system;
}
code:not(pre *) {
background-color: $inlinecode-background-color;
border: 1px solid $inlinecode-border-color;
border-radius: 4px;
// The horizontal padding is added by gfm.css .markdown-body
padding: $spacing-2 0;
// Avoid inline code blocks to be sticked when on multiple lines
line-height: $font-22px;
// Avoid the border to be glued to the other words
margin-right: $spacing-2;
}
code {
@ -566,6 +578,8 @@ $left-gutter: 64px;
background: transparent;
}
border: 1px solid $quinary-content;
code {
white-space: pre; /* we want code blocks to be scrollable and not wrap */
@ -744,6 +758,8 @@ $left-gutter: 64px;
.mx_EventTile_collapsedCodeBlock {
max-height: 30vh;
padding-top: $spacing-12;
padding-bottom: $spacing-12;
}
/* Inserted adjacent to <pre> blocks, (See TextualBody) */

View file

@ -46,9 +46,30 @@ limitations under the License.
// model output always includes a linebreak but we do not want the user
// to see it when writing input in lists
:is(ol, ul) + br:last-of-type {
:is(ol, ul, pre) + br:last-of-type {
display: none;
}
> pre {
font-size: $font-15px;
line-height: $font-24px;
margin-top: 0;
margin-bottom: 0;
padding: $spacing-8 $spacing-12;
background-color: $inlinecode-background-color;
border: 1px solid $inlinecode-border-color;
border-radius: 2px;
}
code {
font-family: $monospace-font-family !important;
background-color: $inlinecode-background-color;
border: 1px solid $inlinecode-border-color;
border-radius: 4px;
padding: $spacing-2;
}
}
.mx_WysiwygComposer_Editor_content_placeholder::before {

View file

@ -50,6 +50,12 @@ limitations under the License.
}
}
.mx_FormattingButtons_disabled {
.mx_FormattingButtons_Icon {
color: $quinary-content;
}
}
.mx_FormattingButtons_Icon {
--size: 16px;
height: var(--size);

View file

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.14288 6.99997L5.47622 5.66663C5.5905 5.55235 5.64765 5.41266 5.64765 5.24758C5.64765 5.0825 5.5905 4.94282 5.47622 4.82854C5.36193 4.71425 5.22225 4.65711 5.05717 4.65711C4.89209 4.65711 4.75241 4.71425 4.63812 4.82854L2.86669 6.59997C2.8032 6.66346 2.75876 6.72695 2.73336 6.79044C2.70796 6.85393 2.69526 6.92377 2.69526 6.99997C2.69526 7.07616 2.70796 7.146 2.73336 7.20949C2.75876 7.27298 2.8032 7.33647 2.86669 7.39996L4.65717 9.19044C4.77145 9.30473 4.91114 9.36187 5.07622 9.36187C5.2413 9.36187 5.38098 9.30473 5.49526 9.19044C5.60955 9.07616 5.66669 8.93647 5.66669 8.77139C5.66669 8.60631 5.60955 8.46663 5.49526 8.35235L4.14288 6.99997ZM9.85717 6.99997L8.50479 8.35235C8.3905 8.46663 8.33336 8.60631 8.33336 8.77139C8.33336 8.93647 8.3905 9.07616 8.50479 9.19044C8.61907 9.30473 8.75876 9.36187 8.92384 9.36187C9.08891 9.36187 9.2286 9.30473 9.34288 9.19044L11.1334 7.39996C11.1969 7.33647 11.2413 7.27298 11.2667 7.20949C11.2921 7.146 11.3048 7.07616 11.3048 6.99997C11.3048 6.92377 11.2921 6.85393 11.2667 6.79044C11.2413 6.72695 11.1969 6.66346 11.1334 6.59997L9.34288 4.80949C9.29209 4.746 9.2286 4.70155 9.15241 4.67616C9.07622 4.65076 9.00003 4.63806 8.92384 4.63806C8.84765 4.63806 8.77463 4.65076 8.70479 4.67616C8.63495 4.70155 8.56828 4.746 8.50479 4.80949C8.3905 4.92377 8.33336 5.06346 8.33336 5.22854C8.33336 5.39362 8.3905 5.5333 8.50479 5.64758L9.85717 6.99997ZM1.28574 13.8571C0.980979 13.8571 0.714312 13.7428 0.48574 13.5143C0.257169 13.2857 0.142883 13.019 0.142883 12.7143V1.28568C0.142883 0.980918 0.257169 0.714251 0.48574 0.485679C0.714312 0.257108 0.980979 0.142822 1.28574 0.142822H12.7143C13.0191 0.142822 13.2857 0.257108 13.5143 0.485679C13.7429 0.714251 13.8572 0.980918 13.8572 1.28568V12.7143C13.8572 13.019 13.7429 13.2857 13.5143 13.5143C13.2857 13.7428 13.0191 13.8571 12.7143 13.8571H1.28574ZM1.28574 12.7143H12.7143V1.28568H1.28574V12.7143Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -224,6 +224,8 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28);
$breadcrumb-placeholder-bg-color: #272c35;
$theme-button-bg-color: #e3e8f0;
$resend-button-divider-color: rgba($header-panel-text-primary-color, 0.74);
$inlinecode-border-color: $quinary-content;
$inlinecode-background-color: $system;
$codeblock-background-color: #2a3039;
$scrollbar-thumb-color: rgba(255, 255, 255, 0.2);
$selected-color: $room-highlight-color;

View file

@ -190,6 +190,8 @@ $appearance-tab-border-color: $room-highlight-color;
$composer-shadow-color: tranparent;
$codeblock-background-color: #2a3039;
$inlinecode-border-color: #2a3039;
$inlinecode-background-color: #2a3039;
/* Bubble tiles */
$eventbubble-self-bg: #14322e;

View file

@ -290,6 +290,8 @@ $appearance-tab-border-color: $input-darker-bg-color;
$composer-shadow-color: tranparent;
$codeblock-background-color: $header-panel-bg-color;
$inlinecode-border-color: $header-panel-bg-color;
$inlinecode-background-color: $header-panel-bg-color;
/* Bubble tiles */
$eventbubble-self-bg: #f0fbf8;

View file

@ -295,6 +295,8 @@ $composer-shadow-color: rgba(0, 0, 0, 0.04);
$breadcrumb-placeholder-bg-color: #e8eef5;
$theme-button-bg-color: $quinary-content;
$resend-button-divider-color: $input-darker-bg-color;
$inlinecode-border-color: $quinary-content;
$inlinecode-background-color: $system;
$codeblock-background-color: $header-panel-bg-color;
$scrollbar-thumb-color: rgba(0, 0, 0, 0.2);
$selected-color: $secondary-accent-color;

View file

@ -85,7 +85,7 @@ export function doesRoomOrThreadHaveUnreadMessages(roomOrThread: Room | Thread):
// https://github.com/vector-im/element-web/issues/2427
// ...and possibly some of the others at
// https://github.com/vector-im/element-web/issues/3363
if (roomOrThread.timeline.at(-1)?.getSender() === myUserId) {
if (roomOrThread.timeline[roomOrThread.timeline.length - 1]?.getSender() === myUserId) {
return false;
}

View file

@ -262,6 +262,8 @@ export default class ForgotPassword extends React.Component<Props, State> {
try {
await this.reset.setNewPassword(this.state.password);
this.setState({ phase: Phase.Done });
return;
} catch (err: any) {
if (err.httpStatus !== 401) {
// 401 = waiting for email verification, else unknown error

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React, { MouseEventHandler, ReactNode } from "react";
import { FormattingFunctions, AllActionStates } from "@matrix-org/matrix-wysiwyg";
import { FormattingFunctions, AllActionStates, ActionState } from "@matrix-org/matrix-wysiwyg";
import classNames from "classnames";
import { Icon as BoldIcon } from "../../../../../../res/img/element-icons/room/composer/bold.svg";
@ -26,6 +26,7 @@ import { Icon as InlineCodeIcon } from "../../../../../../res/img/element-icons/
import { Icon as LinkIcon } from "../../../../../../res/img/element-icons/room/composer/link.svg";
import { Icon as BulletedListIcon } from "../../../../../../res/img/element-icons/room/composer/bulleted_list.svg";
import { Icon as NumberedListIcon } from "../../../../../../res/img/element-icons/room/composer/numbered_list.svg";
import { Icon as CodeBlockIcon } from "../../../../../../res/img/element-icons/room/composer/code_block.svg";
import AccessibleTooltipButton from "../../../elements/AccessibleTooltipButton";
import { Alignment } from "../../../elements/Tooltip";
import { KeyboardShortcut } from "../../../settings/KeyboardShortcut";
@ -53,21 +54,23 @@ function Tooltip({ label, keyCombo }: TooltipProps): JSX.Element {
interface ButtonProps extends TooltipProps {
icon: ReactNode;
isActive: boolean;
actionState: ActionState;
onClick: MouseEventHandler<HTMLButtonElement>;
}
function Button({ label, keyCombo, onClick, isActive, icon }: ButtonProps): JSX.Element {
function Button({ label, keyCombo, onClick, actionState, icon }: ButtonProps): JSX.Element {
return (
<AccessibleTooltipButton
element="button"
onClick={onClick as (e: ButtonEvent) => void}
title={label}
className={classNames("mx_FormattingButtons_Button", {
mx_FormattingButtons_active: isActive,
mx_FormattingButtons_Button_hover: !isActive,
mx_FormattingButtons_active: actionState === "reversed",
mx_FormattingButtons_Button_hover: actionState === "enabled",
mx_FormattingButtons_disabled: actionState === "disabled",
})}
tooltip={keyCombo && <Tooltip label={label} keyCombo={keyCombo} />}
forceHide={actionState === "disabled"}
alignment={Alignment.Top}
>
{icon}
@ -85,53 +88,59 @@ export function FormattingButtons({ composer, actionStates }: FormattingButtonsP
return (
<div className="mx_FormattingButtons">
<Button
isActive={actionStates.bold === "reversed"}
actionState={actionStates.bold}
label={_td("Bold")}
keyCombo={{ ctrlOrCmdKey: true, key: "b" }}
onClick={() => composer.bold()}
icon={<BoldIcon className="mx_FormattingButtons_Icon" />}
/>
<Button
isActive={actionStates.italic === "reversed"}
actionState={actionStates.italic}
label={_td("Italic")}
keyCombo={{ ctrlOrCmdKey: true, key: "i" }}
onClick={() => composer.italic()}
icon={<ItalicIcon className="mx_FormattingButtons_Icon" />}
/>
<Button
isActive={actionStates.underline === "reversed"}
actionState={actionStates.underline}
label={_td("Underline")}
keyCombo={{ ctrlOrCmdKey: true, key: "u" }}
onClick={() => composer.underline()}
icon={<UnderlineIcon className="mx_FormattingButtons_Icon" />}
/>
<Button
isActive={actionStates.strikeThrough === "reversed"}
actionState={actionStates.strikeThrough}
label={_td("Strikethrough")}
onClick={() => composer.strikeThrough()}
icon={<StrikeThroughIcon className="mx_FormattingButtons_Icon" />}
/>
<Button
isActive={actionStates.unorderedList === "reversed"}
actionState={actionStates.unorderedList}
label={_td("Bulleted list")}
onClick={() => composer.unorderedList()}
icon={<BulletedListIcon className="mx_FormattingButtons_Icon" />}
/>
<Button
isActive={actionStates.orderedList === "reversed"}
actionState={actionStates.orderedList}
label={_td("Numbered list")}
onClick={() => composer.orderedList()}
icon={<NumberedListIcon className="mx_FormattingButtons_Icon" />}
/>
<Button
isActive={actionStates.inlineCode === "reversed"}
actionState={actionStates.inlineCode}
label={_td("Code")}
keyCombo={{ ctrlOrCmdKey: true, key: "e" }}
onClick={() => composer.inlineCode()}
icon={<InlineCodeIcon className="mx_FormattingButtons_Icon" />}
/>
<Button
isActive={actionStates.link === "reversed"}
actionState={actionStates.codeBlock}
label={_td("Code block")}
onClick={() => composer.codeBlock()}
icon={<CodeBlockIcon className="mx_FormattingButtons_Icon" />}
/>
<Button
actionState={actionStates.link}
label={_td("Link")}
onClick={() => openLinkModal(composer, composerContext, actionStates.link === "reversed")}
icon={<LinkIcon className="mx_FormattingButtons_Icon" />}

View file

@ -19,6 +19,12 @@ import { useCallback } from "react";
import { useSettingValue } from "../../../../../hooks/useSettings";
function isEnterPressed(event: KeyboardEvent): boolean {
// Ugly but here we need to send the message only if Enter is pressed
// And we need to stop the event propagation on enter to avoid the composer to grow
return event.key === "Enter" && !event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey;
}
export function useInputEventProcessor(onSend: () => void): (event: WysiwygEvent) => WysiwygEvent | null {
const isCtrlEnter = useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
return useCallback(
@ -28,12 +34,12 @@ export function useInputEventProcessor(onSend: () => void): (event: WysiwygEvent
}
const isKeyboardEvent = event instanceof KeyboardEvent;
const isEnterPress =
!isCtrlEnter && (isKeyboardEvent ? event.key === "Enter" : event.inputType === "insertParagraph");
// sendMessage is sent when ctrl+enter is pressed
const isSendMessage = !isKeyboardEvent && event.inputType === "sendMessage";
const isEnterPress = !isCtrlEnter && isKeyboardEvent && isEnterPressed(event);
const isInsertParagraph = !isCtrlEnter && !isKeyboardEvent && event.inputType === "insertParagraph";
// sendMessage is sent when cmd+enter is pressed
const isSendMessage = isCtrlEnter && !isKeyboardEvent && event.inputType === "sendMessage";
if (isEnterPress || isSendMessage) {
if (isEnterPress || isInsertParagraph || isSendMessage) {
event.stopPropagation?.();
event.preventDefault?.();
onSend();

View file

@ -288,6 +288,37 @@ describe("<ForgotPassword>", () => {
});
});
describe("and confirm the email link and submitting the new password", () => {
beforeEach(async () => {
// fake link confirmed by resolving client.setPassword instead of raising an error
mocked(client.setPassword).mockResolvedValue({});
await click(screen.getByText("Reset password"));
});
it("should send the new password (once)", () => {
expect(client.setPassword).toHaveBeenCalledWith(
{
type: "m.login.email.identity",
threepid_creds: {
client_secret: expect.any(String),
sid: testSid,
},
threepidCreds: {
client_secret: expect.any(String),
sid: testSid,
},
},
testPassword,
false,
);
// be sure that the next attempt to set the password would have been sent
jest.advanceTimersByTime(3000);
// it should not retry to set the password
expect(client.setPassword).toHaveBeenCalledTimes(1);
});
});
describe("and submitting it", () => {
beforeEach(async () => {
await click(screen.getByText("Reset password"));

View file

@ -28,6 +28,7 @@ const mockWysiwyg = {
underline: jest.fn(),
strikeThrough: jest.fn(),
inlineCode: jest.fn(),
codeBlock: jest.fn(),
link: jest.fn(),
orderedList: jest.fn(),
unorderedList: jest.fn(),
@ -36,7 +37,7 @@ const mockWysiwyg = {
const openLinkModalSpy = jest.spyOn(LinkModal, "openLinkModal");
const testCases: Record<
Exclude<ActionTypes, "undo" | "redo" | "clear" | "codeBlock">,
Exclude<ActionTypes, "undo" | "redo" | "clear">,
{ label: string; mockFormatFn: jest.Func | jest.SpyInstance }
> = {
bold: { label: "Bold", mockFormatFn: mockWysiwyg.bold },
@ -44,6 +45,7 @@ const testCases: Record<
underline: { label: "Underline", mockFormatFn: mockWysiwyg.underline },
strikeThrough: { label: "Strikethrough", mockFormatFn: mockWysiwyg.strikeThrough },
inlineCode: { label: "Code", mockFormatFn: mockWysiwyg.inlineCode },
codeBlock: { label: "Code block", mockFormatFn: mockWysiwyg.inlineCode },
link: { label: "Link", mockFormatFn: openLinkModalSpy },
orderedList: { label: "Numbered list", mockFormatFn: mockWysiwyg.orderedList },
unorderedList: { label: "Bulleted list", mockFormatFn: mockWysiwyg.unorderedList },
@ -62,6 +64,7 @@ const renderComponent = (props = {}) => {
const classes = {
active: "mx_FormattingButtons_active",
hover: "mx_FormattingButtons_Button_hover",
disabled: "mx_FormattingButtons_disabled",
};
describe("FormattingButtons", () => {
@ -87,6 +90,16 @@ describe("FormattingButtons", () => {
});
});
it("Each button should have disabled class when disabled", () => {
const disabledActionStates = createActionStates("disabled");
renderComponent({ actionStates: disabledActionStates });
Object.values(testCases).forEach((testCase) => {
const { label } = testCase;
expect(screen.getByLabelText(label)).toHaveClass(classes.disabled);
});
});
it("Should call wysiwyg function on button click", async () => {
renderComponent();
@ -98,14 +111,26 @@ describe("FormattingButtons", () => {
}
});
it("Each button should display the tooltip on mouse over", async () => {
it("Each button should display the tooltip on mouse over when not disabled", async () => {
renderComponent();
for (const testCase of Object.values(testCases)) {
const { label } = testCase;
await userEvent.hover(screen.getByLabelText(label));
expect(await screen.findByText(label)).toBeTruthy();
expect(screen.getByText(label)).toBeInTheDocument();
}
});
it("Each button should not display the tooltip on mouse over when disabled", async () => {
const disabledActionStates = createActionStates("disabled");
renderComponent({ actionStates: disabledActionStates });
for (const testCase of Object.values(testCases)) {
const { label } = testCase;
await userEvent.hover(screen.getByLabelText(label));
expect(screen.queryByText(label)).not.toBeInTheDocument();
}
});

View file

@ -17,6 +17,7 @@ limitations under the License.
import "@testing-library/jest-dom";
import React from "react";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { WysiwygComposer } from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer";
import SettingsStore from "../../../../../../src/settings/SettingsStore";
@ -87,6 +88,45 @@ describe("WysiwygComposer", () => {
// Then it sends a message
await waitFor(() => expect(onSend).toBeCalledTimes(1));
});
it("Should not call onSend when Shift+Enter is pressed ", async () => {
//When
await userEvent.type(screen.getByRole("textbox"), "{shift>}{enter}");
// Then it sends a message
await waitFor(() => expect(onSend).toBeCalledTimes(0));
});
it("Should not call onSend when ctrl+Enter is pressed ", async () => {
//When
// Using userEvent.type or .keyboard wasn't working as expected in the case of ctrl+enter
fireEvent(
screen.getByRole("textbox"),
new KeyboardEvent("keydown", {
ctrlKey: true,
code: "Enter",
}),
);
// Then it sends a message
await waitFor(() => expect(onSend).toBeCalledTimes(0));
});
it("Should not call onSend when alt+Enter is pressed ", async () => {
//When
await userEvent.type(screen.getByRole("textbox"), "{alt>}{enter}");
// Then it sends a message
await waitFor(() => expect(onSend).toBeCalledTimes(0));
});
it("Should not call onSend when meta+Enter is pressed ", async () => {
//When
await userEvent.type(screen.getByRole("textbox"), "{meta>}{enter}");
// Then it sends a message
await waitFor(() => expect(onSend).toBeCalledTimes(0));
});
});
describe("When settings require Ctrl+Enter to send", () => {

View file

@ -6493,7 +6493,7 @@ matrix-events-sdk@0.0.1:
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
version "23.1.0"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/2fcc4811dd913bb774dd1c7f67cb693c4456d71e"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/83563c7a01bbeaf7f83f4b7feccc03647b536e7c"
dependencies:
"@babel/runtime" "^7.12.5"
"@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.2"