diff --git a/test/components/structures/MatrixChat-test.tsx b/test/components/structures/MatrixChat-test.tsx
index dd81ecf68c..d86d2ba29f 100644
--- a/test/components/structures/MatrixChat-test.tsx
+++ b/test/components/structures/MatrixChat-test.tsx
@@ -17,9 +17,10 @@ limitations under the License.
import React, { ComponentProps } from "react";
import { fireEvent, render, RenderResult, screen, within } from "@testing-library/react";
import fetchMockJest from "fetch-mock-jest";
-import { ClientEvent } from "matrix-js-sdk/src/client";
+import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
import { SyncState } from "matrix-js-sdk/src/sync";
import { MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
+import * as MatrixJs from "matrix-js-sdk/src/matrix";
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import MatrixChat from "../../../src/components/structures/MatrixChat";
@@ -27,14 +28,21 @@ import * as StorageManager from "../../../src/utils/StorageManager";
import defaultDispatcher from "../../../src/dispatcher/dispatcher";
import { Action } from "../../../src/dispatcher/actions";
import { UserTab } from "../../../src/components/views/dialogs/UserTab";
-import { clearAllModals, flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser } from "../../test-utils";
+import {
+ clearAllModals,
+ filterConsole,
+ flushPromises,
+ getMockClientWithEventEmitter,
+ mockClientMethodsUser,
+} from "../../test-utils";
import * as leaveRoomUtils from "../../../src/utils/leave-behaviour";
describe("", () => {
const userId = "@alice:server.org";
const deviceId = "qwertyui";
const accessToken = "abc123";
- const mockClient = getMockClientWithEventEmitter({
+ // reused in createClient mock below
+ const getMockClientMethods = () => ({
...mockClientMethodsUser(userId),
startClient: jest.fn(),
stopClient: jest.fn(),
@@ -57,7 +65,28 @@ describe("", () => {
store: {
destroy: 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(),
+ getVisibleRooms: jest.fn().mockReturnValue([]),
+ getRooms: jest.fn().mockReturnValue([]),
+ userHasCrossSigningKeys: jest.fn(),
+ setGlobalBlacklistUnverifiedDevices: jest.fn(),
+ setGlobalErrorOnUnknownDevices: jest.fn(),
+ getCrypto: jest.fn(),
+ secretStorage: {
+ isStored: jest.fn().mockReturnValue(null),
+ },
+ getDehydratedDevice: jest.fn(),
});
+ let mockClient = getMockClientWithEventEmitter(getMockClientMethods());
const serverConfig = {
hsUrl: "https://test.com",
hsName: "Test Server",
@@ -88,12 +117,13 @@ describe("", () => {
render();
const localStorageSpy = jest.spyOn(localStorage.__proto__, "getItem").mockReturnValue(undefined);
- beforeEach(() => {
+ beforeEach(async () => {
+ mockClient = getMockClientWithEventEmitter(getMockClientMethods());
fetchMockJest.get("https://test.com/_matrix/client/versions", {
unstable_features: {},
versions: [],
});
- localStorageSpy.mockClear();
+ localStorageSpy.mockReset();
jest.spyOn(StorageManager, "idbLoad").mockRestore();
jest.spyOn(StorageManager, "idbSave").mockResolvedValue(undefined);
jest.spyOn(defaultDispatcher, "dispatch").mockClear();
@@ -130,6 +160,7 @@ describe("", () => {
const getComponentAndWaitForReady = async (): Promise => {
const renderResult = getComponent();
+
// we think we are logged in, but are still waiting for the /sync to complete
await screen.findByText("Logout");
// initial sync
@@ -306,4 +337,148 @@ describe("", () => {
});
});
});
+
+ describe("login via key/pass", () => {
+ let loginClient!: ReturnType;
+
+ const mockCrypto = {
+ getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
+ getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
+ };
+
+ const userName = "ernie";
+ const password = "ilovebert";
+
+ // make test results readable
+ filterConsole("Failed to parse localStorage object");
+
+ const getComponentAndWaitForReady = async (): Promise => {
+ const renderResult = getComponent();
+ // wait for welcome page chrome render
+ await screen.findByText("powered by Matrix");
+
+ // go to login page
+ defaultDispatcher.dispatch({
+ action: "start_login",
+ });
+
+ await flushPromises();
+
+ return renderResult;
+ };
+
+ const waitForSyncAndLoad = async (client: MatrixClient, withoutSecuritySetup?: boolean): Promise => {
+ // need to wait for different elements depending on which flow
+ // without security setup we go to a loading page
+ if (withoutSecuritySetup) {
+ // we think we are logged in, but are still waiting for the /sync to complete
+ await screen.findByText("Logout");
+ // initial sync
+ client.emit(ClientEvent.Sync, SyncState.Prepared, null);
+ // wait for logged in view to load
+ await screen.findByLabelText("User menu");
+
+ // otherwise we stay on login and load from there for longer
+ } else {
+ // 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);
+ }
+
+ // let things settle
+ await flushPromises();
+ // and some more for good measure
+ // this proved to be a little flaky
+ await flushPromises();
+ };
+
+ const getComponentAndLogin = async (withoutSecuritySetup?: boolean): Promise => {
+ await getComponentAndWaitForReady();
+
+ fireEvent.change(screen.getByLabelText("Username"), { target: { value: userName } });
+ fireEvent.change(screen.getByLabelText("Password"), { target: { value: password } });
+
+ // sign in button is an input
+ fireEvent.click(screen.getByDisplayValue("Sign in"));
+
+ await waitForSyncAndLoad(loginClient, withoutSecuritySetup);
+ };
+
+ beforeEach(() => {
+ loginClient = getMockClientWithEventEmitter(getMockClientMethods());
+ // this is used to create a temporary client during login
+ jest.spyOn(MatrixJs, "createClient").mockReturnValue(loginClient);
+
+ loginClient.login.mockClear().mockResolvedValue({});
+ loginClient.loginFlows.mockClear().mockResolvedValue({ flows: [{ type: "m.login.password" }] });
+
+ loginClient.getProfileInfo.mockResolvedValue({
+ displayname: "Ernie",
+ });
+ });
+
+ it("should render login page", async () => {
+ await getComponentAndWaitForReady();
+
+ expect(screen.getAllByText("Sign in")[0]).toBeInTheDocument();
+ });
+
+ describe("post login setup", () => {
+ beforeEach(() => {
+ loginClient.isCryptoEnabled.mockReturnValue(true);
+ loginClient.getCrypto.mockReturnValue(mockCrypto as any);
+ loginClient.userHasCrossSigningKeys.mockClear().mockResolvedValue(false);
+ });
+
+ it("should go straight to logged in view when crypto is not enabled", async () => {
+ loginClient.isCryptoEnabled.mockReturnValue(false);
+
+ await getComponentAndLogin(true);
+
+ expect(loginClient.userHasCrossSigningKeys).not.toHaveBeenCalled();
+ });
+
+ it("should go straight to logged in view when user does not have cross signing keys and server does not support cross signing", async () => {
+ loginClient.doesServerSupportUnstableFeature.mockResolvedValue(false);
+
+ await getComponentAndLogin(false);
+
+ expect(loginClient.doesServerSupportUnstableFeature).toHaveBeenCalledWith(
+ "org.matrix.e2e_cross_signing",
+ );
+
+ await flushPromises();
+
+ // logged in
+ await screen.findByLabelText("User menu");
+ });
+
+ it("should show complete security screen when user has cross signing setup", async () => {
+ loginClient.userHasCrossSigningKeys.mockResolvedValue(true);
+
+ await getComponentAndLogin();
+
+ expect(loginClient.userHasCrossSigningKeys).toHaveBeenCalled();
+
+ await flushPromises();
+
+ // Complete security begin screen is rendered
+ expect(screen.getByText("Unable to verify this device")).toBeInTheDocument();
+ });
+
+ it("should setup e2e when server supports cross signing", async () => {
+ loginClient.doesServerSupportUnstableFeature.mockResolvedValue(true);
+
+ await getComponentAndLogin();
+
+ expect(loginClient.userHasCrossSigningKeys).toHaveBeenCalled();
+
+ await flushPromises();
+
+ // set up keys screen is rendered
+ expect(screen.getByText("Setting up keys")).toBeInTheDocument();
+ });
+ });
+ });
});