Merge branch 'develop' into midhun/new-memberlist
This commit is contained in:
@@ -95,17 +95,17 @@ describe("DeviceListener", () => {
|
||||
},
|
||||
}),
|
||||
getSessionBackupPrivateKey: jest.fn(),
|
||||
isEncryptionEnabledInRoom: jest.fn(),
|
||||
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
|
||||
} as unknown as Mocked<CryptoApi>;
|
||||
mockClient = getMockClientWithEventEmitter({
|
||||
isGuest: jest.fn(),
|
||||
getUserId: jest.fn().mockReturnValue(userId),
|
||||
getSafeUserId: jest.fn().mockReturnValue(userId),
|
||||
getKeyBackupVersion: jest.fn().mockResolvedValue(undefined),
|
||||
getRooms: jest.fn().mockReturnValue([]),
|
||||
isVersionSupported: jest.fn().mockResolvedValue(true),
|
||||
isInitialSyncComplete: jest.fn().mockReturnValue(true),
|
||||
waitForClientWellKnown: jest.fn(),
|
||||
isRoomEncrypted: jest.fn(),
|
||||
getClientWellKnown: jest.fn(),
|
||||
getDeviceId: jest.fn().mockReturnValue(deviceId),
|
||||
setAccountData: jest.fn(),
|
||||
@@ -292,7 +292,7 @@ describe("DeviceListener", () => {
|
||||
mockCrypto!.isCrossSigningReady.mockResolvedValue(false);
|
||||
mockCrypto!.isSecretStorageReady.mockResolvedValue(false);
|
||||
mockClient!.getRooms.mockReturnValue(rooms);
|
||||
mockClient!.isRoomEncrypted.mockReturnValue(true);
|
||||
jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
});
|
||||
|
||||
it("hides setup encryption toast when cross signing and secret storage are ready", async () => {
|
||||
@@ -317,7 +317,7 @@ describe("DeviceListener", () => {
|
||||
});
|
||||
|
||||
it("does not show any toasts when no rooms are encrypted", async () => {
|
||||
mockClient!.isRoomEncrypted.mockReturnValue(false);
|
||||
jest.spyOn(mockClient.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
|
||||
await createAndStart();
|
||||
|
||||
expect(SetupEncryptionToast.showToast).not.toHaveBeenCalled();
|
||||
@@ -354,7 +354,7 @@ describe("DeviceListener", () => {
|
||||
|
||||
it("shows set up encryption toast when user has a key backup available", async () => {
|
||||
// non falsy response
|
||||
mockClient!.getKeyBackupVersion.mockResolvedValue({} as unknown as KeyBackupInfo);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue({} as unknown as KeyBackupInfo);
|
||||
await createAndStart();
|
||||
|
||||
expect(SetupEncryptionToast.showToast).toHaveBeenCalledWith(
|
||||
@@ -673,7 +673,7 @@ describe("DeviceListener", () => {
|
||||
describe("When Room Key Backup is not enabled", () => {
|
||||
beforeEach(() => {
|
||||
// no backup
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue(null);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue(null);
|
||||
});
|
||||
|
||||
it("Should report recovery state as Enabled", async () => {
|
||||
@@ -722,7 +722,7 @@ describe("DeviceListener", () => {
|
||||
});
|
||||
|
||||
// no backup
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue(null);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue(null);
|
||||
|
||||
await createAndStart();
|
||||
|
||||
@@ -872,7 +872,7 @@ describe("DeviceListener", () => {
|
||||
describe("When Room Key Backup is enabled", () => {
|
||||
beforeEach(() => {
|
||||
// backup enabled - just need a mock object
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue({} as KeyBackupInfo);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue({} as KeyBackupInfo);
|
||||
});
|
||||
|
||||
const testCases = [
|
||||
|
||||
@@ -39,10 +39,6 @@ import { Action } from "../../src/dispatcher/actions";
|
||||
import { getFunctionalMembers } from "../../src/utils/room/getFunctionalMembers";
|
||||
import SettingsStore from "../../src/settings/SettingsStore";
|
||||
import { UIFeature } from "../../src/settings/UIFeature";
|
||||
import { VoiceBroadcastInfoState, VoiceBroadcastPlayback, VoiceBroadcastRecording } from "../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./voice-broadcast/utils/test-utils";
|
||||
import { SdkContextClass } from "../../src/contexts/SDKContext";
|
||||
import Modal from "../../src/Modal";
|
||||
import { createAudioContext } from "../../src/audio/compat";
|
||||
import * as ManagedHybrid from "../../src/widgets/ManagedHybrid";
|
||||
|
||||
@@ -403,53 +399,6 @@ describe("LegacyCallHandler", () => {
|
||||
await callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
|
||||
expect(spy).toHaveBeenCalledWith(MatrixClientPeg.safeGet().getRoom(NATIVE_ROOM_ALICE));
|
||||
});
|
||||
|
||||
describe("when listening to a voice broadcast", () => {
|
||||
let voiceBroadcastPlayback: VoiceBroadcastPlayback;
|
||||
|
||||
beforeEach(() => {
|
||||
voiceBroadcastPlayback = new VoiceBroadcastPlayback(
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
"!room:example.com",
|
||||
VoiceBroadcastInfoState.Started,
|
||||
MatrixClientPeg.safeGet().getSafeUserId(),
|
||||
"d42",
|
||||
),
|
||||
MatrixClientPeg.safeGet(),
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore,
|
||||
);
|
||||
SdkContextClass.instance.voiceBroadcastPlaybacksStore.setCurrent(voiceBroadcastPlayback);
|
||||
jest.spyOn(voiceBroadcastPlayback, "pause").mockImplementation();
|
||||
});
|
||||
|
||||
it("and placing a call should pause the broadcast", async () => {
|
||||
callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
|
||||
await untilCallHandlerEvent(callHandler, LegacyCallHandlerEvent.CallState);
|
||||
|
||||
expect(voiceBroadcastPlayback.pause).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when recording a voice broadcast", () => {
|
||||
beforeEach(() => {
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore.setCurrent(
|
||||
new VoiceBroadcastRecording(
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
"!room:example.com",
|
||||
VoiceBroadcastInfoState.Started,
|
||||
MatrixClientPeg.safeGet().getSafeUserId(),
|
||||
"d42",
|
||||
),
|
||||
MatrixClientPeg.safeGet(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("and placing a call should show the info dialog", async () => {
|
||||
callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
|
||||
expect(Modal.createDialog).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("LegacyCallHandler without third party protocols", () => {
|
||||
@@ -528,9 +477,6 @@ describe("LegacyCallHandler without third party protocols", () => {
|
||||
audioElement.id = "remoteAudio";
|
||||
document.body.appendChild(audioElement);
|
||||
|
||||
SdkContextClass.instance.voiceBroadcastPlaybacksStore.clearCurrent();
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore.clearCurrent();
|
||||
|
||||
fetchMock.get(
|
||||
"/media/ring.mp3",
|
||||
{ body: new Blob(["1", "2", "3", "4"], { type: "audio/mpeg" }) },
|
||||
|
||||
@@ -418,7 +418,7 @@ describe("Lifecycle", () => {
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(MatrixClientPeg.start).toHaveBeenCalledWith({ rustCryptoStoreKey: expect.any(Buffer) });
|
||||
expect(MatrixClientPeg.start).toHaveBeenCalledWith({ rustCryptoStoreKey: expect.any(Uint8Array) });
|
||||
});
|
||||
|
||||
describe("with a refresh token", () => {
|
||||
|
||||
@@ -11,8 +11,6 @@ import fetchMockJest from "fetch-mock-jest";
|
||||
|
||||
import { advanceDateAndTime, stubClient } from "../test-utils";
|
||||
import { IMatrixClientPeg, MatrixClientPeg as peg } from "../../src/MatrixClientPeg";
|
||||
import SettingsStore from "../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../src/settings/SettingLevel";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
@@ -81,27 +79,18 @@ describe("MatrixClientPeg", () => {
|
||||
});
|
||||
|
||||
it("should initialise the rust crypto library by default", async () => {
|
||||
const mockSetValue = jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
|
||||
|
||||
const mockInitRustCrypto = jest.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined);
|
||||
|
||||
const cryptoStoreKey = new Uint8Array([1, 2, 3, 4]);
|
||||
await testPeg.start({ rustCryptoStoreKey: cryptoStoreKey });
|
||||
expect(mockInitRustCrypto).toHaveBeenCalledWith({ storageKey: cryptoStoreKey });
|
||||
|
||||
// we should have stashed the setting in the settings store
|
||||
expect(mockSetValue).toHaveBeenCalledWith("feature_rust_crypto", null, SettingLevel.DEVICE, true);
|
||||
});
|
||||
|
||||
it("Should migrate existing login", async () => {
|
||||
const mockSetValue = jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
|
||||
const mockInitRustCrypto = jest.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined);
|
||||
|
||||
await testPeg.start();
|
||||
expect(mockInitRustCrypto).toHaveBeenCalledTimes(1);
|
||||
|
||||
// we should have stashed the setting in the settings store
|
||||
expect(mockSetValue).toHaveBeenCalledWith("feature_rust_crypto", null, SettingLevel.DEVICE, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,8 +43,6 @@ import { mkThread } from "../test-utils/threads";
|
||||
import dis from "../../src/dispatcher/dispatcher";
|
||||
import { ThreadPayload } from "../../src/dispatcher/payloads/ThreadPayload";
|
||||
import { Action } from "../../src/dispatcher/actions";
|
||||
import { VoiceBroadcastChunkEventType, VoiceBroadcastInfoState } from "../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./voice-broadcast/utils/test-utils";
|
||||
import { addReplyToMessageContent } from "../../src/utils/Reply";
|
||||
|
||||
jest.mock("../../src/utils/notifications", () => ({
|
||||
@@ -85,16 +83,13 @@ describe("Notifier", () => {
|
||||
});
|
||||
};
|
||||
|
||||
const mkAudioEvent = (broadcastChunkContent?: object): MatrixEvent => {
|
||||
const chunkContent = broadcastChunkContent ? { [VoiceBroadcastChunkEventType]: broadcastChunkContent } : {};
|
||||
|
||||
const mkAudioEvent = (): MatrixEvent => {
|
||||
return mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
user: "@user:example.com",
|
||||
room: "!room:example.com",
|
||||
content: {
|
||||
...chunkContent,
|
||||
msgtype: MsgType.Audio,
|
||||
body: "test audio message",
|
||||
},
|
||||
@@ -320,24 +315,6 @@ describe("Notifier", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should display the expected notification for a broadcast chunk with sequence = 1", () => {
|
||||
const audioEvent = mkAudioEvent({ sequence: 1 });
|
||||
Notifier.displayPopupNotification(audioEvent, testRoom);
|
||||
expect(MockPlatform.displayNotification).toHaveBeenCalledWith(
|
||||
"@user:example.com (!room1:server)",
|
||||
"@user:example.com started a voice broadcast",
|
||||
"data:image/png;base64,00",
|
||||
testRoom,
|
||||
audioEvent,
|
||||
);
|
||||
});
|
||||
|
||||
it("should display the expected notification for a broadcast chunk with sequence = 2", () => {
|
||||
const audioEvent = mkAudioEvent({ sequence: 2 });
|
||||
Notifier.displayPopupNotification(audioEvent, testRoom);
|
||||
expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should strip reply fallback", () => {
|
||||
const event = mkMessage({
|
||||
msg: "Test",
|
||||
@@ -581,24 +558,6 @@ describe("Notifier", () => {
|
||||
Notifier.evaluateEvent(mkAudioEvent());
|
||||
expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should not show a notification for broadcast info events in any case", () => {
|
||||
// Let client decide to show a notification
|
||||
mockClient.getPushActionsForEvent.mockReturnValue({
|
||||
notify: true,
|
||||
tweaks: {},
|
||||
});
|
||||
|
||||
const broadcastStartedEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
"!other:example.org",
|
||||
VoiceBroadcastInfoState.Started,
|
||||
"@user:example.com",
|
||||
"ABC123",
|
||||
);
|
||||
|
||||
Notifier.evaluateEvent(broadcastStartedEvent);
|
||||
expect(Notifier.displayPopupNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("setPromptHidden", () => {
|
||||
@@ -624,8 +583,7 @@ describe("Notifier", () => {
|
||||
content: { body: "this is a thread root" },
|
||||
}),
|
||||
testRoom.threadsTimelineSets[0]!.getLiveTimeline(),
|
||||
false,
|
||||
false,
|
||||
{ toStartOfTimeline: false, fromCache: false, addToState: true },
|
||||
);
|
||||
|
||||
expect(fn).not.toHaveBeenCalled();
|
||||
|
||||
@@ -147,7 +147,7 @@ describe("RoomNotifs test", () => {
|
||||
|
||||
const itShouldCountPredecessorHighlightWhenThereIsAPredecessorInTheCreateEvent = (): void => {
|
||||
it("and there is a predecessor in the create event, it should count predecessor highlight", () => {
|
||||
room.addLiveEvents([mkCreateEvent(OLD_ROOM_ID)]);
|
||||
room.addLiveEvents([mkCreateEvent(OLD_ROOM_ID)], { addToState: true });
|
||||
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(8);
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, false)).toBe(7);
|
||||
@@ -157,7 +157,7 @@ describe("RoomNotifs test", () => {
|
||||
const itShouldCountPredecessorHighlightWhenThereIsAPredecessorEvent = (): void => {
|
||||
it("and there is a predecessor event, it should count predecessor highlight", () => {
|
||||
client.getVisibleRooms();
|
||||
room.addLiveEvents([mkCreateEvent(OLD_ROOM_ID)]);
|
||||
room.addLiveEvents([mkCreateEvent(OLD_ROOM_ID)], { addToState: true });
|
||||
upsertRoomStateEvents(room, [mkPredecessorEvent(OLD_ROOM_ID)]);
|
||||
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(8);
|
||||
@@ -185,7 +185,7 @@ describe("RoomNotifs test", () => {
|
||||
itShouldCountPredecessorHighlightWhenThereIsAPredecessorEvent();
|
||||
|
||||
it("and there is only a predecessor event, it should not count predecessor highlight", () => {
|
||||
room.addLiveEvents([mkCreateEvent()]);
|
||||
room.addLiveEvents([mkCreateEvent()], { addToState: true });
|
||||
upsertRoomStateEvents(room, [mkPredecessorEvent(OLD_ROOM_ID)]);
|
||||
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(2);
|
||||
@@ -204,7 +204,7 @@ describe("RoomNotifs test", () => {
|
||||
itShouldCountPredecessorHighlightWhenThereIsAPredecessorEvent();
|
||||
|
||||
it("and there is only a predecessor event, it should count predecessor highlight", () => {
|
||||
room.addLiveEvents([mkCreateEvent()]);
|
||||
room.addLiveEvents([mkCreateEvent()], { addToState: true });
|
||||
upsertRoomStateEvents(room, [mkPredecessorEvent(OLD_ROOM_ID)]);
|
||||
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(8);
|
||||
@@ -212,7 +212,7 @@ describe("RoomNotifs test", () => {
|
||||
});
|
||||
|
||||
it("and there is an unknown room in the predecessor event, it should not count predecessor highlight", () => {
|
||||
room.addLiveEvents([mkCreateEvent()]);
|
||||
room.addLiveEvents([mkCreateEvent()], { addToState: true });
|
||||
upsertRoomStateEvents(room, [mkPredecessorEvent("!unknon:example.com")]);
|
||||
|
||||
expect(getUnreadNotificationCount(room, NotificationCountType.Total, false)).toBe(2);
|
||||
|
||||
@@ -18,10 +18,6 @@ describe("SdkConfig", () => {
|
||||
describe("with custom values", () => {
|
||||
beforeEach(() => {
|
||||
SdkConfig.put({
|
||||
voice_broadcast: {
|
||||
chunk_length: 42,
|
||||
max_length: 1337,
|
||||
},
|
||||
feedback: {
|
||||
existing_issues_url: "https://existing",
|
||||
} as any,
|
||||
@@ -30,8 +26,6 @@ describe("SdkConfig", () => {
|
||||
|
||||
it("should return the custom config", () => {
|
||||
const customConfig = JSON.parse(JSON.stringify(DEFAULTS));
|
||||
customConfig.voice_broadcast.chunk_length = 42;
|
||||
customConfig.voice_broadcast.max_length = 1337;
|
||||
customConfig.feedback.existing_issues_url = "https://existing";
|
||||
expect(SdkConfig.get()).toEqual(customConfig);
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ describe("SecurityManager", () => {
|
||||
stubClient();
|
||||
|
||||
const func = jest.fn();
|
||||
accessSecretStorage(func, true);
|
||||
accessSecretStorage(func, { forceReset: true });
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
await expect(spy.mock.lastCall![0]).resolves.toEqual(expect.objectContaining({ __test: true }));
|
||||
|
||||
@@ -66,10 +66,10 @@ describe("SupportedBrowser", () => {
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",
|
||||
// Firefox 131 on macOS Sonoma
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0",
|
||||
// Edge 129 on Windows
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/129.0.2792.79",
|
||||
// Edge 129 on macOS
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/129.0.2792.79",
|
||||
// Edge 131 on Windows
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.2903.70",
|
||||
// Edge 131 on macOS
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.2903.70",
|
||||
// Firefox 131 on Windows
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0",
|
||||
// Firefox 131 on Linux
|
||||
|
||||
@@ -16,29 +16,21 @@ import { SpaceStoreClass } from "../../src/stores/spaces/SpaceStore";
|
||||
import { WidgetLayoutStore } from "../../src/stores/widgets/WidgetLayoutStore";
|
||||
import { WidgetPermissionStore } from "../../src/stores/widgets/WidgetPermissionStore";
|
||||
import WidgetStore from "../../src/stores/WidgetStore";
|
||||
import {
|
||||
VoiceBroadcastPlaybacksStore,
|
||||
VoiceBroadcastPreRecordingStore,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../src/voice-broadcast";
|
||||
|
||||
/**
|
||||
* A class which provides the same API as SdkContextClass but adds additional unsafe setters which can
|
||||
* replace individual stores. This is useful for tests which need to mock out stores.
|
||||
*/
|
||||
export class TestSdkContext extends SdkContextClass {
|
||||
public declare _RightPanelStore?: RightPanelStore;
|
||||
public declare _RoomNotificationStateStore?: RoomNotificationStateStore;
|
||||
public declare _RoomViewStore?: RoomViewStore;
|
||||
public declare _WidgetPermissionStore?: WidgetPermissionStore;
|
||||
public declare _WidgetLayoutStore?: WidgetLayoutStore;
|
||||
public declare _WidgetStore?: WidgetStore;
|
||||
public declare _PosthogAnalytics?: PosthogAnalytics;
|
||||
public declare _SlidingSyncManager?: SlidingSyncManager;
|
||||
public declare _SpaceStore?: SpaceStoreClass;
|
||||
public declare _VoiceBroadcastRecordingsStore?: VoiceBroadcastRecordingsStore;
|
||||
public declare _VoiceBroadcastPreRecordingStore?: VoiceBroadcastPreRecordingStore;
|
||||
public declare _VoiceBroadcastPlaybacksStore?: VoiceBroadcastPlaybacksStore;
|
||||
declare public _RightPanelStore?: RightPanelStore;
|
||||
declare public _RoomNotificationStateStore?: RoomNotificationStateStore;
|
||||
declare public _RoomViewStore?: RoomViewStore;
|
||||
declare public _WidgetPermissionStore?: WidgetPermissionStore;
|
||||
declare public _WidgetLayoutStore?: WidgetLayoutStore;
|
||||
declare public _WidgetStore?: WidgetStore;
|
||||
declare public _PosthogAnalytics?: PosthogAnalytics;
|
||||
declare public _SlidingSyncManager?: SlidingSyncManager;
|
||||
declare public _SpaceStore?: SpaceStoreClass;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -138,7 +138,7 @@ describe("Unread", () => {
|
||||
room: roomId,
|
||||
content: {},
|
||||
});
|
||||
room.addLiveEvents([event]);
|
||||
room.addLiveEvents([event], { addToState: true });
|
||||
|
||||
// Don't care about the code path of hidden events.
|
||||
mocked(haveRendererForEvent).mockClear().mockReturnValue(true);
|
||||
@@ -157,7 +157,7 @@ describe("Unread", () => {
|
||||
content: {},
|
||||
});
|
||||
// Only for timeline events.
|
||||
room.addLiveEvents([event]);
|
||||
room.addLiveEvents([event], { addToState: true });
|
||||
|
||||
expect(doesRoomHaveUnreadMessages(room, false)).toBe(false);
|
||||
});
|
||||
@@ -201,7 +201,7 @@ describe("Unread", () => {
|
||||
content: {},
|
||||
});
|
||||
// Only for timeline events.
|
||||
room.addLiveEvents([event2]);
|
||||
room.addLiveEvents([event2], { addToState: true });
|
||||
|
||||
expect(doesRoomHaveUnreadMessages(room, false)).toBe(true);
|
||||
});
|
||||
@@ -403,7 +403,7 @@ describe("Unread", () => {
|
||||
redactedEvent.makeRedacted(redactedEvent, room);
|
||||
console.log("Event Id", redactedEvent.getId());
|
||||
// Only for timeline events.
|
||||
room.addLiveEvents([redactedEvent]);
|
||||
room.addLiveEvents([redactedEvent], { addToState: true });
|
||||
|
||||
expect(doesRoomHaveUnreadMessages(room, true)).toBe(true);
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
@@ -448,7 +448,7 @@ describe("Unread", () => {
|
||||
room: roomId,
|
||||
content: {},
|
||||
});
|
||||
room.addLiveEvents([event]);
|
||||
room.addLiveEvents([event], { addToState: true });
|
||||
});
|
||||
|
||||
it("an unthreaded receipt for the event makes the room read", () => {
|
||||
@@ -502,7 +502,7 @@ describe("Unread", () => {
|
||||
ts: 100,
|
||||
currentUserId: myId,
|
||||
});
|
||||
room.addLiveEvents(events);
|
||||
room.addLiveEvents(events, { addToState: true });
|
||||
threadEvent = events[1];
|
||||
});
|
||||
|
||||
@@ -555,7 +555,7 @@ describe("Unread", () => {
|
||||
room: roomId,
|
||||
content: {},
|
||||
});
|
||||
room.addLiveEvents([event]);
|
||||
room.addLiveEvents([event], { addToState: true });
|
||||
|
||||
// It still returns false
|
||||
expect(doesRoomHaveUnreadThreads(room)).toBe(false);
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LegacyCallHandler when recording a voice broadcast and placing a call should show the info dialog 1`] = `
|
||||
[MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
[Function],
|
||||
{
|
||||
"description": <p>
|
||||
You can’t start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.
|
||||
</p>,
|
||||
"hasCloseButton": true,
|
||||
"title": "Can’t start a call",
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { HTMLAttributes } from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import { act, render } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import {
|
||||
@@ -79,15 +79,15 @@ describe("RovingTabIndex", () => {
|
||||
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
|
||||
|
||||
// focus on 2nd button and test it is the only active one
|
||||
container.querySelectorAll("button")[2].focus();
|
||||
act(() => container.querySelectorAll("button")[2].focus());
|
||||
checkTabIndexes(container.querySelectorAll("button"), [-1, -1, 0]);
|
||||
|
||||
// focus on 1st button and test it is the only active one
|
||||
container.querySelectorAll("button")[1].focus();
|
||||
act(() => container.querySelectorAll("button")[1].focus());
|
||||
checkTabIndexes(container.querySelectorAll("button"), [-1, 0, -1]);
|
||||
|
||||
// check that the active button does not change even on an explicit blur event
|
||||
container.querySelectorAll("button")[1].blur();
|
||||
act(() => container.querySelectorAll("button")[1].blur());
|
||||
checkTabIndexes(container.querySelectorAll("button"), [-1, 0, -1]);
|
||||
|
||||
// update the children, it should remain on the same button
|
||||
@@ -162,7 +162,7 @@ describe("RovingTabIndex", () => {
|
||||
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
|
||||
|
||||
// focus on 2nd button and test it is the only active one
|
||||
container.querySelectorAll("button")[2].focus();
|
||||
act(() => container.querySelectorAll("button")[2].focus());
|
||||
checkTabIndexes(container.querySelectorAll("button"), [-1, -1, 0]);
|
||||
});
|
||||
|
||||
@@ -390,7 +390,7 @@ describe("RovingTabIndex", () => {
|
||||
</RovingTabIndexProvider>,
|
||||
);
|
||||
|
||||
container.querySelectorAll("button")[0].focus();
|
||||
act(() => container.querySelectorAll("button")[0].focus());
|
||||
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
|
||||
|
||||
await userEvent.keyboard("[ArrowDown]");
|
||||
@@ -423,7 +423,7 @@ describe("RovingTabIndex", () => {
|
||||
</RovingTabIndexProvider>,
|
||||
);
|
||||
|
||||
container.querySelectorAll("button")[0].focus();
|
||||
act(() => container.querySelectorAll("button")[0].focus());
|
||||
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
|
||||
|
||||
const button = container.querySelectorAll("button")[1];
|
||||
|
||||
@@ -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),
|
||||
@@ -146,10 +146,8 @@ describe("<MatrixChat />", () => {
|
||||
matrixRTC: createStubMatrixRTC(),
|
||||
getDehydratedDevice: jest.fn(),
|
||||
whoami: jest.fn(),
|
||||
isRoomEncrypted: jest.fn(),
|
||||
logout: jest.fn(),
|
||||
getDeviceId: jest.fn(),
|
||||
getKeyBackupVersion: jest.fn().mockResolvedValue(null),
|
||||
});
|
||||
let mockClient: Mocked<MatrixClient>;
|
||||
const serverConfig = {
|
||||
@@ -163,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(
|
||||
@@ -202,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
|
||||
@@ -264,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();
|
||||
});
|
||||
@@ -329,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(() => {
|
||||
@@ -809,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 () => {});
|
||||
|
||||
@@ -829,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();
|
||||
@@ -957,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();
|
||||
|
||||
@@ -1011,6 +1003,7 @@ describe("<MatrixChat />", () => {
|
||||
userHasCrossSigningKeys: jest.fn().mockResolvedValue(false),
|
||||
// This needs to not finish immediately because we need to test the screen appears
|
||||
bootstrapCrossSigning: jest.fn().mockImplementation(() => bootstrapDeferred.promise),
|
||||
isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false),
|
||||
};
|
||||
loginClient.getCrypto.mockReturnValue(mockCrypto as any);
|
||||
});
|
||||
@@ -1058,9 +1051,11 @@ describe("<MatrixChat />", () => {
|
||||
},
|
||||
});
|
||||
|
||||
loginClient.isRoomEncrypted.mockImplementation((roomId) => {
|
||||
return roomId === encryptedRoom.roomId;
|
||||
});
|
||||
jest.spyOn(loginClient.getCrypto()!, "isEncryptionEnabledInRoom").mockImplementation(
|
||||
async (roomId) => {
|
||||
return roomId === encryptedRoom.roomId;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("should go straight to logged in view when user is not in any encrypted rooms", async () => {
|
||||
@@ -1126,7 +1121,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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1395,7 +1392,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 () => {
|
||||
|
||||
@@ -23,12 +23,14 @@ import {
|
||||
createTestClient,
|
||||
getMockClientWithEventEmitter,
|
||||
makeBeaconInfoEvent,
|
||||
mockClientMethodsCrypto,
|
||||
mockClientMethodsEvents,
|
||||
mockClientMethodsUser,
|
||||
} from "../../../test-utils";
|
||||
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(),
|
||||
@@ -42,6 +44,7 @@ describe("MessagePanel", function () {
|
||||
const client = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
...mockClientMethodsEvents(),
|
||||
...mockClientMethodsCrypto(),
|
||||
getAccountData: jest.fn(),
|
||||
isUserIgnored: jest.fn().mockReturnValue(false),
|
||||
isRoomEncrypted: jest.fn().mockReturnValue(false),
|
||||
@@ -89,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,26 +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, 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,
|
||||
@@ -44,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";
|
||||
@@ -63,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>;
|
||||
@@ -72,14 +83,14 @@ describe("RoomView", () => {
|
||||
let rooms: Map<string, Room>;
|
||||
let roomCount = 0;
|
||||
let stores: SdkContextClass;
|
||||
let crypto: CryptoApi;
|
||||
|
||||
// mute some noise
|
||||
filterConsole("RVS update", "does not have an m.room.create event", "Current version: 1", "Version capability");
|
||||
|
||||
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");
|
||||
@@ -97,15 +108,17 @@ describe("RoomView", () => {
|
||||
stores.rightPanelStore.useUnitTestClient(cli);
|
||||
|
||||
jest.spyOn(VoipUserMapper.sharedInstance(), "getVirtualRoomForRoom").mockResolvedValue(undefined);
|
||||
crypto = cli.getCrypto()!;
|
||||
jest.spyOn(cli, "getCrypto").mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
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 = () => {
|
||||
@@ -117,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;
|
||||
@@ -164,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 () => {
|
||||
@@ -214,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 () => {
|
||||
@@ -230,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();
|
||||
|
||||
@@ -246,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();
|
||||
@@ -284,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);
|
||||
@@ -341,7 +419,13 @@ describe("RoomView", () => {
|
||||
|
||||
describe("that is encrypted", () => {
|
||||
beforeEach(() => {
|
||||
// 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, true, false),
|
||||
);
|
||||
localRoom.encrypted = true;
|
||||
localRoom.currentState.setStateEvents([
|
||||
new MatrixEvent({
|
||||
@@ -360,7 +444,7 @@ describe("RoomView", () => {
|
||||
|
||||
it("should match the snapshot", async () => {
|
||||
const { container } = await renderRoomView();
|
||||
expect(container).toMatchSnapshot();
|
||||
await waitFor(() => expect(container).toMatchSnapshot());
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -402,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();
|
||||
});
|
||||
|
||||
@@ -420,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";
|
||||
@@ -440,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();
|
||||
@@ -505,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"
|
||||
@@ -314,7 +314,6 @@ exports[`<MatrixChat /> with a soft-logged-out session should show the soft-logo
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
type="submit"
|
||||
>
|
||||
Sign in
|
||||
</div>
|
||||
@@ -357,11 +356,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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,18 @@ exports[`<BeaconViewDialog /> renders a fallback when there are no locations 1`]
|
||||
<div
|
||||
class="mx_MapFallback_bg"
|
||||
/>
|
||||
<div
|
||||
<svg
|
||||
class="mx_MapFallback_icon"
|
||||
/>
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_BeaconViewDialog_mapFallbackMessage"
|
||||
>
|
||||
|
||||
@@ -27,7 +27,7 @@ import { mocked } from "jest-mock";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import { canEditContent } from "../../../../../src/utils/EventUtils";
|
||||
import { copyPlaintext, getSelectedText } from "../../../../../src/utils/strings";
|
||||
@@ -37,9 +37,8 @@ import dispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { ReadPinsEventId } from "../../../../../src/components/views/right_panel/types";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast";
|
||||
import { createMessageEventContent } from "../../../../test-utils/events";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../src/utils/strings", () => ({
|
||||
copyPlaintext: jest.fn(),
|
||||
@@ -233,17 +232,6 @@ describe("MessageContextMenu", () => {
|
||||
expect(document.querySelector('li[aria-label="Forward"]')).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should not allow forwarding a voice broadcast", () => {
|
||||
const broadcastStartEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
"@user:example.com",
|
||||
"ABC123",
|
||||
);
|
||||
createMenu(broadcastStartEvent);
|
||||
expect(document.querySelector('li[aria-label="Forward"]')).toBeFalsy();
|
||||
});
|
||||
|
||||
describe("forwarding beacons", () => {
|
||||
const aliceId = "@alice:server.org";
|
||||
|
||||
@@ -546,8 +534,8 @@ function createMenu(
|
||||
client.getRoom = jest.fn().mockReturnValue(room);
|
||||
|
||||
return render(
|
||||
<RoomContext.Provider value={context as IRoomState}>
|
||||
<ScopedRoomContextProvider {...(context as IRoomState)}>
|
||||
<MessageContextMenu mxEvent={mxEvent} onFinished={jest.fn()} {...props} />
|
||||
</RoomContext.Provider>,
|
||||
</ScopedRoomContextProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ describe("RoomGeneralContextMenu", () => {
|
||||
user: "@user:id",
|
||||
ts: 1000,
|
||||
});
|
||||
room.addLiveEvents([event], {});
|
||||
room.addLiveEvents([event], { addToState: true });
|
||||
|
||||
const { container } = getComponent({});
|
||||
|
||||
@@ -150,7 +150,7 @@ describe("RoomGeneralContextMenu", () => {
|
||||
|
||||
await sleep(0);
|
||||
|
||||
expect(mockClient.setRoomAccountData).toHaveBeenCalledWith(ROOM_ID, "com.famedly.marked_unread", {
|
||||
expect(mockClient.setRoomAccountData).toHaveBeenCalledWith(ROOM_ID, "m.marked_unread", {
|
||||
unread: true,
|
||||
});
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
|
||||
@@ -122,4 +122,34 @@ describe("AccessSecretStorageDialog", () => {
|
||||
|
||||
expect(screen.getByPlaceholderText("Security Phrase")).toHaveFocus();
|
||||
});
|
||||
|
||||
it("Can reset secret storage", async () => {
|
||||
jest.spyOn(mockClient.secretStorage, "checkKey").mockResolvedValue(true);
|
||||
|
||||
const onFinished = jest.fn();
|
||||
const checkPrivateKey = jest.fn().mockResolvedValue(true);
|
||||
renderComponent({ onFinished, checkPrivateKey });
|
||||
|
||||
await userEvent.click(screen.getByText("Reset all"), { delay: null });
|
||||
|
||||
// It will prompt the user to confirm resetting
|
||||
expect(screen.getByText("Reset everything")).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByText("Reset"), { delay: null });
|
||||
|
||||
// Then it will prompt the user to create a key/passphrase
|
||||
await screen.findByText("Set up Secure Backup");
|
||||
document.execCommand = jest.fn().mockReturnValue(true);
|
||||
jest.spyOn(mockClient.getCrypto()!, "createRecoveryKeyFromPassphrase").mockResolvedValue({
|
||||
privateKey: new Uint8Array(),
|
||||
encodedPrivateKey: securityKey,
|
||||
});
|
||||
screen.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await screen.findByText(/Save your Security Key/);
|
||||
screen.getByRole("button", { name: "Copy" }).click();
|
||||
await screen.findByText("Copied!");
|
||||
screen.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await screen.findByText("Secure Backup successful");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,14 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
|
||||
import { MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { screen, act } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { flushPromises, mkEvent, stubClient } from "../../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast";
|
||||
import { createRedactEventDialog } from "../../../../../src/components/views/dialogs/ConfirmRedactDialog";
|
||||
|
||||
describe("ConfirmRedactDialog", () => {
|
||||
@@ -21,15 +18,6 @@ describe("ConfirmRedactDialog", () => {
|
||||
let client: MatrixClient;
|
||||
let mxEvent: MatrixEvent;
|
||||
|
||||
const setUpVoiceBroadcastStartedEvent = () => {
|
||||
mxEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId()!,
|
||||
client.deviceId!,
|
||||
);
|
||||
};
|
||||
|
||||
const confirmDeleteVoiceBroadcastStartedEvent = async () => {
|
||||
act(() => createRedactEventDialog({ mxEvent }));
|
||||
// double-flush promises required for the dialog to show up
|
||||
@@ -68,44 +56,4 @@ describe("ConfirmRedactDialog", () => {
|
||||
`cannot redact event ${mxEvent.getId()} without room ID`,
|
||||
);
|
||||
});
|
||||
|
||||
describe("when redacting a voice broadcast started event", () => {
|
||||
beforeEach(() => {
|
||||
setUpVoiceBroadcastStartedEvent();
|
||||
});
|
||||
|
||||
describe("and the server does not support relation based redactions", () => {
|
||||
beforeEach(() => {
|
||||
client.canSupport.set(Feature.RelationBasedRedactions, ServerSupport.Unsupported);
|
||||
});
|
||||
|
||||
describe("and displaying and confirm the dialog for a voice broadcast", () => {
|
||||
beforeEach(async () => {
|
||||
await confirmDeleteVoiceBroadcastStartedEvent();
|
||||
});
|
||||
|
||||
it("should call redact without `with_rel_types`", () => {
|
||||
expect(client.redactEvent).toHaveBeenCalledWith(roomId, mxEvent.getId(), undefined, {});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the server supports relation based redactions", () => {
|
||||
beforeEach(() => {
|
||||
client.canSupport.set(Feature.RelationBasedRedactions, ServerSupport.Unstable);
|
||||
});
|
||||
|
||||
describe("and displaying and confirm the dialog for a voice broadcast", () => {
|
||||
beforeEach(async () => {
|
||||
await confirmDeleteVoiceBroadcastStartedEvent();
|
||||
});
|
||||
|
||||
it("should call redact with `with_rel_types`", () => {
|
||||
expect(client.redactEvent).toHaveBeenCalledWith(roomId, mxEvent.getId(), undefined, {
|
||||
with_rel_types: [RelationType.Reference],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,7 +67,6 @@ describe("ForwardDialog", () => {
|
||||
getAccountData: jest.fn().mockReturnValue(accountDataEvent),
|
||||
getPushActionsForEvent: jest.fn(),
|
||||
mxcUrlToHttp: jest.fn().mockReturnValue(""),
|
||||
isRoomEncrypted: jest.fn().mockReturnValue(false),
|
||||
getProfileInfo: jest.fn().mockResolvedValue({
|
||||
displayname: "Alice",
|
||||
}),
|
||||
|
||||
@@ -141,16 +141,19 @@ describe("InviteDialog", () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
room = new Room(roomId, mockClient, mockClient.getSafeUserId());
|
||||
room.addLiveEvents([
|
||||
mkMessage({
|
||||
msg: "Hello",
|
||||
relatesTo: undefined,
|
||||
event: true,
|
||||
room: roomId,
|
||||
user: mockClient.getSafeUserId(),
|
||||
ts: Date.now(),
|
||||
}),
|
||||
]);
|
||||
room.addLiveEvents(
|
||||
[
|
||||
mkMessage({
|
||||
msg: "Hello",
|
||||
relatesTo: undefined,
|
||||
event: true,
|
||||
room: roomId,
|
||||
user: mockClient.getSafeUserId(),
|
||||
ts: Date.now(),
|
||||
}),
|
||||
],
|
||||
{ addToState: true },
|
||||
);
|
||||
room.currentState.setStateEvents([
|
||||
mkRoomCreateEvent(bobId, roomId),
|
||||
mkMembership({
|
||||
|
||||
@@ -22,7 +22,6 @@ describe("LogoutDialog", () => {
|
||||
beforeEach(() => {
|
||||
mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsCrypto(),
|
||||
getKeyBackupVersion: jest.fn(),
|
||||
});
|
||||
|
||||
mockCrypto = mocked(mockClient.getCrypto()!);
|
||||
@@ -50,14 +49,14 @@ describe("LogoutDialog", () => {
|
||||
});
|
||||
|
||||
it("Prompts user to connect backup if there is a backup on the server", async () => {
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue({} as KeyBackupInfo);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue({} as KeyBackupInfo);
|
||||
const rendered = renderComponent();
|
||||
await rendered.findByText("Connect this session to Key Backup");
|
||||
expect(rendered.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("Prompts user to set up backup if there is no backup on the server", async () => {
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue(null);
|
||||
mockCrypto.getKeyBackupInfo.mockResolvedValue(null);
|
||||
const rendered = renderComponent();
|
||||
await rendered.findByText("Start using Key Backup");
|
||||
expect(rendered.container).toMatchSnapshot();
|
||||
@@ -69,7 +68,7 @@ describe("LogoutDialog", () => {
|
||||
describe("when there is an error fetching backups", () => {
|
||||
filterConsole("Unable to fetch key backup status");
|
||||
it("prompts user to set up backup", async () => {
|
||||
mockClient.getKeyBackupVersion.mockImplementation(async () => {
|
||||
mockCrypto.getKeyBackupInfo.mockImplementation(async () => {
|
||||
throw new Error("beep");
|
||||
});
|
||||
const rendered = renderComponent();
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
* Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import { Device, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import { ManualDeviceKeyVerificationDialog } from "../../../../../src/components/views/dialogs/ManualDeviceKeyVerificationDialog";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
|
||||
describe("ManualDeviceKeyVerificationDialog", () => {
|
||||
let mockClient: MatrixClient;
|
||||
|
||||
function renderDialog(userId: string, device: Device, onLegacyFinished: (confirm: boolean) => void) {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<ManualDeviceKeyVerificationDialog userId={userId} device={device} onFinished={onLegacyFinished} />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient = stubClient();
|
||||
});
|
||||
|
||||
it("should display the device", () => {
|
||||
// When
|
||||
const deviceId = "XYZ";
|
||||
const device = new Device({
|
||||
userId: mockClient.getUserId()!,
|
||||
deviceId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
|
||||
});
|
||||
const { container } = renderDialog(mockClient.getUserId()!, device, jest.fn());
|
||||
|
||||
// Then
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should display the device of another user", () => {
|
||||
// When
|
||||
const userId = "@alice:example.com";
|
||||
const deviceId = "XYZ";
|
||||
const device = new Device({
|
||||
userId,
|
||||
deviceId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
|
||||
});
|
||||
const { container } = renderDialog(userId, device, jest.fn());
|
||||
|
||||
// Then
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should call onFinished and matrixClient.setDeviceVerified", () => {
|
||||
// When
|
||||
const deviceId = "XYZ";
|
||||
const device = new Device({
|
||||
userId: mockClient.getUserId()!,
|
||||
deviceId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
|
||||
});
|
||||
const onFinished = jest.fn();
|
||||
renderDialog(mockClient.getUserId()!, device, onFinished);
|
||||
|
||||
screen.getByRole("button", { name: "Verify session" }).click();
|
||||
|
||||
// Then
|
||||
expect(onFinished).toHaveBeenCalledWith(true);
|
||||
expect(mockClient.setDeviceVerified).toHaveBeenCalledWith(mockClient.getUserId(), deviceId, true);
|
||||
});
|
||||
|
||||
it("should call onFinished and not matrixClient.setDeviceVerified", () => {
|
||||
// When
|
||||
const deviceId = "XYZ";
|
||||
const device = new Device({
|
||||
userId: mockClient.getUserId()!,
|
||||
deviceId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
|
||||
});
|
||||
const onFinished = jest.fn();
|
||||
renderDialog(mockClient.getUserId()!, device, onFinished);
|
||||
|
||||
screen.getByRole("button", { name: "Cancel" }).click();
|
||||
|
||||
// Then
|
||||
expect(onFinished).toHaveBeenCalledWith(false);
|
||||
expect(mockClient.setDeviceVerified).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -7,111 +7,139 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { EventTimeline, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { render, RenderOptions } from "jest-matrix-react";
|
||||
import { MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { render, screen, act } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { _t } from "../../../../../src/languageHandler";
|
||||
import ShareDialog from "../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { ShareDialog } from "../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { UIFeature } from "../../../../../src/settings/UIFeature";
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
jest.mock("../../../../../src/utils/ShieldUtils");
|
||||
|
||||
function getWrapper(): RenderOptions {
|
||||
return {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>{children}</MatrixClientContext.Provider>
|
||||
),
|
||||
};
|
||||
}
|
||||
import { stubClient, withClientContextRenderOptions } from "../../../../test-utils";
|
||||
import * as StringsModule from "../../../../../src/utils/strings";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks.ts";
|
||||
|
||||
describe("ShareDialog", () => {
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
const ROOM_ID = "!1:example.org";
|
||||
const copyTextFunc = jest.fn();
|
||||
|
||||
beforeEach(async () => {
|
||||
stubClient();
|
||||
room = new Room(ROOM_ID, MatrixClientPeg.get()!, "@alice:example.org");
|
||||
client = stubClient();
|
||||
room = new Room("!1:example.org", client, "@alice:example.org");
|
||||
jest.spyOn(StringsModule, "copyPlaintext").mockImplementation(copyTextFunc);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
copyTextFunc.mockClear();
|
||||
});
|
||||
|
||||
it("renders room share dialog", () => {
|
||||
const { container: withoutEvents } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
expect(withoutEvents).toHaveTextContent(_t("share|title_room"));
|
||||
function renderComponent(target: Room | RoomMember | URL) {
|
||||
return render(<ShareDialog target={target} onFinished={jest.fn()} />, withClientContextRenderOptions(client));
|
||||
}
|
||||
|
||||
jest.spyOn(room, "getLiveTimeline").mockReturnValue({ getEvents: () => [{} as MatrixEvent] } as EventTimeline);
|
||||
const { container: withEvents } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
expect(withEvents).toHaveTextContent(_t("share|permalink_most_recent"));
|
||||
const getUrl = () => new URL("https://matrix.org/");
|
||||
const getRoomMember = () => new RoomMember(room.roomId, "@alice:example.org");
|
||||
|
||||
test.each([
|
||||
{ name: "an URL", title: "Share Link", url: "https://matrix.org/", getTarget: getUrl },
|
||||
{
|
||||
name: "a room member",
|
||||
title: "Share User",
|
||||
url: "https://matrix.to/#/@alice:example.org",
|
||||
getTarget: getRoomMember,
|
||||
},
|
||||
])("should render a share dialog for $name", async ({ title, url, getTarget }) => {
|
||||
const { asFragment } = renderComponent(getTarget());
|
||||
|
||||
expect(screen.getByRole("heading", { name: title })).toBeInTheDocument();
|
||||
expect(screen.getByText(url)).toBeInTheDocument();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Copy link" }));
|
||||
expect(copyTextFunc).toHaveBeenCalledWith(url);
|
||||
});
|
||||
|
||||
it("renders user share dialog", () => {
|
||||
mockRoomMembers(room, 1);
|
||||
const { container } = render(
|
||||
<ShareDialog target={room.getJoinedMembers()[0]} onFinished={jest.fn()} />,
|
||||
getWrapper(),
|
||||
it("should render a share dialog for a room", async () => {
|
||||
const expectedURL = "https://matrix.to/#/!1:example.org";
|
||||
jest.spyOn(room.getLiveTimeline(), "getEvents").mockReturnValue([new MatrixEvent({ event_id: "!eventId" })]);
|
||||
|
||||
const { asFragment } = renderComponent(room);
|
||||
expect(screen.getByRole("heading", { name: "Share Room" })).toBeInTheDocument();
|
||||
expect(screen.getByText(expectedURL)).toBeInTheDocument();
|
||||
expect(screen.getByRole("checkbox", { name: "Link to most recent message" })).toBeInTheDocument();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Copy link" }));
|
||||
expect(copyTextFunc).toHaveBeenCalledWith(expectedURL);
|
||||
|
||||
// Click on the checkbox to link to the most recent message
|
||||
await userEvent.click(screen.getByRole("checkbox", { name: "Link to most recent message" }));
|
||||
const newExpectedURL = "https://matrix.to/#/!1:example.org/!eventId";
|
||||
expect(screen.getByText(newExpectedURL)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render a share dialog for a matrix event", async () => {
|
||||
const matrixEvent = new MatrixEvent({ event_id: "!eventId" });
|
||||
const permalinkCreator = new RoomPermalinkCreator(room);
|
||||
const expectedURL = "https://matrix.to/#/!1:example.org/!eventId";
|
||||
|
||||
const { asFragment } = render(
|
||||
<ShareDialog target={matrixEvent} permalinkCreator={permalinkCreator} onFinished={jest.fn()} />,
|
||||
withClientContextRenderOptions(client),
|
||||
);
|
||||
expect(container).toHaveTextContent(_t("share|title_user"));
|
||||
expect(screen.getByRole("heading", { name: "Share Room Message" })).toBeInTheDocument();
|
||||
expect(screen.getByText(expectedURL)).toBeInTheDocument();
|
||||
expect(screen.getByRole("checkbox", { name: "Link to selected message" })).toBeChecked();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "Copy link" }));
|
||||
expect(copyTextFunc).toHaveBeenCalledWith(expectedURL);
|
||||
|
||||
// Click on the checkbox to link to the room
|
||||
await userEvent.click(screen.getByRole("checkbox", { name: "Link to selected message" }));
|
||||
expect(screen.getByText("https://matrix.to/#/!1:example.org")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders link share dialog", () => {
|
||||
mockRoomMembers(room, 1);
|
||||
const { container } = render(
|
||||
<ShareDialog target={new URL("https://matrix.org")} onFinished={jest.fn()} />,
|
||||
getWrapper(),
|
||||
);
|
||||
expect(container).toHaveTextContent(_t("share|title_link"));
|
||||
it("should change the copy button text when clicked", async () => {
|
||||
jest.useFakeTimers();
|
||||
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
|
||||
// To not be bother with rtl warnings about QR code state update
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
|
||||
renderComponent(room);
|
||||
await user.click(screen.getByRole("button", { name: "Copy link" }));
|
||||
// Move after `copyPlaintext`
|
||||
await jest.advanceTimersToNextTimerAsync();
|
||||
expect(screen.getByRole("button", { name: "Link copied" })).toBeInTheDocument();
|
||||
|
||||
// 2 sec after the button should be back to normal
|
||||
act(() => jest.advanceTimersByTime(2000));
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Copy link" })).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it("renders the QR code if configured", () => {
|
||||
it("should not render the QR code if disabled", () => {
|
||||
const originalGetValue = SettingsStore.getValue;
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => {
|
||||
if (feature === UIFeature.ShareQRCode) return true;
|
||||
if (feature === UIFeature.ShareQRCode) return false;
|
||||
return originalGetValue(feature);
|
||||
});
|
||||
const { container } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
const qrCodesVisible = container.getElementsByClassName("mx_ShareDialog_qrcode_container").length > 0;
|
||||
expect(qrCodesVisible).toBe(true);
|
||||
|
||||
const { asFragment } = renderComponent(room);
|
||||
expect(screen.queryByRole("img", { name: "QR code" })).toBeNull();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the social button if configured", () => {
|
||||
it("should not render the socials if disabled", () => {
|
||||
const originalGetValue = SettingsStore.getValue;
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => {
|
||||
if (feature === UIFeature.ShareSocial) return true;
|
||||
if (feature === UIFeature.ShareSocial) return false;
|
||||
return originalGetValue(feature);
|
||||
});
|
||||
const { container } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
const qrCodesVisible = container.getElementsByClassName("mx_ShareDialog_social_container").length > 0;
|
||||
expect(qrCodesVisible).toBe(true);
|
||||
});
|
||||
it("renders custom title and subtitle", () => {
|
||||
const { container } = render(
|
||||
<ShareDialog
|
||||
target={room}
|
||||
customTitle="test_title_123"
|
||||
subtitle="custom_subtitle_1234"
|
||||
onFinished={jest.fn()}
|
||||
/>,
|
||||
getWrapper(),
|
||||
);
|
||||
expect(container).toHaveTextContent("test_title_123");
|
||||
expect(container).toHaveTextContent("custom_subtitle_1234");
|
||||
|
||||
const { asFragment } = renderComponent(room);
|
||||
expect(screen.queryByRole("link", { name: "Reddit" })).toBeNull();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
/**
|
||||
*
|
||||
* @param count the number of users to create
|
||||
*/
|
||||
function mockRoomMembers(room: Room, count: number) {
|
||||
const members = Array(count)
|
||||
.fill(0)
|
||||
.map((_, index) => new RoomMember(room.roomId, "@alice:example.org"));
|
||||
|
||||
room.currentState.setJoinedMemberCount(members.length);
|
||||
room.getJoinedMembers = jest.fn().mockReturnValue(members);
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ describe("Spotlight Dialog", () => {
|
||||
});
|
||||
|
||||
it("should call getVisibleRooms with MSC3946 dynamic room predecessors", async () => {
|
||||
render(<SpotlightDialog onFinished={() => null} />, { legacyRoot: false });
|
||||
render(<SpotlightDialog onFinished={() => null} />);
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
expect(mockedClient.getVisibleRooms).toHaveBeenCalledWith(true);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Device, MatrixClient, User } from "matrix-js-sdk/src/matrix";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import UntrustedDeviceDialog from "../../../../../src/components/views/dialogs/UntrustedDeviceDialog.tsx";
|
||||
|
||||
describe("<UntrustedDeviceDialog />", () => {
|
||||
let client: MatrixClient;
|
||||
let user: User;
|
||||
let device: Device;
|
||||
const onFinished = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
user = User.createUser("@alice:example.org", client);
|
||||
user.setDisplayName("Alice");
|
||||
device = new Device({ deviceId: "device_id", userId: user.userId, algorithms: [], keys: new Map() });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
onFinished.mockReset();
|
||||
});
|
||||
|
||||
function renderComponent() {
|
||||
return render(<UntrustedDeviceDialog user={user} device={device} onFinished={onFinished} />);
|
||||
}
|
||||
|
||||
it("should display the dialog for the device of another user", () => {
|
||||
const { asFragment } = renderComponent();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should display the dialog for the device of the current user", () => {
|
||||
jest.spyOn(client, "getUserId").mockReturnValue(user.userId);
|
||||
|
||||
const { asFragment } = renderComponent();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should call onFinished without parameter when Done is clicked", () => {
|
||||
renderComponent();
|
||||
screen.getByRole("button", { name: "Done" }).click();
|
||||
expect(onFinished).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it("should call onFinished with sas when Interactively verify by emoji is clicked", () => {
|
||||
renderComponent();
|
||||
screen.getByRole("button", { name: "Interactively verify by emoji" }).click();
|
||||
expect(onFinished).toHaveBeenCalledWith("sas");
|
||||
});
|
||||
});
|
||||
@@ -185,33 +185,6 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = `
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_4yVCeEefiPqp"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Force 15s voice broadcast chunk length
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="false"
|
||||
aria-disabled="false"
|
||||
aria-label="Force 15s voice broadcast chunk length"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_4yVCeEefiPqp"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ManualDeviceKeyVerificationDialog should display the device 1`] = `
|
||||
<div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_QuestionDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Verify session
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
Confirm by comparing the following with the User Settings in your other session:
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceVerifyDialog_cryptoSection"
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<label>
|
||||
Session name
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
my device
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
Session ID
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
<code>
|
||||
XYZ
|
||||
</code>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
Session key
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
<code>
|
||||
<strong>
|
||||
ABCD EFGH
|
||||
</strong>
|
||||
</code>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
If they don't match, the security of your communication may be compromised.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<span
|
||||
class="mx_Dialog_buttons_row"
|
||||
>
|
||||
<button
|
||||
data-testid="dialog-cancel-button"
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="mx_Dialog_primary"
|
||||
data-testid="dialog-primary-button"
|
||||
type="button"
|
||||
>
|
||||
Verify session
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ManualDeviceKeyVerificationDialog should display the device of another user 1`] = `
|
||||
<div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_QuestionDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Verify session
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
Confirm this user's session by comparing the following with their User Settings:
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceVerifyDialog_cryptoSection"
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<label>
|
||||
Session name
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
my device
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
Session ID
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
<code>
|
||||
XYZ
|
||||
</code>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
Session key
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
<code>
|
||||
<strong>
|
||||
ABCD EFGH
|
||||
</strong>
|
||||
</code>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
If they don't match, the security of your communication may be compromised.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<span
|
||||
class="mx_Dialog_buttons_row"
|
||||
>
|
||||
<button
|
||||
data-testid="dialog-cancel-button"
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="mx_Dialog_primary"
|
||||
data-testid="dialog-primary-button"
|
||||
type="button"
|
||||
>
|
||||
Verify session
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,852 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ShareDialog should not render the QR code if disabled 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ShareDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Share Room
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ShareDialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ShareDialog_top"
|
||||
>
|
||||
<span>
|
||||
https://matrix.to/#/!1:example.org
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
Copy link
|
||||
</button>
|
||||
<div
|
||||
class="mx_ShareDialog_social"
|
||||
>
|
||||
<a
|
||||
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Facebook"
|
||||
>
|
||||
<img
|
||||
alt="Facebook"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/home?status=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Twitter"
|
||||
>
|
||||
<img
|
||||
alt="Twitter"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<img
|
||||
alt="LinkedIn"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.reddit.com/submit?url=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Reddit"
|
||||
>
|
||||
<img
|
||||
alt="Reddit"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:?body=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="email"
|
||||
>
|
||||
<img
|
||||
alt="email"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`ShareDialog should not render the socials if disabled 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ShareDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Share Room
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ShareDialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ShareDialog_top"
|
||||
>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
https://matrix.to/#/!1:example.org
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
Copy link
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`ShareDialog should render a share dialog for a matrix event 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ShareDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Share Room Message
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ShareDialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ShareDialog_top"
|
||||
>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
https://matrix.to/#/!1:example.org/!eventId
|
||||
</span>
|
||||
</div>
|
||||
<label>
|
||||
<div
|
||||
class="_container_1wloq_18"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="_input_1wloq_26"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div
|
||||
class="_ui_1wloq_27"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
Link to selected message
|
||||
</label>
|
||||
<button
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
Copy link
|
||||
</button>
|
||||
<div
|
||||
class="mx_ShareDialog_social"
|
||||
>
|
||||
<a
|
||||
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.to/#/!1:example.org/!eventId"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Facebook"
|
||||
>
|
||||
<img
|
||||
alt="Facebook"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/home?status=https://matrix.to/#/!1:example.org/!eventId"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Twitter"
|
||||
>
|
||||
<img
|
||||
alt="Twitter"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.to/#/!1:example.org/!eventId"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<img
|
||||
alt="LinkedIn"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.reddit.com/submit?url=https://matrix.to/#/!1:example.org/!eventId"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Reddit"
|
||||
>
|
||||
<img
|
||||
alt="Reddit"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:?body=https://matrix.to/#/!1:example.org/!eventId"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="email"
|
||||
>
|
||||
<img
|
||||
alt="email"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`ShareDialog should render a share dialog for a room 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ShareDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Share Room
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ShareDialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ShareDialog_top"
|
||||
>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
https://matrix.to/#/!1:example.org
|
||||
</span>
|
||||
</div>
|
||||
<label>
|
||||
<div
|
||||
class="_container_1wloq_18"
|
||||
>
|
||||
<input
|
||||
class="_input_1wloq_26"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div
|
||||
class="_ui_1wloq_27"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
Link to most recent message
|
||||
</label>
|
||||
<button
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
Copy link
|
||||
</button>
|
||||
<div
|
||||
class="mx_ShareDialog_social"
|
||||
>
|
||||
<a
|
||||
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Facebook"
|
||||
>
|
||||
<img
|
||||
alt="Facebook"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/home?status=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Twitter"
|
||||
>
|
||||
<img
|
||||
alt="Twitter"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<img
|
||||
alt="LinkedIn"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.reddit.com/submit?url=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Reddit"
|
||||
>
|
||||
<img
|
||||
alt="Reddit"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:?body=https://matrix.to/#/!1:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="email"
|
||||
>
|
||||
<img
|
||||
alt="email"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`ShareDialog should render a share dialog for a room member 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ShareDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Share User
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ShareDialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ShareDialog_top"
|
||||
>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
https://matrix.to/#/@alice:example.org
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
Copy link
|
||||
</button>
|
||||
<div
|
||||
class="mx_ShareDialog_social"
|
||||
>
|
||||
<a
|
||||
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.to/#/@alice:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Facebook"
|
||||
>
|
||||
<img
|
||||
alt="Facebook"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/home?status=https://matrix.to/#/@alice:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Twitter"
|
||||
>
|
||||
<img
|
||||
alt="Twitter"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.to/#/@alice:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<img
|
||||
alt="LinkedIn"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.reddit.com/submit?url=https://matrix.to/#/@alice:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Reddit"
|
||||
>
|
||||
<img
|
||||
alt="Reddit"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:?body=https://matrix.to/#/@alice:example.org"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="email"
|
||||
>
|
||||
<img
|
||||
alt="email"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`ShareDialog should render a share dialog for an URL 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ShareDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Share Link
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ShareDialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ShareDialog_top"
|
||||
>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span>
|
||||
https://matrix.org/
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="_button_i91xf_17 _has-icon_i91xf_66"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
Copy link
|
||||
</button>
|
||||
<div
|
||||
class="mx_ShareDialog_social"
|
||||
>
|
||||
<a
|
||||
href="https://www.facebook.com/sharer/sharer.php?u=https://matrix.org/"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Facebook"
|
||||
>
|
||||
<img
|
||||
alt="Facebook"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/home?status=https://matrix.org/"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Twitter"
|
||||
>
|
||||
<img
|
||||
alt="Twitter"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/shareArticle?mini=true&url=https://matrix.org/"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<img
|
||||
alt="LinkedIn"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.reddit.com/submit?url=https://matrix.org/"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="Reddit"
|
||||
>
|
||||
<img
|
||||
alt="Reddit"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:?body=https://matrix.org/"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
title="email"
|
||||
>
|
||||
<img
|
||||
alt="email"
|
||||
src="image-file-stub"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -0,0 +1,149 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<UntrustedDeviceDialog /> should display the dialog for the device of another user 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_UntrustedDeviceDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
<div
|
||||
class="mx_E2EIcon mx_E2EIcon_warning"
|
||||
style="width: 24px; height: 24px;"
|
||||
/>
|
||||
Not Trusted
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<p>
|
||||
Alice (@alice:example.org) signed in to a new session without verifying it:
|
||||
</p>
|
||||
<p>
|
||||
(device_id)
|
||||
</p>
|
||||
<p>
|
||||
Ask this user to verify their session, or manually verify it below.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Interactively verify by emoji
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Done
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<UntrustedDeviceDialog /> should display the dialog for the device of the current user 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_UntrustedDeviceDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
<div
|
||||
class="mx_E2EIcon mx_E2EIcon_warning"
|
||||
style="width: 24px; height: 24px;"
|
||||
/>
|
||||
Not Trusted
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<p>
|
||||
You signed in to a new session without verifying it:
|
||||
</p>
|
||||
<p>
|
||||
(device_id)
|
||||
</p>
|
||||
<p>
|
||||
Verify your other session using one of the options below.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Interactively verify by emoji
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Done
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -13,7 +13,7 @@ import { mocked, MockedObject } from "jest-mock";
|
||||
import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { filterConsole, stubClient } from "../../../../../test-utils";
|
||||
import { filterConsole, flushPromises, stubClient } from "../../../../../test-utils";
|
||||
import CreateSecretStorageDialog from "../../../../../../src/async-components/views/dialogs/security/CreateSecretStorageDialog";
|
||||
|
||||
describe("CreateSecretStorageDialog", () => {
|
||||
@@ -77,7 +77,7 @@ describe("CreateSecretStorageDialog", () => {
|
||||
filterConsole("Error fetching backup data from server");
|
||||
|
||||
it("shows an error", async () => {
|
||||
mockClient.getKeyBackupVersion.mockImplementation(async () => {
|
||||
jest.spyOn(mockClient.getCrypto()!, "getKeyBackupInfo").mockImplementation(async () => {
|
||||
throw new Error("bleh bleh");
|
||||
});
|
||||
|
||||
@@ -92,9 +92,44 @@ describe("CreateSecretStorageDialog", () => {
|
||||
expect(result.container).toMatchSnapshot();
|
||||
|
||||
// Now we can get the backup and we retry
|
||||
mockClient.getKeyBackupVersion.mockRestore();
|
||||
jest.spyOn(mockClient.getCrypto()!, "getKeyBackupInfo").mockRestore();
|
||||
await userEvent.click(screen.getByRole("button", { name: "Retry" }));
|
||||
await screen.findByText("Your keys are now being backed up from this device.");
|
||||
});
|
||||
});
|
||||
|
||||
it("resets keys in the right order when resetting secret storage and cross-signing", async () => {
|
||||
const result = renderComponent({ forceReset: true, resetCrossSigning: true });
|
||||
|
||||
await result.findByText(/Set up Secure Backup/);
|
||||
jest.spyOn(mockClient.getCrypto()!, "createRecoveryKeyFromPassphrase").mockResolvedValue({
|
||||
privateKey: new Uint8Array(),
|
||||
encodedPrivateKey: "abcd efgh ijkl",
|
||||
});
|
||||
result.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await result.findByText(/Save your Security Key/);
|
||||
result.getByRole("button", { name: "Copy" }).click();
|
||||
|
||||
// Resetting should reset secret storage, cross signing, and key
|
||||
// backup. We make sure that all three are reset, and done in the
|
||||
// right order.
|
||||
const resetFunctionCallLog: string[] = [];
|
||||
jest.spyOn(mockClient.getCrypto()!, "bootstrapSecretStorage").mockImplementation(async () => {
|
||||
resetFunctionCallLog.push("bootstrapSecretStorage");
|
||||
});
|
||||
jest.spyOn(mockClient.getCrypto()!, "bootstrapCrossSigning").mockImplementation(async () => {
|
||||
resetFunctionCallLog.push("bootstrapCrossSigning");
|
||||
});
|
||||
jest.spyOn(mockClient.getCrypto()!, "resetKeyBackup").mockImplementation(async () => {
|
||||
resetFunctionCallLog.push("resetKeyBackup");
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
result.getByRole("button", { name: "Continue" }).click();
|
||||
|
||||
await result.findByText("Your keys are now being backed up from this device.");
|
||||
|
||||
expect(resetFunctionCallLog).toEqual(["bootstrapSecretStorage", "bootstrapCrossSigning", "resetKeyBackup"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,9 +7,10 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { screen, fireEvent, render, waitFor } from "jest-matrix-react";
|
||||
import { screen, fireEvent, render, waitFor, act } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { Crypto, IMegolmSessionData } from "matrix-js-sdk/src/matrix";
|
||||
import { IMegolmSessionData } from "matrix-js-sdk/src/matrix";
|
||||
import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import * as MegolmExportEncryption from "../../../../../../src/utils/MegolmExportEncryption";
|
||||
import ExportE2eKeysDialog from "../../../../../../src/async-components/views/dialogs/security/ExportE2eKeysDialog";
|
||||
@@ -23,12 +24,12 @@ describe("ExportE2eKeysDialog", () => {
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should have disabled submit button initially", () => {
|
||||
it("should have disabled submit button initially", async () => {
|
||||
const cli = createTestClient();
|
||||
const onFinished = jest.fn();
|
||||
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
|
||||
fireEvent.click(container.querySelector("[type=submit]")!);
|
||||
expect(screen.getByText("Enter passphrase")).toBeInTheDocument();
|
||||
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
|
||||
expect(screen.getByLabelText("Enter passphrase")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should complain about weak passphrases", async () => {
|
||||
@@ -38,7 +39,7 @@ describe("ExportE2eKeysDialog", () => {
|
||||
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
|
||||
const input = screen.getByLabelText("Enter passphrase");
|
||||
await userEvent.type(input, "password");
|
||||
fireEvent.click(container.querySelector("[type=submit]")!);
|
||||
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
|
||||
await expect(screen.findByText("This is a top-10 common password")).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -49,7 +50,7 @@ describe("ExportE2eKeysDialog", () => {
|
||||
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
|
||||
await userEvent.type(screen.getByLabelText("Enter passphrase"), "ThisIsAMoreSecurePW123$$");
|
||||
await userEvent.type(screen.getByLabelText("Confirm passphrase"), "ThisIsAMoreSecurePW124$$");
|
||||
fireEvent.click(container.querySelector("[type=submit]")!);
|
||||
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
|
||||
await expect(screen.findByText("Passphrases must match")).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -62,7 +63,7 @@ describe("ExportE2eKeysDialog", () => {
|
||||
cli.getCrypto = () => {
|
||||
return {
|
||||
exportRoomKeysAsJson,
|
||||
} as unknown as Crypto.CryptoApi;
|
||||
} as unknown as CryptoApi;
|
||||
};
|
||||
|
||||
// Mock the result of encrypting the sessions. If we don't do this, the
|
||||
@@ -74,7 +75,7 @@ describe("ExportE2eKeysDialog", () => {
|
||||
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={jest.fn()} />);
|
||||
await userEvent.type(screen.getByLabelText("Enter passphrase"), passphrase);
|
||||
await userEvent.type(screen.getByLabelText("Confirm passphrase"), passphrase);
|
||||
fireEvent.click(container.querySelector("[type=submit]")!);
|
||||
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
|
||||
|
||||
// Then it exports keys and encrypts them
|
||||
await waitFor(() => expect(exportRoomKeysAsJson).toHaveBeenCalled());
|
||||
|
||||
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React from "react";
|
||||
import { fireEvent, render, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { Crypto } from "matrix-js-sdk/src/matrix";
|
||||
import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import ImportE2eKeysDialog from "../../../../../../src/async-components/views/dialogs/security/ImportE2eKeysDialog";
|
||||
import * as MegolmExportEncryption from "../../../../../../src/utils/MegolmExportEncryption";
|
||||
@@ -67,7 +67,7 @@ describe("ImportE2eKeysDialog", () => {
|
||||
cli.getCrypto = () => {
|
||||
return {
|
||||
importRoomKeysAsJson,
|
||||
} as unknown as Crypto.CryptoApi;
|
||||
} as unknown as CryptoApi;
|
||||
};
|
||||
|
||||
// Mock the result of decrypting the sessions, to avoid needing to
|
||||
|
||||
@@ -28,7 +28,7 @@ describe("<RestoreKeyBackupDialog />", () => {
|
||||
beforeEach(() => {
|
||||
matrixClient = stubClient();
|
||||
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockReturnValue(new Uint8Array(32));
|
||||
jest.spyOn(matrixClient, "getKeyBackupVersion").mockResolvedValue({ version: "1" } as KeyBackupInfo);
|
||||
jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({ version: "1" } as KeyBackupInfo);
|
||||
});
|
||||
|
||||
it("should render", async () => {
|
||||
@@ -99,7 +99,7 @@ describe("<RestoreKeyBackupDialog />", () => {
|
||||
|
||||
test("should restore key backup when passphrase is filled", async () => {
|
||||
// Determine that the passphrase is required
|
||||
jest.spyOn(matrixClient, "getKeyBackupVersion").mockResolvedValue({
|
||||
jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({
|
||||
version: "1",
|
||||
auth_data: {
|
||||
private_key_salt: "salt",
|
||||
|
||||
@@ -10,7 +10,7 @@ import React from "react";
|
||||
import { Room, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { ClientWidgetApi, IWidget, MatrixWidgetType } from "matrix-widget-api";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
import { act, render, RenderResult } from "jest-matrix-react";
|
||||
import { act, render, RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import {
|
||||
ApprovalOpts,
|
||||
@@ -29,7 +29,6 @@ import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
|
||||
import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
|
||||
import { UPDATE_EVENT } from "../../../../../src/stores/AsyncStore";
|
||||
import WidgetStore, { IApp } from "../../../../../src/stores/WidgetStore";
|
||||
import ActiveWidgetStore from "../../../../../src/stores/ActiveWidgetStore";
|
||||
import AppTile from "../../../../../src/components/views/elements/AppTile";
|
||||
@@ -59,16 +58,6 @@ describe("AppTile", () => {
|
||||
let app1: IApp;
|
||||
let app2: IApp;
|
||||
|
||||
const waitForRps = (roomId: string) =>
|
||||
new Promise<void>((resolve) => {
|
||||
const update = () => {
|
||||
if (RightPanelStore.instance.currentCardForRoom(roomId).phase !== RightPanelPhases.Widget) return;
|
||||
RightPanelStore.instance.off(UPDATE_EVENT, update);
|
||||
resolve();
|
||||
};
|
||||
RightPanelStore.instance.on(UPDATE_EVENT, update);
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
@@ -160,29 +149,28 @@ describe("AppTile", () => {
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
// Wait for RPS room 1 updates to fire
|
||||
const rpsUpdated = waitForRps("r1");
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r1",
|
||||
});
|
||||
await rpsUpdated;
|
||||
act(() =>
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r1",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(renderResult.getByText("Example 1")).toBeInTheDocument();
|
||||
await expect(renderResult.findByText("Example 1")).resolves.toBeInTheDocument();
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(true);
|
||||
|
||||
const { container, asFragment } = renderResult;
|
||||
expect(container.getElementsByClassName("mx_Spinner").length).toBeTruthy();
|
||||
const { asFragment } = renderResult;
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
// We want to verify that as we change to room 2, we should close the
|
||||
// right panel and destroy the widget.
|
||||
|
||||
// Switch to room 2
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r2",
|
||||
});
|
||||
act(() =>
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r2",
|
||||
}),
|
||||
);
|
||||
|
||||
renderResult.rerender(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
@@ -233,16 +221,17 @@ describe("AppTile", () => {
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
// Wait for RPS room 1 updates to fire
|
||||
const rpsUpdated1 = waitForRps("r1");
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r1",
|
||||
});
|
||||
await rpsUpdated1;
|
||||
act(() =>
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r1",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(true);
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r2")).toBe(false);
|
||||
await waitFor(() => {
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(true);
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r2")).toBe(false);
|
||||
});
|
||||
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name, roomId) => {
|
||||
if (name === "RightPanel.phases") {
|
||||
@@ -263,13 +252,13 @@ describe("AppTile", () => {
|
||||
}
|
||||
return realGetValue(name, roomId);
|
||||
});
|
||||
// Wait for RPS room 2 updates to fire
|
||||
const rpsUpdated2 = waitForRps("r2");
|
||||
// Switch to room 2
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r2",
|
||||
});
|
||||
act(() =>
|
||||
dis.dispatch({
|
||||
action: Action.ViewRoom,
|
||||
room_id: "r2",
|
||||
}),
|
||||
);
|
||||
renderResult.rerender(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<RightPanel
|
||||
@@ -279,10 +268,11 @@ describe("AppTile", () => {
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
await rpsUpdated2;
|
||||
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(false);
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r2")).toBe(true);
|
||||
await waitFor(() => {
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(false);
|
||||
expect(ActiveWidgetStore.instance.isLive("1", "r2")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves non-persisted widget on container move", async () => {
|
||||
@@ -345,7 +335,7 @@ describe("AppTile", () => {
|
||||
let renderResult: RenderResult;
|
||||
let moveToContainerSpy: jest.SpyInstance<void, [room: Room, widget: IWidget, toContainer: Container]>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} room={r1} />
|
||||
@@ -353,12 +343,12 @@ describe("AppTile", () => {
|
||||
);
|
||||
|
||||
moveToContainerSpy = jest.spyOn(WidgetLayoutStore.instance, "moveToContainer");
|
||||
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
const { container, asFragment } = renderResult;
|
||||
const { asFragment } = renderResult;
|
||||
|
||||
expect(container.querySelector(".mx_Spinner")).toBeFalsy(); // Assert that the spinner is gone
|
||||
expect(asFragment()).toMatchSnapshot(); // Take a snapshot of the pinned widget
|
||||
});
|
||||
|
||||
@@ -459,18 +449,19 @@ describe("AppTile", () => {
|
||||
describe("for a persistent app", () => {
|
||||
let renderResult: RenderResult;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
renderResult = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<AppTile key={app1.id} app={app1} fullWidth={true} room={r1} miniMode={true} showMenubar={false} />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
const { container, asFragment } = renderResult;
|
||||
it("should render", async () => {
|
||||
const { asFragment } = renderResult;
|
||||
|
||||
expect(container.querySelector(".mx_Spinner")).toBeFalsy();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import MiniAvatarUploader from "../../../../../src/components/views/elements/MiniAvatarUploader.tsx";
|
||||
import { stubClient, withClientContextRenderOptions } from "../../../../test-utils";
|
||||
|
||||
const BASE64_GIF = "R0lGODlhAQABAAAAACw=";
|
||||
const AVATAR_FILE = new File([Uint8Array.from(atob(BASE64_GIF), (c) => c.charCodeAt(0))], "avatar.gif", {
|
||||
type: "image/gif",
|
||||
});
|
||||
|
||||
describe("<MiniAvatarUploader />", () => {
|
||||
it("calls setAvatarUrl when a file is uploaded", async () => {
|
||||
const cli = stubClient();
|
||||
mocked(cli.uploadContent).mockResolvedValue({ content_uri: "mxc://example.com/1234" });
|
||||
|
||||
const setAvatarUrl = jest.fn();
|
||||
const user = userEvent.setup();
|
||||
|
||||
const { container, findByText } = render(
|
||||
<MiniAvatarUploader hasAvatar={false} noAvatarLabel="Upload" setAvatarUrl={setAvatarUrl} isUserAvatar />,
|
||||
withClientContextRenderOptions(cli),
|
||||
);
|
||||
|
||||
await findByText("Upload");
|
||||
await user.upload(container.querySelector("input")!, AVATAR_FILE);
|
||||
|
||||
expect(cli.uploadContent).toHaveBeenCalledWith(AVATAR_FILE);
|
||||
expect(setAvatarUrl).toHaveBeenCalledWith("mxc://example.com/1234");
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, render, RenderResult, screen } from "jest-matrix-react";
|
||||
import { render, RenderResult, screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked, Mocked } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
@@ -86,7 +86,7 @@ describe("<Pill>", () => {
|
||||
room: room1Id,
|
||||
msg: "Room 1 Message",
|
||||
});
|
||||
room1.addLiveEvents([room1Message]);
|
||||
room1.addLiveEvents([room1Message], { addToState: true });
|
||||
|
||||
room2 = new Room(room2Id, client, user1Id);
|
||||
room2.currentState.setStateEvents([mkRoomMemberJoinEvent(user2Id, room2Id)]);
|
||||
@@ -214,9 +214,7 @@ describe("<Pill>", () => {
|
||||
});
|
||||
|
||||
// wait for profile query via API
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(renderResult.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
@@ -228,9 +226,7 @@ describe("<Pill>", () => {
|
||||
});
|
||||
|
||||
// wait for profile query via API
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(renderResult.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -41,7 +41,7 @@ describe("<RoomTopic/>", () => {
|
||||
ts: 123,
|
||||
event: true,
|
||||
});
|
||||
room.addLiveEvents([topicEvent]);
|
||||
room.addLiveEvents([topicEvent], { addToState: true });
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
@@ -60,29 +60,9 @@ exports[`AppTile destroys non-persisted right panel widget on room change 1`] =
|
||||
id="1"
|
||||
>
|
||||
<div
|
||||
class="mx_AppTileBody mx_AppTileBody--large"
|
||||
class="mx_AppTile_persistedWrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_AppTileBody_fadeInSpinner"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner_Msg"
|
||||
>
|
||||
Loading…
|
||||
</div>
|
||||
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,9 @@ describe("<VerificationQRCode />", () => {
|
||||
});
|
||||
|
||||
it("renders a QR code", async () => {
|
||||
const { container, getAllByAltText } = render(<VerificationQRCode qrCodeBytes={Buffer.from("asd")} />);
|
||||
const { container, getAllByAltText } = render(
|
||||
<VerificationQRCode qrCodeBytes={new Uint8ClampedArray(Buffer.from("asd"))} />,
|
||||
);
|
||||
// wait for the spinner to go away
|
||||
await waitFor(() => getAllByAltText("QR Code").length === 1);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { createRef } from "react";
|
||||
import { render, waitFor } from "jest-matrix-react";
|
||||
import { render, waitFor, act } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import EmojiPicker from "../../../../../src/components/views/emojipicker/EmojiPicker";
|
||||
@@ -27,12 +27,12 @@ describe("EmojiPicker", function () {
|
||||
|
||||
// Apply a filter and assert that the HTML has changed
|
||||
//@ts-ignore private access
|
||||
ref.current!.onChangeFilter("test");
|
||||
act(() => ref.current!.onChangeFilter("test"));
|
||||
expect(beforeHtml).not.toEqual(container.innerHTML);
|
||||
|
||||
// Clear the filter and assert that the HTML matches what it was before filtering
|
||||
//@ts-ignore private access
|
||||
ref.current!.onChangeFilter("");
|
||||
act(() => ref.current!.onChangeFilter(""));
|
||||
await waitFor(() => expect(beforeHtml).toEqual(container.innerHTML));
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ describe("EmojiPicker", function () {
|
||||
const ep = new EmojiPicker({ onChoose: (str: string) => false, onFinished: jest.fn() });
|
||||
|
||||
//@ts-ignore private access
|
||||
ep.onChangeFilter("heart");
|
||||
act(() => ep.onChangeFilter("heart"));
|
||||
|
||||
//@ts-ignore private access
|
||||
expect(ep.memoizedDataByCategory["people"][0].shortcodes[0]).toEqual("heart");
|
||||
|
||||
@@ -139,7 +139,7 @@ describe("<LocationShareMenu />", () => {
|
||||
const [, onGeolocateCallback] = mocked(mockGeolocate.on).mock.calls.find(([event]) => event === "geolocate")!;
|
||||
|
||||
// set the location
|
||||
onGeolocateCallback(position);
|
||||
act(() => onGeolocateCallback(position));
|
||||
};
|
||||
|
||||
const setLocationClick = () => {
|
||||
@@ -151,7 +151,7 @@ describe("<LocationShareMenu />", () => {
|
||||
lngLat: { lng: position.coords.longitude, lat: position.coords.latitude },
|
||||
} as unknown as maplibregl.MapMouseEvent;
|
||||
// set the location
|
||||
onMapClickCallback(event);
|
||||
act(() => onMapClickCallback(event));
|
||||
};
|
||||
|
||||
const shareTypeLabels: Record<LocationShareType, string> = {
|
||||
|
||||
@@ -13,9 +13,18 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
<svg
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@@ -9,9 +9,18 @@ exports[`<Marker /> renders with location icon when no room member 1`] = `
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
<svg
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
|
||||
@@ -9,9 +9,18 @@ exports[`<SmartMarker /> creates a marker on mount 1`] = `
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
<svg
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
@@ -27,9 +36,18 @@ exports[`<SmartMarker /> removes marker on unmount 1`] = `
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
<svg
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@@ -264,10 +264,12 @@ describe("DateSeparator", () => {
|
||||
fireEvent.click(jumpToLastWeekButton);
|
||||
|
||||
// Expect error to be shown. We have to wait for the UI to transition.
|
||||
expect(await screen.findByTestId("jump-to-date-error-content")).toBeInTheDocument();
|
||||
await expect(screen.findByTestId("jump-to-date-error-content")).resolves.toBeInTheDocument();
|
||||
|
||||
// Expect an option to submit debug logs to be shown when a non-network error occurs
|
||||
expect(await screen.findByTestId("jump-to-date-error-submit-debug-logs-button")).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByTestId("jump-to-date-error-submit-debug-logs-button"),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
[
|
||||
@@ -280,19 +282,20 @@ describe("DateSeparator", () => {
|
||||
),
|
||||
].forEach((fakeError) => {
|
||||
it(`should show error dialog without submit debug logs option when networking error (${fakeError.name}) occurs`, async () => {
|
||||
// Try to jump to "last week" but we want a network error to occur
|
||||
mockClient.timestampToEvent.mockRejectedValue(fakeError);
|
||||
|
||||
// Render the component
|
||||
getComponent();
|
||||
|
||||
// Open the jump to date context menu
|
||||
fireEvent.click(screen.getByTestId("jump-to-date-separator-button"));
|
||||
|
||||
// Try to jump to "last week" but we want a network error to occur
|
||||
mockClient.timestampToEvent.mockRejectedValue(fakeError);
|
||||
const jumpToLastWeekButton = await screen.findByTestId("jump-to-date-last-week");
|
||||
fireEvent.click(jumpToLastWeekButton);
|
||||
|
||||
// Expect error to be shown. We have to wait for the UI to transition.
|
||||
expect(await screen.findByTestId("jump-to-date-error-content")).toBeInTheDocument();
|
||||
await expect(screen.findByTestId("jump-to-date-error-content")).resolves.toBeInTheDocument();
|
||||
|
||||
// The submit debug logs option should *NOT* be shown for network errors.
|
||||
//
|
||||
|
||||
@@ -10,6 +10,7 @@ import React from "react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
|
||||
import EncryptionEvent from "../../../../../src/components/views/messages/EncryptionEvent";
|
||||
import { createTestClient, mkMessage } from "../../../../test-utils";
|
||||
@@ -26,9 +27,9 @@ const renderEncryptionEvent = (client: MatrixClient, event: MatrixEvent) => {
|
||||
);
|
||||
};
|
||||
|
||||
const checkTexts = (title: string, subTitle: string) => {
|
||||
screen.getByText(title);
|
||||
screen.getByText(subTitle);
|
||||
const checkTexts = async (title: string, subTitle: string) => {
|
||||
await screen.findByText(title);
|
||||
await screen.findByText(subTitle);
|
||||
};
|
||||
|
||||
describe("EncryptionEvent", () => {
|
||||
@@ -55,17 +56,19 @@ describe("EncryptionEvent", () => {
|
||||
describe("for an encrypted room", () => {
|
||||
beforeEach(() => {
|
||||
event.event.content!.algorithm = algorithm;
|
||||
mocked(client.isRoomEncrypted).mockReturnValue(true);
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
const room = new Room(roomId, client, client.getUserId()!);
|
||||
mocked(client.getRoom).mockReturnValue(room);
|
||||
});
|
||||
|
||||
it("should show the expected texts", () => {
|
||||
it("should show the expected texts", async () => {
|
||||
renderEncryptionEvent(client, event);
|
||||
checkTexts(
|
||||
"Encryption enabled",
|
||||
"Messages in this room are end-to-end encrypted. " +
|
||||
"When people join, you can verify them in their profile, just tap on their profile picture.",
|
||||
await waitFor(() =>
|
||||
checkTexts(
|
||||
"Encryption enabled",
|
||||
"Messages in this room are end-to-end encrypted. " +
|
||||
"When people join, you can verify them in their profile, just tap on their profile picture.",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -76,9 +79,9 @@ describe("EncryptionEvent", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should show the expected texts", () => {
|
||||
it("should show the expected texts", async () => {
|
||||
renderEncryptionEvent(client, event);
|
||||
checkTexts("Encryption enabled", "Some encryption parameters have been changed.");
|
||||
await waitFor(() => checkTexts("Encryption enabled", "Some encryption parameters have been changed."));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,37 +90,39 @@ describe("EncryptionEvent", () => {
|
||||
event.event.content!.algorithm = "unknown";
|
||||
});
|
||||
|
||||
it("should show the expected texts", () => {
|
||||
it("should show the expected texts", async () => {
|
||||
renderEncryptionEvent(client, event);
|
||||
checkTexts("Encryption enabled", "Ignored attempt to disable encryption");
|
||||
await waitFor(() => checkTexts("Encryption enabled", "Ignored attempt to disable encryption"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("for an unencrypted room", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.isRoomEncrypted).mockReturnValue(false);
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
|
||||
renderEncryptionEvent(client, event);
|
||||
});
|
||||
|
||||
it("should show the expected texts", () => {
|
||||
expect(client.isRoomEncrypted).toHaveBeenCalledWith(roomId);
|
||||
checkTexts("Encryption not enabled", "The encryption used by this room isn't supported.");
|
||||
it("should show the expected texts", async () => {
|
||||
expect(client.getCrypto()!.isEncryptionEnabledInRoom).toHaveBeenCalledWith(roomId);
|
||||
await waitFor(() =>
|
||||
checkTexts("Encryption not enabled", "The encryption used by this room isn't supported."),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("for an encrypted local room", () => {
|
||||
beforeEach(() => {
|
||||
event.event.content!.algorithm = algorithm;
|
||||
mocked(client.isRoomEncrypted).mockReturnValue(true);
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
const localRoom = new LocalRoom(roomId, client, client.getUserId()!);
|
||||
mocked(client.getRoom).mockReturnValue(localRoom);
|
||||
renderEncryptionEvent(client, event);
|
||||
});
|
||||
|
||||
it("should show the expected texts", () => {
|
||||
expect(client.isRoomEncrypted).toHaveBeenCalledWith(roomId);
|
||||
checkTexts("Encryption enabled", "Messages in this chat will be end-to-end encrypted.");
|
||||
it("should show the expected texts", async () => {
|
||||
expect(client.getCrypto()!.isEncryptionEnabledInRoom).toHaveBeenCalledWith(roomId);
|
||||
await checkTexts("Encryption enabled", "Messages in this chat will be end-to-end encrypted.");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,7 +20,8 @@ import {
|
||||
import { MediaEventHelper } from "../../../../../src/utils/MediaEventHelper";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import MFileBody from "../../../../../src/components/views/messages/MFileBody.tsx";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext.ts";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext.ts";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("matrix-encrypt-attachment", () => ({
|
||||
decryptAttachment: jest.fn(),
|
||||
@@ -72,14 +73,14 @@ describe("<MFileBody/>", () => {
|
||||
|
||||
it("should show a download button in file rendering type", async () => {
|
||||
const { container, getByRole } = render(
|
||||
<RoomContext.Provider value={{ timelineRenderingType: TimelineRenderingType.File } as any}>
|
||||
<ScopedRoomContextProvider {...({ timelineRenderingType: TimelineRenderingType.File } as any)}>
|
||||
<MFileBody
|
||||
{...props}
|
||||
mxEvent={mediaEvent}
|
||||
mediaEventHelper={new MediaEventHelper(mediaEvent)}
|
||||
showGenericPlaceholder={false}
|
||||
/>
|
||||
</RoomContext.Provider>,
|
||||
</ScopedRoomContextProvider>,
|
||||
);
|
||||
|
||||
expect(getByRole("link", { name: "Download" })).toBeInTheDocument();
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, RenderResult, waitFor } from "jest-matrix-react";
|
||||
import { act, fireEvent, render, RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
|
||||
import {
|
||||
MatrixEvent,
|
||||
Relations,
|
||||
@@ -226,7 +226,7 @@ describe("MPollBody", () => {
|
||||
clickOption(renderResult, "pizza");
|
||||
|
||||
// When a new vote from me comes in
|
||||
await room.processPollEvents([responseEvent("@me:example.com", "wings", 101)]);
|
||||
await act(() => room.processPollEvents([responseEvent("@me:example.com", "wings", 101)]));
|
||||
|
||||
// Then the new vote is counted, not the old one
|
||||
expect(votesCount(renderResult, "pizza")).toBe("0 votes");
|
||||
@@ -255,7 +255,7 @@ describe("MPollBody", () => {
|
||||
clickOption(renderResult, "pizza");
|
||||
|
||||
// When a new vote from someone else comes in
|
||||
await room.processPollEvents([responseEvent("@xx:example.com", "wings", 101)]);
|
||||
await act(() => room.processPollEvents([responseEvent("@xx:example.com", "wings", 101)]));
|
||||
|
||||
// Then my vote is still for pizza
|
||||
// NOTE: the new event does not affect the counts for other people -
|
||||
@@ -596,11 +596,13 @@ describe("MPollBody", () => {
|
||||
];
|
||||
const renderResult = await newMPollBody(votes, ends);
|
||||
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
await waitFor(() => {
|
||||
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
|
||||
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
|
||||
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
|
||||
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores votes that arrived after the first end poll event", async () => {
|
||||
@@ -890,12 +892,14 @@ async function newMPollBody(
|
||||
room_id: "#myroom:example.com",
|
||||
content: newPollStart(answers, undefined, disclosed),
|
||||
});
|
||||
const result = newMPollBodyFromEvent(mxEvent, relationEvents, endEvents);
|
||||
// flush promises from loading relations
|
||||
const prom = newMPollBodyFromEvent(mxEvent, relationEvents, endEvents);
|
||||
if (waitForResponsesLoad) {
|
||||
await flushPromises();
|
||||
const result = await prom;
|
||||
if (result.queryByTestId("spinner")) {
|
||||
await waitForElementToBeRemoved(() => result.getByTestId("spinner"));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return prom;
|
||||
}
|
||||
|
||||
function getMPollBodyPropsFromEvent(mxEvent: MatrixEvent): IBodyProps {
|
||||
|
||||
@@ -121,12 +121,13 @@ describe("<MPollEndBody />", () => {
|
||||
describe("when poll start event does not exist in current timeline", () => {
|
||||
it("fetches the related poll start event and displays a poll tile", async () => {
|
||||
await setupRoomWithEventsTimeline(pollEndEvent);
|
||||
const { container, getByTestId, getByRole } = getComponent();
|
||||
const { container, getByTestId, getByRole, queryByRole } = getComponent();
|
||||
|
||||
// while fetching event, only icon is shown
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
await waitFor(() => expect(getByRole("progressbar")).toBeInTheDocument());
|
||||
await waitFor(() => expect(queryByRole("progressbar")).not.toBeInTheDocument());
|
||||
|
||||
expect(mockClient.fetchRoomEvent).toHaveBeenCalledWith(roomId, pollStartEvent.getId());
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import dispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import PinningUtils from "../../../../../src/utils/PinningUtils";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../src/dispatcher/dispatcher");
|
||||
|
||||
@@ -117,9 +118,9 @@ describe("<MessageActionBar />", () => {
|
||||
} as unknown as IRoomState;
|
||||
const getComponent = (props = {}, roomContext: Partial<IRoomState> = {}) =>
|
||||
render(
|
||||
<RoomContext.Provider value={{ ...defaultRoomContext, ...roomContext }}>
|
||||
<ScopedRoomContextProvider {...defaultRoomContext} {...roomContext}>
|
||||
<MessageActionBar {...defaultProps} {...props} />
|
||||
</RoomContext.Provider>,
|
||||
</ScopedRoomContextProvider>,
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -14,7 +14,6 @@ import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast";
|
||||
import { mkEvent, mkRoom, stubClient } from "../../../../test-utils";
|
||||
import MessageEvent from "../../../../../src/components/views/messages/MessageEvent";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
@@ -24,10 +23,6 @@ jest.mock("../../../../../src/components/views/messages/UnknownBody", () => ({
|
||||
default: () => <div data-testid="unknown-body" />,
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../src/voice-broadcast/components/VoiceBroadcastBody", () => ({
|
||||
VoiceBroadcastBody: () => <div data-testid="voice-broadcast-body" />,
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../src/components/views/messages/MImageBody", () => ({
|
||||
__esModule: true,
|
||||
default: () => <div data-testid="image-body" />,
|
||||
@@ -81,27 +76,6 @@ describe("MessageEvent", () => {
|
||||
jest.spyOn(SettingsStore, "unwatchSetting").mockImplementation(jest.fn());
|
||||
});
|
||||
|
||||
describe("when a voice broadcast start event occurs", () => {
|
||||
let result: RenderResult;
|
||||
|
||||
beforeEach(() => {
|
||||
event = mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
user: client.getUserId()!,
|
||||
room: room.roomId,
|
||||
content: {
|
||||
state: VoiceBroadcastInfoState.Started,
|
||||
},
|
||||
});
|
||||
result = renderMessageEvent();
|
||||
});
|
||||
|
||||
it("should render a VoiceBroadcast component", () => {
|
||||
result.getByTestId("voice-broadcast-body");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when an image with a caption is sent", () => {
|
||||
let result: RenderResult;
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ import {
|
||||
} from "../../../../../src/components/views/messages/RoomPredecessorTile";
|
||||
import { stubClient, upsertRoomStateEvents } from "../../../../test-utils/test-utils";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import { filterConsole, getRoomContext } from "../../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../src/dispatcher/dispatcher");
|
||||
|
||||
@@ -99,9 +99,9 @@ describe("<RoomPredecessorTile />", () => {
|
||||
expect(createEvent).toBeTruthy();
|
||||
|
||||
return render(
|
||||
<RoomContext.Provider value={getRoomContext(room, {})}>
|
||||
<ScopedRoomContextProvider {...getRoomContext(room, {})}>
|
||||
<RoomPredecessorTile mxEvent={createEvent} />
|
||||
</RoomContext.Provider>,
|
||||
</ScopedRoomContextProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,9 +49,18 @@ exports[`MLocationBody <MLocationBody> without error renders map correctly 1`] =
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
<svg
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 21.325a2.07 2.07 0 0 1-.7-.125 1.84 1.84 0 0 1-.625-.375A39.112 39.112 0 0 1 7.8 17.9c-.833-.95-1.53-1.87-2.087-2.762-.559-.892-.984-1.75-1.276-2.575C4.146 11.738 4 10.95 4 10.2c0-2.5.804-4.492 2.412-5.975C8.021 2.742 9.883 2 12 2s3.98.742 5.587 2.225C19.197 5.708 20 7.7 20 10.2c0 .75-.146 1.538-.438 2.363-.291.824-.716 1.683-1.274 2.574A21.678 21.678 0 0 1 16.2 17.9a39.112 39.112 0 0 1-2.875 2.925 1.84 1.84 0 0 1-.625.375 2.07 2.07 0 0 1-.7.125ZM12 12c.55 0 1.02-.196 1.412-.588.392-.391.588-.862.588-1.412 0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 12 8c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 10 10c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, fireEvent, render } from "jest-matrix-react";
|
||||
import { fireEvent, render } from "jest-matrix-react";
|
||||
import { Filter, EventTimeline, Room, MatrixEvent, M_POLL_START } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { PollHistory } from "../../../../../../src/components/views/polls/pollHistory/PollHistory";
|
||||
@@ -110,7 +110,7 @@ describe("<PollHistory />", () => {
|
||||
expect(getByText("Loading polls")).toBeInTheDocument();
|
||||
|
||||
// flush filter creation request
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(liveTimeline.getPaginationToken).toHaveBeenCalledWith(EventTimeline.BACKWARDS);
|
||||
expect(mockClient.paginateEventTimeline).toHaveBeenCalledWith(liveTimeline, { backwards: true });
|
||||
@@ -140,7 +140,7 @@ describe("<PollHistory />", () => {
|
||||
);
|
||||
|
||||
// flush filter creation request
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
// once per page
|
||||
expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(3);
|
||||
|
||||
@@ -175,7 +175,7 @@ describe("<PollHistory />", () => {
|
||||
|
||||
it("renders a no polls message when there are no active polls in the room", async () => {
|
||||
const { getByText } = getComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(getByText("There are no active polls in this room")).toBeTruthy();
|
||||
});
|
||||
@@ -199,7 +199,7 @@ describe("<PollHistory />", () => {
|
||||
.mockReturnValueOnce("test-pagination-token-3");
|
||||
|
||||
const { getByText } = getComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -212,7 +212,7 @@ describe("<PollHistory />", () => {
|
||||
// load more polls button still in UI, with loader
|
||||
expect(getByText("Load more polls")).toMatchSnapshot();
|
||||
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// no more spinner
|
||||
expect(getByText("Load more polls")).toMatchSnapshot();
|
||||
|
||||
@@ -91,7 +91,7 @@ exports[`<PollHistory /> renders a list of active polls when there are polls in
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-labelledby=":ra:"
|
||||
aria-labelledby=":rc:"
|
||||
class="mx_PollListItem_content"
|
||||
>
|
||||
<span>
|
||||
@@ -116,7 +116,7 @@ exports[`<PollHistory /> renders a list of active polls when there are polls in
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-labelledby=":rg:"
|
||||
aria-labelledby=":rh:"
|
||||
class="mx_PollListItem_content"
|
||||
>
|
||||
<span>
|
||||
|
||||
@@ -15,7 +15,7 @@ import userEvent from "@testing-library/user-event";
|
||||
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import RoomSummaryCard from "../../../../../src/components/views/right_panel/RoomSummaryCard";
|
||||
import ShareDialog from "../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { ShareDialog } from "../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import ExportDialog from "../../../../../src/components/views/dialogs/ExportDialog";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
@@ -30,7 +30,8 @@ import { _t } from "../../../../../src/languageHandler";
|
||||
import { tagRoom } from "../../../../../src/utils/room/tagRoom";
|
||||
import { DefaultTagID } from "../../../../../src/stores/room-list/models";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../src/utils/room/tagRoom");
|
||||
|
||||
@@ -172,14 +173,14 @@ describe("<RoomSummaryCard />", () => {
|
||||
const onSearchChange = jest.fn();
|
||||
const { rerender } = render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={{ timelineRenderingType: TimelineRenderingType.Search } as any}>
|
||||
<ScopedRoomContextProvider {...({ timelineRenderingType: TimelineRenderingType.Search } as any)}>
|
||||
<RoomSummaryCard
|
||||
room={room}
|
||||
permalinkCreator={new RoomPermalinkCreator(room)}
|
||||
onSearchChange={onSearchChange}
|
||||
focusRoomSearch={true}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
@@ -188,13 +189,13 @@ describe("<RoomSummaryCard />", () => {
|
||||
|
||||
rerender(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={{ timelineRenderingType: TimelineRenderingType.Room } as any}>
|
||||
<ScopedRoomContextProvider {...({ timelineRenderingType: TimelineRenderingType.Room } as any)}>
|
||||
<RoomSummaryCard
|
||||
room={room}
|
||||
permalinkCreator={new RoomPermalinkCreator(room)}
|
||||
onSearchChange={onSearchChange}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
expect(screen.getByPlaceholderText("Search messages…")).toHaveValue("");
|
||||
@@ -254,10 +255,7 @@ describe("<RoomSummaryCard />", () => {
|
||||
|
||||
fireEvent.click(getByText("People"));
|
||||
|
||||
expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith(
|
||||
{ phase: RightPanelPhases.RoomMemberList },
|
||||
true,
|
||||
);
|
||||
expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith({ phase: RightPanelPhases.MemberList }, true);
|
||||
});
|
||||
|
||||
it("opens room threads list on button click", () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, screen, waitFor, cleanup, act, within } from "jest-matrix-react";
|
||||
import { fireEvent, render, screen, cleanup, act, within, waitForElementToBeRemoved } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { Mocked, mocked } from "jest-mock";
|
||||
import { Room, User, MatrixClient, RoomMember, MatrixEvent, EventType, Device } from "matrix-js-sdk/src/matrix";
|
||||
@@ -49,7 +49,7 @@ import ErrorDialog from "../../../../../src/components/views/dialogs/ErrorDialog
|
||||
import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
|
||||
import { UIComponent } from "../../../../../src/settings/UIFeature";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import ShareDialog from "../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { ShareDialog } from "../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import BulkRedactDialog from "../../../../../src/components/views/dialogs/BulkRedactDialog";
|
||||
|
||||
jest.mock("../../../../../src/utils/direct-messages", () => ({
|
||||
@@ -188,7 +188,7 @@ describe("<UserInfo />", () => {
|
||||
const defaultProps = {
|
||||
user: defaultUser,
|
||||
// idk what is wrong with this type
|
||||
phase: RightPanelPhases.RoomMemberInfo as RightPanelPhases.RoomMemberInfo,
|
||||
phase: RightPanelPhases.MemberInfo as RightPanelPhases.MemberInfo,
|
||||
onClose: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -439,7 +439,7 @@ describe("<UserInfo />", () => {
|
||||
|
||||
it("renders a device list which can be expanded", async () => {
|
||||
renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// check the button exists with the expected text
|
||||
const devicesButton = screen.getByRole("button", { name: "1 session" });
|
||||
@@ -455,13 +455,13 @@ describe("<UserInfo />", () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||
|
||||
const { container } = renderComponent({
|
||||
phase: RightPanelPhases.SpaceMemberInfo,
|
||||
phase: RightPanelPhases.MemberInfo,
|
||||
verificationRequest,
|
||||
room: mockRoom,
|
||||
});
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Verify" })).toBeInTheDocument());
|
||||
await expect(screen.findByRole("button", { name: "Verify" })).resolves.toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -490,7 +490,7 @@ describe("<UserInfo />", () => {
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
|
||||
const devicesButton = screen.getByRole("button", { name: "1 session" });
|
||||
@@ -538,7 +538,7 @@ describe("<UserInfo />", () => {
|
||||
} as DeviceVerificationStatus);
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
|
||||
const devicesButton = screen.getByRole("button", { name: "1 verified session" });
|
||||
@@ -583,7 +583,7 @@ describe("<UserInfo />", () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, true, true));
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// the dehydrated device should be shown as an unverified device, which means
|
||||
// there should now be a button with the device id ...
|
||||
@@ -618,7 +618,7 @@ describe("<UserInfo />", () => {
|
||||
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
|
||||
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
|
||||
const devicesButton = screen.getByRole("button", { name: "2 sessions" });
|
||||
@@ -649,11 +649,14 @@ describe("<UserInfo />", () => {
|
||||
mockClient.getDomain.mockReturnValue("example.com");
|
||||
|
||||
const { container } = renderComponent({
|
||||
phase: RightPanelPhases.RoomMemberInfo,
|
||||
phase: RightPanelPhases.MemberInfo,
|
||||
room: mockRoom,
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Deactivate user" })).toBeInTheDocument());
|
||||
await expect(screen.findByRole("button", { name: "Deactivate user" })).resolves.toBeInTheDocument();
|
||||
if (screen.queryAllByRole("progressbar").length) {
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
|
||||
}
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -666,7 +669,7 @@ describe("<UserInfo />", () => {
|
||||
it("renders unverified user info", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const userHeading = screen.getByRole("heading", { name: /@user:example.com/ });
|
||||
|
||||
@@ -677,7 +680,7 @@ describe("<UserInfo />", () => {
|
||||
it("renders verified user info", async () => {
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, false, false));
|
||||
renderComponent({ room: mockRoom });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const userHeading = screen.getByRole("heading", { name: /@user:example.com/ });
|
||||
|
||||
@@ -768,7 +771,7 @@ describe("<DeviceItem />", () => {
|
||||
|
||||
it("with unverified user and device, displays button without a label", async () => {
|
||||
renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.getByRole("button", { name: device.displayName! })).toBeInTheDocument();
|
||||
expect(screen.queryByText(/trusted/i)).not.toBeInTheDocument();
|
||||
@@ -776,7 +779,7 @@ describe("<DeviceItem />", () => {
|
||||
|
||||
it("with verified user only, displays button with a 'Not trusted' label", async () => {
|
||||
renderComponent({ isUserVerified: true });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const button = screen.getByRole("button", { name: device.displayName });
|
||||
expect(button).toHaveTextContent(`${device.displayName}Not trusted`);
|
||||
@@ -785,7 +788,7 @@ describe("<DeviceItem />", () => {
|
||||
it("with verified device only, displays no button without a label", async () => {
|
||||
setMockDeviceTrust(true);
|
||||
renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.getByText(device.displayName!)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/trusted/)).not.toBeInTheDocument();
|
||||
@@ -798,7 +801,7 @@ describe("<DeviceItem />", () => {
|
||||
mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
|
||||
mockClient.getUserId.mockReturnValueOnce(defaultUserId);
|
||||
renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
// set trust to be false for isVerified, true for isCrossSigningVerified
|
||||
deferred.resolve({
|
||||
@@ -814,7 +817,7 @@ describe("<DeviceItem />", () => {
|
||||
it("with verified user and device, displays no button and a 'Trusted' label", async () => {
|
||||
setMockDeviceTrust(true);
|
||||
renderComponent({ isUserVerified: true });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
||||
expect(screen.getByText(device.displayName!)).toBeInTheDocument();
|
||||
@@ -824,7 +827,7 @@ describe("<DeviceItem />", () => {
|
||||
it("does not call verifyDevice if client.getUser returns null", async () => {
|
||||
mockClient.getUser.mockReturnValueOnce(null);
|
||||
renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const button = screen.getByRole("button", { name: device.displayName! });
|
||||
expect(button).toBeInTheDocument();
|
||||
@@ -839,7 +842,7 @@ describe("<DeviceItem />", () => {
|
||||
// even more mocking
|
||||
mockClient.isGuest.mockReturnValueOnce(true);
|
||||
renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const button = screen.getByRole("button", { name: device.displayName! });
|
||||
expect(button).toBeInTheDocument();
|
||||
@@ -851,7 +854,7 @@ describe("<DeviceItem />", () => {
|
||||
|
||||
it("with display name", async () => {
|
||||
const { container } = renderComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
@@ -859,7 +862,7 @@ describe("<DeviceItem />", () => {
|
||||
it("without display name", async () => {
|
||||
const device = { deviceId: "deviceId" } as Device;
|
||||
const { container } = renderComponent({ device, userId: defaultUserId });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
@@ -867,7 +870,7 @@ describe("<DeviceItem />", () => {
|
||||
it("ambiguous display name", async () => {
|
||||
const device = { deviceId: "deviceId", ambiguous: true, displayName: "my display name" };
|
||||
const { container } = renderComponent({ device, userId: defaultUserId });
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
@@ -1033,9 +1036,7 @@ describe("<UserOptionsSection />", () => {
|
||||
expect(inviteSpy).toHaveBeenCalledWith([member.userId]);
|
||||
|
||||
// check that the test error message is displayed
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(mockErrorMessage.message)).toBeInTheDocument();
|
||||
});
|
||||
await expect(screen.findByText(mockErrorMessage.message)).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("if calling .invite throws something strange, show default error message", async () => {
|
||||
@@ -1048,9 +1049,7 @@ describe("<UserOptionsSection />", () => {
|
||||
await userEvent.click(inviteButton);
|
||||
|
||||
// check that the default test error message is displayed
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/operation failed/i)).toBeInTheDocument();
|
||||
});
|
||||
await expect(screen.findByText(/operation failed/i)).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -46,7 +46,7 @@ describe("<VerificationPanel />", () => {
|
||||
const request = makeMockVerificationRequest({
|
||||
phase: Phase.Ready,
|
||||
});
|
||||
request.generateQRCode.mockResolvedValue(Buffer.from("test", "utf-8"));
|
||||
request.generateQRCode.mockResolvedValue(new Uint8ClampedArray(Buffer.from("test", "utf-8")));
|
||||
const container = renderComponent({
|
||||
request: request,
|
||||
layout: "dialog",
|
||||
@@ -71,7 +71,7 @@ describe("<VerificationPanel />", () => {
|
||||
const request = makeMockVerificationRequest({
|
||||
phase: Phase.Ready,
|
||||
});
|
||||
request.generateQRCode.mockResolvedValue(Buffer.from("test", "utf-8"));
|
||||
request.generateQRCode.mockResolvedValue(new Uint8ClampedArray(Buffer.from("test", "utf-8")));
|
||||
const container = renderComponent({
|
||||
request: request,
|
||||
member: new User("@other:user"),
|
||||
|
||||
@@ -135,8 +135,9 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
||||
style="--mx-box-flex: 1;"
|
||||
>
|
||||
<a
|
||||
class="_link_1mzip_17"
|
||||
class="_link_ue21z_17"
|
||||
data-kind="primary"
|
||||
data-size="medium"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<p
|
||||
@@ -752,8 +753,9 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
||||
style="--mx-box-flex: 1;"
|
||||
>
|
||||
<a
|
||||
class="_link_1mzip_17"
|
||||
class="_link_ue21z_17"
|
||||
data-kind="primary"
|
||||
data-size="medium"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<p
|
||||
@@ -1406,8 +1408,9 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
||||
style="--mx-box-flex: 1;"
|
||||
>
|
||||
<a
|
||||
class="_link_1mzip_17"
|
||||
class="_link_ue21z_17"
|
||||
data-kind="primary"
|
||||
data-size="medium"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<p
|
||||
|
||||
@@ -88,7 +88,7 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby=":r6s:"
|
||||
aria-labelledby=":r74:"
|
||||
class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
@@ -402,7 +402,7 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-labelledby=":r92:"
|
||||
aria-labelledby=":r9a:"
|
||||
class="_icon-button_bh2qc_17 _subtle-bg_bh2qc_38"
|
||||
data-testid="base-card-close-button"
|
||||
role="button"
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
|
||||
import { createTestClient, mkStubRoom, withClientContextRenderOptions } from "../../../../test-utils";
|
||||
import { UrlPreviewSettings } from "../../../../../src/components/views/room_settings/UrlPreviewSettings.tsx";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore.ts";
|
||||
import dis from "../../../../../src/dispatcher/dispatcher.ts";
|
||||
import { Action } from "../../../../../src/dispatcher/actions.ts";
|
||||
|
||||
describe("UrlPreviewSettings", () => {
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
beforeEach(() => {
|
||||
client = createTestClient();
|
||||
room = mkStubRoom("roomId", "room", client);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
function renderComponent() {
|
||||
return render(<UrlPreviewSettings room={room} />, withClientContextRenderOptions(client));
|
||||
}
|
||||
|
||||
it("should display the correct preview when the setting is in a loading state", () => {
|
||||
jest.spyOn(client, "getCrypto").mockReturnValue(undefined);
|
||||
const { asFragment } = renderComponent();
|
||||
expect(screen.getByText("URL Previews")).toBeInTheDocument();
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should display the correct preview when the room is encrypted and the url preview is enabled", async () => {
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(true);
|
||||
|
||||
const { asFragment } = renderComponent();
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should display the correct preview when the room is unencrypted and the url preview is enabled", async () => {
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
|
||||
jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(true);
|
||||
jest.spyOn(dis, "fire").mockReturnValue(undefined);
|
||||
|
||||
const { asFragment } = renderComponent();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: "enabled" })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("URL previews are enabled by default for participants in this room."),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
screen.getByRole("button", { name: "enabled" }).click();
|
||||
expect(dis.fire).toHaveBeenCalledWith(Action.ViewUserSettings);
|
||||
});
|
||||
|
||||
it("should display the correct preview when the room is unencrypted and the url preview is disabled", async () => {
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(false);
|
||||
jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(false);
|
||||
|
||||
const { asFragment } = renderComponent();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: "disabled" })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("URL previews are disabled by default for participants in this room."),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,236 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UrlPreviewSettings should display the correct preview when the room is encrypted and the url preview is enabled 1`] = `
|
||||
<DocumentFragment>
|
||||
<fieldset
|
||||
class="mx_SettingsFieldset"
|
||||
>
|
||||
<legend
|
||||
class="mx_SettingsFieldset_legend"
|
||||
>
|
||||
URL Previews
|
||||
</legend>
|
||||
<div
|
||||
class="mx_SettingsFieldset_description"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
<p>
|
||||
When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.
|
||||
</p>
|
||||
<p>
|
||||
In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFieldset_content"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
/>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`UrlPreviewSettings should display the correct preview when the room is unencrypted and the url preview is disabled 1`] = `
|
||||
<DocumentFragment>
|
||||
<fieldset
|
||||
class="mx_SettingsFieldset"
|
||||
>
|
||||
<legend
|
||||
class="mx_SettingsFieldset_legend"
|
||||
>
|
||||
URL Previews
|
||||
</legend>
|
||||
<div
|
||||
class="mx_SettingsFieldset_description"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
<p>
|
||||
When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
You have
|
||||
</span>
|
||||
</p>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
disabled
|
||||
</div>
|
||||
URL previews by default.
|
||||
<p />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFieldset_content"
|
||||
>
|
||||
<div>
|
||||
URL previews are disabled by default for participants in this room.
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Enable inline URL previews by default
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="false"
|
||||
aria-disabled="true"
|
||||
aria-label="Enable inline URL previews by default"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch"
|
||||
id="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`UrlPreviewSettings should display the correct preview when the room is unencrypted and the url preview is enabled 1`] = `
|
||||
<DocumentFragment>
|
||||
<fieldset
|
||||
class="mx_SettingsFieldset"
|
||||
>
|
||||
<legend
|
||||
class="mx_SettingsFieldset_legend"
|
||||
>
|
||||
URL Previews
|
||||
</legend>
|
||||
<div
|
||||
class="mx_SettingsFieldset_description"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsection_text"
|
||||
>
|
||||
<p>
|
||||
When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
You have
|
||||
</span>
|
||||
</p>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
enabled
|
||||
</div>
|
||||
URL previews by default.
|
||||
<p />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFieldset_content"
|
||||
>
|
||||
<div>
|
||||
URL previews are enabled by default for participants in this room.
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Enable inline URL previews by default
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Enable inline URL previews by default"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`UrlPreviewSettings should display the correct preview when the setting is in a loading state 1`] = `
|
||||
<DocumentFragment>
|
||||
<fieldset
|
||||
class="mx_SettingsFieldset"
|
||||
>
|
||||
<legend
|
||||
class="mx_SettingsFieldset_legend"
|
||||
>
|
||||
URL Previews
|
||||
</legend>
|
||||
<div
|
||||
class="mx_SettingsFieldset_content"
|
||||
>
|
||||
<svg
|
||||
class="_icon_1ye7b_27"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
style="width: 20px; height: 20px;"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M12 4.031a8 8 0 1 0 8 8 1 1 0 0 1 2 0c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10a1 1 0 1 1 0 2Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</fieldset>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -27,12 +27,12 @@ import {
|
||||
import DocumentOffset from "../../../../../src/editor/offset";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import EditorStateTransfer from "../../../../../src/utils/EditorStateTransfer";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import Autocompleter, { IProviderCompletions } from "../../../../../src/autocomplete/Autocompleter";
|
||||
import NotifProvider from "../../../../../src/autocomplete/NotifProvider";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
describe("<EditMessageComposer/>", () => {
|
||||
const userId = "@alice:server.org";
|
||||
@@ -79,7 +79,7 @@ describe("<EditMessageComposer/>", () => {
|
||||
render(<EditMessageComposerWithMatrixClient editState={editState} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={roomContext}>{children}</RoomContext.Provider>
|
||||
<ScopedRoomContextProvider {...roomContext}>{children}</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
),
|
||||
});
|
||||
@@ -128,7 +128,7 @@ describe("<EditMessageComposer/>", () => {
|
||||
|
||||
const expectedBody = {
|
||||
...editedEvent.getContent(),
|
||||
"body": " * original message + edit",
|
||||
"body": "* original message + edit",
|
||||
"m.new_content": {
|
||||
"body": "original message + edit",
|
||||
"msgtype": "m.text",
|
||||
@@ -160,7 +160,7 @@ describe("<EditMessageComposer/>", () => {
|
||||
const content = createEditContent(model, editedEvent);
|
||||
|
||||
expect(content).toEqual({
|
||||
"body": " * hello world",
|
||||
"body": "* hello world",
|
||||
"msgtype": "m.text",
|
||||
"m.new_content": {
|
||||
"body": "hello world",
|
||||
@@ -183,10 +183,10 @@ describe("<EditMessageComposer/>", () => {
|
||||
const content = createEditContent(model, editedEvent);
|
||||
|
||||
expect(content).toEqual({
|
||||
"body": " * hello *world*",
|
||||
"body": "* hello *world*",
|
||||
"msgtype": "m.text",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": " * hello <em>world</em>",
|
||||
"formatted_body": "* hello <em>world</em>",
|
||||
"m.new_content": {
|
||||
"body": "hello *world*",
|
||||
"msgtype": "m.text",
|
||||
@@ -210,10 +210,10 @@ describe("<EditMessageComposer/>", () => {
|
||||
const content = createEditContent(model, editedEvent);
|
||||
|
||||
expect(content).toEqual({
|
||||
"body": " * blinks __quickly__",
|
||||
"body": "* blinks __quickly__",
|
||||
"msgtype": "m.emote",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": " * blinks <strong>quickly</strong>",
|
||||
"formatted_body": "* blinks <strong>quickly</strong>",
|
||||
"m.new_content": {
|
||||
"body": "blinks __quickly__",
|
||||
"msgtype": "m.emote",
|
||||
@@ -238,7 +238,7 @@ describe("<EditMessageComposer/>", () => {
|
||||
const content = createEditContent(model, editedEvent);
|
||||
|
||||
expect(content).toEqual({
|
||||
"body": " * ✨sparkles✨",
|
||||
"body": "* ✨sparkles✨",
|
||||
"msgtype": "m.emote",
|
||||
"m.new_content": {
|
||||
"body": "✨sparkles✨",
|
||||
@@ -264,7 +264,7 @@ describe("<EditMessageComposer/>", () => {
|
||||
// TODO Edits do not properly strip the double slash used to skip
|
||||
// command processing.
|
||||
expect(content).toEqual({
|
||||
"body": " * //dev/null is my favourite place",
|
||||
"body": "* //dev/null is my favourite place",
|
||||
"msgtype": "m.text",
|
||||
"m.new_content": {
|
||||
"body": "//dev/null is my favourite place",
|
||||
|
||||
@@ -30,7 +30,7 @@ import { mkEncryptedMatrixEvent } from "matrix-js-sdk/src/testing";
|
||||
|
||||
import EventTile, { EventTileProps } from "../../../../../src/components/views/rooms/EventTile";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { filterConsole, flushPromises, getRoomContext, mkEvent, mkMessage, stubClient } from "../../../../test-utils";
|
||||
import { mkThread } from "../../../../test-utils/threads";
|
||||
@@ -40,6 +40,7 @@ import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import PinningUtils from "../../../../../src/utils/PinningUtils";
|
||||
import { Layout } from "../../../../../src/settings/enums/Layout";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
describe("EventTile", () => {
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
@@ -56,13 +57,13 @@ describe("EventTile", () => {
|
||||
}) {
|
||||
return (
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={props.roomContext}>
|
||||
<ScopedRoomContextProvider {...props.roomContext}>
|
||||
<EventTile
|
||||
mxEvent={mxEvent}
|
||||
replacingEventId={mxEvent.replacingEventId()}
|
||||
{...(props.eventTilePropertyOverrides ?? {})}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -70,9 +71,11 @@ describe("EventTile", () => {
|
||||
function getComponent(
|
||||
overrides: Partial<EventTileProps> = {},
|
||||
renderingType: TimelineRenderingType = TimelineRenderingType.Room,
|
||||
roomContext: Partial<IRoomState> = {},
|
||||
) {
|
||||
const context = getRoomContext(room, {
|
||||
timelineRenderingType: renderingType,
|
||||
...roomContext,
|
||||
});
|
||||
return render(<WrappedEventTile roomContext={context} eventTilePropertyOverrides={overrides} />);
|
||||
}
|
||||
@@ -260,7 +263,7 @@ describe("EventTile", () => {
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
expect(eventTiles).toHaveLength(1);
|
||||
@@ -285,7 +288,7 @@ describe("EventTile", () => {
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
expect(eventTiles).toHaveLength(1);
|
||||
@@ -301,6 +304,8 @@ describe("EventTile", () => {
|
||||
[EventShieldReason.UNKNOWN_DEVICE, "unknown or deleted device"],
|
||||
[EventShieldReason.AUTHENTICITY_NOT_GUARANTEED, "can't be guaranteed"],
|
||||
[EventShieldReason.MISMATCHED_SENDER_KEY, "Encrypted by an unverified session"],
|
||||
[EventShieldReason.SENT_IN_CLEAR, "Not encrypted"],
|
||||
[EventShieldReason.VERIFICATION_VIOLATION, "Sender's verified identity has changed"],
|
||||
])("shows the correct reason code for %i (%s)", async (reasonCode: EventShieldReason, expectedText: string) => {
|
||||
mxEvent = await mkEncryptedMatrixEvent({
|
||||
plainContent: { msgtype: "m.text", body: "msg1" },
|
||||
@@ -314,7 +319,7 @@ describe("EventTile", () => {
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const e2eIcons = container.getElementsByClassName("mx_EventTile_e2eIcon");
|
||||
expect(e2eIcons).toHaveLength(1);
|
||||
@@ -346,7 +351,7 @@ describe("EventTile", () => {
|
||||
await mxEvent.attemptDecryption(mockCrypto);
|
||||
|
||||
const { container } = getComponent();
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
expect(eventTiles).toHaveLength(1);
|
||||
@@ -400,7 +405,7 @@ describe("EventTile", () => {
|
||||
const roomContext = getRoomContext(room, {});
|
||||
const { container, rerender } = render(<WrappedEventTile roomContext={roomContext} />);
|
||||
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
expect(eventTiles).toHaveLength(1);
|
||||
@@ -434,8 +439,6 @@ describe("EventTile", () => {
|
||||
});
|
||||
|
||||
it("should update the warning when the event is replaced with an unencrypted one", async () => {
|
||||
jest.spyOn(client, "isRoomEncrypted").mockReturnValue(true);
|
||||
|
||||
// we start out with an event from the trusted device
|
||||
mxEvent = await mkEncryptedMatrixEvent({
|
||||
plainContent: { msgtype: "m.text", body: "msg1" },
|
||||
@@ -449,9 +452,9 @@ describe("EventTile", () => {
|
||||
shieldReason: null,
|
||||
} as EventEncryptionInfo);
|
||||
|
||||
const roomContext = getRoomContext(room, {});
|
||||
const roomContext = getRoomContext(room, { isRoomEncrypted: true });
|
||||
const { container, rerender } = render(<WrappedEventTile roomContext={roomContext} />);
|
||||
await act(flushPromises);
|
||||
await flushPromises();
|
||||
|
||||
const eventTiles = container.getElementsByClassName("mx_EventTile");
|
||||
expect(eventTiles).toHaveLength(1);
|
||||
@@ -578,4 +581,28 @@ describe("EventTile", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should display the not encrypted status for an unencrypted event when the room becomes encrypted", async () => {
|
||||
jest.spyOn(client.getCrypto()!, "getEncryptionInfoForEvent").mockResolvedValue({
|
||||
shieldColour: EventShieldColour.NONE,
|
||||
shieldReason: null,
|
||||
});
|
||||
|
||||
const { rerender } = getComponent();
|
||||
await flushPromises();
|
||||
// The room and the event are unencrypted, the tile should not show the not encrypted status
|
||||
expect(screen.queryByText("Not encrypted")).toBeNull();
|
||||
|
||||
// The room is now encrypted
|
||||
rerender(
|
||||
<WrappedEventTile
|
||||
roomContext={getRoomContext(room, {
|
||||
isRoomEncrypted: true,
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
|
||||
// The event tile should now show the not encrypted status
|
||||
await waitFor(() => expect(screen.getByText("Not encrypted")).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,17 @@ exports[`EventTileThreadToolbar renders 1`] = `
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div />
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 19.071c-.978.978-2.157 1.467-3.536 1.467-1.378 0-2.557-.489-3.535-1.467-.978-.978-1.467-2.157-1.467-3.536 0-1.378.489-2.557 1.467-3.535L7.05 9.879c.2-.2.436-.3.707-.3.271 0 .507.1.707.3.2.2.301.436.301.707 0 .27-.1.506-.3.707l-2.122 2.121a2.893 2.893 0 0 0-.884 2.122c0 .824.295 1.532.884 2.12.59.59 1.296.885 2.121.885s1.533-.295 2.122-.884l2.121-2.121c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707L12 19.07Zm-1.414-4.243c-.2.2-.436.3-.707.3a.967.967 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l4.243-4.242c.2-.2.436-.301.707-.301.271 0 .507.1.707.3.2.2.3.437.3.708 0 .27-.1.506-.3.707l-4.242 4.242Zm6.364-.707c-.2.2-.436.3-.707.3a.968.968 0 0 1-.707-.3.969.969 0 0 1-.301-.707c0-.27.1-.507.3-.707l2.122-2.121c.59-.59.884-1.297.884-2.122s-.295-1.532-.884-2.12a2.893 2.893 0 0 0-2.121-.885c-.825 0-1.532.295-2.122.884l-2.121 2.121c-.2.2-.436.301-.707.301a.968.968 0 0 1-.707-.3.97.97 0 0 1-.3-.708c0-.27.1-.506.3-.707L12 4.93c.978-.978 2.157-1.467 3.536-1.467 1.378 0 2.557.489 3.535 1.467.978.978 1.467 2.157 1.467 3.535 0 1.38-.489 2.558-1.467 3.536l-2.121 2.121Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
|
||||
@@ -8,7 +8,16 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, fireEvent, render, RenderResult, screen, waitFor, waitForElementToBeRemoved } from "jest-matrix-react";
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
RenderResult,
|
||||
screen,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
cleanup,
|
||||
} from "jest-matrix-react";
|
||||
import { Room, MatrixClient, RoomState, RoomMember, User, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { mocked, MockedObject } from "jest-mock";
|
||||
@@ -361,6 +370,7 @@ describe("MemberList", () => {
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
const renderComponent = () => {
|
||||
@@ -397,21 +407,22 @@ describe("MemberList", () => {
|
||||
jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
|
||||
jest.spyOn(room, "canInvite").mockReturnValue(false);
|
||||
|
||||
renderComponent();
|
||||
await flushPromises();
|
||||
const { findByLabelText } = renderComponent();
|
||||
|
||||
// button rendered but disabled
|
||||
expect(screen.getByText("Invite to this room")).toHaveAttribute("aria-disabled", "true");
|
||||
await expect(findByLabelText("You do not have permission to invite users")).resolves.toHaveAttribute(
|
||||
"aria-disabled",
|
||||
"true",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders enabled invite button when current user is a member and has rights to invite", async () => {
|
||||
jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
|
||||
jest.spyOn(room, "canInvite").mockReturnValue(true);
|
||||
|
||||
renderComponent();
|
||||
await flushPromises();
|
||||
const { findByText } = renderComponent();
|
||||
|
||||
expect(screen.getByText("Invite to this room")).not.toBeDisabled();
|
||||
await expect(findByText("Invite to this room")).resolves.not.toBeDisabled();
|
||||
});
|
||||
|
||||
it("opens room inviter on button click", async () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { EventType, MatrixEvent, Room, RoomMember, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix";
|
||||
import { EventType, MatrixEvent, RoomMember, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix";
|
||||
import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
@@ -19,17 +19,14 @@ import {
|
||||
mkStubRoom,
|
||||
mockPlatformPeg,
|
||||
stubClient,
|
||||
waitEnoughCyclesForModal,
|
||||
} from "../../../../test-utils";
|
||||
import MessageComposer from "../../../../../src/components/views/rooms/MessageComposer";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import ResizeNotifier from "../../../../../src/utils/ResizeNotifier";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import { LocalRoom } from "../../../../../src/models/LocalRoom";
|
||||
import { Features } from "../../../../../src/settings/Settings";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
|
||||
import dis from "../../../../../src/dispatcher/dispatcher";
|
||||
@@ -37,31 +34,16 @@ import { E2EStatus } from "../../../../../src/utils/ShieldUtils";
|
||||
import { addTextToComposerRTL } from "../../../../test-utils/composer";
|
||||
import UIStore, { UI_EVENTS } from "../../../../../src/stores/UIStore";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { VoiceBroadcastInfoState, VoiceBroadcastRecording } from "../../../../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
const openStickerPicker = async (): Promise<void> => {
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
await userEvent.click(screen.getByLabelText("Sticker"));
|
||||
});
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
await userEvent.click(screen.getByLabelText("Sticker"));
|
||||
};
|
||||
|
||||
const startVoiceMessage = async (): Promise<void> => {
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
await userEvent.click(screen.getByLabelText("Voice Message"));
|
||||
});
|
||||
};
|
||||
|
||||
const setCurrentBroadcastRecording = (room: Room, state: VoiceBroadcastInfoState): void => {
|
||||
const recording = new VoiceBroadcastRecording(
|
||||
mkVoiceBroadcastInfoStateEvent(room.roomId, state, "@user:example.com", "ABC123"),
|
||||
MatrixClientPeg.safeGet(),
|
||||
state,
|
||||
);
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore.setCurrent(recording);
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
await userEvent.click(screen.getByLabelText("Voice Message"));
|
||||
};
|
||||
|
||||
const expectVoiceMessageRecordingTriggered = (): void => {
|
||||
@@ -82,14 +64,11 @@ describe("MessageComposer", () => {
|
||||
await clearAllModals();
|
||||
jest.useRealTimers();
|
||||
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore.clearCurrent();
|
||||
|
||||
// restore settings
|
||||
act(() => {
|
||||
[
|
||||
"MessageComposerInput.showStickersButton",
|
||||
"MessageComposerInput.showPollsButton",
|
||||
Features.VoiceBroadcast,
|
||||
"feature_wysiwyg_composer",
|
||||
].forEach((setting: string): void => {
|
||||
SettingsStore.setValue(setting, null, SettingLevel.DEVICE, SettingsStore.getDefaultValue(setting));
|
||||
@@ -97,6 +76,45 @@ describe("MessageComposer", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("wysiwyg correctly persists state to and from localStorage", async () => {
|
||||
const room = mkStubRoom("!roomId:server", "Room 1", cli);
|
||||
const messageText = "Test Text";
|
||||
await SettingsStore.setValue("feature_wysiwyg_composer", null, SettingLevel.DEVICE, true);
|
||||
const { renderResult, rawComponent } = wrapAndRender({ room });
|
||||
const { unmount } = renderResult;
|
||||
|
||||
await flushPromises();
|
||||
|
||||
const key = `mx_wysiwyg_state_${room.roomId}`;
|
||||
|
||||
await userEvent.click(screen.getByRole("textbox"));
|
||||
fireEvent.input(screen.getByRole("textbox"), {
|
||||
data: messageText,
|
||||
inputType: "insertText",
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText));
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await flushPromises();
|
||||
|
||||
// assert there is state persisted
|
||||
expect(localStorage.getItem(key)).toBeNull();
|
||||
|
||||
// ensure the right state was persisted to localStorage
|
||||
unmount();
|
||||
|
||||
// assert the persisted state
|
||||
expect(JSON.parse(localStorage.getItem(key)!)).toStrictEqual({
|
||||
content: messageText,
|
||||
isRichText: true,
|
||||
});
|
||||
|
||||
// ensure the correct state is re-loaded
|
||||
render(rawComponent);
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText));
|
||||
}, 10000);
|
||||
|
||||
describe("for a Room", () => {
|
||||
const room = mkStubRoom("!roomId:server", "Room 1", cli);
|
||||
|
||||
@@ -177,22 +195,16 @@ describe("MessageComposer", () => {
|
||||
setting: "MessageComposerInput.showPollsButton",
|
||||
buttonLabel: "Poll",
|
||||
},
|
||||
{
|
||||
setting: Features.VoiceBroadcast,
|
||||
buttonLabel: "Voice broadcast",
|
||||
},
|
||||
].forEach(({ setting, buttonLabel }) => {
|
||||
[true, false].forEach((value: boolean) => {
|
||||
describe(`when ${setting} = ${value}`, () => {
|
||||
beforeEach(async () => {
|
||||
SettingsStore.setValue(setting, null, SettingLevel.DEVICE, value);
|
||||
await act(() => SettingsStore.setValue(setting, null, SettingLevel.DEVICE, value));
|
||||
wrapAndRender({ room });
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
});
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
});
|
||||
|
||||
it(`should${value || "not"} display the button`, () => {
|
||||
it(`should${value ? "" : " not"} display the button`, () => {
|
||||
if (value) {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
expect(screen.getByLabelText(buttonLabel)).toBeInTheDocument();
|
||||
@@ -205,15 +217,17 @@ describe("MessageComposer", () => {
|
||||
describe(`and setting ${setting} to ${!value}`, () => {
|
||||
beforeEach(async () => {
|
||||
// simulate settings update
|
||||
await SettingsStore.setValue(setting, null, SettingLevel.DEVICE, !value);
|
||||
dis.dispatch(
|
||||
{
|
||||
action: Action.SettingUpdated,
|
||||
settingName: setting,
|
||||
newValue: !value,
|
||||
},
|
||||
true,
|
||||
);
|
||||
await act(async () => {
|
||||
await SettingsStore.setValue(setting, null, SettingLevel.DEVICE, !value);
|
||||
dis.dispatch(
|
||||
{
|
||||
action: Action.SettingUpdated,
|
||||
settingName: setting,
|
||||
newValue: !value,
|
||||
},
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it(`should${!value || "not"} display the button`, () => {
|
||||
@@ -273,7 +287,7 @@ describe("MessageComposer", () => {
|
||||
beforeEach(async () => {
|
||||
wrapAndRender({ room }, true, true);
|
||||
await openStickerPicker();
|
||||
resizeCallback(UI_EVENTS.Resize, {});
|
||||
act(() => resizeCallback(UI_EVENTS.Resize, {}));
|
||||
});
|
||||
|
||||
it("should close the menu", () => {
|
||||
@@ -295,7 +309,7 @@ describe("MessageComposer", () => {
|
||||
beforeEach(async () => {
|
||||
wrapAndRender({ room }, true, false);
|
||||
await openStickerPicker();
|
||||
resizeCallback(UI_EVENTS.Resize, {});
|
||||
act(() => resizeCallback(UI_EVENTS.Resize, {}));
|
||||
});
|
||||
|
||||
it("should close the menu", () => {
|
||||
@@ -402,34 +416,6 @@ describe("MessageComposer", () => {
|
||||
expectVoiceMessageRecordingTriggered();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when recording a voice broadcast and trying to start a voice message", () => {
|
||||
beforeEach(async () => {
|
||||
setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Started);
|
||||
wrapAndRender({ room });
|
||||
await startVoiceMessage();
|
||||
await waitEnoughCyclesForModal();
|
||||
});
|
||||
|
||||
it("should not start a voice message and display the info dialog", async () => {
|
||||
expect(screen.queryByLabelText("Stop recording")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Can't start voice message")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is a stopped voice broadcast recording and trying to start a voice message", () => {
|
||||
beforeEach(async () => {
|
||||
setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Stopped);
|
||||
wrapAndRender({ room });
|
||||
await startVoiceMessage();
|
||||
await waitEnoughCyclesForModal();
|
||||
});
|
||||
|
||||
it("should try to start a voice message and should not display the info dialog", async () => {
|
||||
expect(screen.queryByText("Can't start voice message")).not.toBeInTheDocument();
|
||||
expectVoiceMessageRecordingTriggered();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("for a LocalRoom", () => {
|
||||
@@ -443,51 +429,6 @@ describe("MessageComposer", () => {
|
||||
expect(screen.queryByLabelText("Sticker")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("wysiwyg correctly persists state to and from localStorage", async () => {
|
||||
const room = mkStubRoom("!roomId:server", "Room 1", cli);
|
||||
const messageText = "Test Text";
|
||||
await SettingsStore.setValue("feature_wysiwyg_composer", null, SettingLevel.DEVICE, true);
|
||||
const { renderResult, rawComponent } = wrapAndRender({ room });
|
||||
const { unmount, rerender } = renderResult;
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
const key = `mx_wysiwyg_state_${room.roomId}`;
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByRole("textbox"));
|
||||
});
|
||||
fireEvent.input(screen.getByRole("textbox"), {
|
||||
data: messageText,
|
||||
inputType: "insertText",
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText));
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
// assert there is state persisted
|
||||
expect(localStorage.getItem(key)).toBeNull();
|
||||
|
||||
// ensure the right state was persisted to localStorage
|
||||
unmount();
|
||||
|
||||
// assert the persisted state
|
||||
expect(JSON.parse(localStorage.getItem(key)!)).toStrictEqual({
|
||||
content: messageText,
|
||||
isRichText: true,
|
||||
});
|
||||
|
||||
// ensure the correct state is re-loaded
|
||||
rerender(rawComponent);
|
||||
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText));
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
function wrapAndRender(
|
||||
@@ -522,9 +463,9 @@ function wrapAndRender(
|
||||
|
||||
const getRawComponent = (props = {}, context = roomContext, client = mockClient) => (
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={context}>
|
||||
<ScopedRoomContextProvider {...context}>
|
||||
<MessageComposer {...defaultProps} {...props} />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
return {
|
||||
|
||||
@@ -10,11 +10,11 @@ import React from "react";
|
||||
import { render, screen, waitFor } from "jest-matrix-react";
|
||||
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import { createTestClient, getRoomContext, mkStubRoom } from "../../../../test-utils";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import MessageComposerButtons from "../../../../../src/components/views/rooms/MessageComposerButtons";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
describe("MessageComposerButtons", () => {
|
||||
// @ts-ignore - we're deliberately not implementing the whole interface here, but
|
||||
@@ -54,7 +54,7 @@ describe("MessageComposerButtons", () => {
|
||||
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={defaultRoomContext}>{component}</RoomContext.Provider>
|
||||
<ScopedRoomContextProvider {...defaultRoomContext}>{component}</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
}
|
||||
@@ -168,27 +168,4 @@ describe("MessageComposerButtons", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with showVoiceBroadcastButton = true", () => {
|
||||
it("should render the »Voice broadcast« button", () => {
|
||||
wrapAndRender(
|
||||
<MessageComposerButtons
|
||||
{...mockProps}
|
||||
isMenuOpen={true}
|
||||
showLocationButton={true}
|
||||
showPollsButton={true}
|
||||
showStickersButton={true}
|
||||
showVoiceBroadcastButton={true}
|
||||
/>,
|
||||
false,
|
||||
);
|
||||
|
||||
expect(getButtonLabels()).toEqual([
|
||||
"Emoji",
|
||||
"Attachment",
|
||||
"More options",
|
||||
["Sticker", "Voice Message", "Voice broadcast", "Poll", "Location"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,19 +13,19 @@ import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { LocalRoom } from "../../../../../src/models/LocalRoom";
|
||||
import { filterConsole, mkRoomMemberJoinEvent, mkThirdPartyInviteEvent, stubClient } from "../../../../test-utils";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import NewRoomIntro from "../../../../../src/components/views/rooms/NewRoomIntro";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { DirectoryMember } from "../../../../../src/utils/direct-messages";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
const renderNewRoomIntro = (client: MatrixClient, room: Room | LocalRoom) => {
|
||||
render(
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={{ room, roomId: room.roomId } as unknown as IRoomState}>
|
||||
<ScopedRoomContextProvider {...({ room, roomId: room.roomId } as unknown as IRoomState)}>
|
||||
<NewRoomIntro />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -165,7 +165,7 @@ describe("UnreadNotificationBadge", () => {
|
||||
},
|
||||
ts: 5,
|
||||
});
|
||||
room.addLiveEvents([event]);
|
||||
room.addLiveEvents([event], { addToState: true });
|
||||
|
||||
const { container } = render(getComponent(THREAD_ID));
|
||||
expect(container.querySelector(".mx_NotificationBadge_dot")).toBeTruthy();
|
||||
|
||||
@@ -20,6 +20,7 @@ import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelSto
|
||||
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
|
||||
import { UPDATE_EVENT } from "../../../../../src/stores/AsyncStore";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import ResizeNotifier from "../../../../../src/utils/ResizeNotifier.ts";
|
||||
|
||||
describe("<PinnedMessageBanner />", () => {
|
||||
const userId = "@alice:server.org";
|
||||
@@ -28,10 +29,12 @@ describe("<PinnedMessageBanner />", () => {
|
||||
let mockClient: MatrixClient;
|
||||
let room: Room;
|
||||
let permalinkCreator: RoomPermalinkCreator;
|
||||
let resizeNotifier: ResizeNotifier;
|
||||
beforeEach(() => {
|
||||
mockClient = stubClient();
|
||||
room = new Room(roomId, mockClient, userId);
|
||||
permalinkCreator = new RoomPermalinkCreator(room);
|
||||
resizeNotifier = new ResizeNotifier();
|
||||
jest.spyOn(dis, "dispatch").mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
@@ -77,7 +80,7 @@ describe("<PinnedMessageBanner />", () => {
|
||||
*/
|
||||
function renderBanner() {
|
||||
return render(
|
||||
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} />,
|
||||
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} resizeNotifier={resizeNotifier} />,
|
||||
withClientContextRenderOptions(mockClient),
|
||||
);
|
||||
}
|
||||
@@ -145,7 +148,9 @@ describe("<PinnedMessageBanner />", () => {
|
||||
event3.getId()!,
|
||||
]);
|
||||
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2, event3]);
|
||||
rerender(<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} />);
|
||||
rerender(
|
||||
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} resizeNotifier={resizeNotifier} />,
|
||||
);
|
||||
await expect(screen.findByText("Third pinned message")).resolves.toBeVisible();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
@@ -206,6 +211,42 @@ describe("<PinnedMessageBanner />", () => {
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("Notify the timeline to resize", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(resizeNotifier, "notifyTimelineHeightChanged");
|
||||
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]);
|
||||
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]);
|
||||
});
|
||||
|
||||
it("should notify the timeline to resize when we display the banner", async () => {
|
||||
renderBanner();
|
||||
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||
// The banner is displayed, so we need to resize the timeline
|
||||
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(1);
|
||||
|
||||
await userEvent.click(screen.getByRole("button", { name: "View the pinned message in the timeline." }));
|
||||
await expect(screen.findByText("First pinned message")).resolves.toBeVisible();
|
||||
// The banner is already displayed, so we don't need to resize the timeline
|
||||
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should notify the timeline to resize when we hide the banner", async () => {
|
||||
const { rerender } = renderBanner();
|
||||
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||
// The banner is displayed, so we need to resize the timeline
|
||||
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(1);
|
||||
|
||||
// The banner has no event to display and is hidden
|
||||
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([]);
|
||||
jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([]);
|
||||
rerender(
|
||||
<PinnedMessageBanner permalinkCreator={permalinkCreator} room={room} resizeNotifier={resizeNotifier} />,
|
||||
);
|
||||
// The timeline should be resized
|
||||
expect(resizeNotifier.notifyTimelineHeightChanged).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Right button", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]);
|
||||
@@ -217,6 +258,8 @@ describe("<PinnedMessageBanner />", () => {
|
||||
jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false);
|
||||
|
||||
renderBanner();
|
||||
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||
|
||||
expect(screen.getByRole("button", { name: "View all" })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -224,10 +267,12 @@ describe("<PinnedMessageBanner />", () => {
|
||||
// The Right panel is opened on another card
|
||||
jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(true);
|
||||
jest.spyOn(RightPanelStore.instance, "currentCard", "get").mockReturnValue({
|
||||
phase: RightPanelPhases.RoomMemberList,
|
||||
phase: RightPanelPhases.MemberList,
|
||||
});
|
||||
|
||||
renderBanner();
|
||||
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||
|
||||
expect(screen.getByRole("button", { name: "View all" })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -239,6 +284,8 @@ describe("<PinnedMessageBanner />", () => {
|
||||
});
|
||||
|
||||
renderBanner();
|
||||
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||
|
||||
expect(screen.getByRole("button", { name: "Close list" })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -263,6 +310,7 @@ describe("<PinnedMessageBanner />", () => {
|
||||
});
|
||||
|
||||
renderBanner();
|
||||
await expect(screen.findByText("Second pinned message")).resolves.toBeVisible();
|
||||
expect(screen.getByRole("button", { name: "Close list" })).toBeVisible();
|
||||
|
||||
jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false);
|
||||
|
||||
@@ -10,6 +10,7 @@ import React, { ComponentProps } from "react";
|
||||
import { render, screen, waitFor } from "jest-matrix-react";
|
||||
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import {
|
||||
determineAvatarPosition,
|
||||
@@ -20,6 +21,9 @@ import * as languageHandler from "../../../../../src/languageHandler";
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import dispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { formatDate } from "../../../../../src/DateUtils";
|
||||
|
||||
jest.mock("../../../../../src/DateUtils");
|
||||
|
||||
describe("ReadReceiptGroup", () => {
|
||||
describe("TooltipText", () => {
|
||||
@@ -87,6 +91,10 @@ describe("ReadReceiptGroup", () => {
|
||||
describe("<ReadReceiptPerson />", () => {
|
||||
stubClient();
|
||||
|
||||
// We pick a fixed time but this can still vary depending on the locale
|
||||
// the tests are run in. We are not testing date formatting here, so stub it out.
|
||||
mocked(formatDate).mockReturnValue("==MOCK FORMATTED DATE==");
|
||||
|
||||
const ROOM_ID = "roomId";
|
||||
const USER_ID = "@alice:example.org";
|
||||
|
||||
|
||||
@@ -8,9 +8,19 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React from "react";
|
||||
import { CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { EventType, JoinRule, MatrixEvent, PendingEventOrdering, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import {
|
||||
EventType,
|
||||
JoinRule,
|
||||
MatrixEvent,
|
||||
PendingEventOrdering,
|
||||
Room,
|
||||
RoomStateEvent,
|
||||
RoomMember,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
import {
|
||||
act,
|
||||
createEvent,
|
||||
fireEvent,
|
||||
getAllByLabelText,
|
||||
@@ -148,7 +158,7 @@ describe("RoomHeader", () => {
|
||||
|
||||
fireEvent.click(facePile);
|
||||
|
||||
expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.RoomMemberList });
|
||||
expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.MemberList });
|
||||
});
|
||||
|
||||
it("has room info icon that opens the room info panel", async () => {
|
||||
@@ -579,7 +589,7 @@ describe("RoomHeader", () => {
|
||||
state_key: "",
|
||||
room_id: room.roomId,
|
||||
});
|
||||
room.addLiveEvents([joinRuleEvent]);
|
||||
room.addLiveEvents([joinRuleEvent], { addToState: true });
|
||||
|
||||
render(<RoomHeader room={room} />, getWrapper());
|
||||
|
||||
@@ -632,6 +642,52 @@ describe("RoomHeader", () => {
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("updates the icon when the encryption status changes", async () => {
|
||||
// The room starts verified
|
||||
jest.spyOn(ShieldUtils, "shieldStatusForRoom").mockResolvedValue(ShieldUtils.E2EStatus.Verified);
|
||||
render(<RoomHeader room={room} />, getWrapper());
|
||||
await waitFor(() => expect(getByLabelText(document.body, "Verified")).toBeInTheDocument());
|
||||
|
||||
// A new member joins, and the room becomes unverified
|
||||
jest.spyOn(ShieldUtils, "shieldStatusForRoom").mockResolvedValue(ShieldUtils.E2EStatus.Warning);
|
||||
act(() => {
|
||||
room.emit(
|
||||
RoomStateEvent.Members,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "join",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
room.currentState,
|
||||
new RoomMember(room.roomId, "@alice:example.org"),
|
||||
);
|
||||
});
|
||||
await waitFor(() => expect(getByLabelText(document.body, "Untrusted")).toBeInTheDocument());
|
||||
|
||||
// The user becomes verified
|
||||
jest.spyOn(ShieldUtils, "shieldStatusForRoom").mockResolvedValue(ShieldUtils.E2EStatus.Verified);
|
||||
act(() => {
|
||||
MatrixClientPeg.get()!.emit(
|
||||
CryptoEvent.UserTrustStatusChanged,
|
||||
"@alice:example.org",
|
||||
new UserVerificationStatus(true, true, true, false),
|
||||
);
|
||||
});
|
||||
await waitFor(() => expect(getByLabelText(document.body, "Verified")).toBeInTheDocument());
|
||||
|
||||
// An unverified device is added
|
||||
jest.spyOn(ShieldUtils, "shieldStatusForRoom").mockResolvedValue(ShieldUtils.E2EStatus.Warning);
|
||||
act(() => {
|
||||
MatrixClientPeg.get()!.emit(CryptoEvent.DevicesUpdated, ["@alice:example.org"], false);
|
||||
});
|
||||
await waitFor(() => expect(getByLabelText(document.body, "Untrusted")).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
|
||||
it("renders additionalButtons", async () => {
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "../../../../../../src/components/views/rooms/RoomHeader/CallGuestLinkButton";
|
||||
import Modal from "../../../../../../src/Modal";
|
||||
import SdkConfig from "../../../../../../src/SdkConfig";
|
||||
import ShareDialog from "../../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { ShareDialog } from "../../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { _t } from "../../../../../../src/languageHandler";
|
||||
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||
|
||||
|
||||
@@ -9,14 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React from "react";
|
||||
import { render, screen, act, RenderResult } from "jest-matrix-react";
|
||||
import { mocked, Mocked } from "jest-mock";
|
||||
import {
|
||||
MatrixClient,
|
||||
PendingEventOrdering,
|
||||
Room,
|
||||
MatrixEvent,
|
||||
RoomStateEvent,
|
||||
Thread,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, PendingEventOrdering, Room, RoomStateEvent, Thread } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { Widget } from "matrix-widget-api";
|
||||
|
||||
@@ -40,8 +33,6 @@ import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import PlatformPeg from "../../../../../src/PlatformPeg";
|
||||
import BasePlatform from "../../../../../src/BasePlatform";
|
||||
import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMessagingStore";
|
||||
import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
import { TestSdkContext } from "../../../TestSdkContext";
|
||||
import { SDKContext } from "../../../../../src/contexts/SDKContext";
|
||||
import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
|
||||
@@ -61,20 +52,6 @@ describe("RoomTile", () => {
|
||||
} as unknown as BasePlatform);
|
||||
useMockedCalls();
|
||||
|
||||
const setUpVoiceBroadcast = async (state: VoiceBroadcastInfoState): Promise<void> => {
|
||||
voiceBroadcastInfoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
state,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
room.currentState.setStateEvents([voiceBroadcastInfoEvent]);
|
||||
await flushPromises();
|
||||
});
|
||||
};
|
||||
|
||||
const renderRoomTile = (): RenderResult => {
|
||||
return render(
|
||||
<SDKContext.Provider value={sdkContext}>
|
||||
@@ -89,7 +66,6 @@ describe("RoomTile", () => {
|
||||
};
|
||||
|
||||
let client: Mocked<MatrixClient>;
|
||||
let voiceBroadcastInfoEvent: MatrixEvent;
|
||||
let room: Room;
|
||||
let sdkContext: TestSdkContext;
|
||||
let showMessagePreview = false;
|
||||
@@ -303,49 +279,6 @@ describe("RoomTile", () => {
|
||||
});
|
||||
expect(screen.queryByLabelText(/participant/)).toBe(null);
|
||||
});
|
||||
|
||||
describe("and a live broadcast starts", () => {
|
||||
beforeEach(async () => {
|
||||
renderRoomTile();
|
||||
await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
||||
});
|
||||
|
||||
it("should still render the call subtitle", () => {
|
||||
expect(screen.queryByText("Video")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Live")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when a live voice broadcast starts", () => {
|
||||
beforeEach(async () => {
|
||||
renderRoomTile();
|
||||
await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
||||
});
|
||||
|
||||
it("should render the »Live« subtitle", () => {
|
||||
expect(screen.queryByText("Live")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("and the broadcast stops", () => {
|
||||
beforeEach(async () => {
|
||||
const stopEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
voiceBroadcastInfoEvent,
|
||||
);
|
||||
await act(async () => {
|
||||
room.currentState.setStateEvents([stopEvent]);
|
||||
await flushPromises();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not render the »Live« subtitle", () => {
|
||||
expect(screen.queryByText("Live")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import SendMessageComposer, {
|
||||
isQuickReaction,
|
||||
} from "../../../../../src/components/views/rooms/SendMessageComposer";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import EditorModel from "../../../../../src/editor/model";
|
||||
import { createPartCreator } from "../../../editor/mock";
|
||||
import { createTestClient, mkEvent, mkStubRoom, stubClient } from "../../../../test-utils";
|
||||
@@ -30,6 +30,7 @@ import { IRoomState, MainSplitContentType } from "../../../../../src/components/
|
||||
import { mockPlatformPeg } from "../../../../test-utils/platform";
|
||||
import { doMaybeLocalRoomAction } from "../../../../../src/utils/local-room";
|
||||
import { addTextToComposer } from "../../../../test-utils/composer";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../src/utils/local-room", () => ({
|
||||
doMaybeLocalRoomAction: jest.fn(),
|
||||
@@ -77,6 +78,7 @@ describe("<SendMessageComposer/>", () => {
|
||||
canAskToJoin: false,
|
||||
promptAskToJoin: false,
|
||||
viewRoomOpts: { buttons: [] },
|
||||
isRoomEncrypted: false,
|
||||
};
|
||||
describe("createMessageContent", () => {
|
||||
it("sends plaintext messages correctly", () => {
|
||||
@@ -364,9 +366,9 @@ describe("<SendMessageComposer/>", () => {
|
||||
};
|
||||
const getRawComponent = (props = {}, roomContext = defaultRoomContext, client = mockClient) => (
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={roomContext}>
|
||||
<ScopedRoomContextProvider {...roomContext}>
|
||||
<SendMessageComposer {...defaultProps} {...props} />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
const getComponent = (props = {}, roomContext = defaultRoomContext, client = mockClient) => {
|
||||
@@ -385,7 +387,7 @@ describe("<SendMessageComposer/>", () => {
|
||||
|
||||
it("correctly persists state to and from localStorage", () => {
|
||||
const props = { replyToEvent: mockEvent };
|
||||
const { container, unmount, rerender } = getComponent(props);
|
||||
let { container, unmount } = getComponent(props);
|
||||
|
||||
addTextToComposer(container, "Test Text");
|
||||
|
||||
@@ -402,7 +404,7 @@ describe("<SendMessageComposer/>", () => {
|
||||
});
|
||||
|
||||
// ensure the correct model is re-loaded
|
||||
rerender(getRawComponent(props));
|
||||
({ container, unmount } = getComponent(props));
|
||||
expect(container.textContent).toBe("Test Text");
|
||||
expect(spyDispatcher).toHaveBeenCalledWith({
|
||||
action: "reply_to_event",
|
||||
@@ -413,7 +415,7 @@ describe("<SendMessageComposer/>", () => {
|
||||
// now try with localStorage wiped out
|
||||
unmount();
|
||||
localStorage.removeItem(key);
|
||||
rerender(getRawComponent(props));
|
||||
({ container } = getComponent(props));
|
||||
expect(container.textContent).toBe("");
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,534 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import {
|
||||
EventType,
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
Room,
|
||||
RoomState,
|
||||
RoomStateEvent,
|
||||
RoomMember,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
import { act, render, screen, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import { UserIdentityWarning } from "../../../../../src/components/views/rooms/UserIdentityWarning";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
|
||||
const ROOM_ID = "!room:id";
|
||||
|
||||
function mockRoom(): Room {
|
||||
const room = {
|
||||
getEncryptionTargetMembers: jest.fn(async () => []),
|
||||
getMember: jest.fn((userId) => {}),
|
||||
roomId: ROOM_ID,
|
||||
shouldEncryptForInvitedMembers: jest.fn(() => true),
|
||||
} as unknown as Room;
|
||||
|
||||
return room;
|
||||
}
|
||||
|
||||
function mockRoomMember(userId: string, name?: string): RoomMember {
|
||||
return {
|
||||
userId,
|
||||
name: name ?? userId,
|
||||
rawDisplayName: name ?? userId,
|
||||
roomId: ROOM_ID,
|
||||
getMxcAvatarUrl: jest.fn(),
|
||||
} as unknown as RoomMember;
|
||||
}
|
||||
|
||||
function dummyRoomState(): RoomState {
|
||||
return new RoomState(ROOM_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the warning element, given the warning text (excluding the "Learn more"
|
||||
* link). This is needed because the warning text contains a `<b>` tag, so the
|
||||
* normal `getByText` doesn't work.
|
||||
*/
|
||||
function getWarningByText(text: string): Element {
|
||||
return screen.getByText((content?: string, element?: Element | null): boolean => {
|
||||
return (
|
||||
!!element &&
|
||||
element.classList.contains("mx_UserIdentityWarning_main") &&
|
||||
element.textContent === text + " Learn more"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function renderComponent(client: MatrixClient, room: Room) {
|
||||
return render(<UserIdentityWarning room={room} key={ROOM_ID} />, {
|
||||
wrapper: ({ ...rest }) => <MatrixClientContext.Provider value={client} {...rest} />,
|
||||
});
|
||||
}
|
||||
|
||||
describe("UserIdentityWarning", () => {
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
beforeEach(async () => {
|
||||
client = stubClient();
|
||||
room = mockRoom();
|
||||
jest.spyOn(client.getCrypto()!, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
// This tests the basic functionality of the component. If we have a room
|
||||
// member whose identity needs accepting, we should display a warning. When
|
||||
// the "OK" button gets pressed, it should call `pinCurrentUserIdentity`.
|
||||
it("displays a warning when a user's identity needs approval", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
|
||||
mockRoomMember("@alice:example.org", "Alice"),
|
||||
]);
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
crypto.pinCurrentUserIdentity = jest.fn();
|
||||
renderComponent(client, room);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
await userEvent.click(screen.getByRole("button")!);
|
||||
await waitFor(() => expect(crypto.pinCurrentUserIdentity).toHaveBeenCalledWith("@alice:example.org"));
|
||||
});
|
||||
|
||||
// We don't display warnings in non-encrypted rooms, but if encryption is
|
||||
// enabled, then we should display a warning if there are any users whose
|
||||
// identity need accepting.
|
||||
it("displays pending warnings when encryption is enabled", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
|
||||
mockRoomMember("@alice:example.org", "Alice"),
|
||||
]);
|
||||
// Start the room off unencrypted. We shouldn't display anything.
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "isEncryptionEnabledInRoom").mockResolvedValue(false);
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
expect(() => getWarningByText("Alice's (@alice:example.org) identity appears to have changed.")).toThrow();
|
||||
|
||||
// Encryption gets enabled in the room. We should now warn that Alice's
|
||||
// identity changed.
|
||||
jest.spyOn(crypto, "isEncryptionEnabledInRoom").mockResolvedValue(true);
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomEncryption,
|
||||
state_key: "",
|
||||
content: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
// When a user's identity needs approval, or has been approved, the display
|
||||
// should update appropriately.
|
||||
it("updates the display when identity changes", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
|
||||
mockRoomMember("@alice:example.org", "Alice"),
|
||||
]);
|
||||
jest.spyOn(room, "getMember").mockReturnValue(mockRoomMember("@alice:example.org", "Alice"));
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, false),
|
||||
);
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
expect(() => getWarningByText("Alice's (@alice:example.org) identity appears to have changed.")).toThrow();
|
||||
|
||||
// The user changes their identity, so we should show the warning.
|
||||
act(() => {
|
||||
client.emit(
|
||||
CryptoEvent.UserTrustStatusChanged,
|
||||
"@alice:example.org",
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
// Simulate the user's new identity having been approved, so we no
|
||||
// longer show the warning.
|
||||
act(() => {
|
||||
client.emit(
|
||||
CryptoEvent.UserTrustStatusChanged,
|
||||
"@alice:example.org",
|
||||
new UserVerificationStatus(false, false, false, false),
|
||||
);
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(() => getWarningByText("Alice's (@alice:example.org) identity appears to have changed.")).toThrow(),
|
||||
);
|
||||
});
|
||||
|
||||
// We only display warnings about users in the room. When someone
|
||||
// joins/leaves, we should update the warning appropriately.
|
||||
describe("updates the display when a member joins/leaves", () => {
|
||||
it("when invited users can see encrypted messages", async () => {
|
||||
// Nobody in the room yet
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([]);
|
||||
jest.spyOn(room, "getMember").mockImplementation((userId) => mockRoomMember(userId));
|
||||
jest.spyOn(room, "shouldEncryptForInvitedMembers").mockReturnValue(true);
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
|
||||
// Alice joins. Her identity needs approval, so we should show a warning.
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "join",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(getWarningByText("@alice:example.org's identity appears to have changed.")).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
// Bob is invited. His identity needs approval, so we should show a
|
||||
// warning for him after Alice's warning is resolved by her leaving.
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@bob:example.org",
|
||||
content: {
|
||||
membership: "invite",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@carol:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
|
||||
// Alice leaves, so we no longer show her warning, but we will show
|
||||
// a warning for Bob.
|
||||
act(() => {
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "leave",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(() => getWarningByText("@alice:example.org's identity appears to have changed.")).toThrow(),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(getWarningByText("@bob:example.org's identity appears to have changed.")).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it("when invited users cannot see encrypted messages", async () => {
|
||||
// Nobody in the room yet
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([]);
|
||||
jest.spyOn(room, "getMember").mockImplementation((userId) => mockRoomMember(userId));
|
||||
jest.spyOn(room, "shouldEncryptForInvitedMembers").mockReturnValue(false);
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
|
||||
// Alice joins. Her identity needs approval, so we should show a warning.
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "join",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(getWarningByText("@alice:example.org's identity appears to have changed.")).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
// Bob is invited. His identity needs approval, but we don't encrypt
|
||||
// to him, so we won't show a warning. (When Alice leaves, the
|
||||
// display won't be updated to show a warningfor Bob.)
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@bob:example.org",
|
||||
content: {
|
||||
membership: "invite",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@carol:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
|
||||
// Alice leaves, so we no longer show her warning, and we don't show
|
||||
// a warning for Bob.
|
||||
act(() => {
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "leave",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(() => getWarningByText("@alice:example.org's identity appears to have changed.")).toThrow(),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(() => getWarningByText("@bob:example.org's identity appears to have changed.")).toThrow(),
|
||||
);
|
||||
});
|
||||
|
||||
it("when member leaves immediately after component is loaded", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockImplementation(async () => {
|
||||
setTimeout(() => {
|
||||
// Alice immediately leaves after we get the room
|
||||
// membership, so we shouldn't show the warning any more
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "leave",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
});
|
||||
return [mockRoomMember("@alice:example.org")];
|
||||
});
|
||||
jest.spyOn(room, "getMember").mockImplementation((userId) => mockRoomMember(userId));
|
||||
jest.spyOn(room, "shouldEncryptForInvitedMembers").mockReturnValue(false);
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
renderComponent(client, room);
|
||||
|
||||
await sleep(10);
|
||||
expect(() => getWarningByText("@alice:example.org's identity appears to have changed.")).toThrow();
|
||||
});
|
||||
|
||||
it("when member leaves immediately after joining", async () => {
|
||||
// Nobody in the room yet
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([]);
|
||||
jest.spyOn(room, "getMember").mockImplementation((userId) => mockRoomMember(userId));
|
||||
jest.spyOn(room, "shouldEncryptForInvitedMembers").mockReturnValue(false);
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
|
||||
// Alice joins. Her identity needs approval, so we should show a warning.
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "join",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
// ... but she immediately leaves, so we shouldn't show the warning any more
|
||||
client.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({
|
||||
event_id: "$event_id",
|
||||
type: EventType.RoomMember,
|
||||
state_key: "@alice:example.org",
|
||||
content: {
|
||||
membership: "leave",
|
||||
},
|
||||
room_id: ROOM_ID,
|
||||
sender: "@alice:example.org",
|
||||
}),
|
||||
dummyRoomState(),
|
||||
null,
|
||||
);
|
||||
await sleep(10); // give it some time to finish
|
||||
expect(() => getWarningByText("@alice:example.org's identity appears to have changed.")).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
// When we have multiple users whose identity needs approval, one user's
|
||||
// identity no longer needs approval (e.g. their identity was approved),
|
||||
// then we show the next one.
|
||||
it("displays the next user when the current user's identity is approved", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
|
||||
mockRoomMember("@alice:example.org", "Alice"),
|
||||
mockRoomMember("@bob:example.org"),
|
||||
]);
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockResolvedValue(
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
|
||||
renderComponent(client, room);
|
||||
// We should warn about Alice's identity first.
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
// Simulate Alice's new identity having been approved, so now we warn
|
||||
// about Bob's identity.
|
||||
act(() => {
|
||||
client.emit(
|
||||
CryptoEvent.UserTrustStatusChanged,
|
||||
"@alice:example.org",
|
||||
new UserVerificationStatus(false, false, false, false),
|
||||
);
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(getWarningByText("@bob:example.org's identity appears to have changed.")).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
// If we get an update for a user's verification status while we're fetching
|
||||
// that user's verification status, we should display based on the updated
|
||||
// value.
|
||||
describe("handles races between fetching verification status and receiving updates", () => {
|
||||
// First case: check that if the update says that the user identity
|
||||
// needs approval, but the fetch says it doesn't, we show the warning.
|
||||
it("update says identity needs approval", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
|
||||
mockRoomMember("@alice:example.org", "Alice"),
|
||||
]);
|
||||
jest.spyOn(room, "getMember").mockReturnValue(mockRoomMember("@alice:example.org", "Alice"));
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockImplementation(async () => {
|
||||
act(() => {
|
||||
client.emit(
|
||||
CryptoEvent.UserTrustStatusChanged,
|
||||
"@alice:example.org",
|
||||
new UserVerificationStatus(false, false, false, true),
|
||||
);
|
||||
});
|
||||
return Promise.resolve(new UserVerificationStatus(false, false, false, false));
|
||||
});
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
// Second case: check that if the update says that the user identity
|
||||
// doesn't needs approval, but the fetch says it does, we don't show the
|
||||
// warning.
|
||||
it("update says identity doesn't need approval", async () => {
|
||||
jest.spyOn(room, "getEncryptionTargetMembers").mockResolvedValue([
|
||||
mockRoomMember("@alice:example.org", "Alice"),
|
||||
]);
|
||||
jest.spyOn(room, "getMember").mockReturnValue(mockRoomMember("@alice:example.org", "Alice"));
|
||||
const crypto = client.getCrypto()!;
|
||||
jest.spyOn(crypto, "getUserVerificationStatus").mockImplementation(async () => {
|
||||
act(() => {
|
||||
client.emit(
|
||||
CryptoEvent.UserTrustStatusChanged,
|
||||
"@alice:example.org",
|
||||
new UserVerificationStatus(false, false, false, false),
|
||||
);
|
||||
});
|
||||
return Promise.resolve(new UserVerificationStatus(false, false, false, true));
|
||||
});
|
||||
renderComponent(client, room);
|
||||
await sleep(10); // give it some time to finish initialising
|
||||
await waitFor(() =>
|
||||
expect(() =>
|
||||
getWarningByText("Alice's (@alice:example.org) identity appears to have changed."),
|
||||
).toThrow(),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -84,7 +84,7 @@ exports[`ReadReceiptGroup <ReadReceiptPerson /> should render 1`] = `
|
||||
<p
|
||||
class="mx_ReadReceiptGroup_secondary"
|
||||
>
|
||||
Wed, 15 May, 0:00
|
||||
==MOCK FORMATTED DATE==
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x);"
|
||||
>
|
||||
<button
|
||||
aria-labelledby=":r154:"
|
||||
aria-labelledby=":r16c:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -73,7 +73,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
<button
|
||||
aria-disabled="true"
|
||||
aria-label="There's no one here to call"
|
||||
aria-labelledby=":r159:"
|
||||
aria-labelledby=":r16h:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -98,7 +98,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
</button>
|
||||
<button
|
||||
aria-label="Room info"
|
||||
aria-labelledby=":r15e:"
|
||||
aria-labelledby=":r16m:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
@@ -123,7 +123,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
|
||||
</button>
|
||||
<button
|
||||
aria-label="Threads"
|
||||
aria-labelledby=":r15j:"
|
||||
aria-labelledby=":r16r:"
|
||||
class="_icon-button_bh2qc_17"
|
||||
role="button"
|
||||
style="--cpd-icon-button-size: 32px;"
|
||||
|
||||
@@ -8,10 +8,9 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import "@testing-library/jest-dom";
|
||||
import React from "react";
|
||||
import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||
import { fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||
|
||||
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext from "../../../../../../src/contexts/RoomContext";
|
||||
import defaultDispatcher from "../../../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../../../src/dispatcher/actions";
|
||||
import { flushPromises, mkEvent } from "../../../../../test-utils";
|
||||
@@ -23,6 +22,7 @@ import { ComposerInsertPayload, ComposerType } from "../../../../../../src/dispa
|
||||
import { ActionPayload } from "../../../../../../src/dispatcher/payloads";
|
||||
import * as EmojiButton from "../../../../../../src/components/views/rooms/EmojiButton";
|
||||
import { createMocks } from "./utils";
|
||||
import { ScopedRoomContextProvider } from "../../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
describe("EditWysiwygComposer", () => {
|
||||
afterEach(() => {
|
||||
@@ -39,9 +39,9 @@ describe("EditWysiwygComposer", () => {
|
||||
) => {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<RoomContext.Provider value={roomContext}>
|
||||
<ScopedRoomContextProvider {...roomContext}>
|
||||
<EditWysiwygComposer disabled={disabled} editorStateTransfer={_editorStateTransfer} />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
@@ -64,9 +64,9 @@ describe("EditWysiwygComposer", () => {
|
||||
|
||||
rerender(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={{ ...defaultRoomContext, room: undefined }}>
|
||||
<ScopedRoomContextProvider {...defaultRoomContext} room={undefined}>
|
||||
<EditWysiwygComposer disabled={false} editorStateTransfer={editorStateTransfer} />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
@@ -196,9 +196,9 @@ describe("EditWysiwygComposer", () => {
|
||||
// Then
|
||||
screen.getByText("Save").click();
|
||||
const expectedContent = {
|
||||
"body": ` * foo bar`,
|
||||
"body": `* foo bar`,
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": ` * foo bar`,
|
||||
"formatted_body": `* foo bar`,
|
||||
"m.new_content": {
|
||||
body: "foo bar",
|
||||
format: "org.matrix.custom.html",
|
||||
@@ -253,9 +253,7 @@ describe("EditWysiwygComposer", () => {
|
||||
});
|
||||
|
||||
// Wait for event dispatch to happen
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
// Then we don't get it because we are disabled
|
||||
expect(screen.getByRole("textbox")).not.toHaveFocus();
|
||||
@@ -277,10 +275,10 @@ describe("EditWysiwygComposer", () => {
|
||||
);
|
||||
render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={defaultRoomContext}>
|
||||
<ScopedRoomContextProvider {...defaultRoomContext}>
|
||||
<EditWysiwygComposer editorStateTransfer={editorStateTransfer} />
|
||||
<Emoji menuPosition={{ chevronFace: ChevronFace.Top }} />
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
// Same behavior as in RoomView.tsx
|
||||
|
||||
@@ -11,7 +11,6 @@ import React from "react";
|
||||
import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||
|
||||
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext from "../../../../../../src/contexts/RoomContext";
|
||||
import defaultDispatcher from "../../../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../../../src/dispatcher/actions";
|
||||
import { flushPromises } from "../../../../../test-utils";
|
||||
@@ -20,6 +19,7 @@ import { aboveLeftOf } from "../../../../../../src/components/structures/Context
|
||||
import { ComposerInsertPayload, ComposerType } from "../../../../../../src/dispatcher/payloads/ComposerInsertPayload";
|
||||
import { setSelection } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/selection";
|
||||
import { createMocks } from "./utils";
|
||||
import { ScopedRoomContextProvider } from "../../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../../src/components/views/rooms/EmojiButton", () => ({
|
||||
EmojiButton: ({ addEmoji }: { addEmoji: (emoji: string) => void }) => {
|
||||
@@ -66,7 +66,7 @@ describe("SendWysiwygComposer", () => {
|
||||
) => {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<RoomContext.Provider value={defaultRoomContext}>
|
||||
<ScopedRoomContextProvider {...defaultRoomContext}>
|
||||
<SendWysiwygComposer
|
||||
onChange={onChange}
|
||||
onSend={onSend}
|
||||
@@ -75,7 +75,7 @@ describe("SendWysiwygComposer", () => {
|
||||
menuPosition={aboveLeftOf({ top: 0, bottom: 0, right: 0 })}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</RoomContext.Provider>
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user