Move some of the slowest tests from jest to vitest (#34277)
* Remove doubled-up I18n Provider. ModalManager already provides this. * Reconfigure svgr into a vite-friendly `?react` resource query * Move InviteDialog test to vitest * Move RoomHeader tests to Vitest * Share serializer between Jest & Vitest * Attempt to stabilise InviteDialog test * Fix async leaks * Iterate based on copilot review * Fix InviteDialog throttle * Iterate * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Fix lockfile * Iterate --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI
parent
5b0fd416fd
commit
2430e0ab8b
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { vi, expect as viExpect } from "vitest";
|
||||
import { vi, expect as viExpect, beforeAll as viBeforeAll, afterAll as viAfterAll } from "vitest";
|
||||
import { mocked as jestMocked } from "jest-mock";
|
||||
|
||||
export const isJest = typeof jest !== "undefined";
|
||||
@@ -26,6 +26,8 @@ const mocked = adapter.mocked;
|
||||
export { adapter as vi, mocked };
|
||||
|
||||
const _expect = isJest ? (expect as unknown as typeof viExpect) : viExpect;
|
||||
export { _expect as expect };
|
||||
const _beforeAll = isJest ? (beforeAll as unknown as typeof viBeforeAll) : viBeforeAll;
|
||||
const _afterAll = isJest ? (afterAll as unknown as typeof viAfterAll) : viAfterAll;
|
||||
export { _expect as expect, _beforeAll as beforeAll, _afterAll as afterAll };
|
||||
|
||||
export { type Mocked, type MockedObject } from "vitest";
|
||||
|
||||
@@ -21,43 +21,6 @@ declare global {
|
||||
|
||||
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
const REACT_USE_ID = /_r_[a-z0-9]+_/g;
|
||||
|
||||
function normaliseReactUseIds(snapshot: string): string {
|
||||
// React useId values can vary between runs and make snapshots flaky:
|
||||
// https://github.com/element-hq/element-web/issues/31765
|
||||
// Avoid running the regex for DOM snapshots without React useId output.
|
||||
if (!snapshot.includes("_r_")) return snapshot;
|
||||
|
||||
const ids = new Map<string, string>();
|
||||
let nextId = 1;
|
||||
|
||||
return snapshot.replace(REACT_USE_ID, (id) => {
|
||||
let replacement = ids.get(id);
|
||||
if (!replacement) {
|
||||
replacement = `react-use-id-${nextId++}`;
|
||||
ids.set(id, replacement);
|
||||
}
|
||||
return replacement;
|
||||
});
|
||||
}
|
||||
|
||||
// Prevent this serializer from recursively matching the same DOM node when it calls serialize().
|
||||
let isSerializingDomSnapshot = false;
|
||||
|
||||
expect.addSnapshotSerializer({
|
||||
test: (value: unknown): value is Element | DocumentFragment =>
|
||||
!isSerializingDomSnapshot && (value instanceof Element || value instanceof DocumentFragment),
|
||||
print: (value: unknown, serialize: (value: unknown) => string): string => {
|
||||
isSerializingDomSnapshot = true;
|
||||
try {
|
||||
return normaliseReactUseIds(serialize(value));
|
||||
} finally {
|
||||
isSerializingDomSnapshot = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Fake random strings to give a predictable snapshot for IDs
|
||||
jest.mock("matrix-js-sdk/src/randomstring");
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -6,6 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { beforeAll, afterAll } from "../setup/adapter.ts";
|
||||
|
||||
type FilteredConsole = Pick<Console, "log" | "error" | "info" | "debug" | "warn">;
|
||||
|
||||
/**
|
||||
|
||||
@@ -366,6 +366,7 @@ export function createTestClient(): MatrixClient {
|
||||
setRoomTag: vi.fn().mockResolvedValue({}),
|
||||
getExtendedProfileProperty: vi.fn(),
|
||||
setExtendedProfileProperty: vi.fn().mockResolvedValue(undefined),
|
||||
doesServerSupportExtendedProfiles: vi.fn(),
|
||||
} as unknown as MatrixClient;
|
||||
|
||||
client.reEmitter = new ReEmitter(client);
|
||||
|
||||
@@ -1,502 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { findByText, fireEvent, render, screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { type MatrixClient, MatrixError, Room, RoomType } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import { mocked, type Mocked } from "jest-mock-vitest-adapter";
|
||||
import { UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import InviteDialog from "../../../../../src/components/views/dialogs/InviteDialog";
|
||||
import { InviteKind } from "../../../../../src/components/views/dialogs/InviteDialogTypes";
|
||||
import {
|
||||
clearAllModals,
|
||||
filterConsole,
|
||||
flushPromises,
|
||||
getMockClientWithEventEmitter,
|
||||
mkMembership,
|
||||
mkMessage,
|
||||
mkRoomCreateEvent,
|
||||
} from "../../../../test-utils";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import SdkConfig from "../../../../../src/SdkConfig";
|
||||
import { type ValidatedServerConfig } from "../../../../../src/utils/ValidatedServerConfig";
|
||||
import { type IConfigOptions } from "../../../../../src/IConfigOptions";
|
||||
import { SDKContextClass } from "../../../../../src/contexts/SDKContextClass";
|
||||
import { type IProfileInfo } from "../../../../../src/hooks/useProfileInfo";
|
||||
import { DirectoryMember, startDmOnFirstMessage } from "../../../../../src/utils/direct-messages";
|
||||
import { TestSDKContext } from "../../../TestSDKContext.ts";
|
||||
|
||||
const mockGetAccessToken = jest.fn().mockResolvedValue("getAccessToken");
|
||||
jest.mock("../../../../../src/IdentityAuthClient", () =>
|
||||
jest.fn().mockImplementation(() => ({
|
||||
getAccessToken: mockGetAccessToken,
|
||||
})),
|
||||
);
|
||||
|
||||
jest.mock("../../../../../src/utils/direct-messages", () => ({
|
||||
...jest.requireActual("../../../../../src/utils/direct-messages"),
|
||||
__esModule: true,
|
||||
startDmOnFirstMessage: jest.fn(),
|
||||
}));
|
||||
|
||||
const getSearchField = () => screen.getByTestId("invite-dialog-input");
|
||||
|
||||
const enterIntoSearchField = async (value: string) => {
|
||||
const searchField = getSearchField();
|
||||
await userEvent.clear(searchField);
|
||||
await userEvent.type(searchField, value + "{enter}");
|
||||
};
|
||||
|
||||
const pasteIntoSearchField = async (value: string) => {
|
||||
const searchField = getSearchField();
|
||||
await userEvent.clear(searchField);
|
||||
searchField.focus();
|
||||
await userEvent.paste(value);
|
||||
};
|
||||
|
||||
const expectPill = (value: string) => {
|
||||
expect(screen.getByText(value)).toBeInTheDocument();
|
||||
expect(getSearchField()).toHaveValue("");
|
||||
};
|
||||
|
||||
const expectNoPill = (value: string) => {
|
||||
expect(screen.queryByText(value)).not.toBeInTheDocument();
|
||||
expect(getSearchField()).toHaveValue(value);
|
||||
};
|
||||
|
||||
const serverDomain = "example.org";
|
||||
const roomId = "!111111111111111111:example.org";
|
||||
const aliceId = "@alice:example.org";
|
||||
const aliceEmail = "foobar@email.com";
|
||||
const bobId = "@bob:example.org";
|
||||
const bobEmail = "bobbob@example.com"; // bob@example.com is already used as an example in the invite dialog
|
||||
const carolId = "@carol:example.com";
|
||||
const bobbob = "bobbob";
|
||||
|
||||
const aliceProfileInfo: IProfileInfo = {
|
||||
user_id: aliceId,
|
||||
display_name: "Alice",
|
||||
};
|
||||
|
||||
const bobProfileInfo: IProfileInfo = {
|
||||
user_id: bobId,
|
||||
display_name: "Bob",
|
||||
};
|
||||
|
||||
describe("InviteDialog", () => {
|
||||
let mockClient: Mocked<MatrixClient>;
|
||||
let room: Room;
|
||||
let sdkContext: TestSDKContext;
|
||||
|
||||
filterConsole(
|
||||
"Error retrieving profile for userId @carol:example.com",
|
||||
"Error retrieving profile for userId @localpart:server.tld",
|
||||
"Error retrieving profile for userId @localpart:server:tld",
|
||||
"[Invite:Recents] Excluding @alice:example.org from recents",
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient = getMockClientWithEventEmitter({
|
||||
getCrypto: jest.fn().mockReturnValue({
|
||||
getUserVerificationStatus: jest
|
||||
.fn()
|
||||
.mockResolvedValue(new UserVerificationStatus(false, false, true, false)),
|
||||
}),
|
||||
getDomain: jest.fn().mockReturnValue(serverDomain),
|
||||
getUserId: jest.fn().mockReturnValue(bobId),
|
||||
getSafeUserId: jest.fn().mockReturnValue(bobId),
|
||||
isGuest: jest.fn().mockReturnValue(false),
|
||||
getVisibleRooms: jest.fn().mockReturnValue([]),
|
||||
getRoom: jest.fn(),
|
||||
getRooms: jest.fn(),
|
||||
getAccountData: jest.fn(),
|
||||
getPushActionsForEvent: jest.fn(),
|
||||
mxcUrlToHttp: jest.fn().mockReturnValue(""),
|
||||
isRoomEncrypted: jest.fn().mockReturnValue(false),
|
||||
getProfileInfo: jest.fn().mockImplementation(async (userId: string) => {
|
||||
if (userId === aliceId) return aliceProfileInfo;
|
||||
if (userId === bobId) return bobProfileInfo;
|
||||
|
||||
throw new MatrixError({
|
||||
errcode: "M_UNKNOWN",
|
||||
error: "Profile not found",
|
||||
});
|
||||
}),
|
||||
getIdentityServerUrl: jest.fn(),
|
||||
searchUserDirectory: jest.fn().mockResolvedValue({}),
|
||||
lookupThreePid: jest.fn(),
|
||||
registerWithIdentityServer: jest.fn().mockResolvedValue({
|
||||
access_token: "access_token",
|
||||
token: "token",
|
||||
}),
|
||||
getOpenIdToken: jest.fn().mockResolvedValue({}),
|
||||
getIdentityAccount: jest.fn().mockResolvedValue({}),
|
||||
getTerms: jest.fn().mockResolvedValue({ policies: [] }),
|
||||
supportsThreads: jest.fn().mockReturnValue(false),
|
||||
isInitialSyncComplete: jest.fn().mockReturnValue(true),
|
||||
getClientWellKnown: jest.fn().mockResolvedValue({}),
|
||||
invite: jest.fn(),
|
||||
});
|
||||
SdkConfig.put({ validated_server_config: {} as ValidatedServerConfig } as IConfigOptions);
|
||||
DMRoomMap.makeShared(mockClient);
|
||||
jest.clearAllMocks();
|
||||
|
||||
room = new Room(roomId, mockClient, mockClient.getSafeUserId());
|
||||
room.addLiveEvents(
|
||||
[
|
||||
mkMessage({
|
||||
msg: "Hello",
|
||||
relatesTo: undefined,
|
||||
event: true,
|
||||
room: roomId,
|
||||
user: mockClient.getSafeUserId(),
|
||||
ts: Date.now(),
|
||||
}),
|
||||
],
|
||||
{ addToState: true },
|
||||
);
|
||||
room.currentState.setStateEvents([
|
||||
mkRoomCreateEvent(bobId, roomId),
|
||||
mkMembership({
|
||||
event: true,
|
||||
room: roomId,
|
||||
mship: KnownMembership.Join,
|
||||
user: aliceId,
|
||||
skey: aliceId,
|
||||
}),
|
||||
]);
|
||||
jest.spyOn(DMRoomMap.shared(), "getUniqueRoomsWithIndividuals").mockReturnValue({
|
||||
[aliceId]: room,
|
||||
});
|
||||
mockClient.getRooms.mockReturnValue([room]);
|
||||
mockClient.getRoom.mockReturnValue(room);
|
||||
|
||||
sdkContext = new TestSDKContext();
|
||||
// @ts-ignore UserMenuViewModel uses SDKContext in the constructor
|
||||
SDKContextClass.instance = sdkContext;
|
||||
sdkContext._client = mockClient;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await clearAllModals();
|
||||
SDKContextClass.instance.onLoggedOut();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("should label with space name", () => {
|
||||
room.isSpaceRoom = jest.fn().mockReturnValue(true);
|
||||
room.getType = jest.fn().mockReturnValue(RoomType.Space);
|
||||
room.name = "Space";
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
|
||||
expect(screen.queryByText("Invite to Space")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should label with room name", () => {
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
expect(screen.getByText(`Invite to ${roomId}`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not suggest valid unknown MXIDs", async () => {
|
||||
render(
|
||||
<InviteDialog
|
||||
kind={InviteKind.Invite}
|
||||
roomId={roomId}
|
||||
onFinished={jest.fn()}
|
||||
initialText="@localpart:server.tld"
|
||||
/>,
|
||||
);
|
||||
await flushPromises();
|
||||
expect(screen.queryByText("@localpart:server.tld")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not suggest invalid MXIDs", () => {
|
||||
render(
|
||||
<InviteDialog
|
||||
kind={InviteKind.Invite}
|
||||
roomId={roomId}
|
||||
onFinished={jest.fn()}
|
||||
initialText="@localpart:server:tld"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText("@localpart:server:tld")).toBeFalsy();
|
||||
});
|
||||
|
||||
it.each([[InviteKind.Dm], [InviteKind.Invite]] as [typeof InviteKind.Dm | typeof InviteKind.Invite][])(
|
||||
"should lookup inputs which look like email addresses (%s)",
|
||||
async (kind: typeof InviteKind.Dm | typeof InviteKind.Invite) => {
|
||||
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
|
||||
mockClient.lookupThreePid.mockResolvedValue({
|
||||
address: aliceEmail,
|
||||
medium: "email",
|
||||
mxid: aliceId,
|
||||
});
|
||||
mockClient.getProfileInfo.mockResolvedValue({
|
||||
displayname: "Mrs Alice",
|
||||
avatar_url: "mxc://foo/bar",
|
||||
});
|
||||
|
||||
render(
|
||||
<InviteDialog
|
||||
kind={kind}
|
||||
roomId={kind === InviteKind.Invite ? roomId : ""}
|
||||
onFinished={jest.fn()}
|
||||
initialText={aliceEmail}
|
||||
/>,
|
||||
);
|
||||
|
||||
await screen.findByText("Mrs Alice");
|
||||
// expect the email and MXID to be visible
|
||||
await screen.findByText(aliceId);
|
||||
await screen.findByText(aliceEmail);
|
||||
expect(mockClient.lookupThreePid).toHaveBeenCalledWith("email", aliceEmail, expect.anything());
|
||||
expect(mockClient.getProfileInfo).toHaveBeenCalledWith(aliceId);
|
||||
},
|
||||
);
|
||||
|
||||
it("should suggest e-mail even if lookup fails", async () => {
|
||||
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
|
||||
mockClient.lookupThreePid.mockResolvedValue({});
|
||||
|
||||
render(
|
||||
<InviteDialog
|
||||
kind={InviteKind.Invite}
|
||||
roomId={roomId}
|
||||
onFinished={jest.fn()}
|
||||
initialText="foobar@email.com"
|
||||
/>,
|
||||
);
|
||||
|
||||
await screen.findByText("foobar@email.com");
|
||||
await screen.findByText("Invite by email");
|
||||
});
|
||||
|
||||
it("should add pasted values", async () => {
|
||||
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
|
||||
mockClient.lookupThreePid.mockResolvedValue({});
|
||||
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
|
||||
const input = screen.getByTestId("invite-dialog-input");
|
||||
input.focus();
|
||||
await userEvent.paste(`${bobId} ${aliceEmail}`);
|
||||
|
||||
await screen.findAllByText(bobId);
|
||||
await screen.findByText(aliceEmail);
|
||||
expect(input).toHaveValue("");
|
||||
});
|
||||
it("should support pasting one username that is not a mx id or email", async () => {
|
||||
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
|
||||
mockClient.lookupThreePid.mockResolvedValue({});
|
||||
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
|
||||
const input = screen.getByTestId("invite-dialog-input");
|
||||
input.focus();
|
||||
await userEvent.paste(`${bobbob}`);
|
||||
|
||||
await screen.findAllByText(bobId);
|
||||
expect(input).toHaveValue(`${bobbob}`);
|
||||
});
|
||||
|
||||
it("should allow to invite multiple emails to a room", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
|
||||
await enterIntoSearchField(aliceEmail);
|
||||
expectPill(aliceEmail);
|
||||
|
||||
await enterIntoSearchField(bobEmail);
|
||||
expectPill(bobEmail);
|
||||
});
|
||||
|
||||
describe("when encryption by default is disabled", () => {
|
||||
beforeEach(() => {
|
||||
mockClient.getClientWellKnown.mockReturnValue({
|
||||
"io.element.e2ee": {
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow to invite more than one email to a DM", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
||||
|
||||
await enterIntoSearchField(aliceEmail);
|
||||
expectPill(aliceEmail);
|
||||
|
||||
await enterIntoSearchField(bobEmail);
|
||||
expectPill(bobEmail);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not allow to invite more than one email to a DM", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
||||
|
||||
// Start with an email → should convert to a pill
|
||||
await enterIntoSearchField(aliceEmail);
|
||||
expect(screen.getByText("Invites by email can only be sent one at a time")).toBeInTheDocument();
|
||||
expectPill(aliceEmail);
|
||||
|
||||
// Everything else from now on should not convert to a pill
|
||||
|
||||
await enterIntoSearchField(bobEmail);
|
||||
expectNoPill(bobEmail);
|
||||
|
||||
await enterIntoSearchField(aliceId);
|
||||
expectNoPill(aliceId);
|
||||
|
||||
await pasteIntoSearchField(bobEmail);
|
||||
expectNoPill(bobEmail);
|
||||
});
|
||||
|
||||
it("should not allow to invite a MXID and an email to a DM", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
||||
|
||||
// Start with a MXID → should convert to a pill
|
||||
await enterIntoSearchField(carolId);
|
||||
expect(screen.queryByText("Invites by email can only be sent one at a time")).not.toBeInTheDocument();
|
||||
expectPill(carolId);
|
||||
|
||||
// Add an email → should not convert to a pill
|
||||
await enterIntoSearchField(bobEmail);
|
||||
expect(screen.getByText("Invites by email can only be sent one at a time")).toBeInTheDocument();
|
||||
expectNoPill(bobEmail);
|
||||
});
|
||||
|
||||
it("should start a DM if the profile is available", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
||||
await enterIntoSearchField(aliceId);
|
||||
await userEvent.click(screen.getByRole("button", { name: "Go" }));
|
||||
expect(startDmOnFirstMessage).toHaveBeenCalledWith(mockClient, [
|
||||
new DirectoryMember({
|
||||
user_id: aliceId,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("should not allow pasting the same user multiple times", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
|
||||
const input = screen.getByTestId("invite-dialog-input");
|
||||
input.focus();
|
||||
await userEvent.paste(`${bobId}`);
|
||||
await userEvent.paste(`${bobId}`);
|
||||
await userEvent.paste(`${bobId}`);
|
||||
|
||||
expect(input).toHaveValue("");
|
||||
await expect(screen.findAllByText(bobId, { selector: "a" })).resolves.toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should add to selection on click of user tile", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
|
||||
const input = screen.getByTestId("invite-dialog-input");
|
||||
input.focus();
|
||||
await userEvent.keyboard(`${aliceId}`);
|
||||
|
||||
const btn = await screen.findByRole("option", { name: aliceId });
|
||||
fireEvent.click(btn);
|
||||
|
||||
const tile = await findByText(screen.getByTestId("invite-dialog-input-wrapper"), aliceId);
|
||||
expect(tile).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("while the invite is in progress", () => {
|
||||
it("should show a spinner", async () => {
|
||||
mockClient.invite.mockReturnValue(new Promise(() => {}));
|
||||
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
await enterIntoSearchField(bobId);
|
||||
await userEvent.click(screen.getByRole("button", { name: "Invite" }));
|
||||
|
||||
await screen.findByText("Preparing invitations...");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when inviting a user with an unknown profile", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(startDmOnFirstMessage).mockClear();
|
||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
||||
await enterIntoSearchField(carolId);
|
||||
await userEvent.click(screen.getByRole("button", { name: "Go" }));
|
||||
// modal rendering has some weird sleeps - fake timers will mess up the entire test
|
||||
await sleep(100);
|
||||
});
|
||||
|
||||
it("should start the DM directly", () => {
|
||||
expect(startDmOnFirstMessage).toHaveBeenCalledWith(mockClient, [
|
||||
new DirectoryMember({
|
||||
user_id: carolId,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not suggest users from other server when room has m.federate=false", async () => {
|
||||
room.currentState.setStateEvents([mkRoomCreateEvent(bobId, roomId, { "m.federate": false })]);
|
||||
|
||||
render(
|
||||
<InviteDialog
|
||||
kind={InviteKind.Invite}
|
||||
roomId={roomId}
|
||||
onFinished={jest.fn()}
|
||||
initialText="@localpart:server.tld"
|
||||
/>,
|
||||
);
|
||||
await flushPromises();
|
||||
expect(screen.queryByText("@localpart:server.tld")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("when inviting a user whose cryptographic identity we do not know", () => {
|
||||
beforeEach(() => {
|
||||
mocked(mockClient.getCrypto()!.getUserVerificationStatus).mockImplementation(async (u) => {
|
||||
return new UserVerificationStatus(false, false, false, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([InviteKind.Invite, InviteKind.Dm])("with invitekind '%s'", (kind) => {
|
||||
const goButtonName = kind == InviteKind.Invite ? "Invite" : "Go";
|
||||
|
||||
beforeEach(() => {
|
||||
render(
|
||||
<InviteDialog
|
||||
kind={kind as InviteKind.Invite | InviteKind.Dm}
|
||||
roomId={roomId}
|
||||
onFinished={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it("should show a warning when inviting by user id", async () => {
|
||||
await enterIntoSearchField(aliceId);
|
||||
await userEvent.click(screen.getByRole("button", { name: goButtonName }));
|
||||
await screen.findByText("Confirm inviting them", { exact: false });
|
||||
|
||||
expect(mocked(mockClient.getCrypto()!.getUserVerificationStatus)).toHaveBeenCalledTimes(1);
|
||||
expect(mocked(mockClient.getCrypto()!.getUserVerificationStatus)).toHaveBeenCalledWith(aliceId);
|
||||
});
|
||||
|
||||
it("should show a warning when inviting by email address", async () => {
|
||||
await enterIntoSearchField("aaa@bbb");
|
||||
await userEvent.click(screen.getByRole("button", { name: goButtonName }));
|
||||
await screen.findByText("Confirm inviting them", { exact: false });
|
||||
|
||||
// We shouldn't call getUserVerificationStatus on an email address
|
||||
expect(mocked(mockClient.getCrypto()!.getUserVerificationStatus)).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
-278
@@ -1,278 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, getByLabelText, getByText, render, screen, waitFor } from "jest-matrix-react";
|
||||
import { type EventTimeline, JoinRule, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
|
||||
import { SDKContext } from "../../../../../../src/contexts/SDKContext";
|
||||
import { TestSDKContext } from "../../../../TestSDKContext.ts";
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../../test-utils";
|
||||
import {
|
||||
CallGuestLinkButton,
|
||||
JoinRuleDialog,
|
||||
} from "../../../../../../src/components/views/rooms/RoomHeader/CallGuestLinkButton";
|
||||
import Modal from "../../../../../../src/Modal";
|
||||
import SdkConfig from "../../../../../../src/SdkConfig";
|
||||
import { ShareDialog } from "../../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { _t } from "../../../../../../src/languageHandler";
|
||||
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||
|
||||
describe("<CallGuestLinkButton />", () => {
|
||||
const roomId = "!room:server.org";
|
||||
let sdkContext!: TestSDKContext;
|
||||
let modalSpy: jest.SpyInstance;
|
||||
let modalResolve: (value: unknown[] | PromiseLike<unknown[]>) => void;
|
||||
let room: Room;
|
||||
|
||||
const targetUnencrypted =
|
||||
"https://guest_spa_url.com/room/#/!room:server.org?roomId=%21room%3Aserver.org&viaServers=example.org";
|
||||
const targetEncrypted =
|
||||
"https://guest_spa_url.com/room/#/!room:server.org?roomId=%21room%3Aserver.org&perParticipantE2EE=true&viaServers=example.org";
|
||||
const expectedShareDialogProps = {
|
||||
target: targetEncrypted,
|
||||
customTitle: "Conference invite link",
|
||||
subtitle: "Link for external users to join the call without a matrix account:",
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a room using mocked client
|
||||
* And mock isElementVideoRoom
|
||||
*/
|
||||
const makeRoom = (isVideoRoom = true): Room => {
|
||||
const room = new Room(roomId, sdkContext.client!, sdkContext.client!.getSafeUserId());
|
||||
sdkContext.client!.getRoomDirectoryVisibility = jest.fn().mockResolvedValue("public");
|
||||
jest.spyOn(room, "isElementVideoRoom").mockReturnValue(isVideoRoom);
|
||||
// stub
|
||||
jest.spyOn(room, "getPendingEvents").mockReturnValue([]);
|
||||
jest.spyOn(room, "getVersion").mockReturnValue("9");
|
||||
return room;
|
||||
};
|
||||
function mockRoomMembers(room: Room, count: number) {
|
||||
const members = Array(count)
|
||||
.fill(0)
|
||||
.map((_, index) => ({
|
||||
userId: `@user-${index}:example.org`,
|
||||
roomId: room.roomId,
|
||||
membership: KnownMembership.Join,
|
||||
}));
|
||||
|
||||
room.currentState.setJoinedMemberCount(members.length);
|
||||
room.getJoinedMembers = jest.fn().mockReturnValue(members);
|
||||
}
|
||||
|
||||
const getComponent = (room: Room) =>
|
||||
render(<CallGuestLinkButton room={room} />, {
|
||||
wrapper: ({ children }) => <SDKContext.Provider value={sdkContext}>{children}</SDKContext.Provider>,
|
||||
});
|
||||
|
||||
const oldGet = SdkConfig.get;
|
||||
beforeEach(() => {
|
||||
const client = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(),
|
||||
sendStateEvent: jest.fn(),
|
||||
getVisibleRooms: jest.fn().mockReturnValue([]),
|
||||
});
|
||||
sdkContext = new TestSDKContext();
|
||||
sdkContext._client = client;
|
||||
const modalPromise = new Promise<unknown[]>((resolve) => {
|
||||
modalResolve = resolve;
|
||||
});
|
||||
modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue({ finished: modalPromise, close: jest.fn() });
|
||||
room = makeRoom();
|
||||
mockRoomMembers(room, 3);
|
||||
|
||||
jest.spyOn(SdkConfig, "get").mockImplementation((key) => {
|
||||
if (key === "element_call") {
|
||||
return { guest_spa_url: "https://guest_spa_url.com", url: "https://spa_url.com" };
|
||||
}
|
||||
return oldGet(key);
|
||||
});
|
||||
jest.spyOn(room, "hasEncryptionStateEvent").mockReturnValue(true);
|
||||
jest.spyOn(sdkContext.roomViewStore, "isViewingCall").mockReturnValue(true);
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("shows the JoinRuleDialog on click with private join rules", async () => {
|
||||
getComponent(room);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Share call link" }));
|
||||
expect(modalSpy).toHaveBeenCalledWith(JoinRuleDialog, { room, canInvite: false });
|
||||
// pretend public was selected
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
||||
modalResolve([]);
|
||||
await new Promise(process.nextTick);
|
||||
const callParams = modalSpy.mock.calls[1];
|
||||
expect(callParams[0]).toEqual(ShareDialog);
|
||||
expect(callParams[1].target.toString()).toEqual(expectedShareDialogProps.target);
|
||||
expect(callParams[1].subtitle).toEqual(expectedShareDialogProps.subtitle);
|
||||
expect(callParams[1].customTitle).toEqual(expectedShareDialogProps.customTitle);
|
||||
});
|
||||
|
||||
it("shows the ShareDialog on click with public join rules", () => {
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
||||
getComponent(room);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Share call link" }));
|
||||
const callParams = modalSpy.mock.calls[0];
|
||||
expect(callParams[0]).toEqual(ShareDialog);
|
||||
expect(callParams[1].target.toString()).toEqual(expectedShareDialogProps.target);
|
||||
expect(callParams[1].subtitle).toEqual(expectedShareDialogProps.subtitle);
|
||||
expect(callParams[1].customTitle).toEqual(expectedShareDialogProps.customTitle);
|
||||
});
|
||||
|
||||
it("shows the ShareDialog on click with knock join rules", () => {
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Knock);
|
||||
jest.spyOn(room, "canInvite").mockReturnValue(true);
|
||||
getComponent(room);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Share call link" }));
|
||||
const callParams = modalSpy.mock.calls[0];
|
||||
expect(callParams[0]).toEqual(ShareDialog);
|
||||
expect(callParams[1].target.toString()).toEqual(expectedShareDialogProps.target);
|
||||
expect(callParams[1].subtitle).toEqual(expectedShareDialogProps.subtitle);
|
||||
expect(callParams[1].customTitle).toEqual(expectedShareDialogProps.customTitle);
|
||||
});
|
||||
|
||||
it("don't show external conference button if room not public nor knock and the user cannot change join rules", () => {
|
||||
// preparation for if we refactor the related code to not use currentState.
|
||||
jest.spyOn(room, "getLiveTimeline").mockReturnValue({
|
||||
getState: jest.fn().mockReturnValue({
|
||||
maySendStateEvent: jest.fn().mockReturnValue(false),
|
||||
}),
|
||||
} as unknown as EventTimeline);
|
||||
jest.spyOn(room.currentState, "maySendStateEvent").mockReturnValue(false);
|
||||
getComponent(room);
|
||||
expect(screen.queryByLabelText("Share call link")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("don't show external conference button if now guest spa link is configured", () => {
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
||||
jest.spyOn(sdkContext.roomViewStore, "isViewingCall").mockReturnValue(true);
|
||||
|
||||
jest.spyOn(SdkConfig, "get").mockImplementation((key) => {
|
||||
if (key === "element_call") {
|
||||
return { url: "https://example2.com" };
|
||||
}
|
||||
return oldGet(key);
|
||||
});
|
||||
|
||||
getComponent(room);
|
||||
// We only change the SdkConfig and show that this everything else is
|
||||
// configured so that the call link button is shown.
|
||||
expect(screen.queryByLabelText("Share call link")).not.toBeInTheDocument();
|
||||
|
||||
jest.spyOn(SdkConfig, "get").mockImplementation((key) => {
|
||||
if (key === "element_call") {
|
||||
return { guest_spa_url: "https://guest_spa_url.com", url: "https://example2.com" };
|
||||
}
|
||||
return oldGet(key);
|
||||
});
|
||||
|
||||
getComponent(room);
|
||||
expect(getByLabelText(document.body, "Share call link")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("opens the share dialog with the correct share link in an encrypted room", () => {
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
||||
jest.spyOn(sdkContext.roomViewStore, "isViewingCall").mockReturnValue(true);
|
||||
|
||||
getComponent(room);
|
||||
const modalSpy = jest.spyOn(Modal, "createDialog");
|
||||
fireEvent.click(getByLabelText(document.body, _t("voip|get_call_link")));
|
||||
// const target =
|
||||
// "https://guest_spa_url.com/room/#/!room:server.org?roomId=%21room%3Aserver.org&perParticipantE2EE=true&viaServers=example.org";
|
||||
expect(modalSpy).toHaveBeenCalled();
|
||||
const arg0 = modalSpy.mock.calls[0][0];
|
||||
const arg1 = modalSpy.mock.calls[0][1] as any;
|
||||
expect(arg0).toEqual(ShareDialog);
|
||||
const { customTitle, subtitle } = arg1;
|
||||
expect({ customTitle, subtitle }).toEqual({
|
||||
customTitle: "Conference invite link",
|
||||
subtitle: _t("share|share_call_subtitle"),
|
||||
});
|
||||
expect(arg1.target.toString()).toEqual(targetEncrypted);
|
||||
});
|
||||
|
||||
it("share dialog has correct link in an unencrypted room", () => {
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public);
|
||||
jest.spyOn(room, "hasEncryptionStateEvent").mockReturnValue(false);
|
||||
jest.spyOn(sdkContext.roomViewStore, "isViewingCall").mockReturnValue(true);
|
||||
|
||||
getComponent(room);
|
||||
const modalSpy = jest.spyOn(Modal, "createDialog");
|
||||
fireEvent.click(getByLabelText(document.body, _t("voip|get_call_link")));
|
||||
const arg1 = modalSpy.mock.calls[0][1] as any;
|
||||
expect(arg1.target.toString()).toEqual(targetUnencrypted);
|
||||
});
|
||||
|
||||
describe("<JoinRuleDialog />", () => {
|
||||
const onFinished = jest.fn();
|
||||
|
||||
const getComponent = (room: Room, canInvite: boolean = true) =>
|
||||
render(<JoinRuleDialog room={room} canInvite={canInvite} onFinished={onFinished} />, {
|
||||
wrapper: ({ children }) => <SDKContext.Provider value={sdkContext}>{children}</SDKContext.Provider>,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// feature_ask_to_join enabled
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
|
||||
});
|
||||
|
||||
it("shows ask to join if feature is enabled", () => {
|
||||
getComponent(room);
|
||||
expect(screen.getByRole("radio", { name: "Ask to join ( Recommended )" })).toBeInTheDocument();
|
||||
});
|
||||
it("dont show ask to join if feature is enabled but cannot invite", () => {
|
||||
getComponent(room, false);
|
||||
expect(screen.queryByRole("radio", { name: "Ask to join ( Recommended )" })).not.toBeInTheDocument();
|
||||
});
|
||||
it("doesn't show ask to join if feature is disabled", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
getComponent(room);
|
||||
expect(screen.queryByRole("radio", { name: "Ask to join ( Recommended )" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("sends correct state event on click", async () => {
|
||||
const sendStateSpy = jest.spyOn(sdkContext.client!, "sendStateEvent");
|
||||
|
||||
let container;
|
||||
container = getComponent(room).container;
|
||||
fireEvent.click(screen.getByRole("radio", { name: "Ask to join ( Recommended )" }));
|
||||
expect(sendStateSpy).toHaveBeenCalledWith(
|
||||
"!room:server.org",
|
||||
"m.room.join_rules",
|
||||
{ join_rule: "knock" },
|
||||
"",
|
||||
);
|
||||
expect(sendStateSpy).toHaveBeenCalledTimes(1);
|
||||
await waitFor(() => expect(onFinished).toHaveBeenCalledTimes(1), { timeout: 3000 });
|
||||
onFinished.mockClear();
|
||||
sendStateSpy.mockClear();
|
||||
|
||||
container = getComponent(room).container;
|
||||
fireEvent.click(getByText(container, "Anyone"));
|
||||
expect(sendStateSpy).toHaveBeenLastCalledWith(
|
||||
"!room:server.org",
|
||||
"m.room.join_rules",
|
||||
{ join_rule: "public" },
|
||||
"",
|
||||
);
|
||||
expect(sendStateSpy).toHaveBeenCalledTimes(1);
|
||||
container = getComponent(room).container;
|
||||
await waitFor(() => expect(onFinished).toHaveBeenCalledTimes(1), { timeout: 3000 });
|
||||
onFinished.mockClear();
|
||||
sendStateSpy.mockClear();
|
||||
|
||||
fireEvent.click(getByText(container, _t("update_room_access_modal|no_change")));
|
||||
await waitFor(() => expect(onFinished).toHaveBeenCalledTimes(1));
|
||||
// Don't call sendStateEvent if no change is clicked.
|
||||
expect(sendStateSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
-127
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { type MockedObject } from "jest-mock";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||
|
||||
import { VideoRoomChatButton } from "../../../../../../src/components/views/rooms/RoomHeader/VideoRoomChatButton";
|
||||
import { SDKContext } from "../../../../../../src/contexts/SDKContext";
|
||||
import { TestSDKContext } from "../../../../TestSDKContext.ts";
|
||||
import type RightPanelStore from "../../../../../../src/stores/right-panel/RightPanelStore";
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../../test-utils";
|
||||
import { RoomNotificationState } from "../../../../../../src/stores/notifications/RoomNotificationState";
|
||||
import { NotificationLevel } from "../../../../../../src/stores/notifications/NotificationLevel";
|
||||
import { NotificationStateEvents } from "../../../../../../src/stores/notifications/NotificationState";
|
||||
import { RightPanelPhases } from "../../../../../../src/stores/right-panel/RightPanelStorePhases";
|
||||
|
||||
describe("<VideoRoomChatButton />", () => {
|
||||
const roomId = "!room:server.org";
|
||||
let sdkContext!: TestSDKContext;
|
||||
let rightPanelStore!: MockedObject<RightPanelStore>;
|
||||
|
||||
/**
|
||||
* Create a room using mocked client
|
||||
* And mock isElementVideoRoom
|
||||
*/
|
||||
const makeRoom = (isVideoRoom = true): Room => {
|
||||
const room = new Room(roomId, sdkContext.client!, sdkContext.client!.getSafeUserId());
|
||||
jest.spyOn(room, "isElementVideoRoom").mockReturnValue(isVideoRoom);
|
||||
// stub
|
||||
jest.spyOn(room, "getPendingEvents").mockReturnValue([]);
|
||||
return room;
|
||||
};
|
||||
|
||||
const mockRoomNotificationState = (room: Room, level: NotificationLevel): RoomNotificationState => {
|
||||
const roomNotificationState = new RoomNotificationState(room, false);
|
||||
|
||||
// @ts-ignore ugly mocking
|
||||
roomNotificationState._level = level;
|
||||
jest.spyOn(sdkContext.roomNotificationStateStore, "getRoomState").mockReturnValue(roomNotificationState);
|
||||
return roomNotificationState;
|
||||
};
|
||||
|
||||
const getComponent = (room: Room) =>
|
||||
render(<VideoRoomChatButton room={room} />, {
|
||||
wrapper: ({ children }) => <SDKContext.Provider value={sdkContext}>{children}</SDKContext.Provider>,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const client = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(),
|
||||
});
|
||||
rightPanelStore = {
|
||||
showOrHidePhase: jest.fn(),
|
||||
} as unknown as MockedObject<RightPanelStore>;
|
||||
sdkContext = new TestSDKContext();
|
||||
sdkContext._client = client;
|
||||
jest.spyOn(sdkContext, "rightPanelStore", "get").mockReturnValue(rightPanelStore);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("toggles timeline in right panel on click", () => {
|
||||
const room = makeRoom();
|
||||
getComponent(room);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Chat" }));
|
||||
|
||||
expect(sdkContext.rightPanelStore.showOrHidePhase).toHaveBeenCalledWith(RightPanelPhases.Timeline);
|
||||
});
|
||||
|
||||
it("renders button with an unread marker when room is unread", () => {
|
||||
const room = makeRoom();
|
||||
mockRoomNotificationState(room, NotificationLevel.Activity);
|
||||
getComponent(room);
|
||||
|
||||
// snapshot includes `data-indicator` attribute
|
||||
expect(screen.getByRole("button", { name: "Chat" })).toMatchSnapshot();
|
||||
expect(screen.getByRole("button", { name: "Chat" }).hasAttribute("data-indicator")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("adds unread marker when room notification state changes to unread", async () => {
|
||||
const room = makeRoom();
|
||||
// start in read state
|
||||
const notificationState = mockRoomNotificationState(room, NotificationLevel.None);
|
||||
getComponent(room);
|
||||
|
||||
// no unread marker
|
||||
expect(screen.getByRole("button", { name: "Chat" }).hasAttribute("data-indicator")).toBeFalsy();
|
||||
|
||||
// @ts-ignore ugly mocking
|
||||
notificationState._level = NotificationLevel.Highlight;
|
||||
notificationState.emit(NotificationStateEvents.Update);
|
||||
|
||||
// unread marker
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole("button", { name: "Chat" }).hasAttribute("data-indicator")).toBeTruthy(),
|
||||
);
|
||||
});
|
||||
|
||||
it("clears unread marker when room notification state changes to read", async () => {
|
||||
const room = makeRoom();
|
||||
// start in unread state
|
||||
const notificationState = mockRoomNotificationState(room, NotificationLevel.Highlight);
|
||||
getComponent(room);
|
||||
|
||||
// unread marker
|
||||
expect(screen.getByRole("button", { name: "Chat" }).hasAttribute("data-indicator")).toBeTruthy();
|
||||
|
||||
// @ts-ignore ugly mocking
|
||||
notificationState._level = NotificationLevel.None;
|
||||
notificationState.emit(NotificationStateEvents.Update);
|
||||
|
||||
// unread marker cleared
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole("button", { name: "Chat" }).hasAttribute("data-indicator")).toBeFalsy(),
|
||||
);
|
||||
});
|
||||
});
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
<DocumentFragment>
|
||||
<header
|
||||
class="_flex_4dswl_9 mx_RoomHeader light-panel"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;"
|
||||
>
|
||||
<button
|
||||
aria-label="Open room settings"
|
||||
aria-live="off"
|
||||
class="_avatar_va14e_8 mx_BaseAvatar _avatar-imageless_va14e_55"
|
||||
data-color="3"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="button"
|
||||
style="--cpd-avatar-size: 40px;"
|
||||
tabindex="-1"
|
||||
>
|
||||
!
|
||||
</button>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
class="mx_RoomHeader_infoWrapper"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomHeader_info _box-flex_1odfs_9"
|
||||
style="--mx-box-flex: 1;"
|
||||
>
|
||||
<div
|
||||
aria-level="1"
|
||||
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 mx_RoomHeader_heading"
|
||||
dir="auto"
|
||||
role="heading"
|
||||
>
|
||||
<span
|
||||
class="mx_RoomHeader_truncated mx_lineClamp"
|
||||
>
|
||||
!1:example.org
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="menu"
|
||||
aria-label="Video call"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="primary"
|
||||
data-state="closed"
|
||||
id="radix-react-use-id-1"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
aria-labelledby="react-use-id-2"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="menu"
|
||||
aria-label="Voice call"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="primary"
|
||||
data-state="closed"
|
||||
id="radix-react-use-id-3"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
aria-labelledby="react-use-id-4"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m20.958 16.374.039 3.527q0 .427-.33.756-.33.33-.756.33a16 16 0 0 1-6.57-1.105 16.2 16.2 0 0 1-5.563-3.663 16.1 16.1 0 0 1-3.653-5.573 16.3 16.3 0 0 1-1.115-6.56q0-.427.33-.757T4.095 3l3.528.039a1.07 1.07 0 0 1 1.085.93l.543 3.954q.039.271-.039.504a1.1 1.1 0 0 1-.271.426l-1.64 1.64q.505 1.008 1.154 1.909c.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444q.9.65 1.909 1.153l1.64-1.64q.193-.193.426-.27t.504-.04l3.954.543q.406.059.668.359t.262.727"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Threads"
|
||||
aria-labelledby="react-use-id-5"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
class=""
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2m3 7h10q.424 0 .712-.287A.97.97 0 0 0 18 9a.97.97 0 0 0-.288-.713A.97.97 0 0 0 17 8H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 9q0 .424.287.713Q6.576 10 7 10m0 4h6q.424 0 .713-.287A.97.97 0 0 0 14 13a.97.97 0 0 0-.287-.713A.97.97 0 0 0 13 12H7a.97.97 0 0 0-.713.287A.97.97 0 0 0 6 13q0 .424.287.713Q6.576 14 7 14"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
aria-labelledby="react-use-id-6"
|
||||
class="_icon-button_1215g_8"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
class=""
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16v-4a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 12v4q0 .424.287.712.288.288.713.288m0-8q.424 0 .713-.287A.97.97 0 0 0 13 8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8q0 .424.287.713Q11.576 9 12 9m0 13a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</header>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`<VideoRoomChatButton /> renders button with an unread marker when room is unread 1`] = `
|
||||
<button
|
||||
aria-label="Chat"
|
||||
aria-labelledby="react-use-id-1"
|
||||
class="_icon-button_1215g_8"
|
||||
data-indicator="default"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_147l5_17"
|
||||
data-indicator="default"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
class=""
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M2.95 16.3 1.5 21.25a.94.94 0 0 0 .25 1 .94.94 0 0 0 1 .25l4.95-1.45a10.2 10.2 0 0 0 2.1.712Q10.875 22 12 22a9.7 9.7 0 0 0 3.9-.788 10.1 10.1 0 0 0 3.175-2.137q1.35-1.35 2.137-3.175A9.7 9.7 0 0 0 22 12a9.7 9.7 0 0 0-.788-3.9 10.1 10.1 0 0 0-2.137-3.175q-1.35-1.35-3.175-2.137A9.7 9.7 0 0 0 12 2a9.7 9.7 0 0 0-3.9.788 10.1 10.1 0 0 0-3.175 2.137Q3.575 6.275 2.788 8.1A9.7 9.7 0 0 0 2 12q0 1.125.238 2.2.237 1.076.712 2.1"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
`;
|
||||
Reference in New Issue
Block a user