element-web-Github/test/unit-tests/components/views/elements/Pill-test.tsx

279 lines
9.2 KiB
TypeScript
Raw Normal View History

2023-03-08 20:06:50 +08:00
/*
Copyright 2024 New Vector Ltd.
2023-03-08 20:06:50 +08:00
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
2023-03-08 20:06:50 +08:00
*/
import React from "react";
Migrate to React 18 createRoot API (#28256) * Migrate to React 18 createRoot API Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Discard changes to src/components/views/settings/devices/DeviceDetails.tsx * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Attempt to stabilise test Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * legacyRoot? Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve coverage Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update snapshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve coverage Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-20 21:29:23 +08:00
import { render, RenderResult, screen } from "jest-matrix-react";
2023-03-08 20:06:50 +08:00
import userEvent from "@testing-library/user-event";
import { mocked, Mocked } from "jest-mock";
2023-03-21 17:23:20 +08:00
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
2023-03-08 20:06:50 +08:00
import dis from "../../../../../src/dispatcher/dispatcher";
import { Pill, PillProps, PillType } from "../../../../../src/components/views/elements/Pill";
2023-03-08 20:06:50 +08:00
import {
filterConsole,
flushPromises,
2023-03-21 17:23:20 +08:00
mkMessage,
2023-03-08 20:06:50 +08:00
mkRoomCanonicalAliasEvent,
mkRoomMemberJoinEvent,
stubClient,
} from "../../../../test-utils";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { Action } from "../../../../../src/dispatcher/actions";
import { ButtonEvent } from "../../../../../src/components/views/elements/AccessibleButton";
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
2023-03-08 20:06:50 +08:00
describe("<Pill>", () => {
let client: Mocked<MatrixClient>;
const permalinkPrefix = "https://matrix.to/#/";
const room1Alias = "#room1:example.com";
const room1Id = "!room1:example.com";
let room1: Room;
2023-03-21 17:23:20 +08:00
let room1Message: MatrixEvent;
const room2Id = "!room2:example.com";
let room2: Room;
const space1Id = "!space1:example.com";
let space1: Room;
2023-03-08 20:06:50 +08:00
const user1Id = "@user1:example.com";
const user2Id = "@user2:example.com";
const user3Id = "@user3:example.com";
2023-03-08 20:06:50 +08:00
let renderResult: RenderResult;
2023-03-13 22:43:13 +08:00
let pillParentClickHandler: (e: ButtonEvent) => void;
2023-03-08 20:06:50 +08:00
const renderPill = (props: PillProps): void => {
const withDefault = {
inMessage: true,
shouldShowPillAvatar: true,
...props,
} as PillProps;
2023-03-13 22:43:13 +08:00
// wrap Pill with a div to allow testing of event bubbling
renderResult = render(
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
2023-03-13 22:43:13 +08:00
<div onClick={pillParentClickHandler}>
<Pill {...withDefault} />
</div>,
);
2023-03-08 20:06:50 +08:00
};
filterConsole(
"Failed to parse permalink Error: Unknown entity type in permalink",
"Room !room1:example.com does not have an m.room.create event",
2023-03-21 17:23:20 +08:00
"Room !space1:example.com does not have an m.room.create event",
2023-03-08 20:06:50 +08:00
);
beforeEach(() => {
client = mocked(stubClient());
SdkContextClass.instance.client = client;
DMRoomMap.makeShared(client);
2023-03-21 17:23:20 +08:00
room1 = new Room(room1Id, client, user1Id);
2023-03-08 20:06:50 +08:00
room1.name = "Room 1";
const user1JoinRoom1Event = mkRoomMemberJoinEvent(user1Id, room1Id, {
displayname: "User 1",
});
room1.currentState.setStateEvents([
2023-03-21 17:23:20 +08:00
mkRoomCanonicalAliasEvent(user1Id, room1Id, room1Alias),
2023-03-08 20:06:50 +08:00
user1JoinRoom1Event,
]);
room1.getMember(user1Id)!.setMembershipEvent(user1JoinRoom1Event);
2023-03-21 17:23:20 +08:00
room1Message = mkMessage({
id: "$123-456",
event: true,
user: user1Id,
room: room1Id,
msg: "Room 1 Message",
});
room1.addLiveEvents([room1Message]);
room2 = new Room(room2Id, client, user1Id);
room2.currentState.setStateEvents([mkRoomMemberJoinEvent(user2Id, room2Id)]);
2023-03-21 17:23:20 +08:00
room2.name = "Room 2";
2023-03-08 20:06:50 +08:00
space1 = new Room(space1Id, client, client.getSafeUserId());
space1.name = "Space 1";
client.getRooms.mockReturnValue([room1, room2, space1]);
2023-03-08 20:06:50 +08:00
client.getRoom.mockImplementation((roomId: string) => {
if (roomId === room1.roomId) return room1;
2023-03-21 17:23:20 +08:00
if (roomId === room2.roomId) return room2;
if (roomId === space1.roomId) return space1;
2023-03-08 20:06:50 +08:00
return null;
});
client.getProfileInfo.mockImplementation(async (userId: string) => {
if (userId === user2Id) return { displayname: "User 2" };
throw new Error(`Unknown user ${userId}`);
});
jest.spyOn(dis, "dispatch");
2023-03-13 22:43:13 +08:00
pillParentClickHandler = jest.fn();
jest.spyOn(global.Math, "random").mockReturnValue(0.123456);
});
afterEach(() => {
jest.spyOn(global.Math, "random").mockRestore();
2023-03-08 20:06:50 +08:00
});
describe("when rendering a pill for a room", () => {
beforeEach(() => {
renderPill({
url: permalinkPrefix + room1Id,
});
});
it("should render the expected pill", () => {
expect(renderResult.asFragment()).toMatchSnapshot();
});
describe("when hovering the pill", () => {
beforeEach(async () => {
await userEvent.hover(screen.getByText("Room 1"));
});
it("should show a tooltip with the room Id", async () => {
expect(await screen.findByRole("tooltip", { name: room1Id })).toBeInTheDocument();
2023-03-08 20:06:50 +08:00
});
describe("when not hovering the pill any more", () => {
beforeEach(async () => {
await userEvent.unhover(screen.getByText("Room 1"));
});
it("should dimiss a tooltip with the room Id", () => {
expect(screen.queryByRole("tooltip")).not.toBeInTheDocument();
});
});
});
});
it("should not render a non-permalink", () => {
renderPill({
url: "https://example.com/hello",
});
expect(renderResult.asFragment()).toMatchSnapshot();
});
it("should render the expected pill for a space", () => {
renderPill({
url: permalinkPrefix + space1Id,
});
expect(renderResult.asFragment()).toMatchSnapshot();
});
2023-03-08 20:06:50 +08:00
it("should render the expected pill for a room alias", () => {
renderPill({
url: permalinkPrefix + room1Alias,
});
expect(renderResult.asFragment()).toMatchSnapshot();
});
it("should render the expected pill for @room", () => {
renderPill({
room: room1,
type: PillType.AtRoomMention,
});
expect(renderResult.asFragment()).toMatchSnapshot();
});
describe("when rendering a pill for a user in the room", () => {
beforeEach(() => {
renderPill({
room: room1,
url: permalinkPrefix + user1Id,
});
});
it("should render as expected", () => {
expect(renderResult.asFragment()).toMatchSnapshot();
});
describe("when clicking the pill", () => {
beforeEach(async () => {
await userEvent.click(screen.getByText("User 1"));
});
2023-03-13 22:43:13 +08:00
it("should dipsatch a view user action and prevent event bubbling", () => {
2023-03-08 20:06:50 +08:00
expect(dis.dispatch).toHaveBeenCalledWith({
action: Action.ViewUser,
member: room1.getMember(user1Id),
});
2023-03-13 22:43:13 +08:00
expect(pillParentClickHandler).not.toHaveBeenCalled();
2023-03-08 20:06:50 +08:00
});
});
});
it("should render the expected pill for a known user not in the room", async () => {
2023-03-08 20:06:50 +08:00
renderPill({
room: room1,
url: permalinkPrefix + user2Id,
});
// wait for profile query via API
Migrate to React 18 createRoot API (#28256) * Migrate to React 18 createRoot API Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Discard changes to src/components/views/settings/devices/DeviceDetails.tsx * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Attempt to stabilise test Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * legacyRoot? Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve coverage Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update snapshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve coverage Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-20 21:29:23 +08:00
await flushPromises();
2023-03-08 20:06:50 +08:00
expect(renderResult.asFragment()).toMatchSnapshot();
});
it("should render the expected pill for an uknown user not in the room", async () => {
renderPill({
room: room1,
url: permalinkPrefix + user3Id,
});
// wait for profile query via API
Migrate to React 18 createRoot API (#28256) * Migrate to React 18 createRoot API Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Discard changes to src/components/views/settings/devices/DeviceDetails.tsx * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Attempt to stabilise test Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * legacyRoot? Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve coverage Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update snapshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve coverage Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-11-20 21:29:23 +08:00
await flushPromises();
expect(renderResult.asFragment()).toMatchSnapshot();
});
2023-03-08 20:06:50 +08:00
it("should not render anything if the type cannot be detected", () => {
renderPill({
url: permalinkPrefix,
});
2023-03-13 22:43:13 +08:00
expect(renderResult.asFragment()).toMatchInlineSnapshot(`
<DocumentFragment>
<div />
</DocumentFragment>
`);
2023-03-08 20:06:50 +08:00
});
it("should not render an avatar or link when called with inMessage = false and shouldShowPillAvatar = false", () => {
renderPill({
inMessage: false,
shouldShowPillAvatar: false,
url: permalinkPrefix + room1Id,
});
expect(renderResult.asFragment()).toMatchSnapshot();
});
2023-03-21 17:23:20 +08:00
it("should render the expected pill for a message in the same room", () => {
renderPill({
room: room1,
url: `${permalinkPrefix}${room1Id}/${room1Message.getId()}`,
});
expect(renderResult.asFragment()).toMatchSnapshot();
});
it("should render the expected pill for a message in another room", () => {
renderPill({
room: room2,
url: `${permalinkPrefix}${room1Id}/${room1Message.getId()}`,
});
expect(renderResult.asFragment()).toMatchSnapshot();
});
it("should not render a pill with an unknown type", () => {
// @ts-ignore
renderPill({ type: "unknown" });
expect(renderResult.asFragment()).toMatchInlineSnapshot(`
<DocumentFragment>
<div />
</DocumentFragment>
`);
});
2023-03-08 20:06:50 +08:00
});