Merge branch 'develop' into dbkr/key_backup_by_default
This commit is contained in:
commit
6bff653339
477 changed files with 5526 additions and 16820 deletions
|
@ -7,13 +7,13 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { EventTimelineSet, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { EventTimelineSet, PendingEventOrdering, Room, RoomEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { screen, render, waitFor } from "jest-matrix-react";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import FilePanel from "../../../../src/components/structures/FilePanel";
|
||||
import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { mkEvent, stubClient } from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
|
||||
jest.mock("matrix-js-sdk/src/matrix", () => ({
|
||||
|
@ -47,4 +47,43 @@ describe("FilePanel", () => {
|
|||
});
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("addEncryptedLiveEvent", () => {
|
||||
it("should add file msgtype event to filtered timelineSet", async () => {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const room = new Room("!room:server", cli, cli.getSafeUserId(), {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
cli.reEmitter.reEmit(room, [RoomEvent.Timeline]);
|
||||
const timelineSet = new EventTimelineSet(room);
|
||||
room.getOrCreateFilteredTimelineSet = jest.fn().mockReturnValue(timelineSet);
|
||||
mocked(cli.getRoom).mockReturnValue(room);
|
||||
|
||||
let filePanel: FilePanel | null;
|
||||
render(
|
||||
<FilePanel
|
||||
roomId={room.roomId}
|
||||
onClose={jest.fn()}
|
||||
resizeNotifier={new ResizeNotifier()}
|
||||
ref={(ref) => (filePanel = ref)}
|
||||
/>,
|
||||
);
|
||||
await screen.findByText("No files visible in this room");
|
||||
|
||||
const event = mkEvent({
|
||||
type: "m.room.message",
|
||||
user: cli.getSafeUserId(),
|
||||
room: room.roomId,
|
||||
content: {
|
||||
body: "hello",
|
||||
url: "mxc://matrix.org/1234",
|
||||
msgtype: "m.file",
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
filePanel!.addEncryptedLiveEvent(event);
|
||||
|
||||
expect(timelineSet.getLiveTimeline().getEvents()).toContain(event);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
import "core-js/stable/structured-clone";
|
||||
import "fake-indexeddb/auto";
|
||||
import React, { ComponentProps } from "react";
|
||||
import { fireEvent, render, RenderResult, screen, waitFor, within } from "jest-matrix-react";
|
||||
import { fireEvent, render, RenderResult, screen, waitFor, within, act } from "jest-matrix-react";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
import { Mocked, mocked } from "jest-mock";
|
||||
import { ClientEvent, MatrixClient, MatrixEvent, Room, SyncState } from "matrix-js-sdk/src/matrix";
|
||||
|
@ -44,7 +44,6 @@ import {
|
|||
} from "../../../test-utils";
|
||||
import * as leaveRoomUtils from "../../../../src/utils/leave-behaviour";
|
||||
import { OidcClientError } from "../../../../src/utils/oidc/error";
|
||||
import * as voiceBroadcastUtils from "../../../../src/voice-broadcast/utils/cleanUpBroadcasts";
|
||||
import LegacyCallHandler from "../../../../src/LegacyCallHandler";
|
||||
import { CallStore } from "../../../../src/stores/CallStore";
|
||||
import { Call } from "../../../../src/models/Call";
|
||||
|
@ -139,6 +138,7 @@ describe("<MatrixChat />", () => {
|
|||
globalBlacklistUnverifiedDevices: false,
|
||||
// This needs to not finish immediately because we need to test the screen appears
|
||||
bootstrapCrossSigning: jest.fn().mockImplementation(() => bootstrapDeferred.promise),
|
||||
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
|
||||
}),
|
||||
secretStorage: {
|
||||
isStored: jest.fn().mockReturnValue(null),
|
||||
|
@ -148,7 +148,6 @@ describe("<MatrixChat />", () => {
|
|||
whoami: jest.fn(),
|
||||
logout: jest.fn(),
|
||||
getDeviceId: jest.fn(),
|
||||
getKeyBackupVersion: jest.fn().mockResolvedValue(null),
|
||||
});
|
||||
let mockClient: Mocked<MatrixClient>;
|
||||
const serverConfig = {
|
||||
|
@ -162,8 +161,11 @@ describe("<MatrixChat />", () => {
|
|||
};
|
||||
let initPromise: Promise<void> | undefined;
|
||||
let defaultProps: ComponentProps<typeof MatrixChat>;
|
||||
const getComponent = (props: Partial<ComponentProps<typeof MatrixChat>> = {}) =>
|
||||
render(<MatrixChat {...defaultProps} {...props} />);
|
||||
const getComponent = (props: Partial<ComponentProps<typeof MatrixChat>> = {}) => {
|
||||
// MatrixChat does many questionable things which bomb tests in modern React mode,
|
||||
// we'll want to refactor and break up MatrixChat before turning off legacyRoot mode
|
||||
return render(<MatrixChat {...defaultProps} {...props} />, { legacyRoot: true });
|
||||
};
|
||||
|
||||
// make test results readable
|
||||
filterConsole(
|
||||
|
@ -201,7 +203,7 @@ describe("<MatrixChat />", () => {
|
|||
// we are logged in, but are still waiting for the /sync to complete
|
||||
await screen.findByText("Syncing…");
|
||||
// initial sync
|
||||
client.emit(ClientEvent.Sync, SyncState.Prepared, null);
|
||||
await act(() => client.emit(ClientEvent.Sync, SyncState.Prepared, null));
|
||||
}
|
||||
|
||||
// let things settle
|
||||
|
@ -263,7 +265,7 @@ describe("<MatrixChat />", () => {
|
|||
|
||||
// emit a loggedOut event so that all of the Store singletons forget about their references to the mock client
|
||||
// (must be sync otherwise the next test will start before it happens)
|
||||
defaultDispatcher.dispatch({ action: Action.OnLoggedOut }, true);
|
||||
act(() => defaultDispatcher.dispatch({ action: Action.OnLoggedOut }, true));
|
||||
|
||||
localStorage.clear();
|
||||
});
|
||||
|
@ -328,7 +330,7 @@ describe("<MatrixChat />", () => {
|
|||
|
||||
expect(within(dialog).getByText(errorMessage)).toBeInTheDocument();
|
||||
// just check we're back on welcome page
|
||||
await expect(await screen.findByTestId("mx_welcome_screen")).toBeInTheDocument();
|
||||
await expect(screen.findByTestId("mx_welcome_screen")).resolves.toBeInTheDocument();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -808,7 +810,6 @@ describe("<MatrixChat />", () => {
|
|||
jest.spyOn(LegacyCallHandler.instance, "hangupAllCalls")
|
||||
.mockClear()
|
||||
.mockImplementation(() => {});
|
||||
jest.spyOn(voiceBroadcastUtils, "cleanUpBroadcasts").mockImplementation(async () => {});
|
||||
jest.spyOn(PosthogAnalytics.instance, "logout").mockImplementation(() => {});
|
||||
jest.spyOn(EventIndexPeg, "deleteEventIndex").mockImplementation(async () => {});
|
||||
|
||||
|
@ -828,22 +829,12 @@ describe("<MatrixChat />", () => {
|
|||
jest.spyOn(logger, "warn").mockClear();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.spyOn(voiceBroadcastUtils, "cleanUpBroadcasts").mockRestore();
|
||||
});
|
||||
|
||||
it("should hangup all legacy calls", async () => {
|
||||
await getComponentAndWaitForReady();
|
||||
await dispatchLogoutAndWait();
|
||||
expect(LegacyCallHandler.instance.hangupAllCalls).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should cleanup broadcasts", async () => {
|
||||
await getComponentAndWaitForReady();
|
||||
await dispatchLogoutAndWait();
|
||||
expect(voiceBroadcastUtils.cleanUpBroadcasts).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should disconnect all calls", async () => {
|
||||
await getComponentAndWaitForReady();
|
||||
await dispatchLogoutAndWait();
|
||||
|
@ -956,9 +947,11 @@ describe("<MatrixChat />", () => {
|
|||
await screen.findByText("Powered by Matrix");
|
||||
|
||||
// go to login page
|
||||
defaultDispatcher.dispatch({
|
||||
action: "start_login",
|
||||
});
|
||||
act(() =>
|
||||
defaultDispatcher.dispatch({
|
||||
action: "start_login",
|
||||
}),
|
||||
);
|
||||
|
||||
await flushPromises();
|
||||
|
||||
|
@ -1129,7 +1122,9 @@ describe("<MatrixChat />", () => {
|
|||
|
||||
bootstrapDeferred.resolve();
|
||||
|
||||
await expect(await screen.findByRole("heading", { name: "You're in", level: 1 })).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByRole("heading", { name: "You're in", level: 1 }),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1398,7 +1393,9 @@ describe("<MatrixChat />", () => {
|
|||
|
||||
function simulateSessionLockClaim() {
|
||||
localStorage.setItem("react_sdk_session_lock_claimant", "testtest");
|
||||
window.dispatchEvent(new StorageEvent("storage", { key: "react_sdk_session_lock_claimant" }));
|
||||
act(() =>
|
||||
window.dispatchEvent(new StorageEvent("storage", { key: "react_sdk_session_lock_claimant" })),
|
||||
);
|
||||
}
|
||||
|
||||
it("after a session is restored", async () => {
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
|
||||
import { IRoomState } from "../../../../src/components/structures/RoomView";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../src/utils/beacon", () => ({
|
||||
useBeacon: jest.fn(),
|
||||
|
@ -91,9 +92,9 @@ describe("MessagePanel", function () {
|
|||
|
||||
const getComponent = (props = {}, roomContext: Partial<IRoomState> = {}) => (
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={{ ...defaultRoomContext, ...roomContext }}>
|
||||
<ScopedRoomContextProvider {...defaultRoomContext} {...roomContext}>
|
||||
<MessagePanel {...defaultProps} {...props} />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import React from "react";
|
|||
import { mocked, Mocked } from "jest-mock";
|
||||
import { screen, render, act, cleanup } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { MatrixClient, PendingEventOrdering, Room, MatrixEvent, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, PendingEventOrdering, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { Widget, ClientWidgetApi } from "matrix-widget-api";
|
||||
import { UserEvent } from "@testing-library/user-event/dist/types/setup/setup";
|
||||
|
||||
|
@ -26,7 +26,6 @@ import {
|
|||
wrapInSdkContext,
|
||||
mkRoomCreateEvent,
|
||||
mockPlatformPeg,
|
||||
flushPromises,
|
||||
useMockMediaDevices,
|
||||
} from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
|
@ -39,17 +38,7 @@ import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
|||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
import { ViewRoomPayload } from "../../../../src/dispatcher/payloads/ViewRoomPayload";
|
||||
import { TestSdkContext } from "../../TestSdkContext";
|
||||
import {
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastPlaybacksStore,
|
||||
VoiceBroadcastPreRecording,
|
||||
VoiceBroadcastPreRecordingStore,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../voice-broadcast/utils/test-utils";
|
||||
import { RoomViewStore } from "../../../../src/stores/RoomViewStore";
|
||||
import { IRoomStateEventsActionPayload } from "../../../../src/actions/MatrixActionCreators";
|
||||
import { Container, WidgetLayoutStore } from "../../../../src/stores/widgets/WidgetLayoutStore";
|
||||
import WidgetStore from "../../../../src/stores/WidgetStore";
|
||||
import { WidgetType } from "../../../../src/widgets/WidgetType";
|
||||
|
@ -76,15 +65,6 @@ describe("PipContainer", () => {
|
|||
let room: Room;
|
||||
let room2: Room;
|
||||
let alice: RoomMember;
|
||||
let voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore;
|
||||
let voiceBroadcastPreRecordingStore: VoiceBroadcastPreRecordingStore;
|
||||
let voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore;
|
||||
|
||||
const actFlushPromises = async () => {
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
useMockMediaDevices();
|
||||
|
@ -127,13 +107,7 @@ describe("PipContainer", () => {
|
|||
sdkContext = new TestSdkContext();
|
||||
// @ts-ignore PipContainer uses SDKContext in the constructor
|
||||
SdkContextClass.instance = sdkContext;
|
||||
voiceBroadcastRecordingsStore = new VoiceBroadcastRecordingsStore();
|
||||
voiceBroadcastPreRecordingStore = new VoiceBroadcastPreRecordingStore();
|
||||
voiceBroadcastPlaybacksStore = new VoiceBroadcastPlaybacksStore(voiceBroadcastRecordingsStore);
|
||||
sdkContext.client = client;
|
||||
sdkContext._VoiceBroadcastRecordingsStore = voiceBroadcastRecordingsStore;
|
||||
sdkContext._VoiceBroadcastPreRecordingStore = voiceBroadcastPreRecordingStore;
|
||||
sdkContext._VoiceBroadcastPlaybacksStore = voiceBroadcastPlaybacksStore;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
@ -165,12 +139,12 @@ describe("PipContainer", () => {
|
|||
if (!(call instanceof MockedCall)) throw new Error("Failed to create call");
|
||||
|
||||
const widget = new Widget(call.widget);
|
||||
WidgetStore.instance.addVirtualWidget(call.widget, room.roomId);
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
|
||||
stop: () => {},
|
||||
} as unknown as ClientWidgetApi);
|
||||
|
||||
await act(async () => {
|
||||
WidgetStore.instance.addVirtualWidget(call.widget, room.roomId);
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
|
||||
stop: () => {},
|
||||
} as unknown as ClientWidgetApi);
|
||||
|
||||
await call.start();
|
||||
ActiveWidgetStore.instance.setWidgetPersistence(widget.id, room.roomId, true);
|
||||
});
|
||||
|
@ -178,9 +152,11 @@ describe("PipContainer", () => {
|
|||
await fn(call);
|
||||
|
||||
cleanup();
|
||||
call.destroy();
|
||||
ActiveWidgetStore.instance.destroyPersistentWidget(widget.id, room.roomId);
|
||||
WidgetStore.instance.removeVirtualWidget(widget.id, room.roomId);
|
||||
act(() => {
|
||||
call.destroy();
|
||||
ActiveWidgetStore.instance.destroyPersistentWidget(widget.id, room.roomId);
|
||||
WidgetStore.instance.removeVirtualWidget(widget.id, room.roomId);
|
||||
});
|
||||
};
|
||||
|
||||
const withWidget = async (fn: () => Promise<void>): Promise<void> => {
|
||||
|
@ -190,51 +166,10 @@ describe("PipContainer", () => {
|
|||
ActiveWidgetStore.instance.destroyPersistentWidget("1", room.roomId);
|
||||
};
|
||||
|
||||
const makeVoiceBroadcastInfoStateEvent = (): MatrixEvent => {
|
||||
return mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
alice.userId,
|
||||
client.getDeviceId() || "",
|
||||
);
|
||||
};
|
||||
|
||||
const setUpVoiceBroadcastRecording = () => {
|
||||
const infoEvent = makeVoiceBroadcastInfoStateEvent();
|
||||
const voiceBroadcastRecording = new VoiceBroadcastRecording(infoEvent, client);
|
||||
voiceBroadcastRecordingsStore.setCurrent(voiceBroadcastRecording);
|
||||
};
|
||||
|
||||
const setUpVoiceBroadcastPreRecording = () => {
|
||||
const voiceBroadcastPreRecording = new VoiceBroadcastPreRecording(
|
||||
room,
|
||||
alice,
|
||||
client,
|
||||
voiceBroadcastPlaybacksStore,
|
||||
voiceBroadcastRecordingsStore,
|
||||
);
|
||||
voiceBroadcastPreRecordingStore.setCurrent(voiceBroadcastPreRecording);
|
||||
};
|
||||
|
||||
const setUpRoomViewStore = () => {
|
||||
sdkContext._RoomViewStore = new RoomViewStore(defaultDispatcher, sdkContext);
|
||||
};
|
||||
|
||||
const mkVoiceBroadcast = (room: Room): MatrixEvent => {
|
||||
const infoEvent = makeVoiceBroadcastInfoStateEvent();
|
||||
room.currentState.setStateEvents([infoEvent]);
|
||||
defaultDispatcher.dispatch<IRoomStateEventsActionPayload>(
|
||||
{
|
||||
action: "MatrixActions.RoomState.events",
|
||||
event: infoEvent,
|
||||
state: room.currentState,
|
||||
lastStateEvent: null,
|
||||
},
|
||||
true,
|
||||
);
|
||||
return infoEvent;
|
||||
};
|
||||
|
||||
it("hides if there's no content", () => {
|
||||
renderPip();
|
||||
expect(screen.queryByRole("complementary")).toBeNull();
|
||||
|
@ -339,138 +274,4 @@ describe("PipContainer", () => {
|
|||
|
||||
WidgetStore.instance.removeVirtualWidget("1", room.roomId);
|
||||
});
|
||||
|
||||
describe("when there is a voice broadcast recording and pre-recording", () => {
|
||||
beforeEach(async () => {
|
||||
setUpVoiceBroadcastPreRecording();
|
||||
setUpVoiceBroadcastRecording();
|
||||
renderPip();
|
||||
await actFlushPromises();
|
||||
});
|
||||
|
||||
it("should render the voice broadcast recording PiP", () => {
|
||||
// check for the „Live“ badge to be present
|
||||
expect(screen.queryByText("Live")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("and a call it should show both, the call and the recording", async () => {
|
||||
await withCall(async () => {
|
||||
// Broadcast: Check for the „Live“ badge to be present
|
||||
expect(screen.queryByText("Live")).toBeInTheDocument();
|
||||
// Call: Check for the „Leave“ button to be present
|
||||
screen.getByRole("button", { name: "Leave" });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is a voice broadcast playback and pre-recording", () => {
|
||||
beforeEach(async () => {
|
||||
mkVoiceBroadcast(room);
|
||||
setUpVoiceBroadcastPreRecording();
|
||||
renderPip();
|
||||
await actFlushPromises();
|
||||
});
|
||||
|
||||
it("should render the voice broadcast pre-recording PiP", () => {
|
||||
// check for the „Go live“ button
|
||||
expect(screen.queryByText("Go live")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is a voice broadcast pre-recording", () => {
|
||||
beforeEach(async () => {
|
||||
setUpVoiceBroadcastPreRecording();
|
||||
renderPip();
|
||||
await actFlushPromises();
|
||||
});
|
||||
|
||||
it("should render the voice broadcast pre-recording PiP", () => {
|
||||
// check for the „Go live“ button
|
||||
expect(screen.queryByText("Go live")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when listening to a voice broadcast in a room and then switching to another room", () => {
|
||||
beforeEach(async () => {
|
||||
setUpRoomViewStore();
|
||||
viewRoom(room.roomId);
|
||||
mkVoiceBroadcast(room);
|
||||
await actFlushPromises();
|
||||
|
||||
expect(voiceBroadcastPlaybacksStore.getCurrent()).toBeTruthy();
|
||||
|
||||
await voiceBroadcastPlaybacksStore.getCurrent()?.start();
|
||||
viewRoom(room2.roomId);
|
||||
renderPip();
|
||||
});
|
||||
|
||||
it("should render the small voice broadcast playback PiP", () => {
|
||||
// check for the „pause voice broadcast“ button
|
||||
expect(screen.getByLabelText("pause voice broadcast")).toBeInTheDocument();
|
||||
// check for the absence of the „30s forward“ button
|
||||
expect(screen.queryByLabelText("30s forward")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when viewing a room with a live voice broadcast", () => {
|
||||
let startEvent!: MatrixEvent;
|
||||
|
||||
beforeEach(async () => {
|
||||
setUpRoomViewStore();
|
||||
viewRoom(room.roomId);
|
||||
startEvent = mkVoiceBroadcast(room);
|
||||
renderPip();
|
||||
await actFlushPromises();
|
||||
});
|
||||
|
||||
it("should render the voice broadcast playback pip", () => {
|
||||
// check for the „resume voice broadcast“ button
|
||||
expect(screen.queryByLabelText("play voice broadcast")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("and the broadcast stops", () => {
|
||||
beforeEach(async () => {
|
||||
const stopEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
alice.userId,
|
||||
client.getDeviceId() || "",
|
||||
startEvent,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
room.currentState.setStateEvents([stopEvent]);
|
||||
defaultDispatcher.dispatch<IRoomStateEventsActionPayload>(
|
||||
{
|
||||
action: "MatrixActions.RoomState.events",
|
||||
event: stopEvent,
|
||||
state: room.currentState,
|
||||
lastStateEvent: stopEvent,
|
||||
},
|
||||
true,
|
||||
);
|
||||
await flushPromises();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not render the voice broadcast playback pip", () => {
|
||||
// check for the „resume voice broadcast“ button
|
||||
expect(screen.queryByLabelText("play voice broadcast")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and leaving the room", () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
viewRoom(room2.roomId);
|
||||
await flushPromises();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not render the voice broadcast playback pip", () => {
|
||||
// check for the „resume voice broadcast“ button
|
||||
expect(screen.queryByLabelText("play voice broadcast")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -91,7 +91,7 @@ describe("RightPanel", () => {
|
|||
if (name !== "RightPanel.phases") return realGetValue(name, roomId);
|
||||
if (roomId === "r1") {
|
||||
return {
|
||||
history: [{ phase: RightPanelPhases.RoomMemberList }],
|
||||
history: [{ phase: RightPanelPhases.MemberList }],
|
||||
isOpen: true,
|
||||
};
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ describe("RightPanel", () => {
|
|||
await rpsUpdated;
|
||||
await waitFor(() => expect(screen.queryByTestId("spinner")).not.toBeInTheDocument());
|
||||
|
||||
// room one will be in the RoomMemberList phase - confirm this is rendered
|
||||
// room one will be in the MemberList phase - confirm this is rendered
|
||||
expect(container.getElementsByClassName("mx_MemberList")).toHaveLength(1);
|
||||
|
||||
// wait for RPS room 2 updates to fire, then rerender
|
||||
|
|
|
@ -10,27 +10,37 @@ import React, { createRef, RefObject } from "react";
|
|||
import { mocked, MockedObject } from "jest-mock";
|
||||
import {
|
||||
ClientEvent,
|
||||
EventTimeline,
|
||||
EventType,
|
||||
IEvent,
|
||||
JoinRule,
|
||||
MatrixClient,
|
||||
MatrixError,
|
||||
MatrixEvent,
|
||||
Room,
|
||||
RoomEvent,
|
||||
EventType,
|
||||
JoinRule,
|
||||
MatrixError,
|
||||
RoomStateEvent,
|
||||
MatrixEvent,
|
||||
SearchResult,
|
||||
IEvent,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { CryptoApi, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
import { CryptoApi, UserVerificationStatus, CryptoEvent } from "matrix-js-sdk/src/crypto-api";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { fireEvent, render, screen, RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
RenderResult,
|
||||
waitForElementToBeRemoved,
|
||||
waitFor,
|
||||
act,
|
||||
cleanup,
|
||||
} from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import {
|
||||
stubClient,
|
||||
mockPlatformPeg,
|
||||
unmockPlatformPeg,
|
||||
wrapInMatrixClientContext,
|
||||
flushPromises,
|
||||
mkEvent,
|
||||
setupAsyncStoreWithClient,
|
||||
|
@ -45,7 +55,7 @@ import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
|||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
||||
import { ViewRoomPayload } from "../../../../src/dispatcher/payloads/ViewRoomPayload";
|
||||
import { RoomView as _RoomView } from "../../../../src/components/structures/RoomView";
|
||||
import { RoomView } from "../../../../src/components/structures/RoomView";
|
||||
import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
|
@ -64,8 +74,8 @@ import WidgetStore from "../../../../src/stores/WidgetStore";
|
|||
import { ViewRoomErrorPayload } from "../../../../src/dispatcher/payloads/ViewRoomErrorPayload";
|
||||
import { SearchScope } from "../../../../src/Searching";
|
||||
import { MEGOLM_ENCRYPTION_ALGORITHM } from "../../../../src/utils/crypto";
|
||||
|
||||
const RoomView = wrapInMatrixClientContext(_RoomView);
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import { ViewUserPayload } from "../../../../src/dispatcher/payloads/ViewUserPayload.ts";
|
||||
|
||||
describe("RoomView", () => {
|
||||
let cli: MockedObject<MatrixClient>;
|
||||
|
@ -80,8 +90,7 @@ describe("RoomView", () => {
|
|||
|
||||
beforeEach(() => {
|
||||
mockPlatformPeg({ reload: () => {} });
|
||||
stubClient();
|
||||
cli = mocked(MatrixClientPeg.safeGet());
|
||||
cli = mocked(stubClient());
|
||||
|
||||
room = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org");
|
||||
jest.spyOn(room, "findPredecessor");
|
||||
|
@ -106,9 +115,10 @@ describe("RoomView", () => {
|
|||
afterEach(() => {
|
||||
unmockPlatformPeg();
|
||||
jest.clearAllMocks();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
const mountRoomView = async (ref?: RefObject<_RoomView>): Promise<RenderResult> => {
|
||||
const mountRoomView = async (ref?: RefObject<RoomView>): Promise<RenderResult> => {
|
||||
if (stores.roomViewStore.getRoomId() !== room.roomId) {
|
||||
const switchedRoom = new Promise<void>((resolve) => {
|
||||
const subFn = () => {
|
||||
|
@ -120,26 +130,30 @@ describe("RoomView", () => {
|
|||
stores.roomViewStore.on(UPDATE_EVENT, subFn);
|
||||
});
|
||||
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
act(() =>
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
metricsTrigger: undefined,
|
||||
}),
|
||||
);
|
||||
|
||||
await switchedRoom;
|
||||
}
|
||||
|
||||
const roomView = render(
|
||||
<SDKContext.Provider value={stores}>
|
||||
<RoomView
|
||||
// threepidInvite should be optional on RoomView props
|
||||
// it is treated as optional in RoomView
|
||||
threepidInvite={undefined as any}
|
||||
resizeNotifier={new ResizeNotifier()}
|
||||
forceTimeline={false}
|
||||
wrappedRef={ref as any}
|
||||
/>
|
||||
</SDKContext.Provider>,
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<SDKContext.Provider value={stores}>
|
||||
<RoomView
|
||||
// threepidInvite should be optional on RoomView props
|
||||
// it is treated as optional in RoomView
|
||||
threepidInvite={undefined as any}
|
||||
resizeNotifier={new ResizeNotifier()}
|
||||
forceTimeline={false}
|
||||
ref={ref}
|
||||
/>
|
||||
</SDKContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await flushPromises();
|
||||
return roomView;
|
||||
|
@ -167,33 +181,50 @@ describe("RoomView", () => {
|
|||
}
|
||||
|
||||
const roomView = render(
|
||||
<SDKContext.Provider value={stores}>
|
||||
<RoomView
|
||||
// threepidInvite should be optional on RoomView props
|
||||
// it is treated as optional in RoomView
|
||||
threepidInvite={undefined as any}
|
||||
resizeNotifier={new ResizeNotifier()}
|
||||
forceTimeline={false}
|
||||
onRegistered={jest.fn()}
|
||||
/>
|
||||
</SDKContext.Provider>,
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<SDKContext.Provider value={stores}>
|
||||
<RoomView
|
||||
// threepidInvite should be optional on RoomView props
|
||||
// it is treated as optional in RoomView
|
||||
threepidInvite={undefined as any}
|
||||
resizeNotifier={new ResizeNotifier()}
|
||||
forceTimeline={false}
|
||||
onRegistered={jest.fn()}
|
||||
/>
|
||||
</SDKContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await flushPromises();
|
||||
return roomView;
|
||||
};
|
||||
const getRoomViewInstance = async (): Promise<_RoomView> => {
|
||||
const ref = createRef<_RoomView>();
|
||||
const getRoomViewInstance = async (): Promise<RoomView> => {
|
||||
const ref = createRef<RoomView>();
|
||||
await mountRoomView(ref);
|
||||
return ref.current!;
|
||||
};
|
||||
|
||||
it("should show member list right panel phase on Action.ViewUser without `payload.member`", async () => {
|
||||
const spy = jest.spyOn(stores.rightPanelStore, "showOrHidePhase");
|
||||
await renderRoomView(false);
|
||||
|
||||
defaultDispatcher.dispatch<ViewUserPayload>(
|
||||
{
|
||||
action: Action.ViewUser,
|
||||
member: undefined,
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(RightPanelPhases.MemberList);
|
||||
});
|
||||
|
||||
it("when there is no room predecessor, getHiddenHighlightCount should return 0", async () => {
|
||||
const instance = await getRoomViewInstance();
|
||||
expect(instance.getHiddenHighlightCount()).toBe(0);
|
||||
});
|
||||
|
||||
describe("when there is an old room", () => {
|
||||
let instance: _RoomView;
|
||||
let instance: RoomView;
|
||||
let oldRoom: Room;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
@ -217,11 +248,11 @@ describe("RoomView", () => {
|
|||
|
||||
describe("and feature_dynamic_room_predecessors is enabled", () => {
|
||||
beforeEach(() => {
|
||||
instance.setState({ msc3946ProcessDynamicPredecessor: true });
|
||||
act(() => instance.setState({ msc3946ProcessDynamicPredecessor: true }));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.setState({ msc3946ProcessDynamicPredecessor: false });
|
||||
act(() => instance.setState({ msc3946ProcessDynamicPredecessor: false }));
|
||||
});
|
||||
|
||||
it("should pass the setting to findPredecessor", async () => {
|
||||
|
@ -233,8 +264,9 @@ describe("RoomView", () => {
|
|||
|
||||
it("updates url preview visibility on encryption state change", async () => {
|
||||
room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
|
||||
jest.spyOn(cli, "getCrypto").mockReturnValue(crypto);
|
||||
// we should be starting unencrypted
|
||||
expect(cli.isRoomEncrypted(room.roomId)).toEqual(false);
|
||||
expect(await cli.getCrypto()?.isEncryptionEnabledInRoom(room.roomId)).toEqual(false);
|
||||
|
||||
const roomViewInstance = await getRoomViewInstance();
|
||||
|
||||
|
@ -249,31 +281,74 @@ describe("RoomView", () => {
|
|||
expect(roomViewInstance.state.showUrlPreview).toBe(true);
|
||||
|
||||
// now enable encryption
|
||||
cli.isRoomEncrypted.mockReturnValue(true);
|
||||
jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
|
||||
// and fake an encryption event into the room to prompt it to re-check
|
||||
room.addLiveEvents([
|
||||
new MatrixEvent({
|
||||
type: "m.room.encryption",
|
||||
act(() => {
|
||||
const encryptionEvent = new MatrixEvent({
|
||||
type: EventType.RoomEncryption,
|
||||
sender: cli.getUserId()!,
|
||||
content: {},
|
||||
event_id: "someid",
|
||||
room_id: room.roomId,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
||||
cli.emit(RoomStateEvent.Events, encryptionEvent, roomState, null);
|
||||
});
|
||||
|
||||
// URL previews should now be disabled
|
||||
expect(roomViewInstance.state.showUrlPreview).toBe(false);
|
||||
await waitFor(() => expect(roomViewInstance.state.showUrlPreview).toBe(false));
|
||||
});
|
||||
|
||||
it("should not display the timeline when the room encryption is loading", async () => {
|
||||
jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
|
||||
jest.spyOn(cli, "getCrypto").mockReturnValue(crypto);
|
||||
const deferred = defer<boolean>();
|
||||
jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockImplementation(() => deferred.promise);
|
||||
|
||||
const { asFragment, container } = await mountRoomView();
|
||||
expect(container.querySelector(".mx_RoomView_messagePanel")).toBeNull();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
deferred.resolve(true);
|
||||
await waitFor(() => expect(container.querySelector(".mx_RoomView_messagePanel")).not.toBeNull());
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("updates live timeline when a timeline reset happens", async () => {
|
||||
const roomViewInstance = await getRoomViewInstance();
|
||||
const oldTimeline = roomViewInstance.state.liveTimeline;
|
||||
|
||||
room.getUnfilteredTimelineSet().resetLiveTimeline();
|
||||
act(() => room.getUnfilteredTimelineSet().resetLiveTimeline());
|
||||
expect(roomViewInstance.state.liveTimeline).not.toEqual(oldTimeline);
|
||||
});
|
||||
|
||||
it("should update when the e2e status when the user verification changed", async () => {
|
||||
room.currentState.setStateEvents([
|
||||
mkRoomMemberJoinEvent(cli.getSafeUserId(), room.roomId),
|
||||
mkRoomMemberJoinEvent("user@example.com", room.roomId),
|
||||
]);
|
||||
room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
|
||||
// Not all the calls to cli.isRoomEncrypted are migrated, so we need to mock both.
|
||||
mocked(cli.isRoomEncrypted).mockReturnValue(true);
|
||||
jest.spyOn(cli, "getCrypto").mockReturnValue(crypto);
|
||||
jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
jest.spyOn(cli.getCrypto()!, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false),
|
||||
);
|
||||
jest.spyOn(cli.getCrypto()!, "getUserDeviceInfo").mockResolvedValue(
|
||||
new Map([["user@example.com", new Map<string, any>()]]),
|
||||
);
|
||||
|
||||
const { container } = await renderRoomView();
|
||||
await waitFor(() => expect(container.querySelector(".mx_E2EIcon_normal")).toBeInTheDocument());
|
||||
|
||||
const verificationStatus = new UserVerificationStatus(true, true, false);
|
||||
jest.spyOn(cli.getCrypto()!, "getUserVerificationStatus").mockResolvedValue(verificationStatus);
|
||||
cli.emit(CryptoEvent.UserTrustStatusChanged, cli.getSafeUserId(), verificationStatus);
|
||||
await waitFor(() => expect(container.querySelector(".mx_E2EIcon_verified")).toBeInTheDocument());
|
||||
});
|
||||
|
||||
describe("with virtual rooms", () => {
|
||||
it("checks for a virtual room on initial load", async () => {
|
||||
const { container } = await renderRoomView();
|
||||
|
@ -287,7 +362,7 @@ describe("RoomView", () => {
|
|||
await renderRoomView();
|
||||
expect(VoipUserMapper.sharedInstance().getVirtualRoomForRoom).toHaveBeenCalledWith(room.roomId);
|
||||
|
||||
cli.emit(ClientEvent.Room, room);
|
||||
act(() => cli.emit(ClientEvent.Room, room));
|
||||
|
||||
// called again after room event
|
||||
expect(VoipUserMapper.sharedInstance().getVirtualRoomForRoom).toHaveBeenCalledTimes(2);
|
||||
|
@ -411,7 +486,8 @@ describe("RoomView", () => {
|
|||
]);
|
||||
jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(cli.getSafeUserId());
|
||||
jest.spyOn(DMRoomMap.shared(), "getRoomIds").mockReturnValue(new Set([room.roomId]));
|
||||
mocked(cli).isRoomEncrypted.mockReturnValue(true);
|
||||
jest.spyOn(cli, "getCrypto").mockReturnValue(crypto);
|
||||
jest.spyOn(cli.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
await renderRoomView();
|
||||
});
|
||||
|
||||
|
@ -429,6 +505,194 @@ describe("RoomView", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("should show error view if failed to look up room alias", async () => {
|
||||
const { asFragment, findByText } = await renderRoomView(false);
|
||||
|
||||
act(() =>
|
||||
defaultDispatcher.dispatch<ViewRoomErrorPayload>({
|
||||
action: Action.ViewRoomError,
|
||||
room_alias: "#addy:server",
|
||||
room_id: null,
|
||||
err: new MatrixError({ errcode: "M_NOT_FOUND" }),
|
||||
}),
|
||||
);
|
||||
await emitPromise(stores.roomViewStore, UPDATE_EVENT);
|
||||
|
||||
await findByText("Are you sure you're at the right place?");
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("knock rooms", () => {
|
||||
const client = createTestClient();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => setting === "feature_ask_to_join");
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Knock);
|
||||
jest.spyOn(defaultDispatcher, "dispatch");
|
||||
});
|
||||
|
||||
it("allows to request to join", async () => {
|
||||
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(client);
|
||||
jest.spyOn(client, "knockRoom").mockResolvedValue({ room_id: room.roomId });
|
||||
|
||||
await mountRoomView();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Request access" }));
|
||||
await untilDispatch(Action.SubmitAskToJoin, defaultDispatcher);
|
||||
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
|
||||
action: "submit_ask_to_join",
|
||||
roomId: room.roomId,
|
||||
opts: { reason: undefined },
|
||||
});
|
||||
});
|
||||
|
||||
it("allows to cancel a join request", async () => {
|
||||
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(client);
|
||||
jest.spyOn(client, "leave").mockResolvedValue({});
|
||||
jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Knock);
|
||||
|
||||
await mountRoomView();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Cancel request" }));
|
||||
await untilDispatch(Action.CancelAskToJoin, defaultDispatcher);
|
||||
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
|
||||
action: "cancel_ask_to_join",
|
||||
roomId: room.roomId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should close search results when edit is clicked", async () => {
|
||||
room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
|
||||
|
||||
const eventMapper = (obj: Partial<IEvent>) => new MatrixEvent(obj);
|
||||
|
||||
const roomViewRef = createRef<RoomView>();
|
||||
const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef);
|
||||
await waitFor(() => expect(roomViewRef.current).toBeTruthy());
|
||||
// @ts-ignore - triggering a search organically is a lot of work
|
||||
act(() =>
|
||||
roomViewRef.current!.setState({
|
||||
search: {
|
||||
searchId: 1,
|
||||
roomId: room.roomId,
|
||||
term: "search term",
|
||||
scope: SearchScope.Room,
|
||||
promise: Promise.resolve({
|
||||
results: [
|
||||
SearchResult.fromJson(
|
||||
{
|
||||
rank: 1,
|
||||
result: {
|
||||
content: {
|
||||
body: "search term",
|
||||
msgtype: "m.text",
|
||||
},
|
||||
type: "m.room.message",
|
||||
event_id: "$eventId",
|
||||
sender: cli.getSafeUserId(),
|
||||
origin_server_ts: 123456789,
|
||||
room_id: room.roomId,
|
||||
},
|
||||
context: {
|
||||
events_before: [],
|
||||
events_after: [],
|
||||
profile_info: {},
|
||||
},
|
||||
},
|
||||
eventMapper,
|
||||
),
|
||||
],
|
||||
highlights: [],
|
||||
count: 1,
|
||||
}),
|
||||
inProgress: false,
|
||||
count: 1,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector(".mx_RoomView_searchResultsPanel")).toBeVisible();
|
||||
});
|
||||
const prom = waitForElementToBeRemoved(() => container.querySelector(".mx_RoomView_searchResultsPanel"));
|
||||
|
||||
await userEvent.hover(getByText("search term"));
|
||||
await userEvent.click(await findByLabelText("Edit"));
|
||||
|
||||
await prom;
|
||||
});
|
||||
|
||||
it("should switch rooms when edit is clicked on a search result for a different room", async () => {
|
||||
const room2 = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org");
|
||||
rooms.set(room2.roomId, room2);
|
||||
|
||||
room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
|
||||
|
||||
const eventMapper = (obj: Partial<IEvent>) => new MatrixEvent(obj);
|
||||
|
||||
const roomViewRef = createRef<RoomView>();
|
||||
const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef);
|
||||
await waitFor(() => expect(roomViewRef.current).toBeTruthy());
|
||||
// @ts-ignore - triggering a search organically is a lot of work
|
||||
act(() =>
|
||||
roomViewRef.current!.setState({
|
||||
search: {
|
||||
searchId: 1,
|
||||
roomId: room.roomId,
|
||||
term: "search term",
|
||||
scope: SearchScope.All,
|
||||
promise: Promise.resolve({
|
||||
results: [
|
||||
SearchResult.fromJson(
|
||||
{
|
||||
rank: 1,
|
||||
result: {
|
||||
content: {
|
||||
body: "search term",
|
||||
msgtype: "m.text",
|
||||
},
|
||||
type: "m.room.message",
|
||||
event_id: "$eventId",
|
||||
sender: cli.getSafeUserId(),
|
||||
origin_server_ts: 123456789,
|
||||
room_id: room2.roomId,
|
||||
},
|
||||
context: {
|
||||
events_before: [],
|
||||
events_after: [],
|
||||
profile_info: {},
|
||||
},
|
||||
},
|
||||
eventMapper,
|
||||
),
|
||||
],
|
||||
highlights: [],
|
||||
count: 1,
|
||||
}),
|
||||
inProgress: false,
|
||||
count: 1,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector(".mx_RoomView_searchResultsPanel")).toBeVisible();
|
||||
});
|
||||
const prom = untilDispatch(Action.ViewRoom, defaultDispatcher);
|
||||
|
||||
await userEvent.hover(getByText("search term"));
|
||||
await userEvent.click(await findByLabelText("Edit"));
|
||||
|
||||
await expect(prom).resolves.toEqual(expect.objectContaining({ room_id: room2.roomId }));
|
||||
});
|
||||
|
||||
it("fires Action.RoomLoaded", async () => {
|
||||
jest.spyOn(defaultDispatcher, "dispatch");
|
||||
await mountRoomView();
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.RoomLoaded });
|
||||
});
|
||||
|
||||
describe("when there is a RoomView", () => {
|
||||
const widget1Id = "widget1";
|
||||
const widget2Id = "widget2";
|
||||
|
@ -449,7 +713,7 @@ describe("RoomView", () => {
|
|||
skey: id,
|
||||
ts,
|
||||
});
|
||||
room.addLiveEvents([widgetEvent]);
|
||||
room.addLiveEvents([widgetEvent], { addToState: false });
|
||||
room.currentState.setStateEvents([widgetEvent]);
|
||||
cli.emit(RoomStateEvent.Events, widgetEvent, room.currentState, null);
|
||||
await flushPromises();
|
||||
|
@ -514,184 +778,4 @@ describe("RoomView", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should show error view if failed to look up room alias", async () => {
|
||||
const { asFragment, findByText } = await renderRoomView(false);
|
||||
|
||||
defaultDispatcher.dispatch<ViewRoomErrorPayload>({
|
||||
action: Action.ViewRoomError,
|
||||
room_alias: "#addy:server",
|
||||
room_id: null,
|
||||
err: new MatrixError({ errcode: "M_NOT_FOUND" }),
|
||||
});
|
||||
await emitPromise(stores.roomViewStore, UPDATE_EVENT);
|
||||
|
||||
await findByText("Are you sure you're at the right place?");
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("knock rooms", () => {
|
||||
const client = createTestClient();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => setting === "feature_ask_to_join");
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Knock);
|
||||
jest.spyOn(defaultDispatcher, "dispatch");
|
||||
});
|
||||
|
||||
it("allows to request to join", async () => {
|
||||
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(client);
|
||||
jest.spyOn(client, "knockRoom").mockResolvedValue({ room_id: room.roomId });
|
||||
|
||||
await mountRoomView();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Request access" }));
|
||||
await untilDispatch(Action.SubmitAskToJoin, defaultDispatcher);
|
||||
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
|
||||
action: "submit_ask_to_join",
|
||||
roomId: room.roomId,
|
||||
opts: { reason: undefined },
|
||||
});
|
||||
});
|
||||
|
||||
it("allows to cancel a join request", async () => {
|
||||
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(client);
|
||||
jest.spyOn(client, "leave").mockResolvedValue({});
|
||||
jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Knock);
|
||||
|
||||
await mountRoomView();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Cancel request" }));
|
||||
await untilDispatch(Action.CancelAskToJoin, defaultDispatcher);
|
||||
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
|
||||
action: "cancel_ask_to_join",
|
||||
roomId: room.roomId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should close search results when edit is clicked", async () => {
|
||||
room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
|
||||
|
||||
const eventMapper = (obj: Partial<IEvent>) => new MatrixEvent(obj);
|
||||
|
||||
const roomViewRef = createRef<_RoomView>();
|
||||
const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef);
|
||||
// @ts-ignore - triggering a search organically is a lot of work
|
||||
roomViewRef.current!.setState({
|
||||
search: {
|
||||
searchId: 1,
|
||||
roomId: room.roomId,
|
||||
term: "search term",
|
||||
scope: SearchScope.Room,
|
||||
promise: Promise.resolve({
|
||||
results: [
|
||||
SearchResult.fromJson(
|
||||
{
|
||||
rank: 1,
|
||||
result: {
|
||||
content: {
|
||||
body: "search term",
|
||||
msgtype: "m.text",
|
||||
},
|
||||
type: "m.room.message",
|
||||
event_id: "$eventId",
|
||||
sender: cli.getSafeUserId(),
|
||||
origin_server_ts: 123456789,
|
||||
room_id: room.roomId,
|
||||
},
|
||||
context: {
|
||||
events_before: [],
|
||||
events_after: [],
|
||||
profile_info: {},
|
||||
},
|
||||
},
|
||||
eventMapper,
|
||||
),
|
||||
],
|
||||
highlights: [],
|
||||
count: 1,
|
||||
}),
|
||||
inProgress: false,
|
||||
count: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector(".mx_RoomView_searchResultsPanel")).toBeVisible();
|
||||
});
|
||||
const prom = waitForElementToBeRemoved(() => container.querySelector(".mx_RoomView_searchResultsPanel"));
|
||||
|
||||
await userEvent.hover(getByText("search term"));
|
||||
await userEvent.click(await findByLabelText("Edit"));
|
||||
|
||||
await prom;
|
||||
});
|
||||
|
||||
it("should switch rooms when edit is clicked on a search result for a different room", async () => {
|
||||
const room2 = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org");
|
||||
rooms.set(room2.roomId, room2);
|
||||
|
||||
room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
|
||||
|
||||
const eventMapper = (obj: Partial<IEvent>) => new MatrixEvent(obj);
|
||||
|
||||
const roomViewRef = createRef<_RoomView>();
|
||||
const { container, getByText, findByLabelText } = await mountRoomView(roomViewRef);
|
||||
// @ts-ignore - triggering a search organically is a lot of work
|
||||
roomViewRef.current!.setState({
|
||||
search: {
|
||||
searchId: 1,
|
||||
roomId: room.roomId,
|
||||
term: "search term",
|
||||
scope: SearchScope.All,
|
||||
promise: Promise.resolve({
|
||||
results: [
|
||||
SearchResult.fromJson(
|
||||
{
|
||||
rank: 1,
|
||||
result: {
|
||||
content: {
|
||||
body: "search term",
|
||||
msgtype: "m.text",
|
||||
},
|
||||
type: "m.room.message",
|
||||
event_id: "$eventId",
|
||||
sender: cli.getSafeUserId(),
|
||||
origin_server_ts: 123456789,
|
||||
room_id: room2.roomId,
|
||||
},
|
||||
context: {
|
||||
events_before: [],
|
||||
events_after: [],
|
||||
profile_info: {},
|
||||
},
|
||||
},
|
||||
eventMapper,
|
||||
),
|
||||
],
|
||||
highlights: [],
|
||||
count: 1,
|
||||
}),
|
||||
inProgress: false,
|
||||
count: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector(".mx_RoomView_searchResultsPanel")).toBeVisible();
|
||||
});
|
||||
const prom = untilDispatch(Action.ViewRoom, defaultDispatcher);
|
||||
|
||||
await userEvent.hover(getByText("search term"));
|
||||
await userEvent.click(await findByLabelText("Edit"));
|
||||
|
||||
await expect(prom).resolves.toEqual(expect.objectContaining({ room_id: room2.roomId }));
|
||||
});
|
||||
|
||||
it("fires Action.RoomLoaded", async () => {
|
||||
jest.spyOn(defaultDispatcher, "dispatch");
|
||||
await mountRoomView();
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.RoomLoaded });
|
||||
});
|
||||
});
|
||||
|
|
117
test/unit-tests/components/structures/SpaceRoomView-test.tsx
Normal file
117
test/unit-tests/components/structures/SpaceRoomView-test.tsx
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
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 { mocked, MockedObject } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { render, cleanup, screen, fireEvent } from "jest-matrix-react";
|
||||
|
||||
import { stubClient, mockPlatformPeg, unmockPlatformPeg, withClientContextRenderOptions } from "../../../test-utils";
|
||||
import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases";
|
||||
import SpaceRoomView from "../../../../src/components/structures/SpaceRoomView.tsx";
|
||||
import ResizeNotifier from "../../../../src/utils/ResizeNotifier.ts";
|
||||
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks.ts";
|
||||
import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore.ts";
|
||||
import DMRoomMap from "../../../../src/utils/DMRoomMap.ts";
|
||||
|
||||
describe("SpaceRoomView", () => {
|
||||
let cli: MockedObject<MatrixClient>;
|
||||
let space: Room;
|
||||
|
||||
beforeEach(() => {
|
||||
mockPlatformPeg({ reload: () => {} });
|
||||
cli = mocked(stubClient());
|
||||
|
||||
space = new Room(`!space:example.org`, cli, cli.getSafeUserId());
|
||||
space.currentState.setStateEvents([
|
||||
new MatrixEvent({
|
||||
type: "m.room.create",
|
||||
room_id: space.roomId,
|
||||
sender: cli.getSafeUserId(),
|
||||
state_key: "",
|
||||
content: {
|
||||
creator: cli.getSafeUserId(),
|
||||
type: "m.space",
|
||||
},
|
||||
}),
|
||||
new MatrixEvent({
|
||||
type: "m.room.member",
|
||||
room_id: space.roomId,
|
||||
sender: cli.getSafeUserId(),
|
||||
state_key: cli.getSafeUserId(),
|
||||
content: {
|
||||
membership: "join",
|
||||
},
|
||||
}),
|
||||
new MatrixEvent({
|
||||
type: "m.room.member",
|
||||
room_id: space.roomId,
|
||||
sender: "@userA:server",
|
||||
state_key: "@userA:server",
|
||||
content: {
|
||||
membership: "join",
|
||||
},
|
||||
}),
|
||||
new MatrixEvent({
|
||||
type: "m.room.member",
|
||||
room_id: space.roomId,
|
||||
sender: "@userB:server",
|
||||
state_key: "@userB:server",
|
||||
content: {
|
||||
membership: "join",
|
||||
},
|
||||
}),
|
||||
new MatrixEvent({
|
||||
type: "m.room.member",
|
||||
room_id: space.roomId,
|
||||
sender: "@userC:server",
|
||||
state_key: "@userC:server",
|
||||
content: {
|
||||
membership: "join",
|
||||
},
|
||||
}),
|
||||
]);
|
||||
space.updateMyMembership("join");
|
||||
|
||||
DMRoomMap.makeShared(cli);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unmockPlatformPeg();
|
||||
jest.clearAllMocks();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
const renderSpaceRoomView = async (): Promise<ReturnType<typeof render>> => {
|
||||
const resizeNotifier = new ResizeNotifier();
|
||||
const permalinkCreator = new RoomPermalinkCreator(space);
|
||||
|
||||
const spaceRoomView = render(
|
||||
<SpaceRoomView
|
||||
space={space}
|
||||
resizeNotifier={resizeNotifier}
|
||||
permalinkCreator={permalinkCreator}
|
||||
onJoinButtonClicked={jest.fn()}
|
||||
onRejectButtonClicked={jest.fn()}
|
||||
/>,
|
||||
withClientContextRenderOptions(cli),
|
||||
);
|
||||
return spaceRoomView;
|
||||
};
|
||||
|
||||
describe("SpaceLanding", () => {
|
||||
it("should show member list right panel phase on members click on landing", async () => {
|
||||
const spy = jest.spyOn(RightPanelStore.instance, "setCard");
|
||||
const { container } = await renderSpaceRoomView();
|
||||
|
||||
await expect(screen.findByText("Welcome to")).resolves.toBeVisible();
|
||||
fireEvent.click(container.querySelector(".mx_FacePile")!);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith({ phase: RightPanelPhases.MemberList });
|
||||
});
|
||||
});
|
||||
});
|
|
@ -20,7 +20,6 @@ import {
|
|||
|
||||
import ThreadPanel, { ThreadFilterType, ThreadPanelHeader } from "../../../../src/components/structures/ThreadPanel";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext from "../../../../src/contexts/RoomContext";
|
||||
import { _t } from "../../../../src/languageHandler";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
||||
|
@ -28,6 +27,7 @@ import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
|
|||
import { createTestClient, getRoomContext, mkRoom, mockPlatformPeg, stubClient } from "../../../test-utils";
|
||||
import { mkThread } from "../../../test-utils/threads";
|
||||
import { IRoomState } from "../../../../src/components/structures/RoomView";
|
||||
import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../src/utils/Feedback");
|
||||
|
||||
|
@ -81,11 +81,11 @@ describe("ThreadPanel", () => {
|
|||
room: mockRoom,
|
||||
} as unknown as IRoomState;
|
||||
const { container } = render(
|
||||
<RoomContext.Provider value={roomContextObject}>
|
||||
<ScopedRoomContextProvider {...roomContextObject}>
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<ThreadPanelHeader filterOption={ThreadFilterType.All} setFilterOption={() => undefined} />
|
||||
</MatrixClientContext.Provider>
|
||||
</RoomContext.Provider>,
|
||||
</ScopedRoomContextProvider>,
|
||||
);
|
||||
fireEvent.click(getByRole(container, "button", { name: "Mark all as read" }));
|
||||
await waitFor(() =>
|
||||
|
@ -114,8 +114,8 @@ describe("ThreadPanel", () => {
|
|||
|
||||
const TestThreadPanel = () => (
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider
|
||||
value={getRoomContext(room, {
|
||||
<ScopedRoomContextProvider
|
||||
{...getRoomContext(room, {
|
||||
canSendMessages: true,
|
||||
})}
|
||||
>
|
||||
|
@ -125,7 +125,7 @@ describe("ThreadPanel", () => {
|
|||
resizeNotifier={new ResizeNotifier()}
|
||||
permalinkCreator={new RoomPermalinkCreator(room)}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
|
||||
|
@ -209,40 +209,39 @@ describe("ThreadPanel", () => {
|
|||
return event ? Promise.resolve(event) : Promise.reject();
|
||||
});
|
||||
const [allThreads, myThreads] = room.threadsTimelineSets;
|
||||
allThreads!.addLiveEvent(otherThread.rootEvent);
|
||||
allThreads!.addLiveEvent(mixedThread.rootEvent);
|
||||
allThreads!.addLiveEvent(ownThread.rootEvent);
|
||||
myThreads!.addLiveEvent(mixedThread.rootEvent);
|
||||
myThreads!.addLiveEvent(ownThread.rootEvent);
|
||||
allThreads!.addLiveEvent(otherThread.rootEvent, { addToState: true });
|
||||
allThreads!.addLiveEvent(mixedThread.rootEvent, { addToState: true });
|
||||
allThreads!.addLiveEvent(ownThread.rootEvent, { addToState: true });
|
||||
myThreads!.addLiveEvent(mixedThread.rootEvent, { addToState: true });
|
||||
myThreads!.addLiveEvent(ownThread.rootEvent, { addToState: true });
|
||||
|
||||
let events: EventData[] = [];
|
||||
const renderResult = render(<TestThreadPanel />);
|
||||
await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
|
||||
await waitFor(() => {
|
||||
events = findEvents(renderResult.container);
|
||||
expect(findEvents(renderResult.container)).toHaveLength(3);
|
||||
const events = findEvents(renderResult.container);
|
||||
expect(events).toHaveLength(3);
|
||||
expect(events[0]).toEqual(toEventData(otherThread.rootEvent));
|
||||
expect(events[1]).toEqual(toEventData(mixedThread.rootEvent));
|
||||
expect(events[2]).toEqual(toEventData(ownThread.rootEvent));
|
||||
});
|
||||
expect(events[0]).toEqual(toEventData(otherThread.rootEvent));
|
||||
expect(events[1]).toEqual(toEventData(mixedThread.rootEvent));
|
||||
expect(events[2]).toEqual(toEventData(ownThread.rootEvent));
|
||||
await waitFor(() => expect(renderResult.container.querySelector(".mx_ThreadPanel_dropdown")).toBeTruthy());
|
||||
toggleThreadFilter(renderResult.container, ThreadFilterType.My);
|
||||
await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
|
||||
await waitFor(() => {
|
||||
events = findEvents(renderResult.container);
|
||||
expect(findEvents(renderResult.container)).toHaveLength(2);
|
||||
const events = findEvents(renderResult.container);
|
||||
expect(events).toHaveLength(2);
|
||||
expect(events[0]).toEqual(toEventData(mixedThread.rootEvent));
|
||||
expect(events[1]).toEqual(toEventData(ownThread.rootEvent));
|
||||
});
|
||||
expect(events[0]).toEqual(toEventData(mixedThread.rootEvent));
|
||||
expect(events[1]).toEqual(toEventData(ownThread.rootEvent));
|
||||
toggleThreadFilter(renderResult.container, ThreadFilterType.All);
|
||||
await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
|
||||
await waitFor(() => {
|
||||
events = findEvents(renderResult.container);
|
||||
expect(findEvents(renderResult.container)).toHaveLength(3);
|
||||
const events = findEvents(renderResult.container);
|
||||
expect(events).toHaveLength(3);
|
||||
expect(events[0]).toEqual(toEventData(otherThread.rootEvent));
|
||||
expect(events[1]).toEqual(toEventData(mixedThread.rootEvent));
|
||||
expect(events[2]).toEqual(toEventData(ownThread.rootEvent));
|
||||
});
|
||||
expect(events[0]).toEqual(toEventData(otherThread.rootEvent));
|
||||
expect(events[1]).toEqual(toEventData(mixedThread.rootEvent));
|
||||
expect(events[2]).toEqual(toEventData(ownThread.rootEvent));
|
||||
});
|
||||
|
||||
it("correctly filters Thread List with a single, unparticipated thread", async () => {
|
||||
|
@ -259,30 +258,29 @@ describe("ThreadPanel", () => {
|
|||
return event ? Promise.resolve(event) : Promise.reject();
|
||||
});
|
||||
const [allThreads] = room.threadsTimelineSets;
|
||||
allThreads!.addLiveEvent(otherThread.rootEvent);
|
||||
allThreads!.addLiveEvent(otherThread.rootEvent, { addToState: true });
|
||||
|
||||
let events: EventData[] = [];
|
||||
const renderResult = render(<TestThreadPanel />);
|
||||
await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
|
||||
await waitFor(() => {
|
||||
events = findEvents(renderResult.container);
|
||||
expect(findEvents(renderResult.container)).toHaveLength(1);
|
||||
const events = findEvents(renderResult.container);
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0]).toEqual(toEventData(otherThread.rootEvent));
|
||||
});
|
||||
expect(events[0]).toEqual(toEventData(otherThread.rootEvent));
|
||||
await waitFor(() => expect(renderResult.container.querySelector(".mx_ThreadPanel_dropdown")).toBeTruthy());
|
||||
toggleThreadFilter(renderResult.container, ThreadFilterType.My);
|
||||
await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
|
||||
await waitFor(() => {
|
||||
events = findEvents(renderResult.container);
|
||||
expect(findEvents(renderResult.container)).toHaveLength(0);
|
||||
const events = findEvents(renderResult.container);
|
||||
expect(events).toHaveLength(0);
|
||||
});
|
||||
toggleThreadFilter(renderResult.container, ThreadFilterType.All);
|
||||
await waitFor(() => expect(renderResult.container.querySelector(".mx_AutoHideScrollbar")).toBeFalsy());
|
||||
await waitFor(() => {
|
||||
events = findEvents(renderResult.container);
|
||||
expect(findEvents(renderResult.container)).toHaveLength(1);
|
||||
const events = findEvents(renderResult.container);
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0]).toEqual(toEventData(otherThread.rootEvent));
|
||||
});
|
||||
expect(events[0]).toEqual(toEventData(otherThread.rootEvent));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,6 @@ import React, { useState } from "react";
|
|||
|
||||
import ThreadView from "../../../../src/components/structures/ThreadView";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext from "../../../../src/contexts/RoomContext";
|
||||
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
import dispatcher from "../../../../src/dispatcher/dispatcher";
|
||||
|
@ -34,6 +33,7 @@ import { mockPlatformPeg } from "../../../test-utils/platform";
|
|||
import { getRoomContext } from "../../../test-utils/room";
|
||||
import { mkMessage, stubClient } from "../../../test-utils/test-utils";
|
||||
import { mkThread } from "../../../test-utils/threads";
|
||||
import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
describe("ThreadView", () => {
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
|
@ -51,8 +51,8 @@ describe("ThreadView", () => {
|
|||
|
||||
return (
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider
|
||||
value={getRoomContext(room, {
|
||||
<ScopedRoomContextProvider
|
||||
{...getRoomContext(room, {
|
||||
canSendMessages: true,
|
||||
})}
|
||||
>
|
||||
|
@ -63,7 +63,7 @@ describe("ThreadView", () => {
|
|||
initialEvent={initialEvent}
|
||||
resizeNotifier={new ResizeNotifier()}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
,
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
|
|
|
@ -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, waitFor, screen } from "jest-matrix-react";
|
||||
import { render, waitFor, screen, act, cleanup } from "jest-matrix-react";
|
||||
import {
|
||||
ReceiptType,
|
||||
EventTimelineSet,
|
||||
|
@ -28,7 +28,7 @@ import {
|
|||
ThreadFilterType,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import React, { createRef } from "react";
|
||||
import React from "react";
|
||||
import { Mocked, mocked } from "jest-mock";
|
||||
import { forEachRight } from "lodash";
|
||||
|
||||
|
@ -66,7 +66,7 @@ const mkTimeline = (room: Room, events: MatrixEvent[]): [EventTimeline, EventTim
|
|||
getPendingEvents: () => [] as MatrixEvent[],
|
||||
} as unknown as EventTimelineSet;
|
||||
const timeline = new EventTimeline(timelineSet);
|
||||
events.forEach((event) => timeline.addEvent(event, { toStartOfTimeline: false }));
|
||||
events.forEach((event) => timeline.addEvent(event, { toStartOfTimeline: false, addToState: true }));
|
||||
|
||||
return [timeline, timelineSet];
|
||||
};
|
||||
|
@ -150,9 +150,11 @@ const setupPagination = (
|
|||
mocked(client).paginateEventTimeline.mockImplementation(async (tl, { backwards }) => {
|
||||
if (tl === timeline) {
|
||||
if (backwards) {
|
||||
forEachRight(previousPage ?? [], (event) => tl.addEvent(event, { toStartOfTimeline: true }));
|
||||
forEachRight(previousPage ?? [], (event) =>
|
||||
tl.addEvent(event, { toStartOfTimeline: true, addToState: true }),
|
||||
);
|
||||
} else {
|
||||
(nextPage ?? []).forEach((event) => tl.addEvent(event, { toStartOfTimeline: false }));
|
||||
(nextPage ?? []).forEach((event) => tl.addEvent(event, { toStartOfTimeline: false, addToState: true }));
|
||||
}
|
||||
// Prevent any further pagination attempts in this direction
|
||||
tl.setPaginationToken(null, backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS);
|
||||
|
@ -178,7 +180,7 @@ describe("TimelinePanel", () => {
|
|||
const roomId = "#room:example.com";
|
||||
let room: Room;
|
||||
let timelineSet: EventTimelineSet;
|
||||
let timelinePanel: TimelinePanel;
|
||||
let timelinePanel: TimelinePanel | null = null;
|
||||
|
||||
const ev1 = new MatrixEvent({
|
||||
event_id: "ev1",
|
||||
|
@ -197,17 +199,16 @@ describe("TimelinePanel", () => {
|
|||
});
|
||||
|
||||
const renderTimelinePanel = async (): Promise<void> => {
|
||||
const ref = createRef<TimelinePanel>();
|
||||
render(
|
||||
<TimelinePanel
|
||||
timelineSet={timelineSet}
|
||||
manageReadMarkers={true}
|
||||
manageReadReceipts={true}
|
||||
ref={ref}
|
||||
ref={(ref) => (timelinePanel = ref)}
|
||||
/>,
|
||||
);
|
||||
await flushPromises();
|
||||
timelinePanel = ref.current!;
|
||||
await waitFor(() => expect(timelinePanel).toBeTruthy());
|
||||
};
|
||||
|
||||
const setUpTimelineSet = (threadRoot?: MatrixEvent) => {
|
||||
|
@ -232,8 +233,9 @@ describe("TimelinePanel", () => {
|
|||
room = new Room(roomId, client, userId, { pendingEventOrdering: PendingEventOrdering.Detached });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
afterEach(async () => {
|
||||
TimelinePanel.roomReadMarkerTsMap = {};
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it("when there is no event, it should not send any receipt", async () => {
|
||||
|
@ -256,9 +258,8 @@ describe("TimelinePanel", () => {
|
|||
describe("and reading the timeline", () => {
|
||||
beforeEach(async () => {
|
||||
await renderTimelinePanel();
|
||||
timelineSet.addLiveEvent(ev1, {});
|
||||
timelineSet.addLiveEvent(ev1, { addToState: true });
|
||||
await flushPromises();
|
||||
|
||||
// @ts-ignore
|
||||
await timelinePanel.sendReadReceipts();
|
||||
// @ts-ignore Simulate user activity by calling updateReadMarker on the TimelinePanel.
|
||||
|
@ -276,7 +277,7 @@ describe("TimelinePanel", () => {
|
|||
client.setRoomReadMarkers.mockClear();
|
||||
|
||||
// @ts-ignore Simulate user activity by calling updateReadMarker on the TimelinePanel.
|
||||
await timelinePanel.updateReadMarker();
|
||||
await act(() => timelinePanel.updateReadMarker());
|
||||
});
|
||||
|
||||
it("should not send receipts again", () => {
|
||||
|
@ -285,13 +286,13 @@ describe("TimelinePanel", () => {
|
|||
});
|
||||
|
||||
it("and forgetting the read markers, should send the stored marker again", async () => {
|
||||
timelineSet.addLiveEvent(ev2, {});
|
||||
timelineSet.addLiveEvent(ev2, { addToState: true });
|
||||
// Add the event to the room as well as the timeline, so we can find it when we
|
||||
// call findEventById in getEventReadUpTo. This is odd because in our test
|
||||
// setup, timelineSet is not actually the timelineSet of the room.
|
||||
await room.addLiveEvents([ev2], {});
|
||||
await room.addLiveEvents([ev2], { addToState: true });
|
||||
room.addEphemeralEvents([newReceipt(ev2.getId()!, userId, 222, 200)]);
|
||||
await timelinePanel.forgetReadMarker();
|
||||
await timelinePanel!.forgetReadMarker();
|
||||
expect(client.setRoomReadMarkers).toHaveBeenCalledWith(roomId, ev2.getId());
|
||||
});
|
||||
});
|
||||
|
@ -315,7 +316,7 @@ describe("TimelinePanel", () => {
|
|||
|
||||
it("should send a fully read marker and a private receipt", async () => {
|
||||
await renderTimelinePanel();
|
||||
timelineSet.addLiveEvent(ev1, {});
|
||||
act(() => timelineSet.addLiveEvent(ev1, { addToState: true }));
|
||||
await flushPromises();
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -326,6 +327,7 @@ describe("TimelinePanel", () => {
|
|||
// Expect the fully_read marker not to be send yet
|
||||
expect(client.setRoomReadMarkers).not.toHaveBeenCalled();
|
||||
|
||||
await flushPromises();
|
||||
client.sendReadReceipt.mockClear();
|
||||
|
||||
// @ts-ignore simulate user activity
|
||||
|
@ -334,7 +336,7 @@ describe("TimelinePanel", () => {
|
|||
// It should not send the receipt again.
|
||||
expect(client.sendReadReceipt).not.toHaveBeenCalledWith(ev1, ReceiptType.ReadPrivate);
|
||||
// Expect the fully_read marker to be sent after user activity.
|
||||
expect(client.setRoomReadMarkers).toHaveBeenCalledWith(roomId, ev1.getId());
|
||||
await waitFor(() => expect(client.setRoomReadMarkers).toHaveBeenCalledWith(roomId, ev1.getId()));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -361,11 +363,11 @@ describe("TimelinePanel", () => {
|
|||
|
||||
it("should send receipts but no fully_read when reading the thread timeline", async () => {
|
||||
await renderTimelinePanel();
|
||||
timelineSet.addLiveEvent(threadEv1, {});
|
||||
act(() => timelineSet.addLiveEvent(threadEv1, { addToState: true }));
|
||||
await flushPromises();
|
||||
|
||||
// @ts-ignore
|
||||
await timelinePanel.sendReadReceipts();
|
||||
await act(() => timelinePanel.sendReadReceipts());
|
||||
|
||||
// fully_read is not supported for threads per spec
|
||||
expect(client.setRoomReadMarkers).not.toHaveBeenCalled();
|
||||
|
@ -871,7 +873,9 @@ describe("TimelinePanel", () => {
|
|||
// @ts-ignore
|
||||
thread.fetchEditsWhereNeeded = () => Promise.resolve();
|
||||
await thread.addEvent(reply1, false, true);
|
||||
await allThreads.getLiveTimeline().addEvent(thread.rootEvent!, { toStartOfTimeline: true });
|
||||
await allThreads
|
||||
.getLiveTimeline()
|
||||
.addEvent(thread.rootEvent!, { toStartOfTimeline: true, addToState: true });
|
||||
const replyToEvent = jest.spyOn(thread, "replyToEvent", "get");
|
||||
|
||||
const dom = render(
|
||||
|
@ -907,7 +911,9 @@ describe("TimelinePanel", () => {
|
|||
// @ts-ignore
|
||||
realThread.fetchEditsWhereNeeded = () => Promise.resolve();
|
||||
await realThread.addEvent(reply1, true);
|
||||
await allThreads.getLiveTimeline().addEvent(realThread.rootEvent!, { toStartOfTimeline: true });
|
||||
await allThreads
|
||||
.getLiveTimeline()
|
||||
.addEvent(realThread.rootEvent!, { toStartOfTimeline: true, addToState: true });
|
||||
const replyToEvent = jest.spyOn(realThread, "replyToEvent", "get");
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -968,7 +974,9 @@ describe("TimelinePanel", () => {
|
|||
|
||||
events.push(rootEvent);
|
||||
|
||||
events.forEach((event) => timelineSet.getLiveTimeline().addEvent(event, { toStartOfTimeline: true }));
|
||||
events.forEach((event) =>
|
||||
timelineSet.getLiveTimeline().addEvent(event, { toStartOfTimeline: true, addToState: true }),
|
||||
);
|
||||
|
||||
const roomMembership = mkMembership({
|
||||
mship: KnownMembership.Join,
|
||||
|
@ -988,7 +996,10 @@ describe("TimelinePanel", () => {
|
|||
jest.spyOn(roomState, "getMember").mockReturnValue(member);
|
||||
|
||||
jest.spyOn(timelineSet.getLiveTimeline(), "getState").mockReturnValue(roomState);
|
||||
timelineSet.addEventToTimeline(roomMembership, timelineSet.getLiveTimeline(), { toStartOfTimeline: false });
|
||||
timelineSet.addEventToTimeline(roomMembership, timelineSet.getLiveTimeline(), {
|
||||
toStartOfTimeline: false,
|
||||
addToState: true,
|
||||
});
|
||||
|
||||
for (const event of events) {
|
||||
jest.spyOn(event, "isDecryptionFailure").mockReturnValue(true);
|
||||
|
@ -1021,7 +1032,7 @@ describe("TimelinePanel", () => {
|
|||
await waitFor(() => expectEvents(container, [events[1]]));
|
||||
});
|
||||
|
||||
defaultDispatcher.fire(Action.DumpDebugLogs);
|
||||
act(() => defaultDispatcher.fire(Action.DumpDebugLogs));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(spy).toHaveBeenCalledWith(expect.stringContaining("TimelinePanel(Room): Debugging info for roomId")),
|
||||
|
|
|
@ -7,20 +7,14 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, render, RenderResult, screen, waitFor } from "jest-matrix-react";
|
||||
import { DEVICE_CODE_SCOPE, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { render, screen, waitFor } from "jest-matrix-react";
|
||||
import { DEVICE_CODE_SCOPE, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
|
||||
import { mocked } from "jest-mock";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
|
||||
import UnwrappedUserMenu from "../../../../src/components/structures/UserMenu";
|
||||
import { stubClient, wrapInSdkContext } from "../../../test-utils";
|
||||
import {
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../voice-broadcast/utils/test-utils";
|
||||
import { TestSdkContext } from "../../TestSdkContext";
|
||||
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
||||
import LogoutDialog from "../../../../src/components/views/dialogs/LogoutDialog";
|
||||
|
@ -34,71 +28,12 @@ import { UserTab } from "../../../../src/components/views/dialogs/UserTab";
|
|||
|
||||
describe("<UserMenu>", () => {
|
||||
let client: MatrixClient;
|
||||
let renderResult: RenderResult;
|
||||
let sdkContext: TestSdkContext;
|
||||
|
||||
beforeEach(() => {
|
||||
sdkContext = new TestSdkContext();
|
||||
});
|
||||
|
||||
describe("<UserMenu> when video broadcast", () => {
|
||||
let voiceBroadcastInfoEvent: MatrixEvent;
|
||||
let voiceBroadcastRecording: VoiceBroadcastRecording;
|
||||
let voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore;
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
voiceBroadcastInfoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
"!room:example.com",
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId() || "",
|
||||
client.getDeviceId() || "",
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
voiceBroadcastRecordingsStore = new VoiceBroadcastRecordingsStore();
|
||||
sdkContext._VoiceBroadcastRecordingsStore = voiceBroadcastRecordingsStore;
|
||||
|
||||
voiceBroadcastRecording = new VoiceBroadcastRecording(voiceBroadcastInfoEvent, client);
|
||||
});
|
||||
|
||||
describe("when rendered", () => {
|
||||
beforeEach(() => {
|
||||
const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext);
|
||||
renderResult = render(<UserMenu isPanelCollapsed={true} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("and a live voice broadcast starts", () => {
|
||||
beforeEach(() => {
|
||||
act(() => {
|
||||
voiceBroadcastRecordingsStore.setCurrent(voiceBroadcastRecording);
|
||||
});
|
||||
});
|
||||
|
||||
it("should render the live voice broadcast avatar addon", () => {
|
||||
expect(renderResult.queryByTestId("user-menu-live-vb")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("and the broadcast ends", () => {
|
||||
beforeEach(() => {
|
||||
act(() => {
|
||||
voiceBroadcastRecordingsStore.clearCurrent();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not render the live voice broadcast avatar addon", () => {
|
||||
expect(renderResult.queryByTestId("user-menu-live-vb")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("<UserMenu> logout", () => {
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
|
@ -106,7 +41,7 @@ describe("<UserMenu>", () => {
|
|||
|
||||
it("should logout directly if no crypto", async () => {
|
||||
const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext);
|
||||
renderResult = render(<UserMenu isPanelCollapsed={true} />);
|
||||
render(<UserMenu isPanelCollapsed={true} />);
|
||||
|
||||
mocked(client.getRooms).mockReturnValue([
|
||||
{
|
||||
|
@ -128,7 +63,7 @@ describe("<UserMenu>", () => {
|
|||
|
||||
it("should logout directly if no encrypted rooms", async () => {
|
||||
const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext);
|
||||
renderResult = render(<UserMenu isPanelCollapsed={true} />);
|
||||
render(<UserMenu isPanelCollapsed={true} />);
|
||||
|
||||
mocked(client.getRooms).mockReturnValue([
|
||||
{
|
||||
|
@ -152,7 +87,7 @@ describe("<UserMenu>", () => {
|
|||
|
||||
it("should show dialog if some encrypted rooms", async () => {
|
||||
const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext);
|
||||
renderResult = render(<UserMenu isPanelCollapsed={true} />);
|
||||
render(<UserMenu isPanelCollapsed={true} />);
|
||||
|
||||
mocked(client.getRooms).mockReturnValue([
|
||||
{
|
||||
|
|
|
@ -180,11 +180,11 @@ exports[`<MatrixChat /> Multi-tab lockout waits for other tab to stop during sta
|
|||
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"
|
||||
|
@ -357,11 +357,11 @@ exports[`<MatrixChat /> with a soft-logged-out session should show the soft-logo
|
|||
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"
|
||||
|
|
|
@ -62,7 +62,7 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
|
|||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
aria-labelledby=":rbc:"
|
||||
aria-labelledby=":rg4:"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
|
@ -78,7 +78,7 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
|
|||
<button
|
||||
aria-disabled="false"
|
||||
aria-label="Voice call"
|
||||
aria-labelledby=":rbh:"
|
||||
aria-labelledby=":rg9:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -103,7 +103,7 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
|
|||
</button>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
aria-labelledby=":rbm:"
|
||||
aria-labelledby=":rge:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -128,7 +128,7 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
|
|||
</button>
|
||||
<button
|
||||
aria-label="Threads"
|
||||
aria-labelledby=":rbr:"
|
||||
aria-labelledby=":rgj:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -157,7 +157,7 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
|
|||
>
|
||||
<div
|
||||
aria-label="2 members"
|
||||
aria-labelledby=":rc0:"
|
||||
aria-labelledby=":rgo:"
|
||||
class="mx_AccessibleButton mx_FacePile"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
|
@ -280,7 +280,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
|
|||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
aria-labelledby=":rca:"
|
||||
aria-labelledby=":rh2:"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
|
@ -296,7 +296,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
|
|||
<button
|
||||
aria-disabled="false"
|
||||
aria-label="Voice call"
|
||||
aria-labelledby=":rcf:"
|
||||
aria-labelledby=":rh7:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -321,7 +321,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
|
|||
</button>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
aria-labelledby=":rck:"
|
||||
aria-labelledby=":rhc:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -346,7 +346,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
|
|||
</button>
|
||||
<button
|
||||
aria-label="Threads"
|
||||
aria-labelledby=":rcp:"
|
||||
aria-labelledby=":rhh:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -375,7 +375,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
|
|||
>
|
||||
<div
|
||||
aria-label="2 members"
|
||||
aria-labelledby=":rcu:"
|
||||
aria-labelledby=":rhm:"
|
||||
class="mx_AccessibleButton mx_FacePile"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
|
@ -583,7 +583,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
|||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
aria-labelledby=":r70:"
|
||||
aria-labelledby=":rbo:"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
|
@ -599,7 +599,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
|||
<button
|
||||
aria-disabled="false"
|
||||
aria-label="Voice call"
|
||||
aria-labelledby=":r75:"
|
||||
aria-labelledby=":rbt:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -624,7 +624,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
|||
</button>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
aria-labelledby=":r7a:"
|
||||
aria-labelledby=":rc2:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -649,7 +649,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
|||
</button>
|
||||
<button
|
||||
aria-label="Threads"
|
||||
aria-labelledby=":r7f:"
|
||||
aria-labelledby=":rc7:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -678,7 +678,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
|||
>
|
||||
<div
|
||||
aria-label="2 members"
|
||||
aria-labelledby=":r7k:"
|
||||
aria-labelledby=":rcc:"
|
||||
class="mx_AccessibleButton mx_FacePile"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
|
@ -963,7 +963,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
|||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
aria-labelledby=":r96:"
|
||||
aria-labelledby=":rdu:"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
|
@ -979,7 +979,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
|||
<button
|
||||
aria-disabled="false"
|
||||
aria-label="Voice call"
|
||||
aria-labelledby=":r9b:"
|
||||
aria-labelledby=":re3:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -1004,7 +1004,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
|||
</button>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
aria-labelledby=":r9g:"
|
||||
aria-labelledby=":re8:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -1029,7 +1029,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
|||
</button>
|
||||
<button
|
||||
aria-label="Threads"
|
||||
aria-labelledby=":r9l:"
|
||||
aria-labelledby=":red:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -1058,7 +1058,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
|||
>
|
||||
<div
|
||||
aria-label="2 members"
|
||||
aria-labelledby=":r9q:"
|
||||
aria-labelledby=":rei:"
|
||||
class="mx_AccessibleButton mx_FacePile"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
|
@ -1276,6 +1276,571 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`RoomView should not display the timeline when the room encryption is loading 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_RoomView"
|
||||
>
|
||||
<canvas
|
||||
aria-hidden="true"
|
||||
height="768"
|
||||
style="display: block; z-index: 999999; pointer-events: none; position: fixed; top: 0px; right: 0px;"
|
||||
width="0"
|
||||
/>
|
||||
<div
|
||||
class="mx_MainSplit"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomView_body mx_MainSplit_timeline"
|
||||
data-layout="group"
|
||||
>
|
||||
<header
|
||||
class="mx_Flex mx_RoomHeader light-panel"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x);"
|
||||
>
|
||||
<button
|
||||
aria-label="Open room settings"
|
||||
aria-live="off"
|
||||
class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
|
||||
data-color="2"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="button"
|
||||
style="--cpd-avatar-size: 40px;"
|
||||
tabindex="-1"
|
||||
>
|
||||
!
|
||||
</button>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
class="mx_RoomHeader_infoWrapper"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Box mx_RoomHeader_info mx_Box--flex"
|
||||
style="--mx-box-flex: 1;"
|
||||
>
|
||||
<div
|
||||
aria-level="1"
|
||||
class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83 mx_RoomHeader_heading"
|
||||
dir="auto"
|
||||
role="heading"
|
||||
>
|
||||
<span
|
||||
class="mx_RoomHeader_truncated mx_lineClamp"
|
||||
>
|
||||
!6:example.org
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
class="mx_Flex"
|
||||
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-disabled="true"
|
||||
aria-label="There's no one here to call"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_133tf_26"
|
||||
style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
|
||||
>
|
||||
<svg
|
||||
aria-labelledby=":r2c:"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-disabled="true"
|
||||
aria-label="There's no one here to call"
|
||||
aria-labelledby=":r2h:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_133tf_26"
|
||||
style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m20.958 16.374.039 3.527c0 .285-.11.537-.33.756-.22.22-.472.33-.756.33a15.97 15.97 0 0 1-6.57-1.105 16.223 16.223 0 0 1-5.563-3.663 16.084 16.084 0 0 1-3.653-5.573 16.313 16.313 0 0 1-1.115-6.56c0-.285.11-.537.33-.757.22-.22.471-.329.755-.329l3.528.039a1.069 1.069 0 0 1 1.085.93l.543 3.954c.026.181.013.349-.039.504a1.088 1.088 0 0 1-.271.426l-1.64 1.64c.337.672.721 1.308 1.154 1.909.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444c.6.433 1.237.817 1.909 1.153l1.64-1.64a1.08 1.08 0 0 1 .426-.27c.155-.052.323-.065.504-.04l3.954.543a1.069 1.069 0 0 1 .93 1.085Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
aria-labelledby=":r2m:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_133tf_26"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16v-4a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 11a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 12v4c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-8c.283 0 .52-.096.713-.287A.967.967 0 0 0 13 8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 13a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Threads"
|
||||
aria-labelledby=":r2r:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_133tf_26"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2Zm3 7h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 18 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 17 8H7a.968.968 0 0 0-.713.287A.968.968 0 0 0 6 9c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 4h6c.283 0 .52-.096.713-.287A.968.968 0 0 0 14 13a.968.968 0 0 0-.287-.713A.968.968 0 0 0 13 12H7a.967.967 0 0 0-.713.287A.968.968 0 0 0 6 13c0 .283.096.52.287.713.192.191.43.287.713.287Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50"
|
||||
>
|
||||
<div
|
||||
aria-label="0 members"
|
||||
aria-labelledby=":r30:"
|
||||
class="mx_AccessibleButton mx_FacePile"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_stacked-avatars_mcap2_111"
|
||||
/>
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar mx_AuxPanel"
|
||||
role="region"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
<main
|
||||
class="mx_RoomView_timeline mx_RoomView_timeline_rr_enabled"
|
||||
/>
|
||||
<div
|
||||
aria-label="Room status bar"
|
||||
class="mx_RoomView_statusArea"
|
||||
role="region"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomView_statusAreaBox"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomView_statusAreaBox_line"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`RoomView should not display the timeline when the room encryption is loading 2`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_RoomView"
|
||||
>
|
||||
<canvas
|
||||
aria-hidden="true"
|
||||
height="768"
|
||||
style="display: block; z-index: 999999; pointer-events: none; position: fixed; top: 0px; right: 0px;"
|
||||
width="0"
|
||||
/>
|
||||
<div
|
||||
class="mx_MainSplit"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomView_body mx_MainSplit_timeline"
|
||||
data-layout="group"
|
||||
>
|
||||
<header
|
||||
class="mx_Flex mx_RoomHeader light-panel"
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x);"
|
||||
>
|
||||
<button
|
||||
aria-label="Open room settings"
|
||||
aria-live="off"
|
||||
class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
|
||||
data-color="2"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="button"
|
||||
style="--cpd-avatar-size: 40px;"
|
||||
tabindex="-1"
|
||||
>
|
||||
!
|
||||
</button>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
class="mx_RoomHeader_infoWrapper"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Box mx_RoomHeader_info mx_Box--flex"
|
||||
style="--mx-box-flex: 1;"
|
||||
>
|
||||
<div
|
||||
aria-level="1"
|
||||
class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83 mx_RoomHeader_heading"
|
||||
dir="auto"
|
||||
role="heading"
|
||||
>
|
||||
<span
|
||||
class="mx_RoomHeader_truncated mx_lineClamp"
|
||||
>
|
||||
!6:example.org
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
class="mx_Flex"
|
||||
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-disabled="true"
|
||||
aria-label="There's no one here to call"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_133tf_26"
|
||||
style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
|
||||
>
|
||||
<svg
|
||||
aria-labelledby=":r2c:"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-disabled="true"
|
||||
aria-label="There's no one here to call"
|
||||
aria-labelledby=":r2h:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_133tf_26"
|
||||
style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m20.958 16.374.039 3.527c0 .285-.11.537-.33.756-.22.22-.472.33-.756.33a15.97 15.97 0 0 1-6.57-1.105 16.223 16.223 0 0 1-5.563-3.663 16.084 16.084 0 0 1-3.653-5.573 16.313 16.313 0 0 1-1.115-6.56c0-.285.11-.537.33-.757.22-.22.471-.329.755-.329l3.528.039a1.069 1.069 0 0 1 1.085.93l.543 3.954c.026.181.013.349-.039.504a1.088 1.088 0 0 1-.271.426l-1.64 1.64c.337.672.721 1.308 1.154 1.909.433.6 1.444 1.696 1.444 1.696s1.095 1.01 1.696 1.444c.6.433 1.237.817 1.909 1.153l1.64-1.64a1.08 1.08 0 0 1 .426-.27c.155-.052.323-.065.504-.04l3.954.543a1.069 1.069 0 0 1 .93 1.085Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
aria-labelledby=":r2m:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_133tf_26"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17a.97.97 0 0 0 .713-.288A.968.968 0 0 0 13 16v-4a.968.968 0 0 0-.287-.713A.968.968 0 0 0 12 11a.968.968 0 0 0-.713.287A.968.968 0 0 0 11 12v4c0 .283.096.52.287.712.192.192.43.288.713.288Zm0-8c.283 0 .52-.096.713-.287A.967.967 0 0 0 13 8a.967.967 0 0 0-.287-.713A.968.968 0 0 0 12 7a.968.968 0 0 0-.713.287A.967.967 0 0 0 11 8c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 13a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
aria-label="Threads"
|
||||
aria-labelledby=":r2r:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_indicator-icon_133tf_26"
|
||||
style="--cpd-icon-button-size: 100%;"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.293 2.293c-.63.63-1.707.184-1.707-.707V5a2 2 0 0 1 2-2Zm3 7h10a.97.97 0 0 0 .712-.287A.967.967 0 0 0 18 9a.967.967 0 0 0-.288-.713A.968.968 0 0 0 17 8H7a.968.968 0 0 0-.713.287A.968.968 0 0 0 6 9c0 .283.096.52.287.713.192.191.43.287.713.287Zm0 4h6c.283 0 .52-.096.713-.287A.968.968 0 0 0 14 13a.968.968 0 0 0-.287-.713A.968.968 0 0 0 13 12H7a.967.967 0 0 0-.713.287A.968.968 0 0 0 6 13c0 .283.096.52.287.713.192.191.43.287.713.287Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="_typography_yh5dq_162 _font-body-sm-medium_yh5dq_50"
|
||||
>
|
||||
<div
|
||||
aria-label="0 members"
|
||||
aria-labelledby=":r30:"
|
||||
class="mx_AccessibleButton mx_FacePile"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="_stacked-avatars_mcap2_111"
|
||||
/>
|
||||
0
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar mx_AuxPanel"
|
||||
role="region"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
<main
|
||||
class="mx_RoomView_timeline mx_RoomView_timeline_rr_enabled"
|
||||
>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomView_messageListWrapper"
|
||||
>
|
||||
<ol
|
||||
aria-live="polite"
|
||||
class="mx_RoomView_MessageList"
|
||||
style="height: 400px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<div
|
||||
aria-label="Room status bar"
|
||||
class="mx_RoomView_statusArea"
|
||||
role="region"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomView_statusAreaBox"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomView_statusAreaBox_line"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Message composer"
|
||||
class="mx_MessageComposer mx_MessageComposer_e2eStatus"
|
||||
role="region"
|
||||
>
|
||||
<div
|
||||
class="mx_MessageComposer_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_MessageComposer_row"
|
||||
>
|
||||
<div
|
||||
class="mx_MessageComposer_e2eIconWrapper"
|
||||
>
|
||||
<span
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-labelledby=":r3e:"
|
||||
class="mx_E2EIcon mx_E2EIcon_verified mx_MessageComposer_e2eIcon"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SendMessageComposer"
|
||||
>
|
||||
<div
|
||||
class="mx_BasicMessageComposer"
|
||||
>
|
||||
<div
|
||||
aria-label="Formatting"
|
||||
class="mx_MessageComposerFormatBar"
|
||||
role="toolbar"
|
||||
>
|
||||
<button
|
||||
aria-label="Bold"
|
||||
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
/>
|
||||
<button
|
||||
aria-label="Italics"
|
||||
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
/>
|
||||
<button
|
||||
aria-label="Strikethrough"
|
||||
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
/>
|
||||
<button
|
||||
aria-label="Code block"
|
||||
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
/>
|
||||
<button
|
||||
aria-label="Quote"
|
||||
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
/>
|
||||
<button
|
||||
aria-label="Insert link"
|
||||
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-autocomplete="list"
|
||||
aria-disabled="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Send an encrypted message…"
|
||||
aria-multiline="true"
|
||||
class="mx_BasicMessageComposer_input mx_BasicMessageComposer_input_shouldShowPillAvatar mx_BasicMessageComposer_inputEmpty"
|
||||
contenteditable="true"
|
||||
data-testid="basicmessagecomposer"
|
||||
dir="auto"
|
||||
role="textbox"
|
||||
style="--placeholder: 'Send\\ an\\ encrypted\\ message…';"
|
||||
tabindex="0"
|
||||
translate="no"
|
||||
>
|
||||
<div>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MessageComposer_actions"
|
||||
>
|
||||
<div
|
||||
aria-label="Emoji"
|
||||
class="mx_AccessibleButton mx_EmojiButton mx_MessageComposer_button mx_EmojiButton_icon"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-label="Attachment"
|
||||
class="mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_upload"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-label="More options"
|
||||
class="mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
<input
|
||||
multiple=""
|
||||
style="display: none;"
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`RoomView should show error view if failed to look up room alias 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
|
@ -1332,7 +1897,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
|
|||
aria-label="Open room settings"
|
||||
aria-live="off"
|
||||
class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
|
||||
data-color="3"
|
||||
data-color="6"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="button"
|
||||
|
@ -1359,7 +1924,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
|
|||
<span
|
||||
class="mx_RoomHeader_truncated mx_lineClamp"
|
||||
>
|
||||
!10:example.org
|
||||
!13:example.org
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1370,7 +1935,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
aria-labelledby=":r2k:"
|
||||
aria-labelledby=":r7c:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -1395,7 +1960,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
|
|||
</button>
|
||||
<button
|
||||
aria-label="Chat"
|
||||
aria-labelledby=":r2p:"
|
||||
aria-labelledby=":r7h:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -1420,7 +1985,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
|
|||
</button>
|
||||
<button
|
||||
aria-label="Threads"
|
||||
aria-labelledby=":r2u:"
|
||||
aria-labelledby=":r7m:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
@ -1449,7 +2014,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
|
|||
>
|
||||
<div
|
||||
aria-label="0 members"
|
||||
aria-labelledby=":r33:"
|
||||
aria-labelledby=":r7r:"
|
||||
class="mx_AccessibleButton mx_FacePile"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
|
@ -1487,7 +2052,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
|
|||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby=":r3c:"
|
||||
aria-labelledby=":r84:"
|
||||
class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<UserMenu> <UserMenu> when video broadcast when rendered should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_UserMenu"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-label="User menu"
|
||||
class="mx_AccessibleButton mx_UserMenu_contextMenuButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_UserMenu_userAvatar"
|
||||
>
|
||||
<span
|
||||
class="_avatar_mcap2_17 mx_BaseAvatar mx_UserMenu_userAvatar_BaseAvatar _avatar-imageless_mcap2_61"
|
||||
data-color="2"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 32px;"
|
||||
>
|
||||
u
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -8,19 +8,13 @@ Please see LICENSE files in the repository root for full details.
|
|||
|
||||
import React from "react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { act, render, RenderResult, screen, waitFor } from "jest-matrix-react";
|
||||
import { render, RenderResult, screen, waitFor, cleanup } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { MatrixClient, createClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import ForgotPassword from "../../../../../src/components/structures/auth/ForgotPassword";
|
||||
import { ValidatedServerConfig } from "../../../../../src/utils/ValidatedServerConfig";
|
||||
import {
|
||||
clearAllModals,
|
||||
filterConsole,
|
||||
flushPromisesWithFakeTimers,
|
||||
stubClient,
|
||||
waitEnoughCyclesForModal,
|
||||
} from "../../../../test-utils";
|
||||
import { clearAllModals, filterConsole, stubClient, waitEnoughCyclesForModal } from "../../../../test-utils";
|
||||
import AutoDiscoveryUtils from "../../../../../src/utils/AutoDiscoveryUtils";
|
||||
|
||||
jest.mock("matrix-js-sdk/src/matrix", () => ({
|
||||
|
@ -39,11 +33,7 @@ describe("<ForgotPassword>", () => {
|
|||
let renderResult: RenderResult;
|
||||
|
||||
const typeIntoField = async (label: string, value: string): Promise<void> => {
|
||||
await act(async () => {
|
||||
await userEvent.type(screen.getByLabelText(label), value, { delay: null });
|
||||
// the message is shown after some time
|
||||
jest.advanceTimersByTime(500);
|
||||
});
|
||||
await userEvent.type(screen.getByLabelText(label), value, { delay: null });
|
||||
};
|
||||
|
||||
const click = async (element: Element): Promise<void> => {
|
||||
|
@ -78,14 +68,7 @@ describe("<ForgotPassword>", () => {
|
|||
afterEach(async () => {
|
||||
// clean up modals
|
||||
await clearAllModals();
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe("when starting a password reset flow", () => {
|
||||
|
@ -128,13 +111,16 @@ describe("<ForgotPassword>", () => {
|
|||
await typeIntoField("Email address", "not en email");
|
||||
});
|
||||
|
||||
it("should show a message about the wrong format", () => {
|
||||
expect(screen.getByText("The email address doesn't appear to be valid.")).toBeInTheDocument();
|
||||
it("should show a message about the wrong format", async () => {
|
||||
await expect(
|
||||
screen.findByText("The email address doesn't appear to be valid."),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and submitting an unknown email", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(AutoDiscoveryUtils.validateServerConfigWithStaticUrls).mockResolvedValue(serverConfig);
|
||||
await typeIntoField("Email address", testEmail);
|
||||
mocked(client).requestPasswordEmailToken.mockRejectedValue({
|
||||
errcode: "M_THREEPID_NOT_FOUND",
|
||||
|
@ -142,8 +128,8 @@ describe("<ForgotPassword>", () => {
|
|||
await click(screen.getByText("Send email"));
|
||||
});
|
||||
|
||||
it("should show an email not found message", () => {
|
||||
expect(screen.getByText("This email address was not found")).toBeInTheDocument();
|
||||
it("should show an email not found message", async () => {
|
||||
await expect(screen.findByText("This email address was not found")).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -156,13 +142,12 @@ describe("<ForgotPassword>", () => {
|
|||
await click(screen.getByText("Send email"));
|
||||
});
|
||||
|
||||
it("should show an info about that", () => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
"Cannot reach homeserver: " +
|
||||
"Ensure you have a stable internet connection, or get in touch with the server admin",
|
||||
it("should show an info about that", async () => {
|
||||
await expect(
|
||||
screen.findByText(
|
||||
"Cannot reach homeserver: Ensure you have a stable internet connection, or get in touch with the server admin",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -178,8 +163,8 @@ describe("<ForgotPassword>", () => {
|
|||
await click(screen.getByText("Send email"));
|
||||
});
|
||||
|
||||
it("should show the server error", () => {
|
||||
expect(screen.queryByText("server down")).toBeInTheDocument();
|
||||
it("should show the server error", async () => {
|
||||
await expect(screen.findByText("server down")).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -215,8 +200,6 @@ describe("<ForgotPassword>", () => {
|
|||
describe("and clicking »Resend«", () => {
|
||||
beforeEach(async () => {
|
||||
await click(screen.getByText("Resend"));
|
||||
// the message is shown after some time
|
||||
jest.advanceTimersByTime(500);
|
||||
});
|
||||
|
||||
it("should should resend the mail and show the tooltip", () => {
|
||||
|
@ -246,8 +229,10 @@ describe("<ForgotPassword>", () => {
|
|||
await typeIntoField("Confirm new password", testPassword + "asd");
|
||||
});
|
||||
|
||||
it("should show an info about that", () => {
|
||||
expect(screen.getByText("New passwords must match each other.")).toBeInTheDocument();
|
||||
it("should show an info about that", async () => {
|
||||
await expect(
|
||||
screen.findByText("New passwords must match each other."),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -284,7 +269,7 @@ describe("<ForgotPassword>", () => {
|
|||
await click(screen.getByText("Reset password"));
|
||||
});
|
||||
|
||||
it("should send the new password (once)", () => {
|
||||
it("should send the new password (once)", async () => {
|
||||
expect(client.setPassword).toHaveBeenCalledWith(
|
||||
{
|
||||
type: "m.login.email.identity",
|
||||
|
@ -297,19 +282,15 @@ describe("<ForgotPassword>", () => {
|
|||
false,
|
||||
);
|
||||
|
||||
// be sure that the next attempt to set the password would have been sent
|
||||
jest.advanceTimersByTime(3000);
|
||||
// it should not retry to set the password
|
||||
expect(client.setPassword).toHaveBeenCalledTimes(1);
|
||||
await waitFor(() => expect(client.setPassword).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
});
|
||||
|
||||
describe("and submitting it", () => {
|
||||
beforeEach(async () => {
|
||||
await click(screen.getByText("Reset password"));
|
||||
await waitEnoughCyclesForModal({
|
||||
useFakeTimers: true,
|
||||
});
|
||||
await waitEnoughCyclesForModal();
|
||||
});
|
||||
|
||||
it("should send the new password and show the click validation link dialog", async () => {
|
||||
|
@ -367,23 +348,22 @@ describe("<ForgotPassword>", () => {
|
|||
expect(screen.queryByText("Enter your email to reset password")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and validating the link from the mail", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(client.setPassword).mockResolvedValue({});
|
||||
// be sure the next set password attempt was sent
|
||||
jest.advanceTimersByTime(3000);
|
||||
// quad flush promises for the modal to disappear
|
||||
await flushPromisesWithFakeTimers();
|
||||
await flushPromisesWithFakeTimers();
|
||||
await flushPromisesWithFakeTimers();
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
describe("and validating the link from the mail", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(client.setPassword).mockResolvedValue({});
|
||||
await click(screen.getByText("Reset password"));
|
||||
// flush promises for the modal to disappear
|
||||
await waitEnoughCyclesForModal();
|
||||
await waitEnoughCyclesForModal();
|
||||
});
|
||||
|
||||
it("should display the confirm reset view and now show the dialog", () => {
|
||||
expect(screen.queryByText("Your password has been reset.")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Verify your email to continue")).not.toBeInTheDocument();
|
||||
});
|
||||
it("should display the confirm reset view and now show the dialog", async () => {
|
||||
await expect(
|
||||
screen.findByText("Your password has been reset."),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(screen.queryByText("Verify your email to continue")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -391,9 +371,6 @@ describe("<ForgotPassword>", () => {
|
|||
beforeEach(async () => {
|
||||
await click(screen.getByText("Sign out of all devices"));
|
||||
await click(screen.getByText("Reset password"));
|
||||
await waitEnoughCyclesForModal({
|
||||
useFakeTimers: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should show the sign out warning dialog", async () => {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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({});
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,7 +92,7 @@ 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.");
|
||||
});
|
||||
|
@ -125,6 +125,7 @@ describe("CreateSecretStorageDialog", () => {
|
|||
resetFunctionCallLog.push("resetKeyBackup");
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
result.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await result.findByText("Your keys are now being backed up from this device.");
|
||||
|
|
|
@ -7,7 +7,7 @@ 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";
|
||||
|
||||
|
@ -23,12 +23,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 +38,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 +49,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();
|
||||
});
|
||||
|
||||
|
@ -74,7 +74,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());
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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> = {
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -27,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", () => {
|
||||
|
@ -120,9 +120,9 @@ describe("EncryptionEvent", () => {
|
|||
renderEncryptionEvent(client, event);
|
||||
});
|
||||
|
||||
it("should show the expected texts", () => {
|
||||
it("should show the expected texts", async () => {
|
||||
expect(client.getCrypto()!.isEncryptionEnabledInRoom).toHaveBeenCalledWith(roomId);
|
||||
checkTexts("Encryption enabled", "Messages in this chat will be end-to-end encrypted.");
|
||||
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>,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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";
|
||||
|
@ -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([
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -224,7 +224,7 @@ 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();
|
||||
|
|
|
@ -158,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 () => {
|
||||
|
@ -589,7 +589,7 @@ describe("RoomHeader", () => {
|
|||
state_key: "",
|
||||
room_id: room.roomId,
|
||||
});
|
||||
room.addLiveEvents([joinRuleEvent]);
|
||||
room.addLiveEvents([joinRuleEvent], { addToState: true });
|
||||
|
||||
render(<RoomHeader room={room} />, getWrapper());
|
||||
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
|
||||
import React, { ComponentProps } from "react";
|
||||
import { mocked, Mocked } from "jest-mock";
|
||||
import { act, render, RenderResult } from "jest-matrix-react";
|
||||
import { render, RenderResult } from "jest-matrix-react";
|
||||
import { TypedEventEmitter, IMyDevice, MatrixClient, Device } from "matrix-js-sdk/src/matrix";
|
||||
import { VerificationRequest, VerificationRequestEvent } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
|
@ -63,9 +63,7 @@ describe("VerificationRequestToast", () => {
|
|||
otherDeviceId,
|
||||
});
|
||||
const result = renderComponent({ request });
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
expect(result.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
@ -76,9 +74,7 @@ describe("VerificationRequestToast", () => {
|
|||
otherUserId,
|
||||
});
|
||||
const result = renderComponent({ request });
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
expect(result.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
@ -89,9 +85,7 @@ describe("VerificationRequestToast", () => {
|
|||
otherUserId,
|
||||
});
|
||||
renderComponent({ request, toastKey: "testKey" });
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
const dismiss = jest.spyOn(ToastStore.sharedInstance(), "dismissToast");
|
||||
Object.defineProperty(request, "accepting", { value: true });
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue