Merge branch 'develop' into johannes/latest-room-in-space
This commit is contained in:
commit
3766b39361
119 changed files with 4636 additions and 1409 deletions
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { MouseEventHandler } from "react";
|
||||
import { screen, render, RenderResult } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
|
@ -82,28 +82,39 @@ describe("PictureInPictureDragger", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("doesn't leak drag events to children as clicks", async () => {
|
||||
const clickSpy = jest.fn();
|
||||
render(
|
||||
<PictureInPictureDragger draggable={true}>
|
||||
{[
|
||||
({ onStartMoving }) => (
|
||||
<div onMouseDown={onStartMoving} onClick={clickSpy}>
|
||||
Hello
|
||||
</div>
|
||||
),
|
||||
]}
|
||||
</PictureInPictureDragger>,
|
||||
);
|
||||
const target = screen.getByText("Hello");
|
||||
describe("when rendering the dragger", () => {
|
||||
let clickSpy: jest.Mocked<MouseEventHandler>;
|
||||
let target: HTMLElement;
|
||||
|
||||
// A click without a drag motion should go through
|
||||
await userEvent.pointer([{ keys: "[MouseLeft>]", target }, { keys: "[/MouseLeft]" }]);
|
||||
expect(clickSpy).toHaveBeenCalled();
|
||||
beforeEach(() => {
|
||||
clickSpy = jest.fn();
|
||||
render(
|
||||
<PictureInPictureDragger draggable={true}>
|
||||
{[
|
||||
({ onStartMoving }) => (
|
||||
<div onMouseDown={onStartMoving} onClick={clickSpy}>
|
||||
Hello
|
||||
</div>
|
||||
),
|
||||
]}
|
||||
</PictureInPictureDragger>,
|
||||
);
|
||||
target = screen.getByText("Hello");
|
||||
});
|
||||
|
||||
// A drag motion should not trigger a click
|
||||
clickSpy.mockClear();
|
||||
await userEvent.pointer([{ keys: "[MouseLeft>]", target }, { coords: { x: 60, y: 60 } }, "[/MouseLeft]"]);
|
||||
expect(clickSpy).not.toHaveBeenCalled();
|
||||
it("and clicking without a drag motion, it should pass the click to children", async () => {
|
||||
await userEvent.pointer([{ keys: "[MouseLeft>]", target }, { keys: "[/MouseLeft]" }]);
|
||||
expect(clickSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("and clicking with a drag motion above the threshold of 5px, it should not pass the click to children", async () => {
|
||||
await userEvent.pointer([{ keys: "[MouseLeft>]", target }, { coords: { x: 60, y: 2 } }, "[/MouseLeft]"]);
|
||||
expect(clickSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("and clickign with a drag motion below the threshold of 5px, it should pass the click to the children", async () => {
|
||||
await userEvent.pointer([{ keys: "[MouseLeft>]", target }, { coords: { x: 4, y: 4 } }, "[/MouseLeft]"]);
|
||||
expect(clickSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -48,7 +48,7 @@ describe("<RoomSearchView/>", () => {
|
|||
beforeEach(async () => {
|
||||
stubClient();
|
||||
client = MatrixClientPeg.get();
|
||||
client.supportsExperimentalThreads = jest.fn().mockReturnValue(true);
|
||||
client.supportsThreads = jest.fn().mockReturnValue(true);
|
||||
room = new Room("!room:server", client, client.getUserId());
|
||||
mocked(client.getRoom).mockReturnValue(room);
|
||||
permalinkCreator = new RoomPermalinkCreator(room, room.roomId);
|
||||
|
|
|
@ -26,53 +26,14 @@ import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
|
|||
import RoomContext from "../../../src/contexts/RoomContext";
|
||||
import { _t } from "../../../src/languageHandler";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import { shouldShowFeedback } from "../../../src/utils/Feedback";
|
||||
import { RoomPermalinkCreator } from "../../../src/utils/permalinks/Permalinks";
|
||||
import ResizeNotifier from "../../../src/utils/ResizeNotifier";
|
||||
import { createTestClient, getRoomContext, mkStubRoom, mockPlatformPeg, stubClient } from "../../test-utils";
|
||||
import { getRoomContext, mockPlatformPeg, stubClient } from "../../test-utils";
|
||||
import { mkThread } from "../../test-utils/threads";
|
||||
|
||||
jest.mock("../../../src/utils/Feedback");
|
||||
|
||||
describe("ThreadPanel", () => {
|
||||
describe("Feedback prompt", () => {
|
||||
const cli = createTestClient();
|
||||
const room = mkStubRoom("!room:server", "room", cli);
|
||||
mocked(cli.getRoom).mockReturnValue(room);
|
||||
|
||||
it("should show feedback prompt if feedback is enabled", () => {
|
||||
mocked(shouldShowFeedback).mockReturnValue(true);
|
||||
|
||||
render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<ThreadPanel
|
||||
roomId="!room:server"
|
||||
onClose={jest.fn()}
|
||||
resizeNotifier={new ResizeNotifier()}
|
||||
permalinkCreator={new RoomPermalinkCreator(room)}
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
expect(screen.queryByText("Give feedback")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should hide feedback prompt if feedback is disabled", () => {
|
||||
mocked(shouldShowFeedback).mockReturnValue(false);
|
||||
|
||||
render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<ThreadPanel
|
||||
roomId="!room:server"
|
||||
onClose={jest.fn()}
|
||||
resizeNotifier={new ResizeNotifier()}
|
||||
permalinkCreator={new RoomPermalinkCreator(room)}
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
expect(screen.queryByText("Give feedback")).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Header", () => {
|
||||
it("expect that All filter for ThreadPanelHeader properly renders Show: All threads", () => {
|
||||
const { asFragment } = render(
|
||||
|
@ -161,7 +122,7 @@ describe("ThreadPanel", () => {
|
|||
Thread.setServerSideSupport(FeatureSupport.Stable);
|
||||
Thread.setServerSideListSupport(FeatureSupport.Stable);
|
||||
Thread.setServerSideFwdPaginationSupport(FeatureSupport.Stable);
|
||||
jest.spyOn(mockClient, "supportsExperimentalThreads").mockReturnValue(true);
|
||||
jest.spyOn(mockClient, "supportsThreads").mockReturnValue(true);
|
||||
|
||||
room = new Room(ROOM_ID, mockClient, mockClient.getUserId() ?? "", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
|
|
|
@ -117,7 +117,7 @@ describe("ThreadView", () => {
|
|||
stubClient();
|
||||
mockPlatformPeg();
|
||||
mockClient = mocked(MatrixClientPeg.get());
|
||||
jest.spyOn(mockClient, "supportsExperimentalThreads").mockReturnValue(true);
|
||||
jest.spyOn(mockClient, "supportsThreads").mockReturnValue(true);
|
||||
|
||||
room = new Room(ROOM_ID, mockClient, mockClient.getUserId() ?? "", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
|
|
|
@ -362,7 +362,7 @@ describe("TimelinePanel", () => {
|
|||
client = MatrixClientPeg.get();
|
||||
|
||||
Thread.hasServerSideSupport = FeatureSupport.Stable;
|
||||
client.supportsExperimentalThreads = () => true;
|
||||
client.supportsThreads = () => true;
|
||||
const getValueCopy = SettingsStore.getValue;
|
||||
SettingsStore.getValue = jest.fn().mockImplementation((name: string) => {
|
||||
if (name === "feature_threadenabled") return true;
|
||||
|
@ -524,7 +524,7 @@ describe("TimelinePanel", () => {
|
|||
|
||||
const client = MatrixClientPeg.get();
|
||||
client.isRoomEncrypted = () => true;
|
||||
client.supportsExperimentalThreads = () => true;
|
||||
client.supportsThreads = () => true;
|
||||
client.decryptEventIfNeeded = () => Promise.resolve();
|
||||
const authorId = client.getUserId()!;
|
||||
const room = new Room("roomId", client, authorId, {
|
||||
|
|
|
@ -20,22 +20,16 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
|
|||
<span
|
||||
class="mx_BaseAvatar"
|
||||
role="presentation"
|
||||
style="width: 24px; height: 24px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 15.600000000000001px; width: 24px; line-height: 24px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(172, 59, 168); width: 24px; height: 24px; font-size: 15.600000000000001px; line-height: 24px;"
|
||||
>
|
||||
U
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 24px; height: 24px;"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -119,22 +113,16 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
|
|||
<span
|
||||
class="mx_BaseAvatar"
|
||||
role="presentation"
|
||||
style="width: 24px; height: 24px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 15.600000000000001px; width: 24px; line-height: 24px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(172, 59, 168); width: 24px; height: 24px; font-size: 15.600000000000001px; line-height: 24px;"
|
||||
>
|
||||
U
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 24px; height: 24px;"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -215,23 +203,17 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
|
|||
aria-live="off"
|
||||
class="mx_AccessibleButton mx_BaseAvatar"
|
||||
role="button"
|
||||
style="width: 52px; height: 52px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 33.800000000000004px; width: 52px; line-height: 52px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(172, 59, 168); width: 52px; height: 52px; font-size: 33.800000000000004px; line-height: 52px;"
|
||||
>
|
||||
U
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 52px; height: 52px;"
|
||||
/>
|
||||
</span>
|
||||
<h2>
|
||||
@user:example.com
|
||||
|
@ -314,22 +296,16 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
|||
<span
|
||||
class="mx_BaseAvatar"
|
||||
role="presentation"
|
||||
style="width: 24px; height: 24px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 15.600000000000001px; width: 24px; line-height: 24px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(172, 59, 168); width: 24px; height: 24px; font-size: 15.600000000000001px; line-height: 24px;"
|
||||
>
|
||||
U
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 24px; height: 24px;"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -410,23 +386,17 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
|||
aria-live="off"
|
||||
class="mx_AccessibleButton mx_BaseAvatar"
|
||||
role="button"
|
||||
style="width: 52px; height: 52px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 33.800000000000004px; width: 52px; line-height: 52px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(172, 59, 168); width: 52px; height: 52px; font-size: 33.800000000000004px; line-height: 52px;"
|
||||
>
|
||||
U
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 52px; height: 52px;"
|
||||
/>
|
||||
</span>
|
||||
<h2>
|
||||
@user:example.com
|
||||
|
@ -581,22 +551,16 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
|||
<span
|
||||
class="mx_BaseAvatar"
|
||||
role="presentation"
|
||||
style="width: 24px; height: 24px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 15.600000000000001px; width: 24px; line-height: 24px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(172, 59, 168); width: 24px; height: 24px; font-size: 15.600000000000001px; line-height: 24px;"
|
||||
>
|
||||
U
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 24px; height: 24px;"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -672,23 +636,17 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
|||
aria-live="off"
|
||||
class="mx_AccessibleButton mx_BaseAvatar"
|
||||
role="button"
|
||||
style="width: 52px; height: 52px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 33.800000000000004px; width: 52px; line-height: 52px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(172, 59, 168); width: 52px; height: 52px; font-size: 33.800000000000004px; line-height: 52px;"
|
||||
>
|
||||
U
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 52px; height: 52px;"
|
||||
/>
|
||||
</span>
|
||||
<h2>
|
||||
@user:example.com
|
||||
|
|
|
@ -20,22 +20,16 @@ exports[`<UserMenu> when rendered should render as expected 1`] = `
|
|||
<span
|
||||
class="mx_BaseAvatar mx_UserMenu_userAvatar_BaseAvatar"
|
||||
role="presentation"
|
||||
style="width: 32px; height: 32px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 20.8px; width: 32px; line-height: 32px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(54, 139, 214); width: 32px; height: 32px; font-size: 20.8px; line-height: 32px;"
|
||||
>
|
||||
U
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
201
test/components/views/avatars/BaseAvatar-test.tsx
Normal file
201
test/components/views/avatars/BaseAvatar-test.tsx
Normal file
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
Copyright 2023 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 { fireEvent, render } from "@testing-library/react";
|
||||
import { ClientEvent, PendingEventOrdering } from "matrix-js-sdk/src/client";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import React from "react";
|
||||
import { act } from "react-dom/test-utils";
|
||||
import { SyncState } from "matrix-js-sdk/src/sync";
|
||||
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import RoomContext from "../../../../src/contexts/RoomContext";
|
||||
import { getRoomContext } from "../../../test-utils/room";
|
||||
import { stubClient } from "../../../test-utils/test-utils";
|
||||
import BaseAvatar from "../../../../src/components/views/avatars/BaseAvatar";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
|
||||
type Props = React.ComponentPropsWithoutRef<typeof BaseAvatar>;
|
||||
|
||||
describe("<BaseAvatar />", () => {
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
let member: RoomMember;
|
||||
|
||||
function getComponent(props: Partial<Props>) {
|
||||
return (
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={getRoomContext(room, {})}>
|
||||
<BaseAvatar name="" {...props} />
|
||||
</RoomContext.Provider>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function failLoadingImg(container: HTMLElement): void {
|
||||
const img = container.querySelector<HTMLImageElement>("img")!;
|
||||
expect(img).not.toBeNull();
|
||||
act(() => {
|
||||
fireEvent.error(img);
|
||||
});
|
||||
}
|
||||
|
||||
function emitReconnect(): void {
|
||||
act(() => {
|
||||
client.emit(ClientEvent.Sync, SyncState.Prepared, SyncState.Reconnecting);
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
|
||||
room = new Room("!room:example.com", client, client.getUserId() ?? "", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
|
||||
member = new RoomMember(room.roomId, "@bob:example.org");
|
||||
jest.spyOn(room, "getMember").mockReturnValue(member);
|
||||
});
|
||||
|
||||
it("renders with minimal properties", () => {
|
||||
const { container } = render(getComponent({}));
|
||||
|
||||
expect(container.querySelector(".mx_BaseAvatar")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("matches snapshot (avatar)", () => {
|
||||
const { container } = render(
|
||||
getComponent({
|
||||
name: "CoolUser22",
|
||||
title: "Hover title",
|
||||
url: "https://example.com/images/avatar.gif",
|
||||
className: "mx_SomethingArbitrary",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot (avatar + click)", () => {
|
||||
const { container } = render(
|
||||
getComponent({
|
||||
name: "CoolUser22",
|
||||
title: "Hover title",
|
||||
url: "https://example.com/images/avatar.gif",
|
||||
className: "mx_SomethingArbitrary",
|
||||
onClick: () => {},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot (no avatar)", () => {
|
||||
const { container } = render(
|
||||
getComponent({
|
||||
name: "xX_Element_User_Xx",
|
||||
title: ":kiss:",
|
||||
defaultToInitialLetter: true,
|
||||
className: "big-and-bold",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot (no avatar + click)", () => {
|
||||
const { container } = render(
|
||||
getComponent({
|
||||
name: "xX_Element_User_Xx",
|
||||
title: ":kiss:",
|
||||
defaultToInitialLetter: true,
|
||||
className: "big-and-bold",
|
||||
onClick: () => {},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("uses fallback images", () => {
|
||||
const images = [...Array(10)].map((_, i) => `https://example.com/images/${i}.webp`);
|
||||
|
||||
const { container } = render(
|
||||
getComponent({
|
||||
url: images[0],
|
||||
urls: images.slice(1),
|
||||
}),
|
||||
);
|
||||
|
||||
for (const image of images) {
|
||||
expect(container.querySelector("img")!.src).toBe(image);
|
||||
failLoadingImg(container);
|
||||
}
|
||||
});
|
||||
|
||||
it("re-renders on reconnect", () => {
|
||||
const primary = "https://example.com/image.jpeg";
|
||||
const fallback = "https://example.com/fallback.png";
|
||||
const { container } = render(
|
||||
getComponent({
|
||||
url: primary,
|
||||
urls: [fallback],
|
||||
}),
|
||||
);
|
||||
|
||||
failLoadingImg(container);
|
||||
expect(container.querySelector("img")!.src).toBe(fallback);
|
||||
|
||||
emitReconnect();
|
||||
expect(container.querySelector("img")!.src).toBe(primary);
|
||||
});
|
||||
|
||||
it("renders with an image", () => {
|
||||
const url = "https://example.com/images/small/avatar.gif?size=realBig";
|
||||
const { container } = render(getComponent({ url }));
|
||||
|
||||
const img = container.querySelector("img");
|
||||
expect(img!.src).toBe(url);
|
||||
});
|
||||
|
||||
it("renders the initial letter", () => {
|
||||
const { container } = render(getComponent({ name: "Yellow", defaultToInitialLetter: true }));
|
||||
|
||||
const avatar = container.querySelector<HTMLSpanElement>(".mx_BaseAvatar_initial")!;
|
||||
expect(avatar.innerHTML).toBe("Y");
|
||||
});
|
||||
|
||||
it.each([{}, { name: "CoolUser22" }, { name: "XxElement_FanxX", defaultToInitialLetter: true }])(
|
||||
"includes a click handler",
|
||||
(props: Partial<Props>) => {
|
||||
const onClick = jest.fn();
|
||||
|
||||
const { container } = render(
|
||||
getComponent({
|
||||
...props,
|
||||
onClick,
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(container.querySelector(".mx_BaseAvatar")!);
|
||||
});
|
||||
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2022 - 2023 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.
|
||||
|
@ -14,19 +14,25 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { getByTestId, render, waitFor } from "@testing-library/react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { fireEvent, getByTestId, render } from "@testing-library/react";
|
||||
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import React from "react";
|
||||
import { act } from "react-dom/test-utils";
|
||||
|
||||
import MemberAvatar from "../../../../src/components/views/avatars/MemberAvatar";
|
||||
import RoomContext from "../../../../src/contexts/RoomContext";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { mediaFromMxc } from "../../../../src/customisations/Media";
|
||||
import { ViewUserPayload } from "../../../../src/dispatcher/payloads/ViewUserPayload";
|
||||
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import { getRoomContext } from "../../../test-utils/room";
|
||||
import { stubClient } from "../../../test-utils/test-utils";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
|
||||
type Props = React.ComponentPropsWithoutRef<typeof MemberAvatar>;
|
||||
|
||||
describe("MemberAvatar", () => {
|
||||
const ROOM_ID = "roomId";
|
||||
|
@ -35,7 +41,7 @@ describe("MemberAvatar", () => {
|
|||
let room: Room;
|
||||
let member: RoomMember;
|
||||
|
||||
function getComponent(props) {
|
||||
function getComponent(props: Partial<Props>) {
|
||||
return (
|
||||
<RoomContext.Provider value={getRoomContext(room, {})}>
|
||||
<MemberAvatar member={null} width={35} height={35} {...props} />
|
||||
|
@ -44,10 +50,7 @@ describe("MemberAvatar", () => {
|
|||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
stubClient();
|
||||
mockClient = mocked(MatrixClientPeg.get());
|
||||
mockClient = stubClient();
|
||||
|
||||
room = new Room(ROOM_ID, mockClient, mockClient.getUserId() ?? "", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
|
@ -55,22 +58,77 @@ describe("MemberAvatar", () => {
|
|||
|
||||
member = new RoomMember(ROOM_ID, "@bob:example.org");
|
||||
jest.spyOn(room, "getMember").mockReturnValue(member);
|
||||
jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue("http://placekitten.com/400/400");
|
||||
});
|
||||
|
||||
it("shows an avatar for useOnlyCurrentProfiles", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
|
||||
return settingName === "useOnlyCurrentProfiles";
|
||||
});
|
||||
it("supports 'null' members", () => {
|
||||
const { container } = render(getComponent({ member: null }));
|
||||
|
||||
expect(container.querySelector("img")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("matches the snapshot", () => {
|
||||
jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue("http://placekitten.com/400/400");
|
||||
const { container } = render(
|
||||
getComponent({
|
||||
member,
|
||||
fallbackUserId: "Fallback User ID",
|
||||
title: "Hover title",
|
||||
style: {
|
||||
color: "pink",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows an avatar for useOnlyCurrentProfiles", () => {
|
||||
jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue("http://placekitten.com/400/400");
|
||||
|
||||
SettingsStore.setValue("useOnlyCurrentProfiles", null, SettingLevel.DEVICE, true);
|
||||
|
||||
const { container } = render(getComponent({}));
|
||||
|
||||
let avatar: HTMLElement;
|
||||
await waitFor(() => {
|
||||
avatar = getByTestId(container, "avatar-img");
|
||||
expect(avatar).toBeInTheDocument();
|
||||
const avatar = getByTestId<HTMLImageElement>(container, "avatar-img");
|
||||
expect(avatar).toBeInTheDocument();
|
||||
expect(avatar.getAttribute("src")).not.toBe("");
|
||||
});
|
||||
|
||||
it("uses the member's configured avatar", () => {
|
||||
const mxcUrl = "mxc://example.com/avatars/user.tiff";
|
||||
jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue(mxcUrl);
|
||||
|
||||
const { container } = render(getComponent({ member }));
|
||||
|
||||
const img = container.querySelector("img");
|
||||
expect(img).not.toBeNull();
|
||||
expect(img!.src).toBe(mediaFromMxc(mxcUrl).srcHttp);
|
||||
});
|
||||
|
||||
it("uses a fallback when the member has no avatar", () => {
|
||||
jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue(undefined);
|
||||
|
||||
const { container } = render(getComponent({ member }));
|
||||
|
||||
const img = container.querySelector(".mx_BaseAvatar_image");
|
||||
expect(img).not.toBeNull();
|
||||
});
|
||||
|
||||
it("dispatches on click", () => {
|
||||
const { container } = render(getComponent({ member, viewUserOnClick: true }));
|
||||
|
||||
const spy = jest.spyOn(defaultDispatcher, "dispatch");
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(container.querySelector(".mx_BaseAvatar")!);
|
||||
});
|
||||
|
||||
expect(avatar!.getAttribute("src")).not.toBe("");
|
||||
expect(spy).toHaveBeenCalled();
|
||||
const [payload] = spy.mock.lastCall!;
|
||||
expect(payload).toStrictEqual<ViewUserPayload>({
|
||||
action: Action.ViewUser,
|
||||
member,
|
||||
push: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -39,7 +39,7 @@ describe("RoomAvatar", () => {
|
|||
const dmRoomMap = new DMRoomMap(client);
|
||||
jest.spyOn(dmRoomMap, "getUserIdForRoomId");
|
||||
jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
|
||||
jest.spyOn(AvatarModule, "defaultAvatarUrlForString");
|
||||
jest.spyOn(AvatarModule, "getColorForString");
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
|
@ -48,14 +48,14 @@ describe("RoomAvatar", () => {
|
|||
|
||||
afterEach(() => {
|
||||
mocked(DMRoomMap.shared().getUserIdForRoomId).mockReset();
|
||||
mocked(AvatarModule.defaultAvatarUrlForString).mockClear();
|
||||
mocked(AvatarModule.getColorForString).mockClear();
|
||||
});
|
||||
|
||||
it("should render as expected for a Room", () => {
|
||||
const room = new Room("!room:example.com", client, client.getSafeUserId());
|
||||
room.name = "test room";
|
||||
expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
|
||||
expect(AvatarModule.defaultAvatarUrlForString).toHaveBeenCalledWith(room.roomId);
|
||||
expect(AvatarModule.getColorForString).toHaveBeenCalledWith(room.roomId);
|
||||
});
|
||||
|
||||
it("should render as expected for a DM room", () => {
|
||||
|
@ -64,7 +64,7 @@ describe("RoomAvatar", () => {
|
|||
room.name = "DM room";
|
||||
mocked(DMRoomMap.shared().getUserIdForRoomId).mockReturnValue(userId);
|
||||
expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
|
||||
expect(AvatarModule.defaultAvatarUrlForString).toHaveBeenCalledWith(userId);
|
||||
expect(AvatarModule.getColorForString).toHaveBeenCalledWith(userId);
|
||||
});
|
||||
|
||||
it("should render as expected for a LocalRoom", () => {
|
||||
|
@ -73,6 +73,6 @@ describe("RoomAvatar", () => {
|
|||
localRoom.name = "local test room";
|
||||
localRoom.targets.push(new DirectoryMember({ user_id: userId }));
|
||||
expect(render(<RoomAvatar room={localRoom} />).container).toMatchSnapshot();
|
||||
expect(AvatarModule.defaultAvatarUrlForString).toHaveBeenCalledWith(userId);
|
||||
expect(AvatarModule.getColorForString).toHaveBeenCalledWith(userId);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<BaseAvatar /> matches snapshot (avatar + click) 1`] = `
|
||||
<div>
|
||||
<img
|
||||
alt="Avatar"
|
||||
class="mx_AccessibleButton mx_BaseAvatar mx_BaseAvatar_image mx_SomethingArbitrary"
|
||||
data-testid="avatar-img"
|
||||
role="button"
|
||||
src="https://example.com/images/avatar.gif"
|
||||
style="width: 40px; height: 40px;"
|
||||
tabindex="0"
|
||||
title="Hover title"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<BaseAvatar /> matches snapshot (avatar) 1`] = `
|
||||
<div>
|
||||
<img
|
||||
alt=""
|
||||
class="mx_BaseAvatar mx_BaseAvatar_image mx_SomethingArbitrary"
|
||||
data-testid="avatar-img"
|
||||
src="https://example.com/images/avatar.gif"
|
||||
style="width: 40px; height: 40px;"
|
||||
title="Hover title"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<BaseAvatar /> matches snapshot (no avatar + click) 1`] = `
|
||||
<div>
|
||||
<span
|
||||
aria-label="Avatar"
|
||||
aria-live="off"
|
||||
class="mx_AccessibleButton mx_BaseAvatar big-and-bold"
|
||||
role="button"
|
||||
style="width: 40px; height: 40px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(13, 189, 139); width: 40px; height: 40px; font-size: 26px; line-height: 40px;"
|
||||
title=":kiss:"
|
||||
>
|
||||
X
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<BaseAvatar /> matches snapshot (no avatar) 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="mx_BaseAvatar big-and-bold"
|
||||
role="presentation"
|
||||
style="width: 40px; height: 40px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(13, 189, 139); width: 40px; height: 40px; font-size: 26px; line-height: 40px;"
|
||||
title=":kiss:"
|
||||
>
|
||||
X
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,14 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MemberAvatar matches the snapshot 1`] = `
|
||||
<div>
|
||||
<img
|
||||
alt=""
|
||||
class="mx_BaseAvatar mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="http://this.is.a.url//placekitten.com/400/400"
|
||||
style="color: pink; width: 35px; height: 35px;"
|
||||
title="Hover title"
|
||||
/>
|
||||
</div>
|
||||
`;
|
|
@ -5,22 +5,16 @@ exports[`RoomAvatar should render as expected for a DM room 1`] = `
|
|||
<span
|
||||
class="mx_BaseAvatar"
|
||||
role="presentation"
|
||||
style="width: 36px; height: 36px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(13, 189, 139); width: 36px; height: 36px; font-size: 23.400000000000002px; line-height: 36px;"
|
||||
>
|
||||
D
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 36px; height: 36px;"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
@ -30,22 +24,16 @@ exports[`RoomAvatar should render as expected for a LocalRoom 1`] = `
|
|||
<span
|
||||
class="mx_BaseAvatar"
|
||||
role="presentation"
|
||||
style="width: 36px; height: 36px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(172, 59, 168); width: 36px; height: 36px; font-size: 23.400000000000002px; line-height: 36px;"
|
||||
>
|
||||
L
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 36px; height: 36px;"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
@ -55,22 +43,16 @@ exports[`RoomAvatar should render as expected for a Room 1`] = `
|
|||
<span
|
||||
class="mx_BaseAvatar"
|
||||
role="presentation"
|
||||
style="width: 36px; height: 36px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(172, 59, 168); width: 36px; height: 36px; font-size: 23.400000000000002px; line-height: 36px;"
|
||||
>
|
||||
T
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 36px; height: 36px;"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -13,23 +13,17 @@ exports[`<BeaconMarker /> renders marker when beacon has location 1`] = `
|
|||
<span
|
||||
class="mx_BaseAvatar"
|
||||
role="presentation"
|
||||
style="width: 36px; height: 36px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(172, 59, 168); width: 36px; height: 36px; font-size: 23.400000000000002px; line-height: 36px;"
|
||||
title="@alice:server"
|
||||
>
|
||||
A
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 36px; height: 36px;"
|
||||
title="@alice:server"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
71
test/components/views/dialogs/DevtoolsDialog-test.tsx
Normal file
71
test/components/views/dialogs/DevtoolsDialog-test.tsx
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
Copyright 2023 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 { getByLabelText, render } from "@testing-library/react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import DevtoolsDialog from "../../../../src/components/views/dialogs/DevtoolsDialog";
|
||||
|
||||
describe("DevtoolsDialog", () => {
|
||||
let cli: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
function getComponent(roomId: string, onFinished = () => true) {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<DevtoolsDialog roomId={roomId} onFinished={onFinished} />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.get();
|
||||
room = new Room("!id", cli, "@alice:matrix.org");
|
||||
|
||||
jest.spyOn(cli, "getRoom").mockReturnValue(room);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("renders the devtools dialog", () => {
|
||||
const { asFragment } = getComponent(room.roomId);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("copies the roomid", async () => {
|
||||
const user = userEvent.setup();
|
||||
jest.spyOn(navigator.clipboard, "writeText");
|
||||
|
||||
const { container } = getComponent(room.roomId);
|
||||
|
||||
const copyBtn = getByLabelText(container, "Copy");
|
||||
await user.click(copyBtn);
|
||||
const copiedBtn = getByLabelText(container, "Copied!");
|
||||
|
||||
expect(copiedBtn).toBeInTheDocument();
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalled();
|
||||
expect(navigator.clipboard.readText()).resolves.toBe(room.roomId);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
Copyright 2023 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, RenderResult } from "@testing-library/react";
|
||||
import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { flushPromises, mkMessage, stubClient } from "../../../test-utils";
|
||||
import MessageEditHistoryDialog from "../../../../src/components/views/dialogs/MessageEditHistoryDialog";
|
||||
|
||||
describe("<MessageEditHistory />", () => {
|
||||
const roomId = "!aroom:example.com";
|
||||
let client: jest.Mocked<MatrixClient>;
|
||||
let event: MatrixEvent;
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient() as jest.Mocked<MatrixClient>;
|
||||
event = mkMessage({
|
||||
event: true,
|
||||
user: "@user:example.com",
|
||||
room: "!room:example.com",
|
||||
msg: "My Great Message",
|
||||
});
|
||||
});
|
||||
|
||||
async function renderComponent(): Promise<RenderResult> {
|
||||
const result = render(<MessageEditHistoryDialog mxEvent={event} onFinished={jest.fn()} />);
|
||||
await flushPromises();
|
||||
return result;
|
||||
}
|
||||
|
||||
function mockEdits(...edits: { msg: string; ts: number | undefined }[]) {
|
||||
client.relations.mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
events: edits.map(
|
||||
(e) =>
|
||||
new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
room_id: roomId,
|
||||
origin_server_ts: e.ts,
|
||||
content: {
|
||||
body: e.msg,
|
||||
},
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
it("should match the snapshot", async () => {
|
||||
mockEdits({ msg: "My Great Massage", ts: 1234 });
|
||||
|
||||
const { container } = await renderComponent();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should support events with ", async () => {
|
||||
mockEdits(
|
||||
{ msg: "My Great Massage", ts: undefined },
|
||||
{ msg: "My Great Massage?", ts: undefined },
|
||||
{ msg: "My Great Missage", ts: undefined },
|
||||
);
|
||||
|
||||
const { container } = await renderComponent();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,229 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DevtoolsDialog renders the devtools dialog 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_QuestionDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header mx_Dialog_headerWithCancel"
|
||||
>
|
||||
<h2
|
||||
class="mx_Heading_h2 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Developer Tools
|
||||
</h2>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_DevTools_label_left"
|
||||
>
|
||||
Toolbox
|
||||
</div>
|
||||
<div
|
||||
class="mx_CopyableText mx_DevTools_label_right"
|
||||
>
|
||||
Room ID: !id
|
||||
<div
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_DevTools_label_bottom"
|
||||
/>
|
||||
<div
|
||||
class="mx_DevTools_content"
|
||||
>
|
||||
<div>
|
||||
<h3>
|
||||
Room
|
||||
</h3>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Send custom timeline event
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Explore room state
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Explore room account data
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
View servers in room
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Verification explorer
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Active Widgets
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<h3>
|
||||
Other
|
||||
</h3>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Explore account data
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Settings explorer
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Server info
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<h3>
|
||||
Options
|
||||
</h3>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Developer mode
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="false"
|
||||
aria-disabled="false"
|
||||
aria-label="Developer mode"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Show hidden events in timeline
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="false"
|
||||
aria-disabled="false"
|
||||
aria-label="Show hidden events in timeline"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Enable widget screenshots on supported widgets
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="false"
|
||||
aria-disabled="false"
|
||||
aria-label="Enable widget screenshots on supported widgets"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Force 15s voice broadcast chunk length
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="false"
|
||||
aria-disabled="false"
|
||||
aria-label="Force 15s voice broadcast chunk length"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<button>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
|
@ -0,0 +1,322 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<MessageEditHistory /> should match the snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_MessageEditHistoryDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header mx_Dialog_headerWithCancel"
|
||||
>
|
||||
<h2
|
||||
class="mx_Heading_h2 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Message edits
|
||||
</h2>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar mx_ScrollPanel mx_MessageEditHistoryDialog_scrollPanel"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomView_messageListWrapper"
|
||||
>
|
||||
<ol
|
||||
aria-live="polite"
|
||||
class="mx_RoomView_MessageList"
|
||||
>
|
||||
<ul
|
||||
class="mx_MessageEditHistoryDialog_edits"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
aria-label="Thu, Jan 1 1970"
|
||||
class="mx_DateSeparator"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
<h2
|
||||
aria-hidden="true"
|
||||
>
|
||||
Thu, Jan 1 1970
|
||||
</h2>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
class="mx_EventTile"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTile_line"
|
||||
>
|
||||
<span
|
||||
class="mx_MessageTimestamp"
|
||||
>
|
||||
00:00
|
||||
</span>
|
||||
<div
|
||||
class="mx_EventTile_content"
|
||||
>
|
||||
<span
|
||||
class="mx_EventTile_body"
|
||||
dir="auto"
|
||||
>
|
||||
My Great Massage
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MessageActionBar"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Remove
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<MessageEditHistory /> should support events with 1`] = `
|
||||
<div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_MessageEditHistoryDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header mx_Dialog_headerWithCancel"
|
||||
>
|
||||
<h2
|
||||
class="mx_Heading_h2 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Message edits
|
||||
</h2>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar mx_ScrollPanel mx_MessageEditHistoryDialog_scrollPanel"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomView_messageListWrapper"
|
||||
>
|
||||
<ol
|
||||
aria-live="polite"
|
||||
class="mx_RoomView_MessageList"
|
||||
>
|
||||
<ul
|
||||
class="mx_MessageEditHistoryDialog_edits"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
aria-label=", NaN NaN"
|
||||
class="mx_DateSeparator"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
<h2
|
||||
aria-hidden="true"
|
||||
>
|
||||
, NaN NaN
|
||||
</h2>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
class="mx_EventTile"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTile_line"
|
||||
>
|
||||
<span
|
||||
class="mx_MessageTimestamp"
|
||||
>
|
||||
NaN:NaN
|
||||
</span>
|
||||
<div
|
||||
class="mx_EventTile_content"
|
||||
>
|
||||
<span
|
||||
class="mx_EventTile_body markdown-body"
|
||||
dir="auto"
|
||||
>
|
||||
<span>
|
||||
My Great Massage
|
||||
<span
|
||||
class="mx_EditHistoryMessage_deletion"
|
||||
>
|
||||
?
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MessageActionBar"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Remove
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
class="mx_EventTile"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTile_line"
|
||||
>
|
||||
<span
|
||||
class="mx_MessageTimestamp"
|
||||
>
|
||||
NaN:NaN
|
||||
</span>
|
||||
<div
|
||||
class="mx_EventTile_content"
|
||||
>
|
||||
<span
|
||||
class="mx_EventTile_body markdown-body"
|
||||
dir="auto"
|
||||
>
|
||||
<span>
|
||||
My Great M
|
||||
<span
|
||||
class="mx_EditHistoryMessage_deletion"
|
||||
>
|
||||
i
|
||||
</span>
|
||||
<span
|
||||
class="mx_EditHistoryMessage_insertion"
|
||||
>
|
||||
a
|
||||
</span>
|
||||
ssage
|
||||
<span
|
||||
class="mx_EditHistoryMessage_insertion"
|
||||
>
|
||||
?
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MessageActionBar"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Remove
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
class="mx_EventTile"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTile_line"
|
||||
>
|
||||
<span
|
||||
class="mx_MessageTimestamp"
|
||||
>
|
||||
NaN:NaN
|
||||
</span>
|
||||
<div
|
||||
class="mx_EventTile_content"
|
||||
>
|
||||
<span
|
||||
class="mx_EventTile_body"
|
||||
dir="auto"
|
||||
>
|
||||
My Great Missage
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MessageActionBar"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Remove
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
`;
|
|
@ -18,13 +18,16 @@ import React from "react";
|
|||
import { act, render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { EventType, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import dis from "../../../../src/dispatcher/dispatcher";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import RoomCreate from "../../../../src/components/views/messages/RoomCreate";
|
||||
import { stubClient } from "../../../test-utils/test-utils";
|
||||
import { RoomCreate } from "../../../../src/components/views/messages/RoomCreate";
|
||||
import { stubClient, upsertRoomStateEvents } from "../../../test-utils/test-utils";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
import RoomContext from "../../../../src/contexts/RoomContext";
|
||||
import { getRoomContext } from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
|
||||
jest.mock("../../../../src/dispatcher/dispatcher");
|
||||
|
||||
|
@ -33,6 +36,7 @@ describe("<RoomCreate />", () => {
|
|||
const roomId = "!room:server.org";
|
||||
const createEvent = new MatrixEvent({
|
||||
type: EventType.RoomCreate,
|
||||
state_key: "",
|
||||
sender: userId,
|
||||
room_id: roomId,
|
||||
content: {
|
||||
|
@ -40,6 +44,20 @@ describe("<RoomCreate />", () => {
|
|||
},
|
||||
event_id: "$create",
|
||||
});
|
||||
const createEventWithoutPredecessor = new MatrixEvent({
|
||||
type: EventType.RoomCreate,
|
||||
state_key: "",
|
||||
sender: userId,
|
||||
room_id: roomId,
|
||||
content: {},
|
||||
event_id: "$create",
|
||||
});
|
||||
stubClient();
|
||||
const client = mocked(MatrixClientPeg.get());
|
||||
const room = new Room(roomId, client, userId);
|
||||
upsertRoomStateEvents(room, [createEvent]);
|
||||
const roomNoPredecessors = new Room(roomId, client, userId);
|
||||
upsertRoomStateEvents(roomNoPredecessors, [createEventWithoutPredecessor]);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -54,21 +72,34 @@ describe("<RoomCreate />", () => {
|
|||
jest.spyOn(SettingsStore, "setValue").mockRestore();
|
||||
});
|
||||
|
||||
function renderRoomCreate(room: Room) {
|
||||
return render(
|
||||
<RoomContext.Provider value={getRoomContext(room, {})}>
|
||||
<RoomCreate mxEvent={createEvent} />
|
||||
</RoomContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
it("Renders as expected", () => {
|
||||
const roomCreate = render(<RoomCreate mxEvent={createEvent} />);
|
||||
const roomCreate = renderRoomCreate(room);
|
||||
expect(roomCreate.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("Links to the old version of the room", () => {
|
||||
render(<RoomCreate mxEvent={createEvent} />);
|
||||
renderRoomCreate(room);
|
||||
expect(screen.getByText("Click here to see older messages.")).toHaveAttribute(
|
||||
"href",
|
||||
"https://matrix.to/#/old_room_id/tombstone_event_id",
|
||||
);
|
||||
});
|
||||
|
||||
it("Shows an empty div if there is no predecessor", () => {
|
||||
renderRoomCreate(roomNoPredecessors);
|
||||
expect(screen.queryByText("Click here to see older messages.", { exact: false })).toBeNull();
|
||||
});
|
||||
|
||||
it("Opens the old room on click", async () => {
|
||||
render(<RoomCreate mxEvent={createEvent} />);
|
||||
renderRoomCreate(room);
|
||||
const link = screen.getByText("Click here to see older messages.");
|
||||
|
||||
await act(() => userEvent.click(link));
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import { render } from "@testing-library/react";
|
||||
import { MatrixEvent, MsgType, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
|
||||
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
|
||||
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
|
||||
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
|
||||
import React from "react";
|
||||
|
@ -38,7 +37,7 @@ describe("RoomHeaderButtons-test.tsx", function () {
|
|||
|
||||
stubClient();
|
||||
client = MatrixClientPeg.get();
|
||||
client.supportsExperimentalThreads = () => true;
|
||||
client.supportsThreads = () => true;
|
||||
room = new Room(ROOM_ID, client, client.getUserId() ?? "", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
|
@ -173,9 +172,4 @@ describe("RoomHeaderButtons-test.tsx", function () {
|
|||
room.addReceipt(receipt);
|
||||
expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();
|
||||
});
|
||||
|
||||
it("does not explode without a room", () => {
|
||||
client.canSupport.set(Feature.ThreadUnreadNotifications, ServerSupport.Unsupported);
|
||||
expect(() => getComponent()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -72,6 +72,7 @@ const mockRoom = mocked({
|
|||
getMxcAvatarUrl: jest.fn().mockReturnValue("mock-avatar-url"),
|
||||
name: "test room",
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
currentState: {
|
||||
getStateEvents: jest.fn(),
|
||||
on: jest.fn(),
|
||||
|
@ -83,9 +84,12 @@ const mockClient = mocked({
|
|||
getUser: jest.fn(),
|
||||
isGuest: jest.fn().mockReturnValue(false),
|
||||
isUserIgnored: jest.fn(),
|
||||
getIgnoredUsers: jest.fn(),
|
||||
setIgnoredUsers: jest.fn(),
|
||||
isCryptoEnabled: jest.fn(),
|
||||
getUserId: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
isSynapseAdministrator: jest.fn().mockResolvedValue(false),
|
||||
isRoomEncrypted: jest.fn().mockReturnValue(false),
|
||||
doesServerSupportUnstableFeature: jest.fn().mockReturnValue(false),
|
||||
|
@ -386,8 +390,11 @@ describe("<UserOptionsSection />", () => {
|
|||
|
||||
beforeEach(() => {
|
||||
inviteSpy.mockReset();
|
||||
mockClient.setIgnoredUsers.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => Modal.closeCurrentModal("End of test"));
|
||||
|
||||
afterAll(() => {
|
||||
inviteSpy.mockRestore();
|
||||
});
|
||||
|
@ -543,6 +550,52 @@ describe("<UserOptionsSection />", () => {
|
|||
expect(screen.getByText(/operation failed/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a modal before ignoring the user", async () => {
|
||||
const originalCreateDialog = Modal.createDialog;
|
||||
const modalSpy = (Modal.createDialog = jest.fn().mockReturnValue({
|
||||
finished: Promise.resolve([true]),
|
||||
close: () => {},
|
||||
}));
|
||||
|
||||
try {
|
||||
mockClient.getIgnoredUsers.mockReturnValue([]);
|
||||
renderComponent({ isIgnored: false });
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Ignore" }));
|
||||
expect(modalSpy).toHaveBeenCalled();
|
||||
expect(mockClient.setIgnoredUsers).toHaveBeenLastCalledWith([member.userId]);
|
||||
} finally {
|
||||
Modal.createDialog = originalCreateDialog;
|
||||
}
|
||||
});
|
||||
|
||||
it("cancels ignoring the user", async () => {
|
||||
const originalCreateDialog = Modal.createDialog;
|
||||
const modalSpy = (Modal.createDialog = jest.fn().mockReturnValue({
|
||||
finished: Promise.resolve([false]),
|
||||
close: () => {},
|
||||
}));
|
||||
|
||||
try {
|
||||
mockClient.getIgnoredUsers.mockReturnValue([]);
|
||||
renderComponent({ isIgnored: false });
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Ignore" }));
|
||||
expect(modalSpy).toHaveBeenCalled();
|
||||
expect(mockClient.setIgnoredUsers).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
Modal.createDialog = originalCreateDialog;
|
||||
}
|
||||
});
|
||||
|
||||
it("unignores the user", async () => {
|
||||
mockClient.getIgnoredUsers.mockReturnValue([member.userId]);
|
||||
renderComponent({ isIgnored: true });
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Unignore" }));
|
||||
expect(mockClient.setIgnoredUsers).toHaveBeenCalledWith([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("<PowerLevelEditor />", () => {
|
||||
|
|
|
@ -92,7 +92,7 @@ describe("EventTile", () => {
|
|||
|
||||
describe("EventTile thread summary", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(client, "supportsExperimentalThreads").mockReturnValue(true);
|
||||
jest.spyOn(client, "supportsThreads").mockReturnValue(true);
|
||||
});
|
||||
|
||||
it("removes the thread summary when thread is deleted", async () => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2022 - 2023 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.
|
||||
|
@ -23,36 +23,26 @@ import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
|
|||
import { EventStatus } from "matrix-js-sdk/src/models/event-status";
|
||||
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
|
||||
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { mkThread } from "../../../../test-utils/threads";
|
||||
import { UnreadNotificationBadge } from "../../../../../src/components/views/rooms/NotificationBadge/UnreadNotificationBadge";
|
||||
import { mkEvent, mkMessage, stubClient } from "../../../../test-utils/test-utils";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { mkEvent, mkMessage, muteRoom, stubClient } from "../../../../test-utils/test-utils";
|
||||
import * as RoomNotifs from "../../../../../src/RoomNotifs";
|
||||
|
||||
jest.mock("../../../../../src/RoomNotifs");
|
||||
jest.mock("../../../../../src/RoomNotifs", () => ({
|
||||
...(jest.requireActual("../../../../../src/RoomNotifs") as Object),
|
||||
getRoomNotifsState: jest.fn(),
|
||||
}));
|
||||
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
let THREAD_ID: string;
|
||||
|
||||
describe("UnreadNotificationBadge", () => {
|
||||
stubClient();
|
||||
const client = MatrixClientPeg.get();
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
function getComponent(threadId?: string) {
|
||||
return <UnreadNotificationBadge room={room} threadId={threadId} />;
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
client.supportsExperimentalThreads = () => true;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
client = stubClient();
|
||||
client.supportsThreads = () => true;
|
||||
|
||||
room = new Room(ROOM_ID, client, client.getUserId()!, {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
|
@ -145,41 +135,39 @@ describe("UnreadNotificationBadge", () => {
|
|||
});
|
||||
|
||||
it("adds a warning for invites", () => {
|
||||
jest.spyOn(room, "getMyMembership").mockReturnValue("invite");
|
||||
room.updateMyMembership("invite");
|
||||
render(getComponent());
|
||||
expect(screen.queryByText("!")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("hides counter for muted rooms", () => {
|
||||
jest.spyOn(RoomNotifs, "getRoomNotifsState").mockReset().mockReturnValue(RoomNotifs.RoomNotifState.Mute);
|
||||
muteRoom(room);
|
||||
|
||||
const { container } = render(getComponent());
|
||||
expect(container.querySelector(".mx_NotificationBadge")).toBeNull();
|
||||
});
|
||||
|
||||
it("activity renders unread notification badge", () => {
|
||||
act(() => {
|
||||
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 0);
|
||||
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 0);
|
||||
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 0);
|
||||
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 0);
|
||||
|
||||
// Add another event on the thread which is not sent by us.
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
type: "m.room.message",
|
||||
user: "@alice:server.org",
|
||||
room: room.roomId,
|
||||
content: {
|
||||
"msgtype": MsgType.Text,
|
||||
"body": "Hello from Bob",
|
||||
"m.relates_to": {
|
||||
event_id: THREAD_ID,
|
||||
rel_type: RelationType.Thread,
|
||||
},
|
||||
// Add another event on the thread which is not sent by us.
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
type: "m.room.message",
|
||||
user: "@alice:server.org",
|
||||
room: room.roomId,
|
||||
content: {
|
||||
"msgtype": MsgType.Text,
|
||||
"body": "Hello from Bob",
|
||||
"m.relates_to": {
|
||||
event_id: THREAD_ID,
|
||||
rel_type: RelationType.Thread,
|
||||
},
|
||||
ts: 5,
|
||||
});
|
||||
room.addLiveEvents([event]);
|
||||
},
|
||||
ts: 5,
|
||||
});
|
||||
room.addLiveEvents([event]);
|
||||
|
||||
const { container } = render(getComponent(THREAD_ID));
|
||||
expect(container.querySelector(".mx_NotificationBadge_dot")).toBeTruthy();
|
||||
|
|
|
@ -72,7 +72,7 @@ describe("RoomHeader (Enzyme)", () => {
|
|||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
||||
expect(image.prop("src")).toEqual("data:image/png;base64,00");
|
||||
expect(image).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows the room avatar in a room with 2 people", () => {
|
||||
|
@ -86,7 +86,7 @@ describe("RoomHeader (Enzyme)", () => {
|
|||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
||||
expect(image.prop("src")).toEqual("data:image/png;base64,00");
|
||||
expect(image).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows the room avatar in a room with >2 people", () => {
|
||||
|
@ -100,7 +100,7 @@ describe("RoomHeader (Enzyme)", () => {
|
|||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
||||
expect(image.prop("src")).toEqual("data:image/png;base64,00");
|
||||
expect(image).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows the room avatar in a DM with only ourselves", () => {
|
||||
|
@ -114,7 +114,7 @@ describe("RoomHeader (Enzyme)", () => {
|
|||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
||||
expect(image.prop("src")).toEqual("data:image/png;base64,00");
|
||||
expect(image).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows the user avatar in a DM with 2 people", () => {
|
||||
|
@ -148,7 +148,7 @@ describe("RoomHeader (Enzyme)", () => {
|
|||
|
||||
// And there is no image avatar (because it's not set on this room)
|
||||
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
||||
expect(image.prop("src")).toEqual("data:image/png;base64,00");
|
||||
expect(image).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders call buttons normally", () => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2022 - 2023 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.
|
||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import * as React from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
import { render } from "@testing-library/react";
|
||||
import { render, type RenderResult } from "@testing-library/react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import { stubClient } from "../../../test-utils";
|
||||
|
@ -26,6 +26,8 @@ import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
|||
|
||||
const ROOM_ID = "!qPewotXpIctQySfjSy:localhost";
|
||||
|
||||
type Props = React.ComponentPropsWithoutRef<typeof SearchResultTile>;
|
||||
|
||||
describe("SearchResultTile", () => {
|
||||
beforeAll(() => {
|
||||
stubClient();
|
||||
|
@ -35,50 +37,72 @@ describe("SearchResultTile", () => {
|
|||
jest.spyOn(cli, "getRoom").mockReturnValue(room);
|
||||
});
|
||||
|
||||
function renderComponent(props: Partial<Props>): RenderResult {
|
||||
return render(<SearchResultTile timeline={[]} ourEventsIndexes={[1]} {...props} />);
|
||||
}
|
||||
|
||||
it("Sets up appropriate callEventGrouper for m.call. events", () => {
|
||||
const { container } = render(
|
||||
<SearchResultTile
|
||||
timeline={[
|
||||
new MatrixEvent({
|
||||
type: EventType.CallInvite,
|
||||
sender: "@user1:server",
|
||||
room_id: ROOM_ID,
|
||||
origin_server_ts: 1432735824652,
|
||||
content: { call_id: "call.1" },
|
||||
event_id: "$1:server",
|
||||
}),
|
||||
new MatrixEvent({
|
||||
content: {
|
||||
body: "This is an example text message",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: "<b>This is an example text message</b>",
|
||||
msgtype: "m.text",
|
||||
},
|
||||
event_id: "$144429830826TWwbB:localhost",
|
||||
origin_server_ts: 1432735824653,
|
||||
room_id: ROOM_ID,
|
||||
sender: "@example:example.org",
|
||||
type: "m.room.message",
|
||||
unsigned: {
|
||||
age: 1234,
|
||||
},
|
||||
}),
|
||||
new MatrixEvent({
|
||||
type: EventType.CallAnswer,
|
||||
sender: "@user2:server",
|
||||
room_id: ROOM_ID,
|
||||
origin_server_ts: 1432735824654,
|
||||
content: { call_id: "call.1" },
|
||||
event_id: "$2:server",
|
||||
}),
|
||||
]}
|
||||
ourEventsIndexes={[1]}
|
||||
/>,
|
||||
);
|
||||
const { container } = renderComponent({
|
||||
timeline: [
|
||||
new MatrixEvent({
|
||||
type: EventType.CallInvite,
|
||||
sender: "@user1:server",
|
||||
room_id: ROOM_ID,
|
||||
origin_server_ts: 1432735824652,
|
||||
content: { call_id: "call.1" },
|
||||
event_id: "$1:server",
|
||||
}),
|
||||
new MatrixEvent({
|
||||
content: {
|
||||
body: "This is an example text message",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: "<b>This is an example text message</b>",
|
||||
msgtype: "m.text",
|
||||
},
|
||||
event_id: "$144429830826TWwbB:localhost",
|
||||
origin_server_ts: 1432735824653,
|
||||
room_id: ROOM_ID,
|
||||
sender: "@example:example.org",
|
||||
type: "m.room.message",
|
||||
unsigned: {
|
||||
age: 1234,
|
||||
},
|
||||
}),
|
||||
new MatrixEvent({
|
||||
type: EventType.CallAnswer,
|
||||
sender: "@user2:server",
|
||||
room_id: ROOM_ID,
|
||||
origin_server_ts: 1432735824654,
|
||||
content: { call_id: "call.1" },
|
||||
event_id: "$2:server",
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const tiles = container.querySelectorAll<HTMLElement>(".mx_EventTile");
|
||||
expect(tiles.length).toEqual(2);
|
||||
expect(tiles[0].dataset.eventId).toBe("$1:server");
|
||||
expect(tiles[1].dataset.eventId).toBe("$144429830826TWwbB:localhost");
|
||||
expect(tiles[0]!.dataset.eventId).toBe("$1:server");
|
||||
expect(tiles[1]!.dataset.eventId).toBe("$144429830826TWwbB:localhost");
|
||||
});
|
||||
|
||||
it("supports events with missing timestamps", () => {
|
||||
const { container } = renderComponent({
|
||||
timeline: [...Array(20)].map(
|
||||
(_, i) =>
|
||||
new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
sender: "@user1:server",
|
||||
room_id: ROOM_ID,
|
||||
content: { body: `Message #${i}` },
|
||||
event_id: `$${i}:server`,
|
||||
origin_server_ts: undefined,
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const separators = container.querySelectorAll(".mx_DateSeparator");
|
||||
// One separator is always rendered at the top, we don't want any
|
||||
// between messages.
|
||||
expect(separators.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -161,22 +161,16 @@ exports[`<RoomPreviewBar /> with an invite without an invited email for a dm roo
|
|||
<span
|
||||
class="mx_BaseAvatar"
|
||||
role="presentation"
|
||||
style="width: 36px; height: 36px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(172, 59, 168); width: 36px; height: 36px; font-size: 23.400000000000002px; line-height: 36px;"
|
||||
>
|
||||
R
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 36px; height: 36px;"
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
|
@ -236,22 +230,16 @@ exports[`<RoomPreviewBar /> with an invite without an invited email for a non-dm
|
|||
<span
|
||||
class="mx_BaseAvatar"
|
||||
role="presentation"
|
||||
style="width: 36px; height: 36px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(172, 59, 168); width: 36px; height: 36px; font-size: 23.400000000000002px; line-height: 36px;"
|
||||
>
|
||||
R
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 36px; height: 36px;"
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
|
|
|
@ -15,22 +15,16 @@ exports[`RoomTile should render the room 1`] = `
|
|||
<span
|
||||
class="mx_BaseAvatar"
|
||||
role="presentation"
|
||||
style="width: 32px; height: 32px;"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_initial"
|
||||
style="font-size: 20.8px; width: 32px; line-height: 32px;"
|
||||
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
|
||||
data-testid="avatar-img"
|
||||
style="background-color: rgb(172, 59, 168); width: 32px; height: 32px; font-size: 20.8px; line-height: 32px;"
|
||||
>
|
||||
!
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="mx_BaseAvatar_image"
|
||||
data-testid="avatar-img"
|
||||
src="data:image/png;base64,00"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
@ -17,13 +17,21 @@ limitations under the License.
|
|||
import "@testing-library/jest-dom";
|
||||
import React from "react";
|
||||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { EventTimeline, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
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 { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import { createTestClient, flushPromises, getRoomContext, mkEvent, mkStubRoom } from "../../../../test-utils";
|
||||
import {
|
||||
createTestClient,
|
||||
flushPromises,
|
||||
getRoomContext,
|
||||
mkEvent,
|
||||
mkStubRoom,
|
||||
mockPlatformPeg,
|
||||
} 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";
|
||||
|
@ -32,38 +40,54 @@ import dis from "../../../../../src/dispatcher/dispatcher";
|
|||
import { ComposerInsertPayload, ComposerType } from "../../../../../src/dispatcher/payloads/ComposerInsertPayload";
|
||||
import { ActionPayload } from "../../../../../src/dispatcher/payloads";
|
||||
import * as EmojiButton from "../../../../../src/components/views/rooms/EmojiButton";
|
||||
import { setSelection } from "../../../../../src/components/views/rooms/wysiwyg_composer/utils/selection";
|
||||
import * as EventUtils from "../../../../../src/utils/EventUtils";
|
||||
import { SubSelection } from "../../../../../src/components/views/rooms/wysiwyg_composer/types";
|
||||
|
||||
describe("EditWysiwygComposer", () => {
|
||||
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",
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: "Replying <b>to</b> this new content",
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
const mockRoom = mkStubRoom("myfakeroom", "myfakeroom", mockClient) as any;
|
||||
mockRoom.findEventById = jest.fn((eventId) => {
|
||||
return eventId === mockEvent.getId() ? mockEvent : null;
|
||||
});
|
||||
function createMocks(eventContent = "Replying <strong>to</strong> this new content") {
|
||||
const mockClient = createTestClient();
|
||||
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: eventContent,
|
||||
},
|
||||
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 defaultRoomContext: IRoomState = getRoomContext(mockRoom, {
|
||||
liveTimeline: { getEvents: (): MatrixEvent[] => [] } as unknown as EventTimeline,
|
||||
});
|
||||
|
||||
const editorStateTransfer = new EditorStateTransfer(mockEvent);
|
||||
const editorStateTransfer = new EditorStateTransfer(mockEvent);
|
||||
|
||||
const customRender = (disabled = false, _editorStateTransfer = editorStateTransfer) => {
|
||||
return { defaultRoomContext, editorStateTransfer, mockClient, mockEvent };
|
||||
}
|
||||
|
||||
const { editorStateTransfer, defaultRoomContext, mockClient, mockEvent } = createMocks();
|
||||
|
||||
const customRender = (
|
||||
disabled = false,
|
||||
_editorStateTransfer = editorStateTransfer,
|
||||
client = mockClient,
|
||||
roomContext = defaultRoomContext,
|
||||
) => {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={defaultRoomContext}>
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={roomContext}>
|
||||
<EditWysiwygComposer disabled={disabled} editorStateTransfer={_editorStateTransfer} />
|
||||
</RoomContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
|
@ -176,12 +200,13 @@ describe("EditWysiwygComposer", () => {
|
|||
});
|
||||
|
||||
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"));
|
||||
});
|
||||
|
||||
const spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch");
|
||||
afterEach(() => {
|
||||
spyDispatcher.mockRestore();
|
||||
});
|
||||
|
@ -204,7 +229,6 @@ describe("EditWysiwygComposer", () => {
|
|||
|
||||
it("Should send message on save button click", async () => {
|
||||
// When
|
||||
const spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch");
|
||||
fireEvent.input(screen.getByRole("textbox"), {
|
||||
data: "foo bar",
|
||||
inputType: "insertText",
|
||||
|
@ -318,4 +342,290 @@ describe("EditWysiwygComposer", () => {
|
|||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(/🦫/));
|
||||
dis.unregister(dispatcherRef);
|
||||
});
|
||||
|
||||
describe("Keyboard navigation", () => {
|
||||
const setup = async (
|
||||
editorState = editorStateTransfer,
|
||||
client = createTestClient(),
|
||||
roomContext = defaultRoomContext,
|
||||
) => {
|
||||
const spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch");
|
||||
customRender(false, editorState, client, roomContext);
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveAttribute("contentEditable", "true"));
|
||||
return { textbox: screen.getByRole("textbox"), spyDispatcher };
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
|
||||
jest.spyOn(EventUtils, "findEditableEvent").mockReturnValue(mockEvent);
|
||||
});
|
||||
|
||||
function select(selection: SubSelection) {
|
||||
return act(async () => {
|
||||
await setSelection(selection);
|
||||
// the event is not automatically fired by jest
|
||||
document.dispatchEvent(new CustomEvent("selectionchange"));
|
||||
});
|
||||
}
|
||||
|
||||
describe("Moving up", () => {
|
||||
it("Should not moving when caret is not at beginning of the text", async () => {
|
||||
// When
|
||||
const { textbox, spyDispatcher } = await setup();
|
||||
const textNode = textbox.firstChild;
|
||||
await select({
|
||||
anchorNode: textNode,
|
||||
anchorOffset: 1,
|
||||
focusNode: textNode,
|
||||
focusOffset: 2,
|
||||
isForward: true,
|
||||
});
|
||||
|
||||
fireEvent.keyDown(textbox, {
|
||||
key: "ArrowUp",
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(spyDispatcher).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it("Should not moving when the content has changed", async () => {
|
||||
// When
|
||||
const { textbox, spyDispatcher } = await setup();
|
||||
fireEvent.input(textbox, {
|
||||
data: "word",
|
||||
inputType: "insertText",
|
||||
});
|
||||
const textNode = textbox.firstChild;
|
||||
await select({
|
||||
anchorNode: textNode,
|
||||
anchorOffset: 0,
|
||||
focusNode: textNode,
|
||||
focusOffset: 0,
|
||||
isForward: true,
|
||||
});
|
||||
|
||||
fireEvent.keyDown(textbox, {
|
||||
key: "ArrowUp",
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(spyDispatcher).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it("Should moving up", async () => {
|
||||
// When
|
||||
const { textbox, spyDispatcher } = await setup();
|
||||
const textNode = textbox.firstChild;
|
||||
await select({
|
||||
anchorNode: textNode,
|
||||
anchorOffset: 0,
|
||||
focusNode: textNode,
|
||||
focusOffset: 0,
|
||||
isForward: true,
|
||||
});
|
||||
|
||||
fireEvent.keyDown(textbox, {
|
||||
key: "ArrowUp",
|
||||
});
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
// Then
|
||||
await waitFor(() =>
|
||||
expect(spyDispatcher).toBeCalledWith({
|
||||
action: Action.EditEvent,
|
||||
event: mockEvent,
|
||||
timelineRenderingType: defaultRoomContext.timelineRenderingType,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("Should moving up in list", async () => {
|
||||
// When
|
||||
const { mockEvent, defaultRoomContext, mockClient, editorStateTransfer } = createMocks(
|
||||
"<ul><li><strong>Content</strong></li><li>Other Content</li></ul>",
|
||||
);
|
||||
jest.spyOn(EventUtils, "findEditableEvent").mockReturnValue(mockEvent);
|
||||
const { textbox, spyDispatcher } = await setup(editorStateTransfer, mockClient, defaultRoomContext);
|
||||
|
||||
const textNode = textbox.firstChild;
|
||||
await select({
|
||||
anchorNode: textNode,
|
||||
anchorOffset: 0,
|
||||
focusNode: textNode,
|
||||
focusOffset: 0,
|
||||
isForward: true,
|
||||
});
|
||||
|
||||
fireEvent.keyDown(textbox, {
|
||||
key: "ArrowUp",
|
||||
});
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(spyDispatcher).toBeCalledWith({
|
||||
action: Action.EditEvent,
|
||||
event: mockEvent,
|
||||
timelineRenderingType: defaultRoomContext.timelineRenderingType,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Moving down", () => {
|
||||
it("Should not moving when caret is not at the end of the text", async () => {
|
||||
// When
|
||||
const { textbox, spyDispatcher } = await setup();
|
||||
const brNode = textbox.lastChild;
|
||||
await select({
|
||||
anchorNode: brNode,
|
||||
anchorOffset: 0,
|
||||
focusNode: brNode,
|
||||
focusOffset: 0,
|
||||
isForward: true,
|
||||
});
|
||||
|
||||
fireEvent.keyDown(textbox, {
|
||||
key: "ArrowDown",
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(spyDispatcher).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it("Should not moving when the content has changed", async () => {
|
||||
// When
|
||||
const { textbox, spyDispatcher } = await setup();
|
||||
fireEvent.input(textbox, {
|
||||
data: "word",
|
||||
inputType: "insertText",
|
||||
});
|
||||
const brNode = textbox.lastChild;
|
||||
await select({
|
||||
anchorNode: brNode,
|
||||
anchorOffset: 0,
|
||||
focusNode: brNode,
|
||||
focusOffset: 0,
|
||||
isForward: true,
|
||||
});
|
||||
|
||||
fireEvent.keyDown(textbox, {
|
||||
key: "ArrowDown",
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(spyDispatcher).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it("Should moving down", async () => {
|
||||
// When
|
||||
const { textbox, spyDispatcher } = await setup();
|
||||
// Skipping the BR tag
|
||||
const textNode = textbox.childNodes[textbox.childNodes.length - 2];
|
||||
const { length } = textNode.textContent || "";
|
||||
await select({
|
||||
anchorNode: textNode,
|
||||
anchorOffset: length,
|
||||
focusNode: textNode,
|
||||
focusOffset: length,
|
||||
isForward: true,
|
||||
});
|
||||
|
||||
fireEvent.keyDown(textbox, {
|
||||
key: "ArrowDown",
|
||||
});
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
// Then
|
||||
await waitFor(() =>
|
||||
expect(spyDispatcher).toBeCalledWith({
|
||||
action: Action.EditEvent,
|
||||
event: mockEvent,
|
||||
timelineRenderingType: defaultRoomContext.timelineRenderingType,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("Should moving down in list", async () => {
|
||||
// When
|
||||
const { mockEvent, defaultRoomContext, mockClient, editorStateTransfer } = createMocks(
|
||||
"<ul><li><strong>Content</strong></li><li>Other Content</li></ul>",
|
||||
);
|
||||
jest.spyOn(EventUtils, "findEditableEvent").mockReturnValue(mockEvent);
|
||||
const { textbox, spyDispatcher } = await setup(editorStateTransfer, mockClient, defaultRoomContext);
|
||||
|
||||
// Skipping the BR tag and get the text node inside the last LI tag
|
||||
const textNode = textbox.childNodes[textbox.childNodes.length - 2].lastChild?.lastChild || textbox;
|
||||
const { length } = textNode.textContent || "";
|
||||
await select({
|
||||
anchorNode: textNode,
|
||||
anchorOffset: length,
|
||||
focusNode: textNode,
|
||||
focusOffset: length,
|
||||
isForward: true,
|
||||
});
|
||||
|
||||
fireEvent.keyDown(textbox, {
|
||||
key: "ArrowDown",
|
||||
});
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(spyDispatcher).toBeCalledWith({
|
||||
action: Action.EditEvent,
|
||||
event: mockEvent,
|
||||
timelineRenderingType: defaultRoomContext.timelineRenderingType,
|
||||
});
|
||||
});
|
||||
|
||||
it("Should close editing", async () => {
|
||||
// When
|
||||
jest.spyOn(EventUtils, "findEditableEvent").mockReturnValue(undefined);
|
||||
const { textbox, spyDispatcher } = await setup();
|
||||
// Skipping the BR tag
|
||||
const textNode = textbox.childNodes[textbox.childNodes.length - 2];
|
||||
const { length } = textNode.textContent || "";
|
||||
await select({
|
||||
anchorNode: textNode,
|
||||
anchorOffset: length,
|
||||
focusNode: textNode,
|
||||
focusOffset: length,
|
||||
isForward: true,
|
||||
});
|
||||
|
||||
fireEvent.keyDown(textbox, {
|
||||
key: "ArrowDown",
|
||||
});
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
// Then
|
||||
await waitFor(() =>
|
||||
expect(spyDispatcher).toBeCalledWith({
|
||||
action: Action.EditEvent,
|
||||
event: null,
|
||||
timelineRenderingType: defaultRoomContext.timelineRenderingType,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -33,6 +33,8 @@ const mockWysiwyg = {
|
|||
orderedList: jest.fn(),
|
||||
unorderedList: jest.fn(),
|
||||
quote: jest.fn(),
|
||||
indent: jest.fn(),
|
||||
unIndent: jest.fn(),
|
||||
} as unknown as FormattingFunctions;
|
||||
|
||||
const openLinkModalSpy = jest.spyOn(LinkModal, "openLinkModal");
|
||||
|
@ -51,6 +53,8 @@ const testCases: Record<
|
|||
orderedList: { label: "Numbered list", mockFormatFn: mockWysiwyg.orderedList },
|
||||
unorderedList: { label: "Bulleted list", mockFormatFn: mockWysiwyg.unorderedList },
|
||||
quote: { label: "Quote", mockFormatFn: mockWysiwyg.quote },
|
||||
indent: { label: "Indent increase", mockFormatFn: mockWysiwyg.indent },
|
||||
unIndent: { label: "Indent decrease", mockFormatFn: mockWysiwyg.unIndent },
|
||||
};
|
||||
|
||||
const createActionStates = (state: ActionState): AllActionStates => {
|
||||
|
|
|
@ -21,6 +21,7 @@ import userEvent from "@testing-library/user-event";
|
|||
|
||||
import { WysiwygComposer } from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer";
|
||||
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||
import { mockPlatformPeg } from "../../../../../test-utils";
|
||||
|
||||
describe("WysiwygComposer", () => {
|
||||
const customRender = (
|
||||
|
@ -46,6 +47,7 @@ describe("WysiwygComposer", () => {
|
|||
const onChange = jest.fn();
|
||||
const onSend = jest.fn();
|
||||
beforeEach(async () => {
|
||||
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
|
||||
customRender(onChange, onSend);
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveAttribute("contentEditable", "true"));
|
||||
});
|
||||
|
|
|
@ -225,7 +225,7 @@ describe("<Notifications />", () => {
|
|||
}),
|
||||
setAccountData: jest.fn(),
|
||||
sendReadReceipt: jest.fn(),
|
||||
supportsExperimentalThreads: jest.fn().mockReturnValue(true),
|
||||
supportsThreads: jest.fn().mockReturnValue(true),
|
||||
});
|
||||
mockClient.getPushRules.mockResolvedValue(pushRules);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue