/*
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.
*/
// fake-indexeddb needs this and the tests crash without it
// https://github.com/dumbmatter/fakeIndexedDB?tab=readme-ov-file#jsdom-often-used-with-jest
import "core-js/stable/structured-clone";
import "fake-indexeddb/auto";
import React, { ComponentProps } from "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";
import { MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
import * as MatrixJs from "matrix-js-sdk/src/matrix";
import { completeAuthorizationCodeGrant } from "matrix-js-sdk/src/oidc/authorize";
import { logger } from "matrix-js-sdk/src/logger";
import { OidcError } from "matrix-js-sdk/src/oidc/error";
import { BearerTokenResponse } from "matrix-js-sdk/src/oidc/validate";
import { defer, IDeferred, sleep } from "matrix-js-sdk/src/utils";
import { CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
import MatrixChat from "../../../../src/components/structures/MatrixChat";
import * as StorageAccess from "../../../../src/utils/StorageAccess";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../src/dispatcher/actions";
import { UserTab } from "../../../../src/components/views/dialogs/UserTab";
import {
clearAllModals,
createStubMatrixRTC,
filterConsole,
flushPromises,
getMockClientWithEventEmitter,
mockClientMethodsServer,
mockClientMethodsUser,
MockClientWithEventEmitter,
mockPlatformPeg,
resetJsDomAfterEach,
unmockClientPeg,
} 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";
import { PosthogAnalytics } from "../../../../src/PosthogAnalytics";
import PlatformPeg from "../../../../src/PlatformPeg";
import EventIndexPeg from "../../../../src/indexing/EventIndexPeg";
import * as Lifecycle from "../../../../src/Lifecycle";
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY } from "../../../../src/BasePlatform";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { SettingLevel } from "../../../../src/settings/SettingLevel";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import DMRoomMap from "../../../../src/utils/DMRoomMap";
import { ReleaseAnnouncementStore } from "../../../../src/stores/ReleaseAnnouncementStore";
import { DRAFT_LAST_CLEANUP_KEY } from "../../../../src/DraftCleaner";
import { UIFeature } from "../../../../src/settings/UIFeature";
import AutoDiscoveryUtils from "../../../../src/utils/AutoDiscoveryUtils";
import { ValidatedServerConfig } from "../../../../src/utils/ValidatedServerConfig";
import Modal from "../../../../src/Modal.tsx";
jest.mock("matrix-js-sdk/src/oidc/authorize", () => ({
completeAuthorizationCodeGrant: jest.fn(),
}));
// Stub out ThemeWatcher as the necessary bits for themes are done in element-web's index.html and thus are lacking here,
// plus JSDOM's implementation of CSSStyleDeclaration has a bunch of differences to real browsers which cause issues.
jest.mock("../../../../src/settings/watchers/ThemeWatcher");
/** The matrix versions our mock server claims to support */
const SERVER_SUPPORTED_MATRIX_VERSIONS = ["v1.1", "v1.5", "v1.6", "v1.8", "v1.9"];
describe("", () => {
const userId = "@alice:server.org";
const deviceId = "qwertyui";
const accessToken = "abc123";
const refreshToken = "def456";
let bootstrapDeferred: IDeferred;
// reused in createClient mock below
const getMockClientMethods = () => ({
...mockClientMethodsUser(userId),
...mockClientMethodsServer(),
getVersions: jest.fn().mockResolvedValue({ versions: SERVER_SUPPORTED_MATRIX_VERSIONS }),
startClient: function () {
// @ts-ignore
this.emit(ClientEvent.Sync, SyncState.Prepared, null);
},
stopClient: jest.fn(),
setCanResetTimelineCallback: jest.fn(),
isInitialSyncComplete: jest.fn(),
getSyncState: jest.fn(),
getSsoLoginUrl: jest.fn(),
getSyncStateData: jest.fn().mockReturnValue(null),
getThirdpartyProtocols: jest.fn().mockResolvedValue({}),
getClientWellKnown: jest.fn().mockReturnValue({}),
isVersionSupported: jest.fn().mockResolvedValue(false),
initRustCrypto: jest.fn(),
getRoom: jest.fn(),
getMediaHandler: jest.fn().mockReturnValue({
setVideoInput: jest.fn(),
setAudioInput: jest.fn(),
setAudioSettings: jest.fn(),
stopAllStreams: jest.fn(),
} as unknown as MediaHandler),
setAccountData: jest.fn(),
store: {
destroy: jest.fn(),
startup: jest.fn(),
},
login: jest.fn(),
loginFlows: jest.fn(),
isGuest: jest.fn().mockReturnValue(false),
clearStores: jest.fn(),
setGuest: jest.fn(),
setNotifTimelineSet: jest.fn(),
getAccountData: jest.fn(),
doesServerSupportUnstableFeature: jest.fn(),
getDevices: jest.fn().mockResolvedValue({ devices: [] }),
getProfileInfo: jest.fn().mockResolvedValue({
displayname: "Ernie",
}),
getVisibleRooms: jest.fn().mockReturnValue([]),
getRooms: jest.fn().mockReturnValue([]),
setGlobalErrorOnUnknownDevices: jest.fn(),
getCrypto: jest.fn().mockReturnValue({
getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
isCrossSigningReady: jest.fn().mockReturnValue(false),
getUserDeviceInfo: jest.fn().mockReturnValue(new Map()),
getUserVerificationStatus: jest.fn().mockResolvedValue(new UserVerificationStatus(false, false, false)),
getVersion: jest.fn().mockReturnValue("1"),
setDeviceIsolationMode: jest.fn(),
userHasCrossSigningKeys: jest.fn(),
getActiveSessionBackupVersion: jest.fn().mockResolvedValue(null),
globalBlacklistUnverifiedDevices: false,
// This needs to not finish immediately because we need to test the screen appears
bootstrapCrossSigning: jest.fn().mockImplementation(() => bootstrapDeferred.promise),
}),
secretStorage: {
isStored: jest.fn().mockReturnValue(null),
},
matrixRTC: createStubMatrixRTC(),
getDehydratedDevice: jest.fn(),
whoami: jest.fn(),
logout: jest.fn(),
getDeviceId: jest.fn(),
getKeyBackupVersion: jest.fn().mockResolvedValue(null),
});
let mockClient: Mocked;
const serverConfig = {
hsUrl: "https://test.com",
hsName: "Test Server",
hsNameIsDifferent: false,
isUrl: "https://is.com",
isDefault: true,
isNameResolvable: true,
warning: "",
};
let initPromise: Promise | undefined;
let defaultProps: ComponentProps;
const getComponent = (props: Partial> = {}) => {
// 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(, { legacyRoot: true });
};
// make test results readable
filterConsole(
"Failed to parse localStorage object",
"Sync store cannot be used on this browser",
"Crypto store cannot be used on this browser",
"Storage consistency checks failed",
"LegacyCallHandler: missing