Merge branch 'develop' into midhun/new-memberlist
This commit is contained in:
@@ -15,10 +15,11 @@ import RecordingPlayback, {
|
||||
PlaybackLayout,
|
||||
} from "../../../../../src/components/views/audio_messages/RecordingPlayback";
|
||||
import { Playback } from "../../../../../src/audio/Playback";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { createAudioContext } from "../../../../../src/audio/compat";
|
||||
import { flushPromises } from "../../../../test-utils";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../src/WorkerManager", () => ({
|
||||
WorkerManager: jest.fn(() => ({
|
||||
@@ -56,9 +57,9 @@ describe("<RecordingPlayback />", () => {
|
||||
const defaultRoom = { roomId: "!room:server.org", timelineRenderingType: TimelineRenderingType.File } as IRoomState;
|
||||
const getComponent = (props: React.ComponentProps<typeof RecordingPlayback>, room = defaultRoom) =>
|
||||
render(
|
||||
<RoomContext.Provider value={room}>
|
||||
<ScopedRoomContextProvider {...room}>
|
||||
<RecordingPlayback {...props} />
|
||||
</RoomContext.Provider>,
|
||||
</ScopedRoomContextProvider>,
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -14,11 +14,11 @@ exports[`<AuthFooter /> should match snapshot 1`] = `
|
||||
Blog
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/element_hq"
|
||||
href="https://mastodon.matrix.org/@Element"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
Twitter
|
||||
Mastodon
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/element-hq/element-web"
|
||||
|
||||
@@ -30,11 +30,11 @@ exports[`<AuthPage /> should match snapshot 1`] = `
|
||||
Blog
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/element_hq"
|
||||
href="https://mastodon.matrix.org/@Element"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
Twitter
|
||||
Mastodon
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/element-hq/element-web"
|
||||
|
||||
@@ -12,11 +12,11 @@ import { MatrixClient, PendingEventOrdering, Room, RoomMember } from "matrix-js-
|
||||
import React, { ComponentProps } from "react";
|
||||
|
||||
import MemberAvatar from "../../../../../src/components/views/avatars/MemberAvatar";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { getRoomContext } from "../../../../test-utils/room";
|
||||
import { stubClient } from "../../../../test-utils/test-utils";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
describe("MemberAvatar", () => {
|
||||
const ROOM_ID = "roomId";
|
||||
@@ -27,9 +27,9 @@ describe("MemberAvatar", () => {
|
||||
|
||||
function getComponent(props: Partial<ComponentProps<typeof MemberAvatar>>) {
|
||||
return (
|
||||
<RoomContext.Provider value={getRoomContext(room, {})}>
|
||||
<ScopedRoomContextProvider {...getRoomContext(room, {})}>
|
||||
<MemberAvatar member={null} size="35px" {...props} />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,18 @@ exports[`<BeaconViewDialog /> renders a fallback when there are no locations 1`]
|
||||
<div
|
||||
class="mx_MapFallback_bg"
|
||||
/>
|
||||
<div
|
||||
<svg
|
||||
class="mx_MapFallback_icon"
|
||||
/>
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_BeaconViewDialog_mapFallbackMessage"
|
||||
>
|
||||
|
||||
@@ -27,7 +27,7 @@ import { mocked } from "jest-mock";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import { canEditContent } from "../../../../../src/utils/EventUtils";
|
||||
import { copyPlaintext, getSelectedText } from "../../../../../src/utils/strings";
|
||||
@@ -37,9 +37,8 @@ import dispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { ReadPinsEventId } from "../../../../../src/components/views/right_panel/types";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast";
|
||||
import { createMessageEventContent } from "../../../../test-utils/events";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../src/utils/strings", () => ({
|
||||
copyPlaintext: jest.fn(),
|
||||
@@ -233,17 +232,6 @@ describe("MessageContextMenu", () => {
|
||||
expect(document.querySelector('li[aria-label="Forward"]')).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should not allow forwarding a voice broadcast", () => {
|
||||
const broadcastStartEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
"@user:example.com",
|
||||
"ABC123",
|
||||
);
|
||||
createMenu(broadcastStartEvent);
|
||||
expect(document.querySelector('li[aria-label="Forward"]')).toBeFalsy();
|
||||
});
|
||||
|
||||
describe("forwarding beacons", () => {
|
||||
const aliceId = "@alice:server.org";
|
||||
|
||||
@@ -546,8 +534,8 @@ function createMenu(
|
||||
client.getRoom = jest.fn().mockReturnValue(room);
|
||||
|
||||
return render(
|
||||
<RoomContext.Provider value={context as IRoomState}>
|
||||
<ScopedRoomContextProvider {...(context as IRoomState)}>
|
||||
<MessageContextMenu mxEvent={mxEvent} onFinished={jest.fn()} {...props} />
|
||||
</RoomContext.Provider>,
|
||||
</ScopedRoomContextProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ describe("RoomGeneralContextMenu", () => {
|
||||
user: "@user:id",
|
||||
ts: 1000,
|
||||
});
|
||||
room.addLiveEvents([event], {});
|
||||
room.addLiveEvents([event], { addToState: true });
|
||||
|
||||
const { container } = getComponent({});
|
||||
|
||||
@@ -150,7 +150,7 @@ describe("RoomGeneralContextMenu", () => {
|
||||
|
||||
await sleep(0);
|
||||
|
||||
expect(mockClient.setRoomAccountData).toHaveBeenCalledWith(ROOM_ID, "com.famedly.marked_unread", {
|
||||
expect(mockClient.setRoomAccountData).toHaveBeenCalledWith(ROOM_ID, "m.marked_unread", {
|
||||
unread: true,
|
||||
});
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
|
||||
@@ -122,4 +122,34 @@ describe("AccessSecretStorageDialog", () => {
|
||||
|
||||
expect(screen.getByPlaceholderText("Security Phrase")).toHaveFocus();
|
||||
});
|
||||
|
||||
it("Can reset secret storage", async () => {
|
||||
jest.spyOn(mockClient.secretStorage, "checkKey").mockResolvedValue(true);
|
||||
|
||||
const onFinished = jest.fn();
|
||||
const checkPrivateKey = jest.fn().mockResolvedValue(true);
|
||||
renderComponent({ onFinished, checkPrivateKey });
|
||||
|
||||
await userEvent.click(screen.getByText("Reset all"), { delay: null });
|
||||
|
||||
// It will prompt the user to confirm resetting
|
||||
expect(screen.getByText("Reset everything")).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByText("Reset"), { delay: null });
|
||||
|
||||
// Then it will prompt the user to create a key/passphrase
|
||||
await screen.findByText("Set up Secure Backup");
|
||||
document.execCommand = jest.fn().mockReturnValue(true);
|
||||
jest.spyOn(mockClient.getCrypto()!, "createRecoveryKeyFromPassphrase").mockResolvedValue({
|
||||
privateKey: new Uint8Array(),
|
||||
encodedPrivateKey: securityKey,
|
||||
});
|
||||
screen.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await screen.findByText(/Save your Security Key/);
|
||||
screen.getByRole("button", { name: "Copy" }).click();
|
||||
await screen.findByText("Copied!");
|
||||
screen.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await screen.findByText("Secure Backup successful");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,14 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
|
||||
import { MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { screen, act } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { flushPromises, mkEvent, stubClient } from "../../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast";
|
||||
import { createRedactEventDialog } from "../../../../../src/components/views/dialogs/ConfirmRedactDialog";
|
||||
|
||||
describe("ConfirmRedactDialog", () => {
|
||||
@@ -21,15 +18,6 @@ describe("ConfirmRedactDialog", () => {
|
||||
let client: MatrixClient;
|
||||
let mxEvent: MatrixEvent;
|
||||
|
||||
const setUpVoiceBroadcastStartedEvent = () => {
|
||||
mxEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId()!,
|
||||
client.deviceId!,
|
||||
);
|
||||
};
|
||||
|
||||
const confirmDeleteVoiceBroadcastStartedEvent = async () => {
|
||||
act(() => createRedactEventDialog({ mxEvent }));
|
||||
// double-flush promises required for the dialog to show up
|
||||
@@ -68,44 +56,4 @@ describe("ConfirmRedactDialog", () => {
|
||||
`cannot redact event ${mxEvent.getId()} without room ID`,
|
||||
);
|
||||
});
|
||||
|
||||
describe("when redacting a voice broadcast started event", () => {
|
||||
beforeEach(() => {
|
||||
setUpVoiceBroadcastStartedEvent();
|
||||
});
|
||||
|
||||
describe("and the server does not support relation based redactions", () => {
|
||||
beforeEach(() => {
|
||||
client.canSupport.set(Feature.RelationBasedRedactions, ServerSupport.Unsupported);
|
||||
});
|
||||
|
||||
describe("and displaying and confirm the dialog for a voice broadcast", () => {
|
||||
beforeEach(async () => {
|
||||
await confirmDeleteVoiceBroadcastStartedEvent();
|
||||
});
|
||||
|
||||
it("should call redact without `with_rel_types`", () => {
|
||||
expect(client.redactEvent).toHaveBeenCalledWith(roomId, mxEvent.getId(), undefined, {});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the server supports relation based redactions", () => {
|
||||
beforeEach(() => {
|
||||
client.canSupport.set(Feature.RelationBasedRedactions, ServerSupport.Unstable);
|
||||
});
|
||||
|
||||
describe("and displaying and confirm the dialog for a voice broadcast", () => {
|
||||
beforeEach(async () => {
|
||||
await confirmDeleteVoiceBroadcastStartedEvent();
|
||||
});
|
||||
|
||||
it("should call redact with `with_rel_types`", () => {
|
||||
expect(client.redactEvent).toHaveBeenCalledWith(roomId, mxEvent.getId(), undefined, {
|
||||
with_rel_types: [RelationType.Reference],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,7 +67,6 @@ describe("ForwardDialog", () => {
|
||||
getAccountData: jest.fn().mockReturnValue(accountDataEvent),
|
||||
getPushActionsForEvent: jest.fn(),
|
||||
mxcUrlToHttp: jest.fn().mockReturnValue(""),
|
||||
isRoomEncrypted: jest.fn().mockReturnValue(false),
|
||||
getProfileInfo: jest.fn().mockResolvedValue({
|
||||
displayname: "Alice",
|
||||
}),
|
||||
|
||||
@@ -141,16 +141,19 @@ describe("InviteDialog", () => {
|
||||
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(),
|
||||
}),
|
||||
]);
|
||||
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({
|
||||
|
||||
@@ -22,7 +22,6 @@ describe("LogoutDialog", () => {
|
||||
beforeEach(() => {
|
||||
mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsCrypto(),
|
||||
getKeyBackupVersion: jest.fn(),
|
||||
});
|
||||
|
||||
mockCrypto = mocked(mockClient.getCrypto()!);
|
||||
@@ -50,14 +49,14 @@ describe("LogoutDialog", () => {
|
||||
});
|
||||
|
||||
it("Prompts user to connect backup if there is a backup on the server", async () => {
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue({} as KeyBackupInfo);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue({} as KeyBackupInfo);
|
||||
const rendered = renderComponent();
|
||||
await rendered.findByText("Connect this session to Key Backup");
|
||||
expect(rendered.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("Prompts user to set up backup if there is no backup on the server", async () => {
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue(null);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue(null);
|
||||
const rendered = renderComponent();
|
||||
await rendered.findByText("Start using Key Backup");
|
||||
expect(rendered.container).toMatchSnapshot();
|
||||
@@ -69,7 +68,7 @@ describe("LogoutDialog", () => {
|
||||
describe("when there is an error fetching backups", () => {
|
||||
filterConsole("Unable to fetch key backup status");
|
||||
it("prompts user to set up backup", async () => {
|
||||
mockClient.getKeyBackupVersion.mockImplementation(async () => {
|
||||
mockCrypto.getKeyBackupInfo.mockImplementation(async () => {
|
||||
throw new Error("beep");
|
||||
});
|
||||
const rendered = renderComponent();
|
||||
|
||||
@@ -1,104 +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
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import { Device, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import { ManualDeviceKeyVerificationDialog } from "../../../../../src/components/views/dialogs/ManualDeviceKeyVerificationDialog";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
|
||||
describe("ManualDeviceKeyVerificationDialog", () => {
|
||||
let mockClient: MatrixClient;
|
||||
|
||||
function renderDialog(userId: string, device: Device, onLegacyFinished: (confirm: boolean) => void) {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<ManualDeviceKeyVerificationDialog userId={userId} device={device} onFinished={onLegacyFinished} />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient = stubClient();
|
||||
});
|
||||
|
||||
it("should display the device", () => {
|
||||
// When
|
||||
const deviceId = "XYZ";
|
||||
const device = new Device({
|
||||
userId: mockClient.getUserId()!,
|
||||
deviceId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
|
||||
});
|
||||
const { container } = renderDialog(mockClient.getUserId()!, device, jest.fn());
|
||||
|
||||
// Then
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should display the device of another user", () => {
|
||||
// When
|
||||
const userId = "@alice:example.com";
|
||||
const deviceId = "XYZ";
|
||||
const device = new Device({
|
||||
userId,
|
||||
deviceId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
|
||||
});
|
||||
const { container } = renderDialog(userId, device, jest.fn());
|
||||
|
||||
// Then
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should call onFinished and matrixClient.setDeviceVerified", () => {
|
||||
// When
|
||||
const deviceId = "XYZ";
|
||||
const device = new Device({
|
||||
userId: mockClient.getUserId()!,
|
||||
deviceId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
|
||||
});
|
||||
const onFinished = jest.fn();
|
||||
renderDialog(mockClient.getUserId()!, device, onFinished);
|
||||
|
||||
screen.getByRole("button", { name: "Verify session" }).click();
|
||||
|
||||
// Then
|
||||
expect(onFinished).toHaveBeenCalledWith(true);
|
||||
expect(mockClient.setDeviceVerified).toHaveBeenCalledWith(mockClient.getUserId(), deviceId, true);
|
||||
});
|
||||
|
||||
it("should call onFinished and not matrixClient.setDeviceVerified", () => {
|
||||
// When
|
||||
const deviceId = "XYZ";
|
||||
const device = new Device({
|
||||
userId: mockClient.getUserId()!,
|
||||
deviceId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
|
||||
});
|
||||
const onFinished = jest.fn();
|
||||
renderDialog(mockClient.getUserId()!, device, onFinished);
|
||||
|
||||
screen.getByRole("button", { name: "Cancel" }).click();
|
||||
|
||||
// Then
|
||||
expect(onFinished).toHaveBeenCalledWith(false);
|
||||
expect(mockClient.setDeviceVerified).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -7,111 +7,139 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { EventTimeline, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { render, RenderOptions } from "jest-matrix-react";
|
||||
import { MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { render, screen, act } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { _t } from "../../../../../src/languageHandler";
|
||||
import ShareDialog from "../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { ShareDialog } from "../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { UIFeature } from "../../../../../src/settings/UIFeature";
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
jest.mock("../../../../../src/utils/ShieldUtils");
|
||||
|
||||
function getWrapper(): RenderOptions {
|
||||
return {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>{children}</MatrixClientContext.Provider>
|
||||
),
|
||||
};
|
||||
}
|
||||
import { stubClient, withClientContextRenderOptions } from "../../../../test-utils";
|
||||
import * as StringsModule from "../../../../../src/utils/strings";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks.ts";
|
||||
|
||||
describe("ShareDialog", () => {
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
const ROOM_ID = "!1:example.org";
|
||||
const copyTextFunc = jest.fn();
|
||||
|
||||
beforeEach(async () => {
|
||||
stubClient();
|
||||
room = new Room(ROOM_ID, MatrixClientPeg.get()!, "@alice:example.org");
|
||||
client = stubClient();
|
||||
room = new Room("!1:example.org", client, "@alice:example.org");
|
||||
jest.spyOn(StringsModule, "copyPlaintext").mockImplementation(copyTextFunc);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
copyTextFunc.mockClear();
|
||||
});
|
||||
|
||||
it("renders room share dialog", () => {
|
||||
const { container: withoutEvents } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
expect(withoutEvents).toHaveTextContent(_t("share|title_room"));
|
||||
function renderComponent(target: Room | RoomMember | URL) {
|
||||
return render(<ShareDialog target={target} onFinished={jest.fn()} />, withClientContextRenderOptions(client));
|
||||
}
|
||||
|
||||
jest.spyOn(room, "getLiveTimeline").mockReturnValue({ getEvents: () => [{} as MatrixEvent] } as EventTimeline);
|
||||
const { container: withEvents } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
expect(withEvents).toHaveTextContent(_t("share|permalink_most_recent"));
|
||||
const getUrl = () => new URL("https://matrix.org/");
|
||||
const getRoomMember = () => new RoomMember(room.roomId, "@alice:example.org");
|
||||
|
||||
test.each([
|
||||
{ name: "an URL", title: "Share Link", url: "https://matrix.org/", getTarget: getUrl },
|
||||
{
|
||||
name: "a room member",
|
||||
title: "Share User",
|
||||
url: "https://matrix.to/#/@alice:example.org",
|
||||
getTarget: getRoomMember,
|
||||
},
|
||||
])("should render a share dialog for $name", async ({ title, url, getTarget }) => {
|
||||
const { asFragment } = renderComponent(getTarget());
|
||||
|
||||
expect(screen.getByRole("heading", { name: title })).toBeInTheDocument();
|
||||
expect(screen.getByText(url)).toBeInTheDocument();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Copy link" }));
|
||||
expect(copyTextFunc).toHaveBeenCalledWith(url);
|
||||
});
|
||||
|
||||
it("renders user share dialog", () => {
|
||||
mockRoomMembers(room, 1);
|
||||
const { container } = render(
|
||||
<ShareDialog target={room.getJoinedMembers()[0]} onFinished={jest.fn()} />,
|
||||
getWrapper(),
|
||||
it("should render a share dialog for a room", async () => {
|
||||
const expectedURL = "https://matrix.to/#/!1:example.org";
|
||||
jest.spyOn(room.getLiveTimeline(), "getEvents").mockReturnValue([new MatrixEvent({ event_id: "!eventId" })]);
|
||||
|
||||
const { asFragment } = renderComponent(room);
|
||||
expect(screen.getByRole("heading", { name: "Share Room" })).toBeInTheDocument();
|
||||
expect(screen.getByText(expectedURL)).toBeInTheDocument();
|
||||
expect(screen.getByRole("checkbox", { name: "Link to most recent message" })).toBeInTheDocument();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Copy link" }));
|
||||
expect(copyTextFunc).toHaveBeenCalledWith(expectedURL);
|
||||
|
||||
// Click on the checkbox to link to the most recent message
|
||||
await userEvent.click(screen.getByRole("checkbox", { name: "Link to most recent message" }));
|
||||
const newExpectedURL = "https://matrix.to/#/!1:example.org/!eventId";
|
||||
expect(screen.getByText(newExpectedURL)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render a share dialog for a matrix event", async () => {
|
||||
const matrixEvent = new MatrixEvent({ event_id: "!eventId" });
|
||||
const permalinkCreator = new RoomPermalinkCreator(room);
|
||||
const expectedURL = "https://matrix.to/#/!1:example.org/!eventId";
|
||||
|
||||
const { asFragment } = render(
|
||||
<ShareDialog target={matrixEvent} permalinkCreator={permalinkCreator} onFinished={jest.fn()} />,
|
||||
withClientContextRenderOptions(client),
|
||||
);
|
||||
expect(container).toHaveTextContent(_t("share|title_user"));
|
||||
expect(screen.getByRole("heading", { name: "Share Room Message" })).toBeInTheDocument();
|
||||
expect(screen.getByText(expectedURL)).toBeInTheDocument();
|
||||
expect(screen.getByRole("checkbox", { name: "Link to selected message" })).toBeChecked();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Copy link" }));
|
||||
expect(copyTextFunc).toHaveBeenCalledWith(expectedURL);
|
||||
|
||||
// Click on the checkbox to link to the room
|
||||
await userEvent.click(screen.getByRole("checkbox", { name: "Link to selected message" }));
|
||||
expect(screen.getByText("https://matrix.to/#/!1:example.org")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders link share dialog", () => {
|
||||
mockRoomMembers(room, 1);
|
||||
const { container } = render(
|
||||
<ShareDialog target={new URL("https://matrix.org")} onFinished={jest.fn()} />,
|
||||
getWrapper(),
|
||||
);
|
||||
expect(container).toHaveTextContent(_t("share|title_link"));
|
||||
it("should change the copy button text when clicked", async () => {
|
||||
jest.useFakeTimers();
|
||||
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
|
||||
// To not be bother with rtl warnings about QR code state update
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
|
||||
renderComponent(room);
|
||||
await user.click(screen.getByRole("button", { name: "Copy link" }));
|
||||
// Move after `copyPlaintext`
|
||||
await jest.advanceTimersToNextTimerAsync();
|
||||
expect(screen.getByRole("button", { name: "Link copied" })).toBeInTheDocument();
|
||||
|
||||
// 2 sec after the button should be back to normal
|
||||
act(() => jest.advanceTimersByTime(2000));
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Copy link" })).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it("renders the QR code if configured", () => {
|
||||
it("should not render the QR code if disabled", () => {
|
||||
const originalGetValue = SettingsStore.getValue;
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => {
|
||||
if (feature === UIFeature.ShareQRCode) return true;
|
||||
if (feature === UIFeature.ShareQRCode) return false;
|
||||
return originalGetValue(feature);
|
||||
});
|
||||
const { container } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
const qrCodesVisible = container.getElementsByClassName("mx_ShareDialog_qrcode_container").length > 0;
|
||||
expect(qrCodesVisible).toBe(true);
|
||||
|
||||
const { asFragment } = renderComponent(room);
|
||||
expect(screen.queryByRole("img", { name: "QR code" })).toBeNull();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the social button if configured", () => {
|
||||
it("should not render the socials if disabled", () => {
|
||||
const originalGetValue = SettingsStore.getValue;
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => {
|
||||
if (feature === UIFeature.ShareSocial) return true;
|
||||
if (feature === UIFeature.ShareSocial) return false;
|
||||
return originalGetValue(feature);
|
||||
});
|
||||
const { container } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
const qrCodesVisible = container.getElementsByClassName("mx_ShareDialog_social_container").length > 0;
|
||||
expect(qrCodesVisible).toBe(true);
|
||||
});
|
||||
it("renders custom title and subtitle", () => {
|
||||
const { container } = render(
|
||||
<ShareDialog
|
||||
target={room}
|
||||
customTitle="test_title_123"
|
||||
subtitle="custom_subtitle_1234"
|
||||
onFinished={jest.fn()}
|
||||
/>,
|
||||
getWrapper(),
|
||||
);
|
||||
expect(container).toHaveTextContent("test_title_123");
|
||||
expect(container).toHaveTextContent("custom_subtitle_1234");
|
||||
|
||||
const { asFragment } = renderComponent(room);
|
||||
expect(screen.queryByRole("link", { name: "Reddit" })).toBeNull();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
/**
|
||||
*
|
||||
* @param count the number of users to create
|
||||
*/
|
||||
function mockRoomMembers(room: Room, count: number) {
|
||||
const members = Array(count)
|
||||
.fill(0)
|
||||
.map((_, index) => new RoomMember(room.roomId, "@alice:example.org"));
|
||||
|
||||
room.currentState.setJoinedMemberCount(members.length);
|
||||
room.getJoinedMembers = jest.fn().mockReturnValue(members);
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ describe("Spotlight Dialog", () => {
|
||||
});
|
||||
|
||||
it("should call getVisibleRooms with MSC3946 dynamic room predecessors", async () => {
|
||||
render(<SpotlightDialog onFinished={() => null} />, { legacyRoot: false });
|
||||
render(<SpotlightDialog onFinished={() => null} />);
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
expect(mockedClient.getVisibleRooms).toHaveBeenCalledWith(true);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Device, MatrixClient, User } from "matrix-js-sdk/src/matrix";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import UntrustedDeviceDialog from "../../../../../src/components/views/dialogs/UntrustedDeviceDialog.tsx";
|
||||
|
||||
describe("<UntrustedDeviceDialog />", () => {
|
||||
let client: MatrixClient;
|
||||
let user: User;
|
||||
let device: Device;
|
||||
const onFinished = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
user = User.createUser("@alice:example.org", client);
|
||||
user.setDisplayName("Alice");
|
||||
device = new Device({ deviceId: "device_id", userId: user.userId, algorithms: [], keys: new Map() });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
onFinished.mockReset();
|
||||
});
|
||||
|
||||
function renderComponent() {
|
||||
return render(<UntrustedDeviceDialog user={user} device={device} onFinished={onFinished} />);
|
||||
}
|
||||
|
||||
it("should display the dialog for the device of another user", () => {
|
||||
const { asFragment } = renderComponent();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should display the dialog for the device of the current user", () => {
|
||||
jest.spyOn(client, "getUserId").mockReturnValue(user.userId);
|
||||
|
||||
const { asFragment } = renderComponent();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should call onFinished without parameter when Done is clicked", () => {
|
||||
renderComponent();
|
||||
screen.getByRole("button", { name: "Done" }).click();
|
||||
expect(onFinished).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it("should call onFinished with sas when Interactively verify by emoji is clicked", () => {
|
||||
renderComponent();
|
||||
screen.getByRole("button", { name: "Interactively verify by emoji" }).click();
|
||||
expect(onFinished).toHaveBeenCalledWith("sas");
|
||||
});
|
||||
});
|
||||
@@ -185,33 +185,6 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = `
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_4yVCeEefiPqp"
|
||||
>
|
||||
<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"
|
||||
id="mx_SettingsFlag_4yVCeEefiPqp"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ManualDeviceKeyVerificationDialog should display the device 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_QuestionDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Verify session
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
Confirm by comparing the following with the User Settings in your other session:
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceVerifyDialog_cryptoSection"
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<label>
|
||||
Session name
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
my device
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
Session ID
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
<code>
|
||||
XYZ
|
||||
</code>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
Session key
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
<code>
|
||||
<strong>
|
||||
ABCD EFGH
|
||||
</strong>
|
||||
</code>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
If they don't match, the security of your communication may be compromised.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<span
|
||||
class="mx_Dialog_buttons_row"
|
||||
>
|
||||
<button
|
||||
data-testid="dialog-cancel-button"
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="mx_Dialog_primary"
|
||||
data-testid="dialog-primary-button"
|
||||
type="button"
|
||||
>
|
||||
Verify session
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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[`ManualDeviceKeyVerificationDialog should display the device of another user 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_QuestionDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Verify session
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
Confirm this user's session by comparing the following with their User Settings:
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceVerifyDialog_cryptoSection"
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<label>
|
||||
Session name
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
my device
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
Session ID
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
<code>
|
||||
XYZ
|
||||
</code>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
Session key
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
<code>
|
||||
<strong>
|
||||
ABCD EFGH
|
||||
</strong>
|
||||
</code>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
If they don't match, the security of your communication may be compromised.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<span
|
||||
class="mx_Dialog_buttons_row"
|
||||
>
|
||||
<button
|
||||
data-testid="dialog-cancel-button"
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="mx_Dialog_primary"
|
||||
data-testid="dialog-primary-button"
|
||||
type="button"
|
||||
>
|
||||
Verify session
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,852 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ShareDialog should not render the QR code if disabled 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ShareDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Share Room
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ShareDialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ShareDialog_top"
|
||||
>
|
||||
<span>
|
||||
https://matrix.to/#/!1:example.org
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
Copy link
|
||||
</button>
|
||||
<div
|
||||
class="mx_ShareDialog_social"
|
||||
>
|
||||
<a
|
||||
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Facebook"
|
||||
>
|
||||
<img
|
||||
alt="Facebook"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/home?status=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Twitter"
|
||||
>
|
||||
<img
|
||||
alt="Twitter"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<img
|
||||
alt="LinkedIn"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.reddit.com/submit?url=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Reddit"
|
||||
>
|
||||
<img
|
||||
alt="Reddit"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:?body=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="email"
|
||||
>
|
||||
<img
|
||||
alt="email"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`ShareDialog should not render the socials if disabled 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ShareDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Share Room
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ShareDialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ShareDialog_top"
|
||||
>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
https://matrix.to/#/!1:example.org
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
Copy link
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`ShareDialog should render a share dialog for a matrix event 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ShareDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Share Room Message
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ShareDialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ShareDialog_top"
|
||||
>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
https://matrix.to/#/!1:example.org/!eventId
|
||||
</span>
|
||||
</div>
|
||||
<label>
|
||||
<div
|
||||
class="_container_1wloq_18"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="_input_1wloq_26"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div
|
||||
class="_ui_1wloq_27"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
Link to selected message
|
||||
</label>
|
||||
<button
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
Copy link
|
||||
</button>
|
||||
<div
|
||||
class="mx_ShareDialog_social"
|
||||
>
|
||||
<a
|
||||
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.to/#/!1:example.org/!eventId"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Facebook"
|
||||
>
|
||||
<img
|
||||
alt="Facebook"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/home?status=https://matrix.to/#/!1:example.org/!eventId"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Twitter"
|
||||
>
|
||||
<img
|
||||
alt="Twitter"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.to/#/!1:example.org/!eventId"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<img
|
||||
alt="LinkedIn"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.reddit.com/submit?url=https://matrix.to/#/!1:example.org/!eventId"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Reddit"
|
||||
>
|
||||
<img
|
||||
alt="Reddit"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:?body=https://matrix.to/#/!1:example.org/!eventId"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="email"
|
||||
>
|
||||
<img
|
||||
alt="email"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`ShareDialog should render a share dialog for a room 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ShareDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Share Room
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ShareDialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ShareDialog_top"
|
||||
>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
https://matrix.to/#/!1:example.org
|
||||
</span>
|
||||
</div>
|
||||
<label>
|
||||
<div
|
||||
class="_container_1wloq_18"
|
||||
>
|
||||
<input
|
||||
class="_input_1wloq_26"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div
|
||||
class="_ui_1wloq_27"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
Link to most recent message
|
||||
</label>
|
||||
<button
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
Copy link
|
||||
</button>
|
||||
<div
|
||||
class="mx_ShareDialog_social"
|
||||
>
|
||||
<a
|
||||
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Facebook"
|
||||
>
|
||||
<img
|
||||
alt="Facebook"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/home?status=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Twitter"
|
||||
>
|
||||
<img
|
||||
alt="Twitter"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<img
|
||||
alt="LinkedIn"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.reddit.com/submit?url=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Reddit"
|
||||
>
|
||||
<img
|
||||
alt="Reddit"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:?body=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="email"
|
||||
>
|
||||
<img
|
||||
alt="email"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`ShareDialog should render a share dialog for a room member 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ShareDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Share User
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ShareDialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ShareDialog_top"
|
||||
>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
https://matrix.to/#/@alice:example.org
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
Copy link
|
||||
</button>
|
||||
<div
|
||||
class="mx_ShareDialog_social"
|
||||
>
|
||||
<a
|
||||
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.to/#/@alice:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Facebook"
|
||||
>
|
||||
<img
|
||||
alt="Facebook"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/home?status=https://matrix.to/#/@alice:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Twitter"
|
||||
>
|
||||
<img
|
||||
alt="Twitter"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.to/#/@alice:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<img
|
||||
alt="LinkedIn"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.reddit.com/submit?url=https://matrix.to/#/@alice:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Reddit"
|
||||
>
|
||||
<img
|
||||
alt="Reddit"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:?body=https://matrix.to/#/@alice:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="email"
|
||||
>
|
||||
<img
|
||||
alt="email"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`ShareDialog should render a share dialog for an URL 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ShareDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Share Link
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ShareDialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ShareDialog_top"
|
||||
>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
https://matrix.org/
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
Copy link
|
||||
</button>
|
||||
<div
|
||||
class="mx_ShareDialog_social"
|
||||
>
|
||||
<a
|
||||
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.org/"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Facebook"
|
||||
>
|
||||
<img
|
||||
alt="Facebook"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/home?status=https://matrix.org/"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Twitter"
|
||||
>
|
||||
<img
|
||||
alt="Twitter"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.org/"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<img
|
||||
alt="LinkedIn"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.reddit.com/submit?url=https://matrix.org/"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Reddit"
|
||||
>
|
||||
<img
|
||||
alt="Reddit"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:?body=https://matrix.org/"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="email"
|
||||
>
|
||||
<img
|
||||
alt="email"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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,149 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<UntrustedDeviceDialog /> should display the dialog for the device of another user 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_UntrustedDeviceDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
<div
|
||||
class="mx_E2EIcon mx_E2EIcon_warning"
|
||||
style="width: 24px; height: 24px;"
|
||||
/>
|
||||
Not Trusted
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<p>
|
||||
Alice (@alice:example.org) signed in to a new session without verifying it:
|
||||
</p>
|
||||
<p>
|
||||
(device_id)
|
||||
</p>
|
||||
<p>
|
||||
Ask this user to verify their session, or manually verify it below.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Interactively verify by emoji
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Done
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<UntrustedDeviceDialog /> should display the dialog for the device of the current user 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_UntrustedDeviceDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
<div
|
||||
class="mx_E2EIcon mx_E2EIcon_warning"
|
||||
style="width: 24px; height: 24px;"
|
||||
/>
|
||||
Not Trusted
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<p>
|
||||
You signed in to a new session without verifying it:
|
||||
</p>
|
||||
<p>
|
||||
(device_id)
|
||||
</p>
|
||||
<p>
|
||||
Verify your other session using one of the options below.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Interactively verify by emoji
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Done
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -13,7 +13,7 @@ import { mocked, MockedObject } from "jest-mock";
|
||||
import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { filterConsole, stubClient } from "../../../../../test-utils";
|
||||
import { filterConsole, flushPromises, stubClient } from "../../../../../test-utils";
|
||||
import CreateSecretStorageDialog from "../../../../../../src/async-components/views/dialogs/security/CreateSecretStorageDialog";
|
||||
|
||||
describe("CreateSecretStorageDialog", () => {
|
||||
@@ -77,7 +77,7 @@ describe("CreateSecretStorageDialog", () => {
|
||||
filterConsole("Error fetching backup data from server");
|
||||
|
||||
it("shows an error", async () => {
|
||||
mockClient.getKeyBackupVersion.mockImplementation(async () => {
|
||||
jest.spyOn(mockClient.getCrypto()!, "getKeyBackupInfo").mockImplementation(async () => {
|
||||
throw new Error("bleh bleh");
|
||||
});
|
||||
|
||||
@@ -92,9 +92,44 @@ describe("CreateSecretStorageDialog", () => {
|
||||
expect(result.container).toMatchSnapshot();
|
||||
|
||||
// Now we can get the backup and we retry
|
||||
mockClient.getKeyBackupVersion.mockRestore();
|
||||
jest.spyOn(mockClient.getCrypto()!, "getKeyBackupInfo").mockRestore();
|
||||
await userEvent.click(screen.getByRole("button", { name: "Retry" }));
|
||||
await screen.findByText("Your keys are now being backed up from this device.");
|
||||
});
|
||||
});
|
||||
|
||||
it("resets keys in the right order when resetting secret storage and cross-signing", async () => {
|
||||
const result = renderComponent({ forceReset: true, resetCrossSigning: true });
|
||||
|
||||
await result.findByText(/Set up Secure Backup/);
|
||||
jest.spyOn(mockClient.getCrypto()!, "createRecoveryKeyFromPassphrase").mockResolvedValue({
|
||||
privateKey: new Uint8Array(),
|
||||
encodedPrivateKey: "abcd efgh ijkl",
|
||||
});
|
||||
result.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await result.findByText(/Save your Security Key/);
|
||||
result.getByRole("button", { name: "Copy" }).click();
|
||||
|
||||
// Resetting should reset secret storage, cross signing, and key
|
||||
// backup. We make sure that all three are reset, and done in the
|
||||
// right order.
|
||||
const resetFunctionCallLog: string[] = [];
|
||||
jest.spyOn(mockClient.getCrypto()!, "bootstrapSecretStorage").mockImplementation(async () => {
|
||||
resetFunctionCallLog.push("bootstrapSecretStorage");
|
||||
});
|
||||
jest.spyOn(mockClient.getCrypto()!, "bootstrapCrossSigning").mockImplementation(async () => {
|
||||
resetFunctionCallLog.push("bootstrapCrossSigning");
|
||||
});
|
||||
jest.spyOn(mockClient.getCrypto()!, "resetKeyBackup").mockImplementation(async () => {
|
||||
resetFunctionCallLog.push("resetKeyBackup");
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
result.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await result.findByText("Your keys are now being backed up from this device.");
|
||||
|
||||
expect(resetFunctionCallLog).toEqual(["bootstrapSecretStorage", "bootstrapCrossSigning", "resetKeyBackup"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,9 +7,10 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { screen, fireEvent, render, waitFor } from "jest-matrix-react";
|
||||
import { screen, fireEvent, render, waitFor, act } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { Crypto, IMegolmSessionData } from "matrix-js-sdk/src/matrix";
|
||||
import { IMegolmSessionData } from "matrix-js-sdk/src/matrix";
|
||||
import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import * as MegolmExportEncryption from "../../../../../../src/utils/MegolmExportEncryption";
|
||||
import ExportE2eKeysDialog from "../../../../../../src/async-components/views/dialogs/security/ExportE2eKeysDialog";
|
||||
@@ -23,12 +24,12 @@ describe("ExportE2eKeysDialog", () => {
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should have disabled submit button initially", () => {
|
||||
it("should have disabled submit button initially", async () => {
|
||||
const cli = createTestClient();
|
||||
const onFinished = jest.fn();
|
||||
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
|
||||
fireEvent.click(container.querySelector("[type=submit]")!);
|
||||
expect(screen.getByText("Enter passphrase")).toBeInTheDocument();
|
||||
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
|
||||
expect(screen.getByLabelText("Enter passphrase")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should complain about weak passphrases", async () => {
|
||||
@@ -38,7 +39,7 @@ describe("ExportE2eKeysDialog", () => {
|
||||
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
|
||||
const input = screen.getByLabelText("Enter passphrase");
|
||||
await userEvent.type(input, "password");
|
||||
fireEvent.click(container.querySelector("[type=submit]")!);
|
||||
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
|
||||
await expect(screen.findByText("This is a top-10 common password")).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -49,7 +50,7 @@ describe("ExportE2eKeysDialog", () => {
|
||||
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
|
||||
await userEvent.type(screen.getByLabelText("Enter passphrase"), "ThisIsAMoreSecurePW123$$");
|
||||
await userEvent.type(screen.getByLabelText("Confirm passphrase"), "ThisIsAMoreSecurePW124$$");
|
||||
fireEvent.click(container.querySelector("[type=submit]")!);
|
||||
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
|
||||
await expect(screen.findByText("Passphrases must match")).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -62,7 +63,7 @@ describe("ExportE2eKeysDialog", () => {
|
||||
cli.getCrypto = () => {
|
||||
return {
|
||||
exportRoomKeysAsJson,
|
||||
} as unknown as Crypto.CryptoApi;
|
||||
} as unknown as CryptoApi;
|
||||
};
|
||||
|
||||
// Mock the result of encrypting the sessions. If we don't do this, the
|
||||
@@ -74,7 +75,7 @@ describe("ExportE2eKeysDialog", () => {
|
||||
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={jest.fn()} />);
|
||||
await userEvent.type(screen.getByLabelText("Enter passphrase"), passphrase);
|
||||
await userEvent.type(screen.getByLabelText("Confirm passphrase"), passphrase);
|
||||
fireEvent.click(container.querySelector("[type=submit]")!);
|
||||
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
|
||||
|
||||
// Then it exports keys and encrypts them
|
||||
await waitFor(() => expect(exportRoomKeysAsJson).toHaveBeenCalled());
|
||||
|
||||
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React from "react";
|
||||
import { fireEvent, render, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { Crypto } from "matrix-js-sdk/src/matrix";
|
||||
import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import ImportE2eKeysDialog from "../../../../../../src/async-components/views/dialogs/security/ImportE2eKeysDialog";
|
||||
import * as MegolmExportEncryption from "../../../../../../src/utils/MegolmExportEncryption";
|
||||
@@ -67,7 +67,7 @@ describe("ImportE2eKeysDialog", () => {
|
||||
cli.getCrypto = () => {
|
||||
return {
|
||||
importRoomKeysAsJson,
|
||||
} as unknown as Crypto.CryptoApi;
|
||||
} as unknown as CryptoApi;
|
||||
};
|
||||
|
||||
// Mock the result of decrypting the sessions, to avoid needing to
|
||||
|
||||
@@ -28,7 +28,7 @@ describe("<RestoreKeyBackupDialog />", () => {
|
||||
beforeEach(() => {
|
||||
matrixClient = stubClient();
|
||||
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockReturnValue(new Uint8Array(32));
|
||||
jest.spyOn(matrixClient, "getKeyBackupVersion").mockResolvedValue({ version: "1" } as KeyBackupInfo);
|
||||
jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({ version: "1" } as KeyBackupInfo);
|
||||
});
|
||||
|
||||
it("should render", async () => {
|
||||
@@ -99,7 +99,7 @@ describe("<RestoreKeyBackupDialog />", () => {
|
||||
|
||||
test("should restore key backup when passphrase is filled", async () => {
|
||||
// Determine that the passphrase is required
|
||||
jest.spyOn(matrixClient, "getKeyBackupVersion").mockResolvedValue({
|
||||
jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({
|
||||
version: "1",
|
||||
auth_data: {
|
||||
private_key_salt: "salt",
|
||||
|
||||
@@ -10,7 +10,7 @@ import React from "react";
|
||||
import { Room, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { ClientWidgetApi, IWidget, MatrixWidgetType } from "matrix-widget-api";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
import { act, render, RenderResult } from "jest-matrix-react";
|
||||
import { act, render, RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import {
|
||||
ApprovalOpts,
|
||||
@@ -29,7 +29,6 @@ import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
|
||||
import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
|
||||
import { UPDATE_EVENT } from "../../../../../src/stores/AsyncStore";
|
||||
import WidgetStore, { IApp } from "../../../../../src/stores/WidgetStore";
|
||||
import ActiveWidgetStore from "../../../../../src/stores/ActiveWidgetStore";
|
||||
import AppTile from "../../../../../src/components/views/elements/AppTile";
|
||||
@@ -59,16 +58,6 @@ describe("AppTile", () => {
|
||||
let app1: IApp;
|
||||
let app2: IApp;
|
||||
|
||||
const waitForRps = (roomId: string) =>
|
||||
new Promise<void>((resolve) => {
|
||||
const update = () => {
|
||||
if (RightPanelStore.instance.currentCardForRoom(roomId).phase !== RightPanelPhases.Widget) return;
|
||||
RightPanelStore.instance.off(UPDATE_EVENT, update);
|
||||
resolve();
|
||||
};
|
||||
RightPanelStore.instance.on(UPDATE_EVENT, update);
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
@@ -160,29 +149,28 @@ describe("AppTile", () => {
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
// Wait for RPS room 1 updates to fire
|
||||
const rpsUpdated = waitForRps("r1");
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r1",
|
||||
});
|
||||
await rpsUpdated;
|
||||
act(() =>
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r1",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(renderResult.getByText("Example 1")).toBeInTheDocument();
|
||||
await expect(renderResult.findByText("Example 1")).resolves.toBeInTheDocument();
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(true);
|
||||
|
||||
const { container, asFragment } = renderResult;
|
||||
expect(container.getElementsByClassName("mx_Spinner").length).toBeTruthy();
|
||||
const { asFragment } = renderResult;
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
// We want to verify that as we change to room 2, we should close the
|
||||
// right panel and destroy the widget.
|
||||
|
||||
// Switch to room 2
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r2",
|
||||
});
|
||||
act(() =>
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r2",
|
||||
}),
|
||||
);
|
||||
|
||||
renderResult.rerender(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
@@ -233,16 +221,17 @@ describe("AppTile", () => {
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
// Wait for RPS room 1 updates to fire
|
||||
const rpsUpdated1 = waitForRps("r1");
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r1",
|
||||
});
|
||||
await rpsUpdated1;
|
||||
act(() =>
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r1",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(true);
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r2")).toBe(false);
|
||||
await waitFor(() => {
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(true);
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r2")).toBe(false);
|
||||
});
|
||||
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name, roomId) => {
|
||||
if (name === "RightPanel.phases") {
|
||||
@@ -263,13 +252,13 @@ describe("AppTile", () => {
|
||||
}
|
||||
return realGetValue(name, roomId);
|
||||
});
|
||||
// Wait for RPS room 2 updates to fire
|
||||
const rpsUpdated2 = waitForRps("r2");
|
||||
// Switch to room 2
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r2",
|
||||
});
|
||||
act(() =>
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r2",
|
||||
}),
|
||||
);
|
||||
renderResult.rerender(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<RightPanel
|
||||
@@ -279,10 +268,11 @@ describe("AppTile", () => {
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await rpsUpdated2;
|
||||
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(false);
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r2")).toBe(true);
|
||||
await waitFor(() => {
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(false);
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r2")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves non-persisted widget on container move", async () => {
|
||||
@@ -345,7 +335,7 @@ describe("AppTile", () => {
|
||||
let renderResult: RenderResult;
|
||||
let moveToContainerSpy: jest.SpyInstance<void, [room: Room, widget: IWidget, toContainer: Container]>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} />
|
||||
@@ -353,12 +343,12 @@ describe("AppTile", () => {
|
||||
);
|
||||
|
||||
moveToContainerSpy = jest.spyOn(WidgetLayoutStore.instance, "moveToContainer");
|
||||
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
const { container, asFragment } = renderResult;
|
||||
const { asFragment } = renderResult;
|
||||
|
||||
expect(container.querySelector(".mx_Spinner")).toBeFalsy(); // Assert that the spinner is gone
|
||||
expect(asFragment()).toMatchSnapshot(); // Take a snapshot of the pinned widget
|
||||
});
|
||||
|
||||
@@ -459,18 +449,19 @@ describe("AppTile", () => {
|
||||
describe("for a persistent app", () => {
|
||||
let renderResult: RenderResult;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} fullWidth={true} room={r1} miniMode={true} showMenubar={false} />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
const { container, asFragment } = renderResult;
|
||||
it("should render", async () => {
|
||||
const { asFragment } = renderResult;
|
||||
|
||||
expect(container.querySelector(".mx_Spinner")).toBeFalsy();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import MiniAvatarUploader from "../../../../../src/components/views/elements/MiniAvatarUploader.tsx";
|
||||
import { stubClient, withClientContextRenderOptions } from "../../../../test-utils";
|
||||
|
||||
const BASE64_GIF = "R0lGODlhAQABAAAAACw=";
|
||||
const AVATAR_FILE = new File([Uint8Array.from(atob(BASE64_GIF), (c) => c.charCodeAt(0))], "avatar.gif", {
|
||||
type: "image/gif",
|
||||
});
|
||||
|
||||
describe("<MiniAvatarUploader />", () => {
|
||||
it("calls setAvatarUrl when a file is uploaded", async () => {
|
||||
const cli = stubClient();
|
||||
mocked(cli.uploadContent).mockResolvedValue({ content_uri: "mxc://example.com/1234" });
|
||||
|
||||
const setAvatarUrl = jest.fn();
|
||||
const user = userEvent.setup();
|
||||
|
||||
const { container, findByText } = render(
|
||||
<MiniAvatarUploader hasAvatar={false} noAvatarLabel="Upload" setAvatarUrl={setAvatarUrl} isUserAvatar />,
|
||||
withClientContextRenderOptions(cli),
|
||||
);
|
||||
|
||||
await findByText("Upload");
|
||||
await user.upload(container.querySelector("input")!, AVATAR_FILE);
|
||||
|
||||
expect(cli.uploadContent).toHaveBeenCalledWith(AVATAR_FILE);
|
||||
expect(setAvatarUrl).toHaveBeenCalledWith("mxc://example.com/1234");
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, render, RenderResult, screen } from "jest-matrix-react";
|
||||
import { render, RenderResult, screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked, Mocked } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
@@ -86,7 +86,7 @@ describe("<Pill>", () => {
|
||||
room: room1Id,
|
||||
msg: "Room 1 Message",
|
||||
});
|
||||
room1.addLiveEvents([room1Message]);
|
||||
room1.addLiveEvents([room1Message], { addToState: true });
|
||||
|
||||
room2 = new Room(room2Id, client, user1Id);
|
||||
room2.currentState.setStateEvents([mkRoomMemberJoinEvent(user2Id, room2Id)]);
|
||||
@@ -214,9 +214,7 @@ describe("<Pill>", () => {
|
||||
});
|
||||
|
||||
// wait for profile query via API
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(renderResult.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
@@ -228,9 +226,7 @@ describe("<Pill>", () => {
|
||||
});
|
||||
|
||||
// wait for profile query via API
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(renderResult.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -41,7 +41,7 @@ describe("<RoomTopic/>", () => {
|
||||
ts: 123,
|
||||
event: true,
|
||||
});
|
||||
room.addLiveEvents([topicEvent]);
|
||||
room.addLiveEvents([topicEvent], { addToState: true });
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
@@ -60,29 +60,9 @@ exports[`AppTile destroys non-persisted right panel widget on room change 1`] =
|
||||
id="1"
|
||||
>
|
||||
<div
|
||||
class="mx_AppTileBody mx_AppTileBody--large"
|
||||
class="mx_AppTile_persistedWrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_AppTileBody_fadeInSpinner"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner_Msg"
|
||||
>
|
||||
Loading…
|
||||
</div>
|
||||
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,9 @@ describe("<VerificationQRCode />", () => {
|
||||
});
|
||||
|
||||
it("renders a QR code", async () => {
|
||||
const { container, getAllByAltText } = render(<VerificationQRCode qrCodeBytes={Buffer.from("asd")} />);
|
||||
const { container, getAllByAltText } = render(
|
||||
<VerificationQRCode qrCodeBytes={new Uint8ClampedArray(Buffer.from("asd"))} />,
|
||||
);
|
||||
// wait for the spinner to go away
|
||||
await waitFor(() => getAllByAltText("QR Code").length === 1);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { createRef } from "react";
|
||||
import { render, waitFor } from "jest-matrix-react";
|
||||
import { render, waitFor, act } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import EmojiPicker from "../../../../../src/components/views/emojipicker/EmojiPicker";
|
||||
@@ -27,12 +27,12 @@ describe("EmojiPicker", function () {
|
||||
|
||||
// Apply a filter and assert that the HTML has changed
|
||||
//@ts-ignore private access
|
||||
ref.current!.onChangeFilter("test");
|
||||
act(() => ref.current!.onChangeFilter("test"));
|
||||
expect(beforeHtml).not.toEqual(container.innerHTML);
|
||||
|
||||
// Clear the filter and assert that the HTML matches what it was before filtering
|
||||
//@ts-ignore private access
|
||||
ref.current!.onChangeFilter("");
|
||||
act(() => ref.current!.onChangeFilter(""));
|
||||
await waitFor(() => expect(beforeHtml).toEqual(container.innerHTML));
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ describe("EmojiPicker", function () {
|
||||
const ep = new EmojiPicker({ onChoose: (str: string) => false, onFinished: jest.fn() });
|
||||
|
||||
//@ts-ignore private access
|
||||
ep.onChangeFilter("heart");
|
||||
act(() => ep.onChangeFilter("heart"));
|
||||
|
||||
//@ts-ignore private access
|
||||
expect(ep.memoizedDataByCategory["people"][0].shortcodes[0]).toEqual("heart");
|
||||
|
||||
@@ -139,7 +139,7 @@ describe("<LocationShareMenu />", () => {
|
||||
const [, onGeolocateCallback] = mocked(mockGeolocate.on).mock.calls.find(([event]) => event === "geolocate")!;
|
||||
|
||||
// set the location
|
||||
onGeolocateCallback(position);
|
||||
act(() => onGeolocateCallback(position));
|
||||
};
|
||||
|
||||
const setLocationClick = () => {
|
||||
@@ -151,7 +151,7 @@ describe("<LocationShareMenu />", () => {
|
||||
lngLat: { lng: position.coords.longitude, lat: position.coords.latitude },
|
||||
} as unknown as maplibregl.MapMouseEvent;
|
||||
// set the location
|
||||
onMapClickCallback(event);
|
||||
act(() => onMapClickCallback(event));
|
||||
};
|
||||
|
||||
const shareTypeLabels: Record<LocationShareType, string> = {
|
||||
|
||||
@@ -13,9 +13,18 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
<svg
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@@ -9,9 +9,18 @@ exports[`<Marker /> renders with location icon when no room member 1`] = `
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
<svg
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
|
||||
@@ -9,9 +9,18 @@ exports[`<SmartMarker /> creates a marker on mount 1`] = `
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
<svg
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
@@ -27,9 +36,18 @@ exports[`<SmartMarker /> removes marker on unmount 1`] = `
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
<svg
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@@ -264,10 +264,12 @@ describe("DateSeparator", () => {
|
||||
fireEvent.click(jumpToLastWeekButton);
|
||||
|
||||
// Expect error to be shown. We have to wait for the UI to transition.
|
||||
expect(await screen.findByTestId("jump-to-date-error-content")).toBeInTheDocument();
|
||||
await expect(screen.findByTestId("jump-to-date-error-content")).resolves.toBeInTheDocument();
|
||||
|
||||
// Expect an option to submit debug logs to be shown when a non-network error occurs
|
||||
expect(await screen.findByTestId("jump-to-date-error-submit-debug-logs-button")).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByTestId("jump-to-date-error-submit-debug-logs-button"),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
[
|
||||
@@ -280,19 +282,20 @@ describe("DateSeparator", () => {
|
||||
),
|
||||
].forEach((fakeError) => {
|
||||
it(`should show error dialog without submit debug logs option when networking error (${fakeError.name}) occurs`, async () => {
|
||||
// Try to jump to "last week" but we want a network error to occur
|
||||
mockClient.timestampToEvent.mockRejectedValue(fakeError);
|
||||
|
||||
// Render the component
|
||||
getComponent();
|
||||
|
||||
// Open the jump to date context menu
|
||||
fireEvent.click(screen.getByTestId("jump-to-date-separator-button"));
|
||||
|
||||
// Try to jump to "last week" but we want a network error to occur
|
||||
mockClient.timestampToEvent.mockRejectedValue(fakeError);
|
||||
const jumpToLastWeekButton = await screen.findByTestId("jump-to-date-last-week");
|
||||
fireEvent.click(jumpToLastWeekButton);
|
||||
|
||||
// Expect error to be shown. We have to wait for the UI to transition.
|
||||
expect(await screen.findByTestId("jump-to-date-error-content")).toBeInTheDocument();
|
||||
await expect(screen.findByTestId("jump-to-date-error-content")).resolves.toBeInTheDocument();
|
||||
|
||||
// The submit debug logs option should *NOT* be shown for network errors.
|
||||
//
|
||||
|
||||
@@ -10,6 +10,7 @@ import React from "react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
|
||||
import EncryptionEvent from "../../../../../src/components/views/messages/EncryptionEvent";
|
||||
import { createTestClient, mkMessage } from "../../../../test-utils";
|
||||
@@ -26,9 +27,9 @@ const renderEncryptionEvent = (client: MatrixClient, event: MatrixEvent) => {
|
||||
);
|
||||
};
|
||||
|
||||
const checkTexts = (title: string, subTitle: string) => {
|
||||
screen.getByText(title);
|
||||
screen.getByText(subTitle);
|
||||
const checkTexts = async (title: string, subTitle: string) => {
|
||||
await screen.findByText(title);
|
||||
await screen.findByText(subTitle);
|
||||
};
|
||||
|
||||
describe("EncryptionEvent", () => {
|
||||
@@ -55,17 +56,19 @@ describe("EncryptionEvent", () => {
|
||||
describe("for an encrypted room", () => {
|
||||
beforeEach(() => {
|
||||
event.event.content!.algorithm = algorithm;
|
||||
mocked(client.isRoomEncrypted).mockReturnValue(true);
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
const room = new Room(roomId, client, client.getUserId()!);
|
||||
mocked(client.getRoom).mockReturnValue(room);
|
||||
});
|
||||
|
||||
it("should show the expected texts", () => {
|
||||
it("should show the expected texts", async () => {
|
||||
renderEncryptionEvent(client, event);
|
||||
checkTexts(
|
||||
"Encryption enabled",
|
||||
"Messages in this room are end-to-end encrypted. " +
|
||||
"When people join, you can verify them in their profile, just tap on their profile picture.",
|
||||
await waitFor(() =>
|
||||
checkTexts(
|
||||
"Encryption enabled",
|
||||
"Messages in this room are end-to-end encrypted. " +
|
||||
"When people join, you can verify them in their profile, just tap on their profile picture.",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -76,9 +79,9 @@ describe("EncryptionEvent", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should show the expected texts", () => {
|
||||
it("should show the expected texts", async () => {
|
||||
renderEncryptionEvent(client, event);
|
||||
checkTexts("Encryption enabled", "Some encryption parameters have been changed.");
|
||||
await waitFor(() => checkTexts("Encryption enabled", "Some encryption parameters have been changed."));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,37 +90,39 @@ describe("EncryptionEvent", () => {
|
||||
event.event.content!.algorithm = "unknown";
|
||||
});
|
||||
|
||||
it("should show the expected texts", () => {
|
||||
it("should show the expected texts", async () => {
|
||||
renderEncryptionEvent(client, event);
|
||||
checkTexts("Encryption enabled", "Ignored attempt to disable encryption");
|
||||
await waitFor(() => checkTexts("Encryption enabled", "Ignored attempt to disable encryption"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("for an unencrypted room", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.isRoomEncrypted).mockReturnValue(false);
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
|
||||
renderEncryptionEvent(client, event);
|
||||
});
|
||||
|
||||
it("should show the expected texts", () => {
|
||||
expect(client.isRoomEncrypted).toHaveBeenCalledWith(roomId);
|
||||
checkTexts("Encryption not enabled", "The encryption used by this room isn't supported.");
|
||||
it("should show the expected texts", async () => {
|
||||
expect(client.getCrypto()!.isEncryptionEnabledInRoom).toHaveBeenCalledWith(roomId);
|
||||
await waitFor(() =>
|
||||
checkTexts("Encryption not enabled", "The encryption used by this room isn't supported."),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("for an encrypted local room", () => {
|
||||
beforeEach(() => {
|
||||
event.event.content!.algorithm = algorithm;
|
||||
mocked(client.isRoomEncrypted).mockReturnValue(true);
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
const localRoom = new LocalRoom(roomId, client, client.getUserId()!);
|
||||
mocked(client.getRoom).mockReturnValue(localRoom);
|
||||
renderEncryptionEvent(client, event);
|
||||
});
|
||||
|
||||
it("should show the expected texts", () => {
|
||||
expect(client.isRoomEncrypted).toHaveBeenCalledWith(roomId);
|
||||
checkTexts("Encryption enabled", "Messages in this chat will be end-to-end encrypted.");
|
||||
it("should show the expected texts", async () => {
|
||||
expect(client.getCrypto()!.isEncryptionEnabledInRoom).toHaveBeenCalledWith(roomId);
|
||||
await checkTexts("Encryption enabled", "Messages in this chat will be end-to-end encrypted.");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,7 +20,8 @@ import {
|
||||
import { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import MFileBody from "../../../../../src/components/views/messages/MFileBody.tsx";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext.ts";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext.ts";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("matrix-encrypt-attachment", () => ({
|
||||
decryptAttachment: jest.fn(),
|
||||
@@ -72,14 +73,14 @@ describe("<MFileBody/>", () => {
|
||||
|
||||
it("should show a download button in file rendering type", async () => {
|
||||
const { container, getByRole } = render(
|
||||
<RoomContext.Provider value={{ timelineRenderingType: TimelineRenderingType.File } as any}>
|
||||
<ScopedRoomContextProvider {...({ timelineRenderingType: TimelineRenderingType.File } as any)}>
|
||||
<MFileBody
|
||||
{...props}
|
||||
mxEvent={mediaEvent}
|
||||
mediaEventHelper={new MediaEventHelper(mediaEvent)}
|
||||
showGenericPlaceholder={false}
|
||||
/>
|
||||
</RoomContext.Provider>,
|
||||
</ScopedRoomContextProvider>,
|
||||
);
|
||||
|
||||
expect(getByRole("link", { name: "Download" })).toBeInTheDocument();
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, RenderResult, waitFor } from "jest-matrix-react";
|
||||
import { act, fireEvent, render, RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
|
||||
import {
|
||||
MatrixEvent,
|
||||
Relations,
|
||||
@@ -226,7 +226,7 @@ describe("MPollBody", () => {
|
||||
clickOption(renderResult, "pizza");
|
||||
|
||||
// When a new vote from me comes in
|
||||
await room.processPollEvents([responseEvent("@me:example.com", "wings", 101)]);
|
||||
await act(() => room.processPollEvents([responseEvent("@me:example.com", "wings", 101)]));
|
||||
|
||||
// Then the new vote is counted, not the old one
|
||||
expect(votesCount(renderResult, "pizza")).toBe("0 votes");
|
||||
@@ -255,7 +255,7 @@ describe("MPollBody", () => {
|
||||
clickOption(renderResult, "pizza");
|
||||
|
||||
// When a new vote from someone else comes in
|
||||
await room.processPollEvents([responseEvent("@xx:example.com", "wings", 101)]);
|
||||
await act(() => room.processPollEvents([responseEvent("@xx:example.com", "wings", 101)]));
|
||||
|
||||
// Then my vote is still for pizza
|
||||
// NOTE: the new event does not affect the counts for other people -
|
||||
@@ -596,11 +596,13 @@ describe("MPollBody", () => {
|
||||
];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
await waitFor(() => {
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores votes that arrived after the first end poll event", async () => {
|
||||
@@ -890,12 +892,14 @@ async function newMPollBody(
|
||||
room_id: "#myroom:example.com",
|
||||
content: newPollStart(answers, undefined, disclosed),
|
||||
});
|
||||
const result = newMPollBodyFromEvent(mxEvent, relationEvents, endEvents);
|
||||
// flush promises from loading relations
|
||||
const prom = newMPollBodyFromEvent(mxEvent, relationEvents, endEvents);
|
||||
if (waitForResponsesLoad) {
|
||||
await flushPromises();
|
||||
const result = await prom;
|
||||
if (result.queryByTestId("spinner")) {
|
||||
await waitForElementToBeRemoved(() => result.getByTestId("spinner"));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return prom;
|
||||
}
|
||||
|
||||
function getMPollBodyPropsFromEvent(mxEvent: MatrixEvent): IBodyProps {
|
||||
|
||||
@@ -121,12 +121,13 @@ describe("<MPollEndBody />", () => {
|
||||
describe("when poll start event does not exist in current timeline", () => {
|
||||
it("fetches the related poll start event and displays a poll tile", async () => {
|
||||
await setupRoomWithEventsTimeline(pollEndEvent);
|
||||
const { container, getByTestId, getByRole } = getComponent();
|
||||
const { container, getByTestId, getByRole, queryByRole } = getComponent();
|
||||
|
||||
// while fetching event, only icon is shown
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
await waitFor(() => expect(getByRole("progressbar")).toBeInTheDocument());
|
||||
await waitFor(() => expect(queryByRole("progressbar")).not.toBeInTheDocument());
|
||||
|
||||
expect(mockClient.fetchRoomEvent).toHaveBeenCalledWith(roomId, pollStartEvent.getId());
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import dispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import PinningUtils from "../../../../../src/utils/PinningUtils";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../src/dispatcher/dispatcher");
|
||||
|
||||
@@ -117,9 +118,9 @@ describe("<MessageActionBar />", () => {
|
||||
} as unknown as IRoomState;
|
||||
const getComponent = (props = {}, roomContext: Partial<IRoomState> = {}) =>
|
||||
render(
|
||||
<RoomContext.Provider value={{ ...defaultRoomContext, ...roomContext }}>
|
||||
<ScopedRoomContextProvider {...defaultRoomContext} {...roomContext}>
|
||||
<MessageActionBar {...defaultProps} {...props} />
|
||||
</RoomContext.Provider>,
|
||||
</ScopedRoomContextProvider>,
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -14,7 +14,6 @@ import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast";
|
||||
import { mkEvent, mkRoom, stubClient } from "../../../../test-utils";
|
||||
import MessageEvent from "../../../../../src/components/views/messages/MessageEvent";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
@@ -24,10 +23,6 @@ jest.mock("../../../../../src/components/views/messages/UnknownBody", () => ({
|
||||
default: () => <div data-testid="unknown-body" />,
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../src/voice-broadcast/components/VoiceBroadcastBody", () => ({
|
||||
VoiceBroadcastBody: () => <div data-testid="voice-broadcast-body" />,
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../src/components/views/messages/MImageBody", () => ({
|
||||
__esModule: true,
|
||||
default: () => <div data-testid="image-body" />,
|
||||
@@ -81,27 +76,6 @@ describe("MessageEvent", () => {
|
||||
jest.spyOn(SettingsStore, "unwatchSetting").mockImplementation(jest.fn());
|
||||
});
|
||||
|
||||
describe("when a voice broadcast start event occurs", () => {
|
||||
let result: RenderResult;
|
||||
|
||||
beforeEach(() => {
|
||||
event = mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
user: client.getUserId()!,
|
||||
room: room.roomId,
|
||||
content: {
|
||||
state: VoiceBroadcastInfoState.Started,
|
||||
},
|
||||
});
|
||||
result = renderMessageEvent();
|
||||
});
|
||||
|
||||
it("should render a VoiceBroadcast component", () => {
|
||||
result.getByTestId("voice-broadcast-body");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when an image with a caption is sent", () => {
|
||||
let result: RenderResult;
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ import {
|
||||
} from "../../../../../src/components/views/messages/RoomPredecessorTile";
|
||||
import { stubClient, upsertRoomStateEvents } from "../../../../test-utils/test-utils";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import { filterConsole, getRoomContext } from "../../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../src/dispatcher/dispatcher");
|
||||
|
||||
@@ -99,9 +99,9 @@ describe("<RoomPredecessorTile />", () => {
|
||||
expect(createEvent).toBeTruthy();
|
||||
|
||||
return render(
|
||||
<RoomContext.Provider value={getRoomContext(room, {})}>
|
||||
<ScopedRoomContextProvider {...getRoomContext(room, {})}>
|
||||
<RoomPredecessorTile mxEvent={createEvent} />
|
||||
</RoomContext.Provider>,
|
||||
</ScopedRoomContextProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,9 +49,18 @@ exports[`MLocationBody <MLocationBody> without error renders map correctly 1`] =
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
<svg
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, fireEvent, render } from "jest-matrix-react";
|
||||
import { fireEvent, render } from "jest-matrix-react";
|
||||
import { Filter, EventTimeline, Room, MatrixEvent, M_POLL_START } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { PollHistory } from "../../../../../../src/components/views/polls/pollHistory/PollHistory";
|
||||
@@ -110,7 +110,7 @@ describe("<PollHistory />", () => {
|
||||
expect(getByText("Loading polls")).toBeInTheDocument();
|
||||
|
||||
// flush filter creation request
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(liveTimeline.getPaginationToken).toHaveBeenCalledWith(EventTimeline.BACKWARDS);
|
||||
expect(mockClient.paginateEventTimeline).toHaveBeenCalledWith(liveTimeline, { backwards: true });
|
||||
@@ -140,7 +140,7 @@ describe("<PollHistory />", () => {
|
||||
);
|
||||
|
||||
// flush filter creation request
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
// once per page
|
||||
expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(3);
|
||||
|
||||
@@ -175,7 +175,7 @@ describe("<PollHistory />", () => {
|
||||
|
||||
it("renders a no polls message when there are no active polls in the room", async () => {
|
||||
const { getByText } = getComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(getByText("There are no active polls in this room")).toBeTruthy();
|
||||
});
|
||||
@@ -199,7 +199,7 @@ describe("<PollHistory />", () => {
|
||||
.mockReturnValueOnce("test-pagination-token-3");
|
||||
|
||||
const { getByText } = getComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -212,7 +212,7 @@ describe("<PollHistory />", () => {
|
||||
// load more polls button still in UI, with loader
|
||||
expect(getByText("Load more polls")).toMatchSnapshot();
|
||||
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// no more spinner
|
||||
expect(getByText("Load more polls")).toMatchSnapshot();
|
||||
|
||||
@@ -91,7 +91,7 @@ exports[`<PollHistory /> renders a list of active polls when there are polls in
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-labelledby=":ra:"
|
||||
aria-labelledby=":rc:"
|
||||
class="mx_PollListItem_content"
|
||||
>
|
||||
<span>
|
||||
@@ -116,7 +116,7 @@ exports[`<PollHistory /> renders a list of active polls when there are polls in
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-labelledby=":rg:"
|
||||
aria-labelledby=":rh:"
|
||||
class="mx_PollListItem_content"
|
||||
>
|
||||
<span>
|
||||
|
||||
@@ -15,7 +15,7 @@ import userEvent from "@testing-library/user-event";
|
||||
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import RoomSummaryCard from "../../../../../src/components/views/right_panel/RoomSummaryCard";
|
||||
import ShareDialog from "../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { ShareDialog } from "../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import ExportDialog from "../../../../../src/components/views/dialogs/ExportDialog";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
@@ -30,7 +30,8 @@ import { _t } from "../../../../../src/languageHandler";
|
||||
import { tagRoom } from "../../../../../src/utils/room/tagRoom";
|
||||
import { DefaultTagID } from "../../../../../src/stores/room-list/models";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../src/utils/room/tagRoom");
|
||||
|
||||
@@ -172,14 +173,14 @@ describe("<RoomSummaryCard />", () => {
|
||||
const onSearchChange = jest.fn();
|
||||
const { rerender } = render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={{ timelineRenderingType: TimelineRenderingType.Search } as any}>
|
||||
<ScopedRoomContextProvider {...({ timelineRenderingType: TimelineRenderingType.Search } as any)}>
|
||||
<RoomSummaryCard
|
||||
room={room}
|
||||
permalinkCreator={new RoomPermalinkCreator(room)}
|
||||
onSearchChange={onSearchChange}
|
||||
focusRoomSearch={true}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
@@ -188,13 +189,13 @@ describe("<RoomSummaryCard />", () => {
|
||||
|
||||
rerender(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={{ timelineRenderingType: TimelineRenderingType.Room } as any}>
|
||||
<ScopedRoomContextProvider {...({ timelineRenderingType: TimelineRenderingType.Room } as any)}>
|
||||
<RoomSummaryCard
|
||||
room={room}
|
||||
permalinkCreator={new RoomPermalinkCreator(room)}
|
||||
onSearchChange={onSearchChange}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
expect(screen.getByPlaceholderText("Search messages…")).toHaveValue("");
|
||||
@@ -254,10 +255,7 @@ describe("<RoomSummaryCard />", () => {
|
||||
|
||||
fireEvent.click(getByText("People"));
|
||||
|
||||
expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith(
|
||||
{ phase: RightPanelPhases.RoomMemberList },
|
||||
true,
|
||||
);
|
||||
expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith({ phase: RightPanelPhases.MemberList }, true);
|
||||
});
|
||||
|
||||
it("opens room threads list on button click", () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, screen, waitFor, cleanup, act, within } from "jest-matrix-react";
|
||||
import { fireEvent, render, screen, cleanup, act, within, waitForElementToBeRemoved } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { Mocked, mocked } from "jest-mock";
|
||||
import { Room, User, MatrixClient, RoomMember, MatrixEvent, EventType, Device } from "matrix-js-sdk/src/matrix";
|
||||
@@ -49,7 +49,7 @@ import ErrorDialog from "../../../../../src/components/views/dialogs/ErrorDialog
|
||||
import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
|
||||
import { UIComponent } from "../../../../../src/settings/UIFeature";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import ShareDialog from "../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { ShareDialog } from "../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import BulkRedactDialog from "../../../../../src/components/views/dialogs/BulkRedactDialog";
|
||||
|
||||
jest.mock("../../../../../src/utils/direct-messages", () => ({
|
||||
@@ -188,7 +188,7 @@ describe("<UserInfo />", () => {
|
||||
const defaultProps = {
|
||||
user: defaultUser,
|
||||
// idk what is wrong with this type
|
||||
phase: RightPanelPhases.RoomMemberInfo as RightPanelPhases.RoomMemberInfo,
|
||||
phase: RightPanelPhases.MemberInfo as RightPanelPhases.MemberInfo,
|
||||
onClose: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -439,7 +439,7 @@ describe("<UserInfo />", () => {
|
||||
|
||||
it("renders a device list which can be expanded", async () => {
|
||||
renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// check the button exists with the expected text
|
||||
const devicesButton = screen.getByRole("button", { name: "1 session" });
|
||||
@@ -455,13 +455,13 @@ describe("<UserInfo />", () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||
|
||||
const { container } = renderComponent({
|
||||
phase: RightPanelPhases.SpaceMemberInfo,
|
||||
phase: RightPanelPhases.MemberInfo,
|
||||
verificationRequest,
|
||||
room: mockRoom,
|
||||
});
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Verify" })).toBeInTheDocument());
|
||||
await expect(screen.findByRole("button", { name: "Verify" })).resolves.toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -490,7 +490,7 @@ describe("<UserInfo />", () => {
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
|
||||
const devicesButton = screen.getByRole("button", { name: "1 session" });
|
||||
@@ -538,7 +538,7 @@ describe("<UserInfo />", () => {
|
||||
} as DeviceVerificationStatus);
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
|
||||
const devicesButton = screen.getByRole("button", { name: "1 verified session" });
|
||||
@@ -583,7 +583,7 @@ describe("<UserInfo />", () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, true, true));
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// the dehydrated device should be shown as an unverified device, which means
|
||||
// there should now be a button with the device id ...
|
||||
@@ -618,7 +618,7 @@ describe("<UserInfo />", () => {
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
|
||||
const devicesButton = screen.getByRole("button", { name: "2 sessions" });
|
||||
@@ -649,11 +649,14 @@ describe("<UserInfo />", () => {
|
||||
mockClient.getDomain.mockReturnValue("example.com");
|
||||
|
||||
const { container } = renderComponent({
|
||||
phase: RightPanelPhases.RoomMemberInfo,
|
||||
phase: RightPanelPhases.MemberInfo,
|
||||
room: mockRoom,
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Deactivate user" })).toBeInTheDocument());
|
||||
await expect(screen.findByRole("button", { name: "Deactivate user" })).resolves.toBeInTheDocument();
|
||||
if (screen.queryAllByRole("progressbar").length) {
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
|
||||
}
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -666,7 +669,7 @@ describe("<UserInfo />", () => {
|
||||
it("renders unverified user info", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const userHeading = screen.getByRole("heading", { name: /@user:example.com/ });
|
||||
|
||||
@@ -677,7 +680,7 @@ describe("<UserInfo />", () => {
|
||||
it("renders verified user info", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, false, false));
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const userHeading = screen.getByRole("heading", { name: /@user:example.com/ });
|
||||
|
||||
@@ -768,7 +771,7 @@ describe("<DeviceItem />", () => {
|
||||
|
||||
it("with unverified user and device, displays button without a label", async () => {
|
||||
renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.getByRole("button", { name: device.displayName! })).toBeInTheDocument();
|
||||
expect(screen.queryByText(/trusted/i)).not.toBeInTheDocument();
|
||||
@@ -776,7 +779,7 @@ describe("<DeviceItem />", () => {
|
||||
|
||||
it("with verified user only, displays button with a 'Not trusted' label", async () => {
|
||||
renderComponent({ isUserVerified: true });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const button = screen.getByRole("button", { name: device.displayName });
|
||||
expect(button).toHaveTextContent(`${device.displayName}Not trusted`);
|
||||
@@ -785,7 +788,7 @@ describe("<DeviceItem />", () => {
|
||||
it("with verified device only, displays no button without a label", async () => {
|
||||
setMockDeviceTrust(true);
|
||||
renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.getByText(device.displayName!)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/trusted/)).not.toBeInTheDocument();
|
||||
@@ -798,7 +801,7 @@ describe("<DeviceItem />", () => {
|
||||
mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
|
||||
mockClient.getUserId.mockReturnValueOnce(defaultUserId);
|
||||
renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// set trust to be false for isVerified, true for isCrossSigningVerified
|
||||
deferred.resolve({
|
||||
@@ -814,7 +817,7 @@ describe("<DeviceItem />", () => {
|
||||
it("with verified user and device, displays no button and a 'Trusted' label", async () => {
|
||||
setMockDeviceTrust(true);
|
||||
renderComponent({ isUserVerified: true });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
||||
expect(screen.getByText(device.displayName!)).toBeInTheDocument();
|
||||
@@ -824,7 +827,7 @@ describe("<DeviceItem />", () => {
|
||||
it("does not call verifyDevice if client.getUser returns null", async () => {
|
||||
mockClient.getUser.mockReturnValueOnce(null);
|
||||
renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const button = screen.getByRole("button", { name: device.displayName! });
|
||||
expect(button).toBeInTheDocument();
|
||||
@@ -839,7 +842,7 @@ describe("<DeviceItem />", () => {
|
||||
// even more mocking
|
||||
mockClient.isGuest.mockReturnValueOnce(true);
|
||||
renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const button = screen.getByRole("button", { name: device.displayName! });
|
||||
expect(button).toBeInTheDocument();
|
||||
@@ -851,7 +854,7 @@ describe("<DeviceItem />", () => {
|
||||
|
||||
it("with display name", async () => {
|
||||
const { container } = renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
@@ -859,7 +862,7 @@ describe("<DeviceItem />", () => {
|
||||
it("without display name", async () => {
|
||||
const device = { deviceId: "deviceId" } as Device;
|
||||
const { container } = renderComponent({ device, userId: defaultUserId });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
@@ -867,7 +870,7 @@ describe("<DeviceItem />", () => {
|
||||
it("ambiguous display name", async () => {
|
||||
const device = { deviceId: "deviceId", ambiguous: true, displayName: "my display name" };
|
||||
const { container } = renderComponent({ device, userId: defaultUserId });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
@@ -1033,9 +1036,7 @@ describe("<UserOptionsSection />", () => {
|
||||
expect(inviteSpy).toHaveBeenCalledWith([member.userId]);
|
||||
|
||||
// check that the test error message is displayed
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(mockErrorMessage.message)).toBeInTheDocument();
|
||||
});
|
||||
await expect(screen.findByText(mockErrorMessage.message)).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("if calling .invite throws something strange, show default error message", async () => {
|
||||
@@ -1048,9 +1049,7 @@ describe("<UserOptionsSection />", () => {
|
||||
await userEvent.click(inviteButton);
|
||||
|
||||
// check that the default test error message is displayed
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/operation failed/i)).toBeInTheDocument();
|
||||
});
|
||||
await expect(screen.findByText(/operation failed/i)).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -46,7 +46,7 @@ describe("<VerificationPanel />", () => {
|
||||
const request = makeMockVerificationRequest({
|
||||
phase: Phase.Ready,
|
||||
});
|
||||
request.generateQRCode.mockResolvedValue(Buffer.from("test", "utf-8"));
|
||||
request.generateQRCode.mockResolvedValue(new Uint8ClampedArray(Buffer.from("test", "utf-8")));
|
||||
const container = renderComponent({
|
||||
request: request,
|
||||
layout: "dialog",
|
||||
@@ -71,7 +71,7 @@ describe("<VerificationPanel />", () => {
|
||||
const request = makeMockVerificationRequest({
|
||||
phase: Phase.Ready,
|
||||
});
|
||||
request.generateQRCode.mockResolvedValue(Buffer.from("test", "utf-8"));
|
||||
request.generateQRCode.mockResolvedValue(new Uint8ClampedArray(Buffer.from("test", "utf-8")));
|
||||
const container = renderComponent({
|
||||
request: request,
|
||||
member: new User("@other:user"),
|
||||
|
||||
@@ -135,8 +135,9 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
||||
style="--mx-box-flex: 1;"
|
||||
>
|
||||
<a
|
||||
class="_link_1mzip_17"
|
||||
class="_link_ue21z_17"
|
||||
data-kind="primary"
|
||||
data-size="medium"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<p
|
||||
@@ -752,8 +753,9 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
||||
style="--mx-box-flex: 1;"
|
||||
>
|
||||
<a
|
||||
class="_link_1mzip_17"
|
||||
class="_link_ue21z_17"
|
||||
data-kind="primary"
|
||||
data-size="medium"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<p
|
||||
@@ -1406,8 +1408,9 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
||||
style="--mx-box-flex: 1;"
|
||||
>
|
||||
<a
|
||||
class="_link_1mzip_17"
|
||||
class="_link_ue21z_17"
|
||||
data-kind="primary"
|
||||
data-size="medium"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<p
|
||||
|
||||
@@ -88,7 +88,7 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby=":r6s:"
|
||||
aria-labelledby=":r74:"
|
||||
class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
@@ -402,7 +402,7 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby=":r92:"
|
||||
aria-labelledby=":r9a:"
|
||||
class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
|
||||
import { createTestClient, mkStubRoom, withClientContextRenderOptions } from "../../../../test-utils";
|
||||
import { UrlPreviewSettings } from "../../../../../src/components/views/room_settings/UrlPreviewSettings.tsx";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore.ts";
|
||||
import dis from "../../../../../src/dispatcher/dispatcher.ts";
|
||||
import { Action } from "../../../../../src/dispatcher/actions.ts";
|
||||
|
||||
describe("UrlPreviewSettings", () => {
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
beforeEach(() => {
|
||||
client = createTestClient();
|
||||
room = mkStubRoom("roomId", "room", client);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
function renderComponent() {
|
||||
return render(<UrlPreviewSettings room={room} />, withClientContextRenderOptions(client));
|
||||
}
|
||||
|
||||
it("should display the correct preview when the setting is in a loading state", () => {
|
||||
jest.spyOn(client, "getCrypto").mockReturnValue(undefined);
|
||||
const { asFragment } = renderComponent();
|
||||
expect(screen.getByText("URL Previews")).toBeInTheDocument();
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should display the correct preview when the room is encrypted and the url preview is enabled", async () => {
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(true);
|
||||
|
||||
const { asFragment } = renderComponent();
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should display the correct preview when the room is unencrypted and the url preview is enabled", async () => {
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
|
||||
jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(true);
|
||||
jest.spyOn(dis, "fire").mockReturnValue(undefined);
|
||||
|
||||
const { asFragment } = renderComponent();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: "enabled" })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("URL previews are enabled by default for participants in this room."),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
screen.getByRole("button", { name: "enabled" }).click();
|
||||
expect(dis.fire).toHaveBeenCalledWith(Action.ViewUserSettings);
|
||||
});
|
||||
|
||||
it("should display the correct preview when the room is unencrypted and the url preview is disabled", async () => {
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
|
||||
jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(false);
|
||||
|
||||
const { asFragment } = renderComponent();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: "disabled" })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("URL previews are disabled by default for participants in this room."),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,236 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UrlPreviewSettings should display the correct preview when the room is encrypted and the url preview is enabled 1`] = `
|
||||
<DocumentFragment>
|
||||
<fieldset
|
||||
class="mx_SettingsFieldset"
|
||||
>
|
||||
<legend
|
||||
class="mx_SettingsFieldset_legend"
|
||||
>
|
||||
URL Previews
|
||||
</legend>
|
||||
<div
|
||||
class="mx_SettingsFieldset_description"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
<p>
|
||||
When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.
|
||||
</p>
|
||||
<p>
|
||||
In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFieldset_content"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
/>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`UrlPreviewSettings should display the correct preview when the room is unencrypted and the url preview is disabled 1`] = `
|
||||
<DocumentFragment>
|
||||
<fieldset
|
||||
class="mx_SettingsFieldset"
|
||||
>
|
||||
<legend
|
||||
class="mx_SettingsFieldset_legend"
|
||||
>
|
||||
URL Previews
|
||||
</legend>
|
||||
<div
|
||||
class="mx_SettingsFieldset_description"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
<p>
|
||||
When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
You have
|
||||
</span>
|
||||
</p>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
disabled
|
||||
</div>
|
||||
URL previews by default.
|
||||
<p />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFieldset_content"
|
||||
>
|
||||
<div>
|
||||
URL previews are disabled by default for participants in this room.
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Enable inline URL previews by default
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="false"
|
||||
aria-disabled="true"
|
||||
aria-label="Enable inline URL previews by default"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch"
|
||||
id="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`UrlPreviewSettings should display the correct preview when the room is unencrypted and the url preview is enabled 1`] = `
|
||||
<DocumentFragment>
|
||||
<fieldset
|
||||
class="mx_SettingsFieldset"
|
||||
>
|
||||
<legend
|
||||
class="mx_SettingsFieldset_legend"
|
||||
>
|
||||
URL Previews
|
||||
</legend>
|
||||
<div
|
||||
class="mx_SettingsFieldset_description"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
<p>
|
||||
When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
You have
|
||||
</span>
|
||||
</p>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
enabled
|
||||
</div>
|
||||
URL previews by default.
|
||||
<p />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFieldset_content"
|
||||
>
|
||||
<div>
|
||||
URL previews are enabled by default for participants in this room.
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Enable inline URL previews by default
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Enable inline URL previews by default"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`UrlPreviewSettings should display the correct preview when the setting is in a loading state 1`] = `
|
||||
<DocumentFragment>
|
||||
<fieldset
|
||||
class="mx_SettingsFieldset"
|
||||
>
|
||||
<legend
|
||||
class="mx_SettingsFieldset_legend"
|
||||
>
|
||||
URL Previews
|
||||
</legend>
|
||||
<div
|
||||
class="mx_SettingsFieldset_content"
|
||||
>
|
||||
<svg
|
||||
class="_icon_1ye7b_27"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
style="width: 20px; height: 20px;"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M12 4.031a8 8 0 1 0 8 8 1 1 0 0 1 2 0c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10a1 1 0 1 1 0 2Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</fieldset>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -27,12 +27,12 @@ import {
|
||||
import DocumentOffset from "../../../../../src/editor/offset";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import EditorStateTransfer from "../../../../../src/utils/EditorStateTransfer";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import Autocompleter, { IProviderCompletions } from "../../../../../src/autocomplete/Autocompleter";
|
||||
import NotifProvider from "../../../../../src/autocomplete/NotifProvider";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
describe("<EditMessageComposer/>", () => {
|
||||
const userId = "@alice:server.org";
|
||||
@@ -79,7 +79,7 @@ describe("<EditMessageComposer/>", () => {
|
||||
render(<EditMessageComposerWithMatrixClient editState={editState} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={roomContext}>{children}</RoomContext.Provider>
|
||||
<ScopedRoomContextProvider {...roomContext}>{children}</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
),
|
||||
});
|
||||
@@ -128,7 +128,7 @@ describe("<EditMessageComposer/>", () => {
|
||||
|
||||
const expectedBody = {
|
||||
...editedEvent.getContent(),
|
||||
"body": " * original message + edit",
|
||||
"body": "* original message + edit",
|
||||
"m.new_content": {
|
||||
"body": "original message + edit",
|
||||
"msgtype": "m.text",
|
||||
@@ -160,7 +160,7 @@ describe("<EditMessageComposer/>", () => {
|
||||
const content = createEditContent(model, editedEvent);
|
||||
|
||||
expect(content).toEqual({
|
||||
"body": " * hello world",
|
||||
"body": "* hello world",
|
||||
"msgtype": "m.text",
|
||||
"m.new_content": {
|
||||
"body": "hello world",
|
||||
@@ -183,10 +183,10 @@ describe("<EditMessageComposer/>", () => {
|
||||
const content = createEditContent(model, editedEvent);
|
||||
|
||||
expect(content).toEqual({
|
||||
"body": " * hello *world*",
|
||||
"body": "* hello *world*",
|
||||
"msgtype": "m.text",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": " * hello <em>world</em>",
|
||||
"formatted_body": "* hello <em>world</em>",
|
||||
"m.new_content": {
|
||||
"body": "hello *world*",
|
||||
"msgtype": "m.text",
|
||||
@@ -210,10 +210,10 @@ describe("<EditMessageComposer/>", () => {
|
||||
const content = createEditContent(model, editedEvent);
|
||||
|
||||
expect(content).toEqual({
|
||||
"body": " * blinks __quickly__",
|
||||
"body": "* blinks __quickly__",
|
||||
"msgtype": "m.emote",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": " * blinks <strong>quickly</strong>",
|
||||
"formatted_body": "* blinks <strong>quickly</strong>",
|
||||
"m.new_content": {
|
||||
"body": "blinks __quickly__",
|
||||
"msgtype": "m.emote",
|
||||
@@ -238,7 +238,7 @@ describe("<EditMessageComposer/>", () => {
|
||||
const content = createEditContent(model, editedEvent);
|
||||
|
||||
expect(content).toEqual({
|
||||
"body": " * ✨sparkles✨",
|
||||
"body": "* ✨sparkles✨",
|
||||
"msgtype": "m.emote",
|
||||
"m.new_content": {
|
||||
"body": "✨sparkles✨",
|
||||
@@ -264,7 +264,7 @@ describe("<EditMessageComposer/>", () => {
|
||||
// TODO Edits do not properly strip the double slash used to skip
|
||||
// command processing.
|
||||
expect(content).toEqual({
|
||||
"body": " * //dev/null is my favourite place",
|
||||
"body": "* //dev/null is my favourite place",
|
||||
"msgtype": "m.text",
|
||||
"m.new_content": {
|
||||
"body": "//dev/null is my favourite place",
|
||||
|
||||
@@ -30,7 +30,7 @@ import { mkEncryptedMatrixEvent } from "matrix-js-sdk/src/testing";
|
||||
|
||||
import EventTile, { EventTileProps } from "../../../../../src/components/views/rooms/EventTile";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { filterConsole, flushPromises, getRoomContext, mkEvent, mkMessage, stubClient } from "../../../../test-utils";
|
||||
import { mkThread } from "../../../../test-utils/threads";
|
||||
@@ -40,6 +40,7 @@ import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import PinningUtils from "../../../../../src/utils/PinningUtils";
|
||||
import { Layout } from "../../../../../src/settings/enums/Layout";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
describe("EventTile", () => {
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
@@ -56,13 +57,13 @@ describe("EventTile", () => {
|
||||
}) {
|
||||
return (
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={props.roomContext}>
|
||||
<ScopedRoomContextProvider {...props.roomContext}>
|
||||
<EventTile
|
||||
mxEvent={mxEvent}
|
||||
replacingEventId={mxEvent.replacingEventId()}
|
||||
{...(props.eventTilePropertyOverrides ?? {})}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -70,9 +71,11 @@ describe("EventTile", () => {
|
||||
function getComponent(
|
||||
overrides: Partial<EventTileProps> = {},
|
||||
renderingType: TimelineRenderingType = TimelineRenderingType.Room,
|
||||
roomContext: Partial<IRoomState> = {},
|
||||
) {
|
||||
const context = getRoomContext(room, {
|
||||
timelineRenderingType: renderingType,
|
||||
...roomContext,
|
||||
});
|
||||
return render(<WrappedEventTile roomContext={context} eventTilePropertyOverrides={overrides} />);
|
||||
}
|
||||
@@ -260,7 +263,7 @@ describe("EventTile", () => {
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
expect(eventTiles).toHaveLength(1);
|
||||
@@ -285,7 +288,7 @@ describe("EventTile", () => {
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
expect(eventTiles).toHaveLength(1);
|
||||
@@ -301,6 +304,8 @@ describe("EventTile", () => {
|
||||
[EventShieldReason.UNKNOWN_DEVICE, "unknown or deleted device"],
|
||||
[EventShieldReason.AUTHENTICITY_NOT_GUARANTEED, "can't be guaranteed"],
|
||||
[EventShieldReason.MISMATCHED_SENDER_KEY, "Encrypted by an unverified session"],
|
||||
[EventShieldReason.SENT_IN_CLEAR, "Not encrypted"],
|
||||
[EventShieldReason.VERIFICATION_VIOLATION, "Sender's verified identity has changed"],
|
||||
])("shows the correct reason code for %i (%s)", async (reasonCode: EventShieldReason, expectedText: string) => {
|
||||
mxEvent = await mkEncryptedMatrixEvent({
|
||||
plainContent: { msgtype: "m.text", body: "msg1" },
|
||||
@@ -314,7 +319,7 @@ describe("EventTile", () => {
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const e2eIcons = container.getElementsByClassName("mx_EventTile_e2eIcon");
|
||||
expect(e2eIcons).toHaveLength(1);
|
||||
@@ -346,7 +351,7 @@ describe("EventTile", () => {
|
||||
await mxEvent.attemptDecryption(mockCrypto);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
expect(eventTiles).toHaveLength(1);
|
||||
@@ -400,7 +405,7 @@ describe("EventTile", () => {
|
||||
const roomContext = getRoomContext(room, {});
|
||||
const { container, rerender } = render(<WrappedEventTile roomContext={roomContext} />);
|
||||
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
expect(eventTiles).toHaveLength(1);
|
||||
@@ -434,8 +439,6 @@ describe("EventTile", () => {
|
||||
});
|
||||
|
||||
it("should update the warning when the event is replaced with an unencrypted one", async () => {
|
||||
jest.spyOn(client, "isRoomEncrypted").mockReturnValue(true);
|
||||
|
||||
// we start out with an event from the trusted device
|
||||
mxEvent = await mkEncryptedMatrixEvent({
|
||||
plainContent: { msgtype: "m.text", body: "msg1" },
|
||||
@@ -449,9 +452,9 @@ describe("EventTile", () => {
|
||||
shieldReason: null,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const roomContext = getRoomContext(room, {});
|
||||
const roomContext = getRoomContext(room, { isRoomEncrypted: true });
|
||||
const { container, rerender } = render(<WrappedEventTile roomContext={roomContext} />);
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
expect(eventTiles).toHaveLength(1);
|
||||
@@ -578,4 +581,28 @@ describe("EventTile", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should display the not encrypted status for an unencrypted event when the room becomes encrypted", async () => {
|
||||
jest.spyOn(client.getCrypto()!, "getEncryptionInfoForEvent").mockResolvedValue({
|
||||
shieldColour: EventShieldColour.NONE,
|
||||
shieldReason: null,
|
||||
});
|
||||
|
||||
const { rerender } = getComponent();
|
||||
await flushPromises();
|
||||
// The room and the event are unencrypted, the tile should not show the not encrypted status
|
||||
expect(screen.queryByText("Not encrypted")).toBeNull();
|
||||
|
||||
// The room is now encrypted
|
||||
rerender(
|
||||
<WrappedEventTile
|
||||
roomContext={getRoomContext(room, {
|
||||
isRoomEncrypted: true,
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
|
||||
// The event tile should now show the not encrypted status
|
||||
await waitFor(() => expect(screen.getByText("Not encrypted")).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,17 @@ exports[`EventTileThreadToolbar renders 1`] = `
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div />
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
|
||||
@@ -8,7 +8,16 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, fireEvent, render, RenderResult, screen, waitFor, waitForElementToBeRemoved } from "jest-matrix-react";
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
RenderResult,
|
||||
screen,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
cleanup,
|
||||
} from "jest-matrix-react";
|
||||
import { Room, MatrixClient, RoomState, RoomMember, User, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { mocked, MockedObject } from "jest-mock";
|
||||
@@ -361,6 +370,7 @@ describe("MemberList", () => {
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
const renderComponent = () => {
|
||||
@@ -397,21 +407,22 @@ describe("MemberList", () => {
|
||||
jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
|
||||
jest.spyOn(room, "canInvite").mockReturnValue(false);
|
||||
|
||||
renderComponent();
|
||||
await flushPromises();
|
||||
const { findByLabelText } = renderComponent();
|
||||
|
||||
// button rendered but disabled
|
||||
expect(screen.getByText("Invite to this room")).toHaveAttribute("aria-disabled", "true");
|
||||
await expect(findByLabelText("You do not have permission to invite users")).resolves.toHaveAttribute(
|
||||
"aria-disabled",
|
||||
"true",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders enabled invite button when current user is a member and has rights to invite", async () => {
|
||||
jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
|
||||
jest.spyOn(room, "canInvite").mockReturnValue(true);
|
||||
|
||||
renderComponent();
|
||||
await flushPromises();
|
||||
const { findByText } = renderComponent();
|
||||
|
||||
expect(screen.getByText("Invite to this room")).not.toBeDisabled();
|
||||
await expect(findByText("Invite to this room")).resolves.not.toBeDisabled();
|
||||
});
|
||||
|
||||
it("opens room inviter on button click", async () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { EventType, MatrixEvent, Room, RoomMember, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix";
|
||||
import { EventType, MatrixEvent, RoomMember, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix";
|
||||
import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
@@ -19,17 +19,14 @@ import {
|
||||
mkStubRoom,
|
||||
mockPlatformPeg,
|
||||
stubClient,
|
||||
waitEnoughCyclesForModal,
|
||||
} from "../../../../test-utils";
|
||||
import MessageComposer from "../../../../../src/components/views/rooms/MessageComposer";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import ResizeNotifier from "../../../../../src/utils/ResizeNotifier";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import { LocalRoom } from "../../../../../src/models/LocalRoom";
|
||||
import { Features } from "../../../../../src/settings/Settings";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
|
||||
import dis from "../../../../../src/dispatcher/dispatcher";
|
||||
@@ -37,31 +34,16 @@ import { E2EStatus } from "../../../../../src/utils/ShieldUtils";
|
||||
import { addTextToComposerRTL } from "../../../../test-utils/composer";
|
||||
import UIStore, { UI_EVENTS } from "../../../../../src/stores/UIStore";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { VoiceBroadcastInfoState, VoiceBroadcastRecording } from "../../../../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
const openStickerPicker = async (): Promise<void> => {
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
await userEvent.click(screen.getByLabelText("Sticker"));
|
||||
});
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
await userEvent.click(screen.getByLabelText("Sticker"));
|
||||
};
|
||||
|
||||
const startVoiceMessage = async (): Promise<void> => {
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
await userEvent.click(screen.getByLabelText("Voice Message"));
|
||||
});
|
||||
};
|
||||
|
||||
const setCurrentBroadcastRecording = (room: Room, state: VoiceBroadcastInfoState): void => {
|
||||
const recording = new VoiceBroadcastRecording(
|
||||
mkVoiceBroadcastInfoStateEvent(room.roomId, state, "@user:example.com", "ABC123"),
|
||||
MatrixClientPeg.safeGet(),
|
||||
state,
|
||||
);
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore.setCurrent(recording);
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
await userEvent.click(screen.getByLabelText("Voice Message"));
|
||||
};
|
||||
|
||||
const expectVoiceMessageRecordingTriggered = (): void => {
|
||||
@@ -82,14 +64,11 @@ describe("MessageComposer", () => {
|
||||
await clearAllModals();
|
||||
jest.useRealTimers();
|
||||
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore.clearCurrent();
|
||||
|
||||
// restore settings
|
||||
act(() => {
|
||||
[
|
||||
"MessageComposerInput.showStickersButton",
|
||||
"MessageComposerInput.showPollsButton",
|
||||
Features.VoiceBroadcast,
|
||||
"feature_wysiwyg_composer",
|
||||
].forEach((setting: string): void => {
|
||||
SettingsStore.setValue(setting, null, SettingLevel.DEVICE, SettingsStore.getDefaultValue(setting));
|
||||
@@ -97,6 +76,45 @@ describe("MessageComposer", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("wysiwyg correctly persists state to and from localStorage", async () => {
|
||||
const room = mkStubRoom("!roomId:server", "Room 1", cli);
|
||||
const messageText = "Test Text";
|
||||
await SettingsStore.setValue("feature_wysiwyg_composer", null, SettingLevel.DEVICE, true);
|
||||
const { renderResult, rawComponent } = wrapAndRender({ room });
|
||||
const { unmount } = renderResult;
|
||||
|
||||
await flushPromises();
|
||||
|
||||
const key = `mx_wysiwyg_state_${room.roomId}`;
|
||||
|
||||
await userEvent.click(screen.getByRole("textbox"));
|
||||
fireEvent.input(screen.getByRole("textbox"), {
|
||||
data: messageText,
|
||||
inputType: "insertText",
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText));
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await flushPromises();
|
||||
|
||||
// assert there is state persisted
|
||||
expect(localStorage.getItem(key)).toBeNull();
|
||||
|
||||
// ensure the right state was persisted to localStorage
|
||||
unmount();
|
||||
|
||||
// assert the persisted state
|
||||
expect(JSON.parse(localStorage.getItem(key)!)).toStrictEqual({
|
||||
content: messageText,
|
||||
isRichText: true,
|
||||
});
|
||||
|
||||
// ensure the correct state is re-loaded
|
||||
render(rawComponent);
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText));
|
||||
}, 10000);
|
||||
|
||||
describe("for a Room", () => {
|
||||
const room = mkStubRoom("!roomId:server", "Room 1", cli);
|
||||
|
||||
@@ -177,22 +195,16 @@ describe("MessageComposer", () => {
|
||||
setting: "MessageComposerInput.showPollsButton",
|
||||
buttonLabel: "Poll",
|
||||
},
|
||||
{
|
||||
setting: Features.VoiceBroadcast,
|
||||
buttonLabel: "Voice broadcast",
|
||||
},
|
||||
].forEach(({ setting, buttonLabel }) => {
|
||||
[true, false].forEach((value: boolean) => {
|
||||
describe(`when ${setting} = ${value}`, () => {
|
||||
beforeEach(async () => {
|
||||
SettingsStore.setValue(setting, null, SettingLevel.DEVICE, value);
|
||||
await act(() => SettingsStore.setValue(setting, null, SettingLevel.DEVICE, value));
|
||||
wrapAndRender({ room });
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
});
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
});
|
||||
|
||||
it(`should${value || "not"} display the button`, () => {
|
||||
it(`should${value ? "" : " not"} display the button`, () => {
|
||||
if (value) {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect(screen.getByLabelText(buttonLabel)).toBeInTheDocument();
|
||||
@@ -205,15 +217,17 @@ describe("MessageComposer", () => {
|
||||
describe(`and setting ${setting} to ${!value}`, () => {
|
||||
beforeEach(async () => {
|
||||
// simulate settings update
|
||||
await SettingsStore.setValue(setting, null, SettingLevel.DEVICE, !value);
|
||||
dis.dispatch(
|
||||
{
|
||||
action: Action.SettingUpdated,
|
||||
settingName: setting,
|
||||
newValue: !value,
|
||||
},
|
||||
true,
|
||||
);
|
||||
await act(async () => {
|
||||
await SettingsStore.setValue(setting, null, SettingLevel.DEVICE, !value);
|
||||
dis.dispatch(
|
||||
{
|
||||
action: Action.SettingUpdated,
|
||||
settingName: setting,
|
||||
newValue: !value,
|
||||
},
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it(`should${!value || "not"} display the button`, () => {
|
||||
@@ -273,7 +287,7 @@ describe("MessageComposer", () => {
|
||||
beforeEach(async () => {
|
||||
wrapAndRender({ room }, true, true);
|
||||
await openStickerPicker();
|
||||
resizeCallback(UI_EVENTS.Resize, {});
|
||||
act(() => resizeCallback(UI_EVENTS.Resize, {}));
|
||||
});
|
||||
|
||||
it("should close the menu", () => {
|
||||
@@ -295,7 +309,7 @@ describe("MessageComposer", () => {
|
||||
beforeEach(async () => {
|
||||
wrapAndRender({ room }, true, false);
|
||||
await openStickerPicker();
|
||||
resizeCallback(UI_EVENTS.Resize, {});
|
||||
act(() => resizeCallback(UI_EVENTS.Resize, {}));
|
||||
});
|
||||
|
||||
it("should close the menu", () => {
|
||||
@@ -402,34 +416,6 @@ describe("MessageComposer", () => {
|
||||
expectVoiceMessageRecordingTriggered();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when recording a voice broadcast and trying to start a voice message", () => {
|
||||
beforeEach(async () => {
|
||||
setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Started);
|
||||
wrapAndRender({ room });
|
||||
await startVoiceMessage();
|
||||
await waitEnoughCyclesForModal();
|
||||
});
|
||||
|
||||
it("should not start a voice message and display the info dialog", async () => {
|
||||
expect(screen.queryByLabelText("Stop recording")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Can't start voice message")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is a stopped voice broadcast recording and trying to start a voice message", () => {
|
||||
beforeEach(async () => {
|
||||
setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Stopped);
|
||||
wrapAndRender({ room });
|
||||
await startVoiceMessage();
|
||||
await waitEnoughCyclesForModal();
|
||||
});
|
||||
|
||||
it("should try to start a voice message and should not display the info dialog", async () => {
|
||||
expect(screen.queryByText("Can't start voice message")).not.toBeInTheDocument();
|
||||
expectVoiceMessageRecordingTriggered();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("for a LocalRoom", () => {
|
||||
@@ -443,51 +429,6 @@ describe("MessageComposer", () => {
|
||||
expect(screen.queryByLabelText("Sticker")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("wysiwyg correctly persists state to and from localStorage", async () => {
|
||||
const room = mkStubRoom("!roomId:server", "Room 1", cli);
|
||||
const messageText = "Test Text";
|
||||
await SettingsStore.setValue("feature_wysiwyg_composer", null, SettingLevel.DEVICE, true);
|
||||
const { renderResult, rawComponent } = wrapAndRender({ room });
|
||||
const { unmount, rerender } = renderResult;
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
const key = `mx_wysiwyg_state_${room.roomId}`;
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole("textbox"));
|
||||
});
|
||||
fireEvent.input(screen.getByRole("textbox"), {
|
||||
data: messageText,
|
||||
inputType: "insertText",
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText));
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
// assert there is state persisted
|
||||
expect(localStorage.getItem(key)).toBeNull();
|
||||
|
||||
// ensure the right state was persisted to localStorage
|
||||
unmount();
|
||||
|
||||
// assert the persisted state
|
||||
expect(JSON.parse(localStorage.getItem(key)!)).toStrictEqual({
|
||||
content: messageText,
|
||||
isRichText: true,
|
||||
});
|
||||
|
||||
// ensure the correct state is re-loaded
|
||||
rerender(rawComponent);
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText));
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
function wrapAndRender(
|
||||
@@ -522,9 +463,9 @@ function wrapAndRender(
|
||||
|
||||
const getRawComponent = (props = {}, context = roomContext, client = mockClient) => (
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={context}>
|
||||
<ScopedRoomContextProvider {...context}>
|
||||
<MessageComposer {...defaultProps} {...props} />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
return {
|
||||
|
||||
@@ -10,11 +10,11 @@ import React from "react";
|
||||
import { render, screen, waitFor } from "jest-matrix-react";
|
||||
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import { createTestClient, getRoomContext, mkStubRoom } from "../../../../test-utils";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import MessageComposerButtons from "../../../../../src/components/views/rooms/MessageComposerButtons";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
describe("MessageComposerButtons", () => {
|
||||
// @ts-ignore - we're deliberately not implementing the whole interface here, but
|
||||
@@ -54,7 +54,7 @@ describe("MessageComposerButtons", () => {
|
||||
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={defaultRoomContext}>{component}</RoomContext.Provider>
|
||||
<ScopedRoomContextProvider {...defaultRoomContext}>{component}</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
}
|
||||
@@ -168,27 +168,4 @@ describe("MessageComposerButtons", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with showVoiceBroadcastButton = true", () => {
|
||||
it("should render the »Voice broadcast« button", () => {
|
||||
wrapAndRender(
|
||||
<MessageComposerButtons
|
||||
{...mockProps}
|
||||
isMenuOpen={true}
|
||||
showLocationButton={true}
|
||||
showPollsButton={true}
|
||||
showStickersButton={true}
|
||||
showVoiceBroadcastButton={true}
|
||||
/>,
|
||||
false,
|
||||
);
|
||||
|
||||
expect(getButtonLabels()).toEqual([
|
||||
"Emoji",
|
||||
"Attachment",
|
||||
"More options",
|
||||
["Sticker", "Voice Message", "Voice broadcast", "Poll", "Location"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,19 +13,19 @@ import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { LocalRoom } from "../../../../../src/models/LocalRoom";
|
||||
import { filterConsole, mkRoomMemberJoinEvent, mkThirdPartyInviteEvent, stubClient } from "../../../../test-utils";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import NewRoomIntro from "../../../../../src/components/views/rooms/NewRoomIntro";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { DirectoryMember } from "../../../../../src/utils/direct-messages";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
const renderNewRoomIntro = (client: MatrixClient, room: Room | LocalRoom) => {
|
||||
render(
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={{ room, roomId: room.roomId } as unknown as IRoomState}>
|
||||
<ScopedRoomContextProvider {...({ room, roomId: room.roomId } as unknown as IRoomState)}>
|
||||
<NewRoomIntro />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -165,7 +165,7 @@ describe("UnreadNotificationBadge", () => {
|
||||
},
|
||||
ts: 5,
|
||||
});
|
||||
room.addLiveEvents([event]);
|
||||
room.addLiveEvents([event], { addToState: true });
|
||||
|
||||
const { container } = render(getComponent(THREAD_ID));
|
||||
expect(container.querySelector(".mx_NotificationBadge_dot")).toBeTruthy();
|
||||
|
||||
@@ -20,6 +20,7 @@ import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelSto
|
||||
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
|
||||
import { UPDATE_EVENT } from "../../../../../src/stores/AsyncStore";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import ResizeNotifier from "../../../../../src/utils/ResizeNotifier.ts";
|
||||
|
||||
describe("<PinnedMessageBanner />", () => {
|
||||
const userId = "@alice:server.org";
|
||||
@@ -28,10 +29,12 @@ describe("<PinnedMessageBanner />", () => {
|
||||
let mockClient: MatrixClient;
|
||||
let room: Room;
|
||||
let permalinkCreator: RoomPermalinkCreator;
|
||||
let resizeNotifier: ResizeNotifier;
|
||||
beforeEach(() => {
|
||||
mockClient = stubClient();
|
||||
room = new Room(roomId, mockClient, userId);
|
||||
permalinkCreator = new RoomPermalinkCreator(room);
|
||||
resizeNotifier = new ResizeNotifier();
|
||||
jest.spyOn(dis, "dispatch").mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
@@ -77,7 +80,7 @@ describe("<PinnedMessageBanner />", () => {
|
||||
*/
|
||||
function renderBanner() {
|
||||
return render(
|
||||
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} />,
|
||||
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} resizeNotifier={resizeNotifier} />,
|
||||
withClientContextRenderOptions(mockClient),
|
||||
);
|
||||
}
|
||||
@@ -145,7 +148,9 @@ describe("<PinnedMessageBanner />", () => {
|
||||
event3.getId()!,
|
||||
]);
|
||||
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2, event3]);
|
||||
rerender(<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} />);
|
||||
rerender(
|
||||
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} resizeNotifier={resizeNotifier} />,
|
||||
);
|
||||
await expect(screen.findByText("Third pinned message")).resolves.toBeVisible();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
@@ -206,6 +211,42 @@ describe("<PinnedMessageBanner />", () => {
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("Notify the timeline to resize", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(resizeNotifier, "notifyTimelineHeightChanged");
|
||||
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]);
|
||||
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]);
|
||||
});
|
||||
|
||||
it("should notify the timeline to resize when we display the banner", async () => {
|
||||
renderBanner();
|
||||
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||
// The banner is displayed, so we need to resize the timeline
|
||||
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(1);
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "View the pinned message in the timeline." }));
|
||||
await expect(screen.findByText("First pinned message")).resolves.toBeVisible();
|
||||
// The banner is already displayed, so we don't need to resize the timeline
|
||||
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should notify the timeline to resize when we hide the banner", async () => {
|
||||
const { rerender } = renderBanner();
|
||||
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||
// The banner is displayed, so we need to resize the timeline
|
||||
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(1);
|
||||
|
||||
// The banner has no event to display and is hidden
|
||||
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([]);
|
||||
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([]);
|
||||
rerender(
|
||||
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} resizeNotifier={resizeNotifier} />,
|
||||
);
|
||||
// The timeline should be resized
|
||||
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Right button", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]);
|
||||
@@ -217,6 +258,8 @@ describe("<PinnedMessageBanner />", () => {
|
||||
jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false);
|
||||
|
||||
renderBanner();
|
||||
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||
|
||||
expect(screen.getByRole("button", { name: "View all" })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -224,10 +267,12 @@ describe("<PinnedMessageBanner />", () => {
|
||||
// The Right panel is opened on another card
|
||||
jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(true);
|
||||
jest.spyOn(RightPanelStore.instance, "currentCard", "get").mockReturnValue({
|
||||
phase: RightPanelPhases.RoomMemberList,
|
||||
phase: RightPanelPhases.MemberList,
|
||||
});
|
||||
|
||||
renderBanner();
|
||||
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||
|
||||
expect(screen.getByRole("button", { name: "View all" })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -239,6 +284,8 @@ describe("<PinnedMessageBanner />", () => {
|
||||
});
|
||||
|
||||
renderBanner();
|
||||
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||
|
||||
expect(screen.getByRole("button", { name: "Close list" })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -263,6 +310,7 @@ describe("<PinnedMessageBanner />", () => {
|
||||
});
|
||||
|
||||
renderBanner();
|
||||
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||
expect(screen.getByRole("button", { name: "Close list" })).toBeVisible();
|
||||
|
||||
jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false);
|
||||
|
||||
@@ -10,6 +10,7 @@ import React, { ComponentProps } from "react";
|
||||
import { render, screen, waitFor } from "jest-matrix-react";
|
||||
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import {
|
||||
determineAvatarPosition,
|
||||
@@ -20,6 +21,9 @@ import * as languageHandler from "../../../../../src/languageHandler";
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import dispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { formatDate } from "../../../../../src/DateUtils";
|
||||
|
||||
jest.mock("../../../../../src/DateUtils");
|
||||
|
||||
describe("ReadReceiptGroup", () => {
|
||||
describe("TooltipText", () => {
|
||||
@@ -87,6 +91,10 @@ describe("ReadReceiptGroup", () => {
|
||||
describe("<ReadReceiptPerson />", () => {
|
||||
stubClient();
|
||||
|
||||
// We pick a fixed time but this can still vary depending on the locale
|
||||
// the tests are run in. We are not testing date formatting here, so stub it out.
|
||||
mocked(formatDate).mockReturnValue("==MOCK FORMATTED DATE==");
|
||||
|
||||
const ROOM_ID = "roomId";
|
||||
const USER_ID = "@alice:example.org";
|
||||
|
||||
|
||||
@@ -8,9 +8,19 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React from "react";
|
||||
import { CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { EventType, JoinRule, MatrixEvent, PendingEventOrdering, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import {
|
||||
EventType,
|
||||
JoinRule,
|
||||
MatrixEvent,
|
||||
PendingEventOrdering,
|
||||
Room,
|
||||
RoomStateEvent,
|
||||
RoomMember,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
import {
|
||||
act,
|
||||
createEvent,
|
||||
fireEvent,
|
||||
getAllByLabelText,
|
||||
@@ -148,7 +158,7 @@ describe("RoomHeader", () => {
|
||||
|
||||
fireEvent.click(facePile);
|
||||
|
||||
expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.RoomMemberList });
|
||||
expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.MemberList });
|
||||
});
|
||||
|
||||
it("has room info icon that opens the room info panel", async () => {
|
||||
@@ -579,7 +589,7 @@ describe("RoomHeader", () => {
|
||||
state_key: "",
|
||||
room_id: room.roomId,
|
||||
});
|
||||
room.addLiveEvents([joinRuleEvent]);
|
||||
room.addLiveEvents([joinRuleEvent], { addToState: true });
|
||||
|
||||
render(<RoomHeader room={room} />, getWrapper());
|
||||
|
||||
@@ -632,6 +642,52 @@ describe("RoomHeader", () => {
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("updates the icon when the encryption status changes", async () => {
|
||||
// The room starts verified
|
||||
jest.spyOn(ShieldUtils, "shieldStatusForRoom").mockResolvedValue(ShieldUtils.E2EStatus.Verified);
|
||||
render(<RoomHeader room={room} />, getWrapper());
|
||||
await waitFor(() => expect(getByLabelText(document.body, "Verified")).toBeInTheDocument());
|
||||
|
||||
// A new member joins, and the room becomes unverified
|
||||
jest.spyOn(ShieldUtils, "shieldStatusForRoom").mockResolvedValue(ShieldUtils.E2EStatus.Warning);
|
||||
act(() => {
|
||||
room.emit(
|
||||
RoomStateEvent.Members,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "join",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
room.currentState,
|
||||
new RoomMember(room.roomId, "@alice:example.org"),
|
||||
);
|
||||
});
|
||||
await waitFor(() => expect(getByLabelText(document.body, "Untrusted")).toBeInTheDocument());
|
||||
|
||||
// The user becomes verified
|
||||
jest.spyOn(ShieldUtils, "shieldStatusForRoom").mockResolvedValue(ShieldUtils.E2EStatus.Verified);
|
||||
act(() => {
|
||||
MatrixClientPeg.get()!.emit(
|
||||
CryptoEvent.UserTrustStatusChanged,
|
||||
"@alice:example.org",
|
||||
new UserVerificationStatus(true, true, true, false),
|
||||
);
|
||||
});
|
||||
await waitFor(() => expect(getByLabelText(document.body, "Verified")).toBeInTheDocument());
|
||||
|
||||
// An unverified device is added
|
||||
jest.spyOn(ShieldUtils, "shieldStatusForRoom").mockResolvedValue(ShieldUtils.E2EStatus.Warning);
|
||||
act(() => {
|
||||
MatrixClientPeg.get()!.emit(CryptoEvent.DevicesUpdated, ["@alice:example.org"], false);
|
||||
});
|
||||
await waitFor(() => expect(getByLabelText(document.body, "Untrusted")).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
|
||||
it("renders additionalButtons", async () => {
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} 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 { ShareDialog } from "../../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { _t } from "../../../../../../src/languageHandler";
|
||||
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||
|
||||
|
||||
@@ -9,14 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React from "react";
|
||||
import { render, screen, act, RenderResult } from "jest-matrix-react";
|
||||
import { mocked, Mocked } from "jest-mock";
|
||||
import {
|
||||
MatrixClient,
|
||||
PendingEventOrdering,
|
||||
Room,
|
||||
MatrixEvent,
|
||||
RoomStateEvent,
|
||||
Thread,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, PendingEventOrdering, Room, RoomStateEvent, Thread } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { Widget } from "matrix-widget-api";
|
||||
|
||||
@@ -40,8 +33,6 @@ import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import PlatformPeg from "../../../../../src/PlatformPeg";
|
||||
import BasePlatform from "../../../../../src/BasePlatform";
|
||||
import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMessagingStore";
|
||||
import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
import { TestSdkContext } from "../../../TestSdkContext";
|
||||
import { SDKContext } from "../../../../../src/contexts/SDKContext";
|
||||
import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
|
||||
@@ -61,20 +52,6 @@ describe("RoomTile", () => {
|
||||
} as unknown as BasePlatform);
|
||||
useMockedCalls();
|
||||
|
||||
const setUpVoiceBroadcast = async (state: VoiceBroadcastInfoState): Promise<void> => {
|
||||
voiceBroadcastInfoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
state,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
room.currentState.setStateEvents([voiceBroadcastInfoEvent]);
|
||||
await flushPromises();
|
||||
});
|
||||
};
|
||||
|
||||
const renderRoomTile = (): RenderResult => {
|
||||
return render(
|
||||
<SDKContext.Provider value={sdkContext}>
|
||||
@@ -89,7 +66,6 @@ describe("RoomTile", () => {
|
||||
};
|
||||
|
||||
let client: Mocked<MatrixClient>;
|
||||
let voiceBroadcastInfoEvent: MatrixEvent;
|
||||
let room: Room;
|
||||
let sdkContext: TestSdkContext;
|
||||
let showMessagePreview = false;
|
||||
@@ -303,49 +279,6 @@ describe("RoomTile", () => {
|
||||
});
|
||||
expect(screen.queryByLabelText(/participant/)).toBe(null);
|
||||
});
|
||||
|
||||
describe("and a live broadcast starts", () => {
|
||||
beforeEach(async () => {
|
||||
renderRoomTile();
|
||||
await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
||||
});
|
||||
|
||||
it("should still render the call subtitle", () => {
|
||||
expect(screen.queryByText("Video")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Live")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when a live voice broadcast starts", () => {
|
||||
beforeEach(async () => {
|
||||
renderRoomTile();
|
||||
await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
||||
});
|
||||
|
||||
it("should render the »Live« subtitle", () => {
|
||||
expect(screen.queryByText("Live")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("and the broadcast stops", () => {
|
||||
beforeEach(async () => {
|
||||
const stopEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
voiceBroadcastInfoEvent,
|
||||
);
|
||||
await act(async () => {
|
||||
room.currentState.setStateEvents([stopEvent]);
|
||||
await flushPromises();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not render the »Live« subtitle", () => {
|
||||
expect(screen.queryByText("Live")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import SendMessageComposer, {
|
||||
isQuickReaction,
|
||||
} from "../../../../../src/components/views/rooms/SendMessageComposer";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import EditorModel from "../../../../../src/editor/model";
|
||||
import { createPartCreator } from "../../../editor/mock";
|
||||
import { createTestClient, mkEvent, mkStubRoom, stubClient } from "../../../../test-utils";
|
||||
@@ -30,6 +30,7 @@ import { IRoomState, MainSplitContentType } from "../../../../../src/components/
|
||||
import { mockPlatformPeg } from "../../../../test-utils/platform";
|
||||
import { doMaybeLocalRoomAction } from "../../../../../src/utils/local-room";
|
||||
import { addTextToComposer } from "../../../../test-utils/composer";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../src/utils/local-room", () => ({
|
||||
doMaybeLocalRoomAction: jest.fn(),
|
||||
@@ -77,6 +78,7 @@ describe("<SendMessageComposer/>", () => {
|
||||
canAskToJoin: false,
|
||||
promptAskToJoin: false,
|
||||
viewRoomOpts: { buttons: [] },
|
||||
isRoomEncrypted: false,
|
||||
};
|
||||
describe("createMessageContent", () => {
|
||||
it("sends plaintext messages correctly", () => {
|
||||
@@ -364,9 +366,9 @@ describe("<SendMessageComposer/>", () => {
|
||||
};
|
||||
const getRawComponent = (props = {}, roomContext = defaultRoomContext, client = mockClient) => (
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={roomContext}>
|
||||
<ScopedRoomContextProvider {...roomContext}>
|
||||
<SendMessageComposer {...defaultProps} {...props} />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
const getComponent = (props = {}, roomContext = defaultRoomContext, client = mockClient) => {
|
||||
@@ -385,7 +387,7 @@ describe("<SendMessageComposer/>", () => {
|
||||
|
||||
it("correctly persists state to and from localStorage", () => {
|
||||
const props = { replyToEvent: mockEvent };
|
||||
const { container, unmount, rerender } = getComponent(props);
|
||||
let { container, unmount } = getComponent(props);
|
||||
|
||||
addTextToComposer(container, "Test Text");
|
||||
|
||||
@@ -402,7 +404,7 @@ describe("<SendMessageComposer/>", () => {
|
||||
});
|
||||
|
||||
// ensure the correct model is re-loaded
|
||||
rerender(getRawComponent(props));
|
||||
({ container, unmount } = getComponent(props));
|
||||
expect(container.textContent).toBe("Test Text");
|
||||
expect(spyDispatcher).toHaveBeenCalledWith({
|
||||
action: "reply_to_event",
|
||||
@@ -413,7 +415,7 @@ describe("<SendMessageComposer/>", () => {
|
||||
// now try with localStorage wiped out
|
||||
unmount();
|
||||
localStorage.removeItem(key);
|
||||
rerender(getRawComponent(props));
|
||||
({ container } = getComponent(props));
|
||||
expect(container.textContent).toBe("");
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,534 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import {
|
||||
EventType,
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
Room,
|
||||
RoomState,
|
||||
RoomStateEvent,
|
||||
RoomMember,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
import { act, render, screen, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import { UserIdentityWarning } from "../../../../../src/components/views/rooms/UserIdentityWarning";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
|
||||
const ROOM_ID = "!room:id";
|
||||
|
||||
function mockRoom(): Room {
|
||||
const room = {
|
||||
getEncryptionTargetMembers: jest.fn(async () => []),
|
||||
getMember: jest.fn((userId) => {}),
|
||||
roomId: ROOM_ID,
|
||||
shouldEncryptForInvitedMembers: jest.fn(() => true),
|
||||
} as unknown as Room;
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
function mockRoomMember(userId: string, name?: string): RoomMember {
|
||||
return {
|
||||
userId,
|
||||
name: name ?? userId,
|
||||
rawDisplayName: name ?? userId,
|
||||
roomId: ROOM_ID,
|
||||
getMxcAvatarUrl: jest.fn(),
|
||||
} as unknown as RoomMember;
|
||||
}
|
||||
|
||||
function dummyRoomState(): RoomState {
|
||||
return new RoomState(ROOM_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the warning element, given the warning text (excluding the "Learn more"
|
||||
* link). This is needed because the warning text contains a `<b>` tag, so the
|
||||
* normal `getByText` doesn't work.
|
||||
*/
|
||||
function getWarningByText(text: string): Element {
|
||||
return screen.getByText((content?: string, element?: Element | null): boolean => {
|
||||
return (
|
||||
!!element &&
|
||||
element.classList.contains("mx_UserIdentityWarning_main") &&
|
||||
element.textContent === text + " Learn more"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function renderComponent(client: MatrixClient, room: Room) {
|
||||
return render(<UserIdentityWarning room={room} key={ROOM_ID} />, {
|
||||
wrapper: ({ ...rest }) => <MatrixClientContext.Provider value={client} {...rest} />,
|
||||
});
|
||||
}
|
||||
|
||||
describe("UserIdentityWarning", () => {
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
beforeEach(async () => {
|
||||
client = stubClient();
|
||||
room = mockRoom();
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
// This tests the basic functionality of the component. If we have a room
|
||||
// member whose identity needs accepting, we should display a warning. When
|
||||
// the "OK" button gets pressed, it should call `pinCurrentUserIdentity`.
|
||||
it("displays a warning when a user's identity needs approval", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
|
||||
mockRoomMember("@alice:example.org", "Alice"),
|
||||
]);
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
crypto.pinCurrentUserIdentity = jest.fn();
|
||||
renderComponent(client, room);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
await userEvent.click(screen.getByRole("button")!);
|
||||
await waitFor(() => expect(crypto.pinCurrentUserIdentity).toHaveBeenCalledWith("@alice:example.org"));
|
||||
});
|
||||
|
||||
// We don't display warnings in non-encrypted rooms, but if encryption is
|
||||
// enabled, then we should display a warning if there are any users whose
|
||||
// identity need accepting.
|
||||
it("displays pending warnings when encryption is enabled", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
|
||||
mockRoomMember("@alice:example.org", "Alice"),
|
||||
]);
|
||||
// Start the room off unencrypted. We shouldn't display anything.
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "isEncryptionEnabledInRoom").mockResolvedValue(false);
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
expect(() => getWarningByText("Alice's (@alice:example.org) identity appears to have changed.")).toThrow();
|
||||
|
||||
// Encryption gets enabled in the room. We should now warn that Alice's
|
||||
// identity changed.
|
||||
jest.spyOn(crypto, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomEncryption,
|
||||
state_key: "",
|
||||
content: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
// When a user's identity needs approval, or has been approved, the display
|
||||
// should update appropriately.
|
||||
it("updates the display when identity changes", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
|
||||
mockRoomMember("@alice:example.org", "Alice"),
|
||||
]);
|
||||
jest.spyOn(room, "getMember").mockReturnValue(mockRoomMember("@alice:example.org", "Alice"));
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, false),
|
||||
);
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
expect(() => getWarningByText("Alice's (@alice:example.org) identity appears to have changed.")).toThrow();
|
||||
|
||||
// The user changes their identity, so we should show the warning.
|
||||
act(() => {
|
||||
client.emit(
|
||||
CryptoEvent.UserTrustStatusChanged,
|
||||
"@alice:example.org",
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
// Simulate the user's new identity having been approved, so we no
|
||||
// longer show the warning.
|
||||
act(() => {
|
||||
client.emit(
|
||||
CryptoEvent.UserTrustStatusChanged,
|
||||
"@alice:example.org",
|
||||
new UserVerificationStatus(false, false, false, false),
|
||||
);
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(() => getWarningByText("Alice's (@alice:example.org) identity appears to have changed.")).toThrow(),
|
||||
);
|
||||
});
|
||||
|
||||
// We only display warnings about users in the room. When someone
|
||||
// joins/leaves, we should update the warning appropriately.
|
||||
describe("updates the display when a member joins/leaves", () => {
|
||||
it("when invited users can see encrypted messages", async () => {
|
||||
// Nobody in the room yet
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([]);
|
||||
jest.spyOn(room, "getMember").mockImplementation((userId) => mockRoomMember(userId));
|
||||
jest.spyOn(room, "shouldEncryptForInvitedMembers").mockReturnValue(true);
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
|
||||
// Alice joins. Her identity needs approval, so we should show a warning.
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "join",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(getWarningByText("@alice:example.org's identity appears to have changed.")).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
// Bob is invited. His identity needs approval, so we should show a
|
||||
// warning for him after Alice's warning is resolved by her leaving.
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@bob:example.org",
|
||||
content: {
|
||||
membership: "invite",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@carol:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
|
||||
// Alice leaves, so we no longer show her warning, but we will show
|
||||
// a warning for Bob.
|
||||
act(() => {
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "leave",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(() => getWarningByText("@alice:example.org's identity appears to have changed.")).toThrow(),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(getWarningByText("@bob:example.org's identity appears to have changed.")).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it("when invited users cannot see encrypted messages", async () => {
|
||||
// Nobody in the room yet
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([]);
|
||||
jest.spyOn(room, "getMember").mockImplementation((userId) => mockRoomMember(userId));
|
||||
jest.spyOn(room, "shouldEncryptForInvitedMembers").mockReturnValue(false);
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
|
||||
// Alice joins. Her identity needs approval, so we should show a warning.
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "join",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(getWarningByText("@alice:example.org's identity appears to have changed.")).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
// Bob is invited. His identity needs approval, but we don't encrypt
|
||||
// to him, so we won't show a warning. (When Alice leaves, the
|
||||
// display won't be updated to show a warningfor Bob.)
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@bob:example.org",
|
||||
content: {
|
||||
membership: "invite",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@carol:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
|
||||
// Alice leaves, so we no longer show her warning, and we don't show
|
||||
// a warning for Bob.
|
||||
act(() => {
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "leave",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(() => getWarningByText("@alice:example.org's identity appears to have changed.")).toThrow(),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(() => getWarningByText("@bob:example.org's identity appears to have changed.")).toThrow(),
|
||||
);
|
||||
});
|
||||
|
||||
it("when member leaves immediately after component is loaded", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockImplementation(async () => {
|
||||
setTimeout(() => {
|
||||
// Alice immediately leaves after we get the room
|
||||
// membership, so we shouldn't show the warning any more
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "leave",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
});
|
||||
return [mockRoomMember("@alice:example.org")];
|
||||
});
|
||||
jest.spyOn(room, "getMember").mockImplementation((userId) => mockRoomMember(userId));
|
||||
jest.spyOn(room, "shouldEncryptForInvitedMembers").mockReturnValue(false);
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
renderComponent(client, room);
|
||||
|
||||
await sleep(10);
|
||||
expect(() => getWarningByText("@alice:example.org's identity appears to have changed.")).toThrow();
|
||||
});
|
||||
|
||||
it("when member leaves immediately after joining", async () => {
|
||||
// Nobody in the room yet
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([]);
|
||||
jest.spyOn(room, "getMember").mockImplementation((userId) => mockRoomMember(userId));
|
||||
jest.spyOn(room, "shouldEncryptForInvitedMembers").mockReturnValue(false);
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
|
||||
// Alice joins. Her identity needs approval, so we should show a warning.
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "join",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
// ... but she immediately leaves, so we shouldn't show the warning any more
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "leave",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
await sleep(10); // give it some time to finish
|
||||
expect(() => getWarningByText("@alice:example.org's identity appears to have changed.")).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
// When we have multiple users whose identity needs approval, one user's
|
||||
// identity no longer needs approval (e.g. their identity was approved),
|
||||
// then we show the next one.
|
||||
it("displays the next user when the current user's identity is approved", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
|
||||
mockRoomMember("@alice:example.org", "Alice"),
|
||||
mockRoomMember("@bob:example.org"),
|
||||
]);
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
|
||||
renderComponent(client, room);
|
||||
// We should warn about Alice's identity first.
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
// Simulate Alice's new identity having been approved, so now we warn
|
||||
// about Bob's identity.
|
||||
act(() => {
|
||||
client.emit(
|
||||
CryptoEvent.UserTrustStatusChanged,
|
||||
"@alice:example.org",
|
||||
new UserVerificationStatus(false, false, false, false),
|
||||
);
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(getWarningByText("@bob:example.org's identity appears to have changed.")).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
// If we get an update for a user's verification status while we're fetching
|
||||
// that user's verification status, we should display based on the updated
|
||||
// value.
|
||||
describe("handles races between fetching verification status and receiving updates", () => {
|
||||
// First case: check that if the update says that the user identity
|
||||
// needs approval, but the fetch says it doesn't, we show the warning.
|
||||
it("update says identity needs approval", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
|
||||
mockRoomMember("@alice:example.org", "Alice"),
|
||||
]);
|
||||
jest.spyOn(room, "getMember").mockReturnValue(mockRoomMember("@alice:example.org", "Alice"));
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockImplementation(async () => {
|
||||
act(() => {
|
||||
client.emit(
|
||||
CryptoEvent.UserTrustStatusChanged,
|
||||
"@alice:example.org",
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
});
|
||||
return Promise.resolve(new UserVerificationStatus(false, false, false, false));
|
||||
});
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
// Second case: check that if the update says that the user identity
|
||||
// doesn't needs approval, but the fetch says it does, we don't show the
|
||||
// warning.
|
||||
it("update says identity doesn't need approval", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
|
||||
mockRoomMember("@alice:example.org", "Alice"),
|
||||
]);
|
||||
jest.spyOn(room, "getMember").mockReturnValue(mockRoomMember("@alice:example.org", "Alice"));
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockImplementation(async () => {
|
||||
act(() => {
|
||||
client.emit(
|
||||
CryptoEvent.UserTrustStatusChanged,
|
||||
"@alice:example.org",
|
||||
new UserVerificationStatus(false, false, false, false),
|
||||
);
|
||||
});
|
||||
return Promise.resolve(new UserVerificationStatus(false, false, false, true));
|
||||
});
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
await waitFor(() =>
|
||||
expect(() =>
|
||||
getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
|
||||
).toThrow(),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -84,7 +84,7 @@ exports[`ReadReceiptGroup <ReadReceiptPerson /> should render 1`] = `
|
||||
<p
|
||||
class="mx_ReadReceiptGroup_secondary"
|
||||
>
|
||||
Wed, 15 May, 0:00
|
||||
==MOCK FORMATTED DATE==
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x);"
|
||||
>
|
||||
<button
|
||||
aria-labelledby=":r154:"
|
||||
aria-labelledby=":r16c:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -73,7 +73,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
<button
|
||||
aria-disabled="true"
|
||||
aria-label="There's no one here to call"
|
||||
aria-labelledby=":r159:"
|
||||
aria-labelledby=":r16h:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -98,7 +98,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
</button>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
aria-labelledby=":r15e:"
|
||||
aria-labelledby=":r16m:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -123,7 +123,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
</button>
|
||||
<button
|
||||
aria-label="Threads"
|
||||
aria-labelledby=":r15j:"
|
||||
aria-labelledby=":r16r:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
||||
@@ -8,10 +8,9 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import "@testing-library/jest-dom";
|
||||
import React from "react";
|
||||
import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||
import { fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||
|
||||
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 { flushPromises, mkEvent } from "../../../../../test-utils";
|
||||
@@ -23,6 +22,7 @@ import { ComposerInsertPayload, ComposerType } from "../../../../../../src/dispa
|
||||
import { ActionPayload } from "../../../../../../src/dispatcher/payloads";
|
||||
import * as EmojiButton from "../../../../../../src/components/views/rooms/EmojiButton";
|
||||
import { createMocks } from "./utils";
|
||||
import { ScopedRoomContextProvider } from "../../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
describe("EditWysiwygComposer", () => {
|
||||
afterEach(() => {
|
||||
@@ -39,9 +39,9 @@ describe("EditWysiwygComposer", () => {
|
||||
) => {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={roomContext}>
|
||||
<ScopedRoomContextProvider {...roomContext}>
|
||||
<EditWysiwygComposer disabled={disabled} editorStateTransfer={_editorStateTransfer} />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
@@ -64,9 +64,9 @@ describe("EditWysiwygComposer", () => {
|
||||
|
||||
rerender(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={{ ...defaultRoomContext, room: undefined }}>
|
||||
<ScopedRoomContextProvider {...defaultRoomContext} room={undefined}>
|
||||
<EditWysiwygComposer disabled={false} editorStateTransfer={editorStateTransfer} />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
@@ -196,9 +196,9 @@ describe("EditWysiwygComposer", () => {
|
||||
// Then
|
||||
screen.getByText("Save").click();
|
||||
const expectedContent = {
|
||||
"body": ` * foo bar`,
|
||||
"body": `* foo bar`,
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": ` * foo bar`,
|
||||
"formatted_body": `* foo bar`,
|
||||
"m.new_content": {
|
||||
body: "foo bar",
|
||||
format: "org.matrix.custom.html",
|
||||
@@ -253,9 +253,7 @@ describe("EditWysiwygComposer", () => {
|
||||
});
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
// Then we don't get it because we are disabled
|
||||
expect(screen.getByRole("textbox")).not.toHaveFocus();
|
||||
@@ -277,10 +275,10 @@ describe("EditWysiwygComposer", () => {
|
||||
);
|
||||
render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={defaultRoomContext}>
|
||||
<ScopedRoomContextProvider {...defaultRoomContext}>
|
||||
<EditWysiwygComposer editorStateTransfer={editorStateTransfer} />
|
||||
<Emoji menuPosition={{ chevronFace: ChevronFace.Top }} />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
// Same behavior as in RoomView.tsx
|
||||
|
||||
@@ -11,7 +11,6 @@ import React from "react";
|
||||
import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||
|
||||
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 { flushPromises } from "../../../../../test-utils";
|
||||
@@ -20,6 +19,7 @@ import { aboveLeftOf } from "../../../../../../src/components/structures/Context
|
||||
import { ComposerInsertPayload, ComposerType } from "../../../../../../src/dispatcher/payloads/ComposerInsertPayload";
|
||||
import { setSelection } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/selection";
|
||||
import { createMocks } from "./utils";
|
||||
import { ScopedRoomContextProvider } from "../../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../../src/components/views/rooms/EmojiButton", () => ({
|
||||
EmojiButton: ({ addEmoji }: { addEmoji: (emoji: string) => void }) => {
|
||||
@@ -66,7 +66,7 @@ describe("SendWysiwygComposer", () => {
|
||||
) => {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={defaultRoomContext}>
|
||||
<ScopedRoomContextProvider {...defaultRoomContext}>
|
||||
<SendWysiwygComposer
|
||||
onChange={onChange}
|
||||
onSend={onSend}
|
||||
@@ -75,7 +75,7 @@ describe("SendWysiwygComposer", () => {
|
||||
menuPosition={aboveLeftOf({ top: 0, bottom: 0, right: 0 })}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ import { PlainTextComposer } from "../../../../../../../src/components/views/roo
|
||||
import * as mockUseSettingsHook from "../../../../../../../src/hooks/useSettings";
|
||||
import * as mockKeyboard from "../../../../../../../src/Keyboard";
|
||||
import { createMocks } from "../utils";
|
||||
import RoomContext from "../../../../../../../src/contexts/RoomContext";
|
||||
import { ScopedRoomContextProvider } from "../../../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
describe("PlainTextComposer", () => {
|
||||
const customRender = (
|
||||
@@ -275,9 +275,9 @@ describe("PlainTextComposer", () => {
|
||||
const { defaultRoomContext } = createMocks();
|
||||
|
||||
render(
|
||||
<RoomContext.Provider value={defaultRoomContext}>
|
||||
<ScopedRoomContextProvider {...defaultRoomContext}>
|
||||
<PlainTextComposer onChange={jest.fn()} onSend={jest.fn()} disabled={false} initialContent="" />
|
||||
</RoomContext.Provider>,
|
||||
</ScopedRoomContextProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("autocomplete-wrapper")).toBeInTheDocument();
|
||||
|
||||
@@ -11,12 +11,12 @@ import React, { createRef } from "react";
|
||||
import { render, screen, waitFor } from "jest-matrix-react";
|
||||
|
||||
import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext from "../../../../../../../src/contexts/RoomContext";
|
||||
import { WysiwygAutocomplete } from "../../../../../../../src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete";
|
||||
import { getRoomContext, mkStubRoom, stubClient } from "../../../../../../test-utils";
|
||||
import Autocomplete from "../../../../../../../src/components/views/rooms/Autocomplete";
|
||||
import Autocompleter, { ICompletion } from "../../../../../../../src/autocomplete/Autocompleter";
|
||||
import AutocompleteProvider from "../../../../../../../src/autocomplete/AutocompleteProvider";
|
||||
import { ScopedRoomContextProvider } from "../../../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
const mockCompletion: ICompletion[] = [
|
||||
{
|
||||
@@ -71,7 +71,7 @@ describe("WysiwygAutocomplete", () => {
|
||||
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={mockRoomContext}>
|
||||
<ScopedRoomContextProvider {...mockRoomContext}>
|
||||
<WysiwygAutocomplete
|
||||
ref={autocompleteRef}
|
||||
suggestion={null}
|
||||
@@ -80,7 +80,7 @@ describe("WysiwygAutocomplete", () => {
|
||||
handleAtRoomMention={mockHandleAtRoomMention}
|
||||
{...props}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,7 +18,6 @@ import defaultDispatcher from "../../../../../../../src/dispatcher/dispatcher";
|
||||
import * as EventUtils from "../../../../../../../src/utils/EventUtils";
|
||||
import { Action } from "../../../../../../../src/dispatcher/actions";
|
||||
import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext from "../../../../../../../src/contexts/RoomContext";
|
||||
import {
|
||||
ComposerContext,
|
||||
getDefaultContextValue,
|
||||
@@ -32,20 +31,21 @@ import Autocompleter, { ICompletion } from "../../../../../../../src/autocomplet
|
||||
import AutocompleteProvider from "../../../../../../../src/autocomplete/AutocompleteProvider";
|
||||
import * as Permalinks from "../../../../../../../src/utils/permalinks/Permalinks";
|
||||
import { PermalinkParts } from "../../../../../../../src/utils/permalinks/PermalinkConstructor";
|
||||
import { ScopedRoomContextProvider } from "../../../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
describe("WysiwygComposer", () => {
|
||||
const customRender = (onChange = jest.fn(), onSend = jest.fn(), disabled = false, initialContent?: string) => {
|
||||
const { mockClient, defaultRoomContext } = createMocks();
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={defaultRoomContext}>
|
||||
<ScopedRoomContextProvider {...defaultRoomContext}>
|
||||
<WysiwygComposer
|
||||
onChange={onChange}
|
||||
onSend={onSend}
|
||||
disabled={disabled}
|
||||
initialContent={initialContent}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
@@ -523,7 +523,7 @@ describe("WysiwygComposer", () => {
|
||||
) => {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={roomContext}>
|
||||
<ScopedRoomContextProvider {...roomContext}>
|
||||
<ComposerContext.Provider
|
||||
value={getDefaultContextValue({ editorStateTransfer: _editorStateTransfer })}
|
||||
>
|
||||
@@ -537,7 +537,7 @@ describe("WysiwygComposer", () => {
|
||||
}
|
||||
/>
|
||||
</ComposerContext.Provider>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -88,9 +88,9 @@ describe("createMessageContent", () => {
|
||||
|
||||
// Then
|
||||
expect(content).toEqual({
|
||||
"body": " * *__hello__ world*",
|
||||
"body": "* *__hello__ world*",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": ` * ${message}`,
|
||||
"formatted_body": `* ${message}`,
|
||||
"msgtype": "m.text",
|
||||
"m.new_content": {
|
||||
body: "*__hello__ world*",
|
||||
|
||||
@@ -418,8 +418,8 @@ describe("message", () => {
|
||||
// Then
|
||||
const { msgtype, format } = mockEvent.getContent();
|
||||
const expectedContent = {
|
||||
"body": ` * ${newMessage}`,
|
||||
"formatted_body": ` * ${newMessage}`,
|
||||
"body": `* ${newMessage}`,
|
||||
"formatted_body": `* ${newMessage}`,
|
||||
"m.new_content": {
|
||||
body: "Replying to this new content",
|
||||
format: "org.matrix.custom.html",
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { render, screen, waitFor } from "jest-matrix-react";
|
||||
import { render, screen, waitFor, cleanup } from "jest-matrix-react";
|
||||
import { MatrixClient, MatrixError, ThreepidMedium } from "matrix-js-sdk/src/matrix";
|
||||
import React from "react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
@@ -48,54 +48,13 @@ describe("AddRemoveThreepids", () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
clearAllModals();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
const clientProviderWrapper: React.FC = ({ children }: React.PropsWithChildren) => (
|
||||
<MatrixClientContext.Provider value={client}>{children}</MatrixClientContext.Provider>
|
||||
);
|
||||
|
||||
it("should render a loader while loading", async () => {
|
||||
render(
|
||||
<AddRemoveThreepids
|
||||
mode="hs"
|
||||
medium={ThreepidMedium.Email}
|
||||
threepids={[]}
|
||||
isLoading={true}
|
||||
onChange={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText("Loading…")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render email addresses", async () => {
|
||||
const { container } = render(
|
||||
<AddRemoveThreepids
|
||||
mode="hs"
|
||||
medium={ThreepidMedium.Email}
|
||||
threepids={[EMAIL1]}
|
||||
isLoading={false}
|
||||
onChange={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render phone numbers", async () => {
|
||||
const { container } = render(
|
||||
<AddRemoveThreepids
|
||||
mode="hs"
|
||||
medium={ThreepidMedium.Phone}
|
||||
threepids={[PHONE1]}
|
||||
isLoading={false}
|
||||
onChange={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should handle no email addresses", async () => {
|
||||
const { container } = render(
|
||||
<AddRemoveThreepids
|
||||
@@ -107,6 +66,7 @@ describe("AddRemoveThreepids", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
await expect(screen.findByText("Email Address")).resolves.toBeVisible();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -127,7 +87,7 @@ describe("AddRemoveThreepids", () => {
|
||||
},
|
||||
);
|
||||
|
||||
const input = screen.getByRole("textbox", { name: "Email Address" });
|
||||
const input = await screen.findByRole("textbox", { name: "Email Address" });
|
||||
await userEvent.type(input, EMAIL1.address);
|
||||
const addButton = screen.getByRole("button", { name: "Add" });
|
||||
await userEvent.click(addButton);
|
||||
@@ -166,7 +126,7 @@ describe("AddRemoveThreepids", () => {
|
||||
},
|
||||
);
|
||||
|
||||
const input = screen.getByRole("textbox", { name: "Email Address" });
|
||||
const input = await screen.findByRole("textbox", { name: "Email Address" });
|
||||
await userEvent.type(input, EMAIL1.address);
|
||||
const addButton = screen.getByRole("button", { name: "Add" });
|
||||
await userEvent.click(addButton);
|
||||
@@ -210,7 +170,7 @@ describe("AddRemoveThreepids", () => {
|
||||
},
|
||||
);
|
||||
|
||||
const countryDropdown = screen.getByRole("button", { name: /Country Dropdown/ });
|
||||
const countryDropdown = await screen.findByRole("button", { name: /Country Dropdown/ });
|
||||
await userEvent.click(countryDropdown);
|
||||
const gbOption = screen.getByRole("option", { name: "🇬🇧 United Kingdom (+44)" });
|
||||
await userEvent.click(gbOption);
|
||||
@@ -270,7 +230,7 @@ describe("AddRemoveThreepids", () => {
|
||||
},
|
||||
);
|
||||
|
||||
const removeButton = screen.getByRole("button", { name: /Remove/ });
|
||||
const removeButton = await screen.findByRole("button", { name: /Remove/ });
|
||||
await userEvent.click(removeButton);
|
||||
|
||||
expect(screen.getByText(`Remove ${EMAIL1.address}?`)).toBeVisible();
|
||||
@@ -297,7 +257,7 @@ describe("AddRemoveThreepids", () => {
|
||||
},
|
||||
);
|
||||
|
||||
const removeButton = screen.getByRole("button", { name: /Remove/ });
|
||||
const removeButton = await screen.findByRole("button", { name: /Remove/ });
|
||||
await userEvent.click(removeButton);
|
||||
|
||||
expect(screen.getByText(`Remove ${EMAIL1.address}?`)).toBeVisible();
|
||||
@@ -326,7 +286,7 @@ describe("AddRemoveThreepids", () => {
|
||||
},
|
||||
);
|
||||
|
||||
const removeButton = screen.getByRole("button", { name: /Remove/ });
|
||||
const removeButton = await screen.findByRole("button", { name: /Remove/ });
|
||||
await userEvent.click(removeButton);
|
||||
|
||||
expect(screen.getByText(`Remove ${PHONE1.address}?`)).toBeVisible();
|
||||
@@ -357,7 +317,7 @@ describe("AddRemoveThreepids", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(screen.getByText(EMAIL1.address)).toBeVisible();
|
||||
await expect(screen.findByText(EMAIL1.address)).resolves.toBeVisible();
|
||||
const shareButton = screen.getByRole("button", { name: /Share/ });
|
||||
await userEvent.click(shareButton);
|
||||
|
||||
@@ -408,7 +368,7 @@ describe("AddRemoveThreepids", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(screen.getByText(PHONE1.address)).toBeVisible();
|
||||
await expect(screen.findByText(PHONE1.address)).resolves.toBeVisible();
|
||||
const shareButton = screen.getByRole("button", { name: /Share/ });
|
||||
await userEvent.click(shareButton);
|
||||
|
||||
@@ -452,7 +412,7 @@ describe("AddRemoveThreepids", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(screen.getByText(EMAIL1.address)).toBeVisible();
|
||||
await expect(screen.findByText(EMAIL1.address)).resolves.toBeVisible();
|
||||
const revokeButton = screen.getByRole("button", { name: /Revoke/ });
|
||||
await userEvent.click(revokeButton);
|
||||
|
||||
@@ -475,7 +435,7 @@ describe("AddRemoveThreepids", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(screen.getByText(PHONE1.address)).toBeVisible();
|
||||
await expect(screen.findByText(PHONE1.address)).resolves.toBeVisible();
|
||||
const revokeButton = screen.getByRole("button", { name: /Revoke/ });
|
||||
await userEvent.click(revokeButton);
|
||||
|
||||
@@ -596,4 +556,48 @@ describe("AddRemoveThreepids", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should render a loader while loading", async () => {
|
||||
render(
|
||||
<AddRemoveThreepids
|
||||
mode="hs"
|
||||
medium={ThreepidMedium.Email}
|
||||
threepids={[]}
|
||||
isLoading={true}
|
||||
onChange={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText("Loading…")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render email addresses", async () => {
|
||||
const { container } = render(
|
||||
<AddRemoveThreepids
|
||||
mode="hs"
|
||||
medium={ThreepidMedium.Email}
|
||||
threepids={[EMAIL1]}
|
||||
isLoading={false}
|
||||
onChange={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await expect(screen.findByText(EMAIL1.address)).resolves.toBeVisible();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render phone numbers", async () => {
|
||||
const { container } = render(
|
||||
<AddRemoveThreepids
|
||||
mode="hs"
|
||||
medium={ThreepidMedium.Phone}
|
||||
threepids={[PHONE1]}
|
||||
isLoading={false}
|
||||
onChange={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await expect(screen.findByText(PHONE1.address)).resolves.toBeVisible();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
import React from "react";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import { render, screen, fireEvent } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import AvatarSetting from "../../../../../src/components/views/settings/AvatarSetting";
|
||||
@@ -16,6 +16,9 @@ const BASE64_GIF = "R0lGODlhAQABAAAAACw=";
|
||||
const AVATAR_FILE = new File([Uint8Array.from(atob(BASE64_GIF), (c) => c.charCodeAt(0))], "avatar.gif", {
|
||||
type: "image/gif",
|
||||
});
|
||||
const GENERIC_FILE = new File([Uint8Array.from(atob(BASE64_GIF), (c) => c.charCodeAt(0))], "not-avatar.doc", {
|
||||
type: "application/msword",
|
||||
});
|
||||
|
||||
describe("<AvatarSetting />", () => {
|
||||
beforeEach(() => {
|
||||
@@ -70,4 +73,45 @@ describe("<AvatarSetting />", () => {
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(AVATAR_FILE);
|
||||
});
|
||||
|
||||
it("should noop when selecting no file", async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
render(
|
||||
<AvatarSetting
|
||||
placeholderId="blee"
|
||||
placeholderName="boo"
|
||||
avatar="mxc://example.org/my-avatar"
|
||||
avatarAltText="Avatar of Peter Fox"
|
||||
onChange={onChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
const fileInput = screen.getByAltText("Upload");
|
||||
// Can't use userEvent.upload here as it doesn't support uploading invalid files
|
||||
fireEvent.change(fileInput, { target: { files: [] } });
|
||||
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should show error if user tries to use non-image file", async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
render(
|
||||
<AvatarSetting
|
||||
placeholderId="blee"
|
||||
placeholderName="boo"
|
||||
avatar="mxc://example.org/my-avatar"
|
||||
avatarAltText="Avatar of Peter Fox"
|
||||
onChange={onChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
const fileInput = screen.getByAltText("Upload");
|
||||
// Can't use userEvent.upload here as it doesn't support uploading invalid files
|
||||
fireEvent.change(fileInput, { target: { files: [GENERIC_FILE] } });
|
||||
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
await expect(screen.findByRole("heading", { name: "Upload Failed" })).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,7 +59,7 @@ describe("<JoinRuleSettings />", () => {
|
||||
onError: jest.fn(),
|
||||
};
|
||||
const getComponent = (props: Partial<JoinRuleSettingsProps> = {}) =>
|
||||
render(<JoinRuleSettings {...defaultProps} {...props} />, { legacyRoot: false });
|
||||
render(<JoinRuleSettings {...defaultProps} {...props} />);
|
||||
|
||||
const setRoomStateEvents = (
|
||||
room: Room,
|
||||
|
||||
@@ -915,7 +915,7 @@ describe("<Notifications />", () => {
|
||||
user: "@alice:example.org",
|
||||
ts: 1,
|
||||
});
|
||||
await room.addLiveEvents([message]);
|
||||
await room.addLiveEvents([message], { addToState: true });
|
||||
|
||||
const { container } = await getComponentAndWait();
|
||||
const clearNotificationEl = getByTestId(container, "clear-notifications");
|
||||
|
||||
@@ -28,14 +28,13 @@ describe("<SecureBackupPanel />", () => {
|
||||
const client = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
...mockClientMethodsCrypto(),
|
||||
getKeyBackupVersion: jest.fn().mockReturnValue("1"),
|
||||
getClientWellKnown: jest.fn(),
|
||||
});
|
||||
|
||||
const getComponent = () => render(<SecureBackupPanel />);
|
||||
|
||||
beforeEach(() => {
|
||||
client.getKeyBackupVersion.mockResolvedValue({
|
||||
jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({
|
||||
version: "1",
|
||||
algorithm: "test",
|
||||
auth_data: {
|
||||
@@ -52,7 +51,6 @@ describe("<SecureBackupPanel />", () => {
|
||||
});
|
||||
|
||||
mocked(client.secretStorage.hasKey).mockClear().mockResolvedValue(false);
|
||||
client.getKeyBackupVersion.mockClear();
|
||||
|
||||
mocked(accessSecretStorage).mockClear().mockResolvedValue();
|
||||
});
|
||||
@@ -65,8 +63,8 @@ describe("<SecureBackupPanel />", () => {
|
||||
});
|
||||
|
||||
it("handles error fetching backup", async () => {
|
||||
// getKeyBackupVersion can fail for various reasons
|
||||
client.getKeyBackupVersion.mockImplementation(async () => {
|
||||
// getKeyBackupInfo can fail for various reasons
|
||||
jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockImplementation(async () => {
|
||||
throw new Error("beep beep");
|
||||
});
|
||||
const renderResult = getComponent();
|
||||
@@ -75,9 +73,9 @@ describe("<SecureBackupPanel />", () => {
|
||||
});
|
||||
|
||||
it("handles absence of backup", async () => {
|
||||
client.getKeyBackupVersion.mockResolvedValue(null);
|
||||
jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockResolvedValue(null);
|
||||
getComponent();
|
||||
// flush getKeyBackupVersion promise
|
||||
// flush getKeyBackupInfo promise
|
||||
await flushPromises();
|
||||
expect(screen.getByText("Back up your keys before signing out to avoid losing them.")).toBeInTheDocument();
|
||||
});
|
||||
@@ -120,7 +118,7 @@ describe("<SecureBackupPanel />", () => {
|
||||
});
|
||||
|
||||
it("deletes backup after confirmation", async () => {
|
||||
client.getKeyBackupVersion
|
||||
jest.spyOn(client.getCrypto()!, "getKeyBackupInfo")
|
||||
.mockResolvedValueOnce({
|
||||
version: "1",
|
||||
algorithm: "test",
|
||||
@@ -157,7 +155,7 @@ describe("<SecureBackupPanel />", () => {
|
||||
// flush checkKeyBackup promise
|
||||
await flushPromises();
|
||||
|
||||
client.getKeyBackupVersion.mockClear();
|
||||
jest.spyOn(client.getCrypto()!, "getKeyBackupInfo").mockClear();
|
||||
mocked(client.getCrypto()!).isKeyBackupTrusted.mockClear();
|
||||
|
||||
fireEvent.click(screen.getByText("Reset"));
|
||||
@@ -167,7 +165,7 @@ describe("<SecureBackupPanel />", () => {
|
||||
await flushPromises();
|
||||
|
||||
// backup status refreshed
|
||||
expect(client.getKeyBackupVersion).toHaveBeenCalled();
|
||||
expect(client.getCrypto()!.getKeyBackupInfo).toHaveBeenCalled();
|
||||
expect(client.getCrypto()!.isKeyBackupTrusted).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ describe("SetIntegrationManager", () => {
|
||||
deleteThreePid: jest.fn(),
|
||||
});
|
||||
|
||||
let stores: SdkContextClass;
|
||||
let stores!: SdkContextClass;
|
||||
|
||||
const getComponent = () => (
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
|
||||
@@ -11,14 +11,14 @@ exports[`AddRemoveThreepids should handle no email addresses 1`] = `
|
||||
>
|
||||
<input
|
||||
autocomplete="email"
|
||||
id="mx_Field_3"
|
||||
id="mx_Field_1"
|
||||
label="Email Address"
|
||||
placeholder="Email Address"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
for="mx_Field_3"
|
||||
for="mx_Field_1"
|
||||
>
|
||||
Email Address
|
||||
</label>
|
||||
@@ -61,14 +61,14 @@ exports[`AddRemoveThreepids should render email addresses 1`] = `
|
||||
>
|
||||
<input
|
||||
autocomplete="email"
|
||||
id="mx_Field_1"
|
||||
id="mx_Field_14"
|
||||
label="Email Address"
|
||||
placeholder="Email Address"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
for="mx_Field_1"
|
||||
for="mx_Field_14"
|
||||
>
|
||||
Email Address
|
||||
</label>
|
||||
@@ -148,14 +148,14 @@ exports[`AddRemoveThreepids should render phone numbers 1`] = `
|
||||
</span>
|
||||
<input
|
||||
autocomplete="tel-national"
|
||||
id="mx_Field_2"
|
||||
id="mx_Field_15"
|
||||
label="Phone Number"
|
||||
placeholder="Phone Number"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
for="mx_Field_2"
|
||||
for="mx_Field_15"
|
||||
>
|
||||
Phone Number
|
||||
</label>
|
||||
|
||||
@@ -10,7 +10,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
||||
class="mx_SettingsSubsectionHeading"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
|
||||
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
|
||||
>
|
||||
Message layout
|
||||
</h3>
|
||||
@@ -19,14 +19,14 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
||||
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_LayoutSwitcher_LayoutSelector"
|
||||
class="_root_ssths_24 mx_LayoutSwitcher_LayoutSelector"
|
||||
>
|
||||
<div
|
||||
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
>
|
||||
<label
|
||||
aria-label="Modern"
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r0:"
|
||||
>
|
||||
<div
|
||||
@@ -149,11 +149,11 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
>
|
||||
<label
|
||||
aria-label="Message bubbles"
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r9:"
|
||||
>
|
||||
<div
|
||||
@@ -275,11 +275,11 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
>
|
||||
<label
|
||||
aria-label="IRC (experimental)"
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:ri:"
|
||||
>
|
||||
<div
|
||||
@@ -402,13 +402,13 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
||||
</div>
|
||||
</form>
|
||||
<form
|
||||
class="_root_dgy0u_24"
|
||||
class="_root_ssths_24"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40"
|
||||
class="_inline-field_ssths_40"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_qnvru_18"
|
||||
@@ -427,16 +427,16 @@ exports[`<LayoutSwitcher /> should render 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:rr:"
|
||||
>
|
||||
Show compact text and messages
|
||||
</label>
|
||||
<span
|
||||
class="_message_dgy0u_98 _help-message_dgy0u_104"
|
||||
class="_message_ssths_93 _help-message_ssths_99"
|
||||
id="radix-:rs:"
|
||||
>
|
||||
Modern layout must be selected to use this feature.
|
||||
|
||||
@@ -10,7 +10,7 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
||||
class="mx_SettingsSubsectionHeading"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
|
||||
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
|
||||
>
|
||||
Theme
|
||||
</h3>
|
||||
@@ -19,13 +19,13 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
||||
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24"
|
||||
class="_root_ssths_24"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40"
|
||||
class="_inline-field_ssths_40"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_qnvru_18"
|
||||
@@ -43,10 +43,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r28:"
|
||||
>
|
||||
Match system theme
|
||||
@@ -55,13 +55,13 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
||||
</div>
|
||||
</form>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -81,10 +81,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r29:"
|
||||
>
|
||||
Light
|
||||
@@ -92,10 +92,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -114,10 +114,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r2a:"
|
||||
>
|
||||
Dark
|
||||
@@ -125,10 +125,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -147,10 +147,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r2b:"
|
||||
>
|
||||
High contrast
|
||||
@@ -158,10 +158,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -180,10 +180,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r2c:"
|
||||
>
|
||||
Alice theme
|
||||
@@ -195,13 +195,13 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
||||
class="mx_ThemeChoicePanel_CustomTheme"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
|
||||
class="_root_ssths_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
|
||||
>
|
||||
<div
|
||||
class="_field_dgy0u_34"
|
||||
class="_field_ssths_34"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r2d:"
|
||||
>
|
||||
Add custom theme
|
||||
@@ -219,7 +219,7 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="_message_dgy0u_98 _help-message_dgy0u_104"
|
||||
class="_message_ssths_93 _help-message_ssths_99"
|
||||
id="radix-:r2e:"
|
||||
>
|
||||
Enter the URL of a custom theme you want to apply.
|
||||
@@ -287,7 +287,7 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
||||
class="mx_SettingsSubsectionHeading"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
|
||||
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
|
||||
>
|
||||
Theme
|
||||
</h3>
|
||||
@@ -296,13 +296,13 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
||||
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24"
|
||||
class="_root_ssths_24"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40"
|
||||
class="_inline-field_ssths_40"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_qnvru_18"
|
||||
@@ -320,10 +320,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r10:"
|
||||
>
|
||||
Match system theme
|
||||
@@ -332,13 +332,13 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
||||
</div>
|
||||
</form>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -358,10 +358,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r11:"
|
||||
>
|
||||
Light
|
||||
@@ -369,10 +369,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -391,10 +391,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r12:"
|
||||
>
|
||||
Dark
|
||||
@@ -402,10 +402,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -424,10 +424,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r13:"
|
||||
>
|
||||
High contrast
|
||||
@@ -435,10 +435,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -457,10 +457,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r14:"
|
||||
>
|
||||
Alice theme
|
||||
@@ -472,13 +472,13 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
||||
class="mx_ThemeChoicePanel_CustomTheme"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
|
||||
class="_root_ssths_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
|
||||
>
|
||||
<div
|
||||
class="_field_dgy0u_34"
|
||||
class="_field_ssths_34"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r15:"
|
||||
>
|
||||
Add custom theme
|
||||
@@ -496,7 +496,7 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="_message_dgy0u_98 _help-message_dgy0u_104"
|
||||
class="_message_ssths_93 _help-message_ssths_99"
|
||||
id="radix-:r16:"
|
||||
>
|
||||
Enter the URL of a custom theme you want to apply.
|
||||
@@ -564,7 +564,7 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
||||
class="mx_SettingsSubsectionHeading"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
|
||||
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
|
||||
>
|
||||
Theme
|
||||
</h3>
|
||||
@@ -573,13 +573,13 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
||||
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24"
|
||||
class="_root_ssths_24"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40"
|
||||
class="_inline-field_ssths_40"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_qnvru_18"
|
||||
@@ -597,10 +597,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r0:"
|
||||
>
|
||||
Match system theme
|
||||
@@ -609,13 +609,13 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
||||
</div>
|
||||
</form>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -635,10 +635,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r1:"
|
||||
>
|
||||
Light
|
||||
@@ -646,10 +646,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -668,10 +668,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r2:"
|
||||
>
|
||||
Dark
|
||||
@@ -679,10 +679,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -701,10 +701,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r3:"
|
||||
>
|
||||
High contrast
|
||||
|
||||
@@ -79,9 +79,7 @@ describe("<LoginWithQR />", () => {
|
||||
|
||||
describe("MSC4108", () => {
|
||||
const getComponent = (props: { client: MatrixClient; onFinished?: () => void }) => (
|
||||
<React.StrictMode>
|
||||
<LoginWithQR {...defaultProps} {...props} />
|
||||
</React.StrictMode>
|
||||
<LoginWithQR {...defaultProps} {...props} />
|
||||
);
|
||||
|
||||
test("render QR then back", async () => {
|
||||
|
||||
@@ -56,7 +56,6 @@ describe("<LoginWithQRSection />", () => {
|
||||
const defaultProps = {
|
||||
onShowQr: () => {},
|
||||
versions: makeVersions({ "org.matrix.msc4108": true }),
|
||||
wellKnown: {},
|
||||
};
|
||||
|
||||
const getComponent = (props = {}) => <LoginWithQRSection {...defaultProps} {...props} />;
|
||||
|
||||
@@ -716,7 +716,7 @@ describe("<Notifications />", () => {
|
||||
user: "@alice:example.org",
|
||||
ts: 1,
|
||||
});
|
||||
room.addLiveEvents([message]);
|
||||
room.addLiveEvents([message], { addToState: true });
|
||||
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -17,7 +17,6 @@ import userEvent from "@testing-library/user-event";
|
||||
import RolesRoomSettingsTab from "../../../../../../../src/components/views/settings/tabs/room/RolesRoomSettingsTab";
|
||||
import { mkStubRoom, withClientContextRenderOptions, stubClient } from "../../../../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../../../../src/MatrixClientPeg";
|
||||
import { VoiceBroadcastInfoEventType } from "../../../../../../../src/voice-broadcast";
|
||||
import SettingsStore from "../../../../../../../src/settings/SettingsStore";
|
||||
import { ElementCall } from "../../../../../../../src/models/Call";
|
||||
|
||||
@@ -27,16 +26,11 @@ describe("RolesRoomSettingsTab", () => {
|
||||
let cli: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
const renderTab = (propRoom: Room = room): RenderResult => {
|
||||
return render(<RolesRoomSettingsTab room={propRoom} />, withClientContextRenderOptions(cli));
|
||||
};
|
||||
|
||||
const getVoiceBroadcastsSelect = (): HTMLElement => {
|
||||
return renderTab().container.querySelector("select[label='Voice broadcasts']")!;
|
||||
};
|
||||
|
||||
const getVoiceBroadcastsSelectedOption = (): HTMLElement => {
|
||||
return renderTab().container.querySelector("select[label='Voice broadcasts'] option:checked")!;
|
||||
const renderTab = async (propRoom: Room = room): Promise<RenderResult> => {
|
||||
const renderResult = render(<RolesRoomSettingsTab room={propRoom} />, withClientContextRenderOptions(cli));
|
||||
// Wait for the tab to be ready
|
||||
await waitFor(() => expect(screen.getByText("Permissions")).toBeInTheDocument());
|
||||
return renderResult;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -45,7 +39,7 @@ describe("RolesRoomSettingsTab", () => {
|
||||
room = mkStubRoom(roomId, "test room", cli);
|
||||
});
|
||||
|
||||
it("should allow an Admin to demote themselves but not others", () => {
|
||||
it("should allow an Admin to demote themselves but not others", async () => {
|
||||
mocked(cli.getRoom).mockReturnValue(room);
|
||||
// @ts-ignore - mocked doesn't support overloads properly
|
||||
mocked(room.currentState.getStateEvents).mockImplementation((type, key) => {
|
||||
@@ -67,32 +61,12 @@ describe("RolesRoomSettingsTab", () => {
|
||||
return null;
|
||||
});
|
||||
mocked(room.currentState.mayClientSendStateEvent).mockReturnValue(true);
|
||||
const { container } = renderTab();
|
||||
const { container } = await renderTab();
|
||||
|
||||
expect(container.querySelector(`[placeholder="${cli.getUserId()}"]`)).not.toBeDisabled();
|
||||
expect(container.querySelector(`[placeholder="@admin:server"]`)).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should initially show »Moderator« permission for »Voice broadcasts«", () => {
|
||||
expect(getVoiceBroadcastsSelectedOption().textContent).toBe("Moderator");
|
||||
});
|
||||
|
||||
describe("when setting »Default« permission for »Voice broadcasts«", () => {
|
||||
beforeEach(() => {
|
||||
fireEvent.change(getVoiceBroadcastsSelect(), {
|
||||
target: { value: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("should update the power levels", () => {
|
||||
expect(cli.sendStateEvent).toHaveBeenCalledWith(roomId, EventType.RoomPowerLevels, {
|
||||
events: {
|
||||
[VoiceBroadcastInfoEventType]: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Element Call", () => {
|
||||
const setGroupCallsEnabled = (val: boolean): void => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
@@ -122,12 +96,12 @@ describe("RolesRoomSettingsTab", () => {
|
||||
});
|
||||
|
||||
describe("Join Element calls", () => {
|
||||
it("defaults to moderator for joining calls", () => {
|
||||
expect(getJoinCallSelectedOption(renderTab())?.textContent).toBe("Moderator");
|
||||
it("defaults to moderator for joining calls", async () => {
|
||||
expect(getJoinCallSelectedOption(await renderTab())?.textContent).toBe("Moderator");
|
||||
});
|
||||
|
||||
it("can change joining calls power level", () => {
|
||||
const tab = renderTab();
|
||||
it("can change joining calls power level", async () => {
|
||||
const tab = await renderTab();
|
||||
|
||||
fireEvent.change(getJoinCallSelect(tab), {
|
||||
target: { value: 0 },
|
||||
@@ -143,12 +117,12 @@ describe("RolesRoomSettingsTab", () => {
|
||||
});
|
||||
|
||||
describe("Start Element calls", () => {
|
||||
it("defaults to moderator for starting calls", () => {
|
||||
expect(getStartCallSelectedOption(renderTab())?.textContent).toBe("Moderator");
|
||||
it("defaults to moderator for starting calls", async () => {
|
||||
expect(getStartCallSelectedOption(await renderTab())?.textContent).toBe("Moderator");
|
||||
});
|
||||
|
||||
it("can change starting calls power level", () => {
|
||||
const tab = renderTab();
|
||||
it("can change starting calls power level", async () => {
|
||||
const tab = await renderTab();
|
||||
|
||||
fireEvent.change(getStartCallSelect(tab), {
|
||||
target: { value: 0 },
|
||||
@@ -164,10 +138,10 @@ describe("RolesRoomSettingsTab", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("hides when group calls disabled", () => {
|
||||
it("hides when group calls disabled", async () => {
|
||||
setGroupCallsEnabled(false);
|
||||
|
||||
const tab = renderTab();
|
||||
const tab = await renderTab();
|
||||
|
||||
expect(getStartCallSelect(tab)).toBeFalsy();
|
||||
expect(getStartCallSelectedOption(tab)).toBeFalsy();
|
||||
@@ -250,7 +224,7 @@ describe("RolesRoomSettingsTab", () => {
|
||||
return null;
|
||||
});
|
||||
mocked(room.currentState.mayClientSendStateEvent).mockReturnValue(true);
|
||||
const { container } = renderTab();
|
||||
const { container } = await renderTab();
|
||||
|
||||
const selector = container.querySelector(`[placeholder="${cli.getUserId()}"]`)!;
|
||||
fireEvent.change(selector, { target: { value: "50" } });
|
||||
|
||||
@@ -75,7 +75,7 @@ describe("<SecurityRoomSettingsTab />", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
client.sendStateEvent.mockReset().mockResolvedValue({ event_id: "test" });
|
||||
client.isRoomEncrypted.mockReturnValue(false);
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
|
||||
client.getClientWellKnown.mockReturnValue(undefined);
|
||||
jest.spyOn(SettingsStore, "getValue").mockRestore();
|
||||
|
||||
@@ -313,7 +313,7 @@ describe("<SecurityRoomSettingsTab />", () => {
|
||||
setRoomStateEvents(room);
|
||||
getComponent(room);
|
||||
|
||||
expect(screen.getByLabelText("Encrypted")).not.toBeChecked();
|
||||
await waitFor(() => expect(screen.getByLabelText("Encrypted")).not.toBeChecked());
|
||||
|
||||
fireEvent.click(screen.getByLabelText("Encrypted"));
|
||||
|
||||
@@ -330,7 +330,7 @@ describe("<SecurityRoomSettingsTab />", () => {
|
||||
setRoomStateEvents(room);
|
||||
getComponent(room);
|
||||
|
||||
expect(screen.getByLabelText("Encrypted")).not.toBeChecked();
|
||||
await waitFor(() => expect(screen.getByLabelText("Encrypted")).not.toBeChecked());
|
||||
|
||||
fireEvent.click(screen.getByLabelText("Encrypted"));
|
||||
|
||||
@@ -416,12 +416,12 @@ describe("<SecurityRoomSettingsTab />", () => {
|
||||
expect(screen.getByText("Once enabled, encryption cannot be disabled.")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays unencrypted rooms with toggle disabled", () => {
|
||||
it("displays unencrypted rooms with toggle disabled", async () => {
|
||||
const room = new Room(roomId, client, userId);
|
||||
setRoomStateEvents(room);
|
||||
getComponent(room);
|
||||
|
||||
expect(screen.getByLabelText("Encrypted")).not.toBeChecked();
|
||||
await waitFor(() => expect(screen.getByLabelText("Encrypted")).not.toBeChecked());
|
||||
expect(screen.getByLabelText("Encrypted").getAttribute("aria-disabled")).toEqual("true");
|
||||
expect(screen.queryByText("Once enabled, encryption cannot be disabled.")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Your server requires encryption to be disabled.")).toBeInTheDocument();
|
||||
|
||||
@@ -34,7 +34,6 @@ describe("<SecurityUserSettingsTab />", () => {
|
||||
...mockClientMethodsCrypto(),
|
||||
getRooms: jest.fn().mockReturnValue([]),
|
||||
getIgnoredUsers: jest.fn(),
|
||||
getKeyBackupVersion: jest.fn(),
|
||||
});
|
||||
|
||||
const sdkContext = new SdkContextClass();
|
||||
|
||||
@@ -277,9 +277,7 @@ describe("<SessionManagerTab />", () => {
|
||||
mockClient.getDevices.mockRejectedValue({ httpStatus: 404 });
|
||||
const { container } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
expect(container.getElementsByClassName("mx_Spinner").length).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -302,9 +300,7 @@ describe("<SessionManagerTab />", () => {
|
||||
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(mockCrypto.getDeviceVerificationStatus).toHaveBeenCalledTimes(3);
|
||||
expect(
|
||||
@@ -337,9 +333,7 @@ describe("<SessionManagerTab />", () => {
|
||||
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
// twice for each device
|
||||
expect(mockClient.getAccountData).toHaveBeenCalledTimes(4);
|
||||
@@ -356,9 +350,7 @@ describe("<SessionManagerTab />", () => {
|
||||
|
||||
const { getByTestId, queryByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
|
||||
// application metadata section not rendered
|
||||
@@ -369,9 +361,7 @@ describe("<SessionManagerTab />", () => {
|
||||
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice] });
|
||||
const { queryByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(queryByTestId("other-sessions-section")).toBeFalsy();
|
||||
});
|
||||
@@ -382,9 +372,7 @@ describe("<SessionManagerTab />", () => {
|
||||
});
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(getByTestId("other-sessions-section")).toBeTruthy();
|
||||
});
|
||||
@@ -395,9 +383,7 @@ describe("<SessionManagerTab />", () => {
|
||||
});
|
||||
const { getByTestId, container } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
fireEvent.click(getByTestId("unverified-devices-cta"));
|
||||
|
||||
@@ -908,7 +894,8 @@ describe("<SessionManagerTab />", () => {
|
||||
});
|
||||
|
||||
it("deletes a device when interactive auth is not required", async () => {
|
||||
mockClient.deleteMultipleDevices.mockResolvedValue({});
|
||||
const deferredDeleteMultipleDevices = defer<{}>();
|
||||
mockClient.deleteMultipleDevices.mockReturnValue(deferredDeleteMultipleDevices.promise);
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice],
|
||||
});
|
||||
@@ -933,6 +920,7 @@ describe("<SessionManagerTab />", () => {
|
||||
fireEvent.click(signOutButton);
|
||||
await confirmSignout(getByTestId);
|
||||
await prom;
|
||||
deferredDeleteMultipleDevices.resolve({});
|
||||
|
||||
// delete called
|
||||
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(
|
||||
@@ -991,7 +979,7 @@ describe("<SessionManagerTab />", () => {
|
||||
|
||||
const { getByTestId, getByLabelText } = render(getComponent());
|
||||
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// reset mock count after initial load
|
||||
mockClient.getDevices.mockClear();
|
||||
@@ -1025,7 +1013,7 @@ describe("<SessionManagerTab />", () => {
|
||||
fireEvent.submit(getByLabelText("Password"));
|
||||
});
|
||||
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// called again with auth
|
||||
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith([alicesMobileDevice.device_id], {
|
||||
@@ -1551,7 +1539,7 @@ describe("<SessionManagerTab />", () => {
|
||||
});
|
||||
const { getByTestId, container } = render(getComponent());
|
||||
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// filter for inactive sessions
|
||||
await setFilter(container, DeviceSecurityVariation.Inactive);
|
||||
@@ -1577,9 +1565,7 @@ describe("<SessionManagerTab />", () => {
|
||||
it("lets you change the pusher state", async () => {
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
|
||||
|
||||
@@ -1598,9 +1584,7 @@ describe("<SessionManagerTab />", () => {
|
||||
it("lets you change the local notification settings state", async () => {
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
|
||||
|
||||
@@ -1621,9 +1605,7 @@ describe("<SessionManagerTab />", () => {
|
||||
it("updates the UI when another session changes the local notifications", async () => {
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
|
||||
|
||||
|
||||
@@ -42,14 +42,14 @@ exports[`<AccountUserSettingsTab /> 3pids should display 3pid email addresses an
|
||||
>
|
||||
<input
|
||||
autocomplete="email"
|
||||
id="mx_Field_9"
|
||||
id="mx_Field_3"
|
||||
label="Email Address"
|
||||
placeholder="Email Address"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
for="mx_Field_9"
|
||||
for="mx_Field_3"
|
||||
>
|
||||
Email Address
|
||||
</label>
|
||||
@@ -145,14 +145,14 @@ exports[`<AccountUserSettingsTab /> 3pids should display 3pid email addresses an
|
||||
</span>
|
||||
<input
|
||||
autocomplete="tel-national"
|
||||
id="mx_Field_10"
|
||||
id="mx_Field_4"
|
||||
label="Phone Number"
|
||||
placeholder="Phone Number"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
for="mx_Field_10"
|
||||
for="mx_Field_4"
|
||||
>
|
||||
Phone Number
|
||||
</label>
|
||||
|
||||
@@ -23,7 +23,7 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
||||
class="mx_SettingsSubsectionHeading"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
|
||||
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
|
||||
>
|
||||
Theme
|
||||
</h3>
|
||||
@@ -32,13 +32,13 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
||||
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -58,10 +58,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r0:"
|
||||
>
|
||||
Light
|
||||
@@ -69,10 +69,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-dark"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-dark"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -92,10 +92,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r1:"
|
||||
>
|
||||
Dark
|
||||
@@ -103,10 +103,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
|
||||
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_1vw5h_18"
|
||||
@@ -126,10 +126,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label"
|
||||
for="radix-:r2:"
|
||||
>
|
||||
High contrast
|
||||
@@ -153,7 +153,7 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
||||
class="mx_SettingsSubsectionHeading"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
|
||||
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
|
||||
>
|
||||
Message layout
|
||||
</h3>
|
||||
@@ -162,14 +162,14 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
||||
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
|
||||
>
|
||||
<form
|
||||
class="_root_dgy0u_24 mx_LayoutSwitcher_LayoutSelector"
|
||||
class="_root_ssths_24 mx_LayoutSwitcher_LayoutSelector"
|
||||
>
|
||||
<div
|
||||
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
>
|
||||
<label
|
||||
aria-label="Modern"
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:r3:"
|
||||
>
|
||||
<div
|
||||
@@ -292,11 +292,11 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
>
|
||||
<label
|
||||
aria-label="Message bubbles"
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:rc:"
|
||||
>
|
||||
<div
|
||||
@@ -418,11 +418,11 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
|
||||
>
|
||||
<label
|
||||
aria-label="IRC (experimental)"
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:rl:"
|
||||
>
|
||||
<div
|
||||
@@ -545,13 +545,13 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
||||
</div>
|
||||
</form>
|
||||
<form
|
||||
class="_root_dgy0u_24"
|
||||
class="_root_ssths_24"
|
||||
>
|
||||
<div
|
||||
class="_inline-field_dgy0u_40"
|
||||
class="_inline-field_ssths_40"
|
||||
>
|
||||
<div
|
||||
class="_inline-field-control_dgy0u_52"
|
||||
class="_inline-field-control_ssths_52"
|
||||
>
|
||||
<div
|
||||
class="_container_qnvru_18"
|
||||
@@ -570,16 +570,16 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="_inline-field-body_dgy0u_46"
|
||||
class="_inline-field-body_ssths_46"
|
||||
>
|
||||
<label
|
||||
class="_label_dgy0u_67"
|
||||
class="_label_ssths_67"
|
||||
for="radix-:ru:"
|
||||
>
|
||||
Show compact text and messages
|
||||
</label>
|
||||
<span
|
||||
class="_message_dgy0u_98 _help-message_dgy0u_104"
|
||||
class="_message_ssths_93 _help-message_ssths_99"
|
||||
id="radix-:rv:"
|
||||
>
|
||||
Modern layout must be selected to use this feature.
|
||||
|
||||
@@ -85,7 +85,6 @@ exports[`<MjolnirUserSettingsTab /> renders correctly when user has no ignored u
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
type="submit"
|
||||
>
|
||||
Ignore
|
||||
</div>
|
||||
@@ -150,7 +149,6 @@ exports[`<MjolnirUserSettingsTab /> renders correctly when user has no ignored u
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
type="submit"
|
||||
>
|
||||
Subscribe
|
||||
</div>
|
||||
|
||||
@@ -388,7 +388,7 @@ exports[`<SessionManagerTab /> goes to filtered list from security recommendatio
|
||||
>
|
||||
<input
|
||||
aria-label="Select all"
|
||||
aria-labelledby=":r4e:"
|
||||
aria-labelledby=":r3s:"
|
||||
data-testid="device-select-all-checkbox"
|
||||
id="device-select-all-checkbox"
|
||||
type="checkbox"
|
||||
|
||||
@@ -135,7 +135,17 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings with guest spa url
|
||||
<div
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
<div />
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m12.897 2.817 2.336 4.733 5.223.76a1 1 0 0 1 .555 1.705L17.23 13.7l.892 5.202a1 1 0 0 1-1.45 1.054L12 17.5l-4.672 2.456a1 1 0 0 1-1.451-1.054l.892-5.202-3.78-3.685a1 1 0 0 1 .555-1.706l5.223-.759 2.336-4.733a1 1 0 0 1 1.794 0Z"
|
||||
/>
|
||||
</svg>
|
||||
Favourites
|
||||
</div>
|
||||
<div
|
||||
@@ -167,7 +177,20 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings with guest spa url
|
||||
<div
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
<div />
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 15c-1.1 0-2.042-.392-2.825-1.175C8.392 13.042 8 12.1 8 11s.392-2.042 1.175-2.825C9.958 7.392 10.9 7 12 7s2.042.392 2.825 1.175C15.608 8.958 16 9.9 16 11s-.392 2.042-1.175 2.825C14.042 14.608 13.1 15 12 15Z"
|
||||
/>
|
||||
<path
|
||||
d="M19.528 18.583A9.962 9.962 0 0 0 22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 2.52.933 4.824 2.472 6.583A9.976 9.976 0 0 0 12 22a9.976 9.976 0 0 0 7.528-3.417ZM8.75 16.388c-.915.221-1.818.538-2.709.95a8 8 0 1 1 11.918 0 14.679 14.679 0 0 0-2.709-.95A13.76 13.76 0 0 0 12 16c-1.1 0-2.183.13-3.25.387Z"
|
||||
/>
|
||||
</svg>
|
||||
People
|
||||
</div>
|
||||
<div
|
||||
@@ -396,7 +419,17 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings without guest spa u
|
||||
<div
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
<div />
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m12.897 2.817 2.336 4.733 5.223.76a1 1 0 0 1 .555 1.705L17.23 13.7l.892 5.202a1 1 0 0 1-1.45 1.054L12 17.5l-4.672 2.456a1 1 0 0 1-1.451-1.054l.892-5.202-3.78-3.685a1 1 0 0 1 .555-1.706l5.223-.759 2.336-4.733a1 1 0 0 1 1.794 0Z"
|
||||
/>
|
||||
</svg>
|
||||
Favourites
|
||||
</div>
|
||||
<div
|
||||
@@ -428,7 +461,20 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings without guest spa u
|
||||
<div
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
<div />
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 15c-1.1 0-2.042-.392-2.825-1.175C8.392 13.042 8 12.1 8 11s.392-2.042 1.175-2.825C9.958 7.392 10.9 7 12 7s2.042.392 2.825 1.175C15.608 8.958 16 9.9 16 11s-.392 2.042-1.175 2.825C14.042 14.608 13.1 15 12 15Z"
|
||||
/>
|
||||
<path
|
||||
d="M19.528 18.583A9.962 9.962 0 0 0 22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 2.52.933 4.824 2.472 6.583A9.976 9.976 0 0 0 12 22a9.976 9.976 0 0 0 7.528-3.417ZM8.75 16.388c-.915.221-1.818.538-2.709.95a8 8 0 1 1 11.918 0 14.679 14.679 0 0 0-2.709-.95A13.76 13.76 0 0 0 12 16c-1.1 0-2.183.13-3.25.387Z"
|
||||
/>
|
||||
</svg>
|
||||
People
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -8,6 +8,7 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
|
||||
>
|
||||
<div
|
||||
class="mx_UserMenu"
|
||||
data-floating-ui-inert=""
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
@@ -42,6 +43,7 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
|
||||
<ul
|
||||
aria-label="Spaces"
|
||||
class="mx_AutoHideScrollbar mx_SpaceTreeLevel"
|
||||
data-floating-ui-inert=""
|
||||
data-rbd-droppable-context-id="0"
|
||||
data-rbd-droppable-id="top-level-spaces"
|
||||
role="tree"
|
||||
@@ -236,6 +238,7 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
|
||||
aria-label="Threads"
|
||||
aria-labelledby=":r14:"
|
||||
class="_icon-button_bh2qc_17 mx_ThreadsActivityCentreButton"
|
||||
data-floating-ui-inert=""
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
@@ -260,6 +263,7 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
|
||||
</button>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
data-floating-ui-inert=""
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
tabindex="-1"
|
||||
/>
|
||||
@@ -272,6 +276,7 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
|
||||
/>
|
||||
<span
|
||||
aria-owns=":r19:"
|
||||
data-floating-ui-inert=""
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
/>
|
||||
<span
|
||||
@@ -286,6 +291,7 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
|
||||
aria-expanded="false"
|
||||
aria-label="Quick settings"
|
||||
class="mx_AccessibleButton mx_QuickSettingsButton"
|
||||
data-floating-ui-inert=""
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
|
||||
@@ -477,9 +477,7 @@ exports[`ThreadsActivityCentre should order the room with the same notification
|
||||
|
||||
exports[`ThreadsActivityCentre should render the release announcement 1`] = `
|
||||
<body>
|
||||
<div
|
||||
data-floating-ui-inert=""
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="mx_ThreadsActivityCentre_container"
|
||||
>
|
||||
@@ -491,6 +489,7 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
|
||||
aria-label="Threads"
|
||||
aria-labelledby=":rc:"
|
||||
class="_icon-button_bh2qc_17 mx_ThreadsActivityCentreButton"
|
||||
data-floating-ui-inert=""
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
@@ -515,6 +514,7 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
|
||||
</button>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
data-floating-ui-inert=""
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
tabindex="-1"
|
||||
/>
|
||||
@@ -527,6 +527,7 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
|
||||
/>
|
||||
<span
|
||||
aria-owns=":rh:"
|
||||
data-floating-ui-inert=""
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
/>
|
||||
<span
|
||||
@@ -585,7 +586,6 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
|
||||
>
|
||||
<span
|
||||
data-floating-ui-focus-guard=""
|
||||
data-floating-ui-inert=""
|
||||
data-type="inside"
|
||||
role="button"
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
@@ -648,7 +648,6 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
|
||||
</div>
|
||||
<span
|
||||
data-floating-ui-focus-guard=""
|
||||
data-floating-ui-inert=""
|
||||
data-type="inside"
|
||||
role="button"
|
||||
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user