Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/

import { cleanup, render, waitFor } from "jest-matrix-react";
import { mocked, type MockedObject } from "jest-mock";
// @vitest-environment happy-dom

import { describe, it, test, expect, beforeEach, afterEach, vi, type MockedObject } from "vitest";
import { cleanup, render, waitFor } from "test-utils-rtl";
import React, { createRef, type RefObject } from "react";
import {
ClientRendezvousFailureReason,
Expand All @@ -25,44 +27,46 @@ import {
MatrixHttpApi,
OAuthGrantType,
} from "matrix-js-sdk/src/matrix";
import fetchMock from "@fetch-mock/jest";
import fetchMock from "@fetch-mock/vitest";
import { mockPlatformPeg } from "test-utils";

import LoginWithQR, { LoginWithQRFailureReason } from "../../../../../../src/components/views/auth/LoginWithQR";
import { Click, Mode, Phase } from "../../../../../../src/components/views/auth/LoginWithQR-types";
import { mockPlatformPeg } from "../../../../../test-utils";
import LoginWithQR, { LoginWithQRFailureReason } from "./LoginWithQR";
import { Click, Mode, Phase } from "./LoginWithQR-types";

jest.mock("matrix-js-sdk/src/rendezvous/transports");
jest.mock("matrix-js-sdk/src/rendezvous/channels");
jest.mock("matrix-js-sdk/src/rendezvous/channels/MSC4108SecureChannel.ts");
vi.mock("matrix-js-sdk/src/rendezvous/transports");
vi.mock("matrix-js-sdk/src/rendezvous/channels");
vi.mock("matrix-js-sdk/src/rendezvous/channels/MSC4108SecureChannel.ts");

const mockedFlow = jest.fn();
const mockedFlow = vi.fn();

jest.mock("../../../../../../src/components/views/auth/LoginWithQRFlow", () => (props: Record<string, any>) => {
mockedFlow(props);
return <div />;
});
vi.mock("./LoginWithQRFlow", () => ({
default: (props: Record<string, any>) => {
mockedFlow(props);
return <div />;
},
}));

function makeClient() {
const cli = mocked({
getUser: jest.fn(),
isGuest: jest.fn().mockReturnValue(false),
isUserIgnored: jest.fn(),
getUserId: jest.fn(),
on: jest.fn(),
isSynapseAdministrator: jest.fn().mockResolvedValue(false),
isRoomEncrypted: jest.fn().mockReturnValue(false),
mxcUrlToHttp: jest.fn().mockReturnValue("mock-mxcUrlToHttp"),
doesServerSupportUnstableFeature: jest.fn().mockReturnValue(true),
removeListener: jest.fn(),
requestLoginToken: jest.fn(),
const cli = {
getUser: vi.fn(),
isGuest: vi.fn().mockReturnValue(false),
isUserIgnored: vi.fn(),
getUserId: vi.fn(),
on: vi.fn(),
isSynapseAdministrator: vi.fn().mockResolvedValue(false),
isRoomEncrypted: vi.fn().mockReturnValue(false),
mxcUrlToHttp: vi.fn().mockReturnValue("mock-mxcUrlToHttp"),
doesServerSupportUnstableFeature: vi.fn().mockReturnValue(true),
removeListener: vi.fn(),
requestLoginToken: vi.fn(),
currentState: {
on: jest.fn(),
on: vi.fn(),
},
getClientWellKnown: jest.fn().mockReturnValue({}),
getCrypto: jest.fn().mockReturnValue({}),
getDomain: jest.fn(),
getAuthMetadata: jest.fn().mockReturnValue(makeDelegatedAuthMetadata()),
} as unknown as MatrixClient);
getClientWellKnown: vi.fn().mockReturnValue({}),
getCrypto: vi.fn().mockReturnValue({}),
getDomain: vi.fn(),
getAuthMetadata: vi.fn().mockReturnValue(makeDelegatedAuthMetadata()),
} as unknown as MockedObject<MatrixClient>;

cli.http = new MatrixHttpApi(cli, {
baseUrl: "https://server/",
Expand All @@ -82,18 +86,18 @@ describe("<LoginWithQR />", () => {
const defaultProps = {
legacy: true,
mode: Mode.Show,
onFinished: jest.fn(),
onFinished: vi.fn(),
} as const;

beforeEach(() => {
mockedFlow.mockReset();
jest.resetAllMocks();
vi.resetAllMocks();
client = makeClient();
});

afterEach(() => {
jest.clearAllMocks();
jest.useRealTimers();
vi.clearAllMocks();
vi.useRealTimers();
cleanup();
});

Expand All @@ -112,11 +116,11 @@ describe("<LoginWithQR />", () => {
);

test("render QR then back", async () => {
const onFinished = jest.fn();
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockReturnValue(unresolvedPromise());
jest.spyOn(MSC4108SignInWithQR.prototype, "generateCode");
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols");
jest.spyOn(MSC4108SignInWithQR.prototype, "cancel");
const onFinished = vi.fn();
vi.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockReturnValue(unresolvedPromise());
vi.spyOn(MSC4108SignInWithQR.prototype, "generateCode");
vi.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols");
vi.spyOn(MSC4108SignInWithQR.prototype, "cancel");
const ref = createRef<LoginWithQR>();
render(getComponent({ client, onFinished, ref }));

Expand All @@ -140,8 +144,8 @@ describe("<LoginWithQR />", () => {
});

test("should open a new channel if expires before qr scan", async () => {
const onFinished = jest.fn();
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockReturnValue(unresolvedPromise());
const onFinished = vi.fn();
vi.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockReturnValue(unresolvedPromise());
const ref = createRef<LoginWithQR>();
render(getComponent({ client, onFinished, ref }));

Expand All @@ -159,27 +163,26 @@ describe("<LoginWithQR />", () => {

// Expire the channel
rendezvous.onFailure!(ClientRendezvousFailureReason.Expired);
await jest.runAllTimersAsync();
await waitFor(() => expect(ref.current!.state.rendezvous).toBeDefined());
await waitFor(() => expect(ref.current!.state.rendezvous).toBeDefined(), { timeout: 2000 });
expect(ref.current!.state.rendezvous).not.toBe(rendezvous);
});

test("failed to connect", async () => {
render(getComponent({ client }));
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({});
jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockRejectedValue(
vi.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({});
vi.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockRejectedValue(
new HTTPError("Internal Server Error", 500),
);
const fn = jest.spyOn(MSC4108SignInWithQR.prototype, "cancel");
const fn = vi.spyOn(MSC4108SignInWithQR.prototype, "cancel");
await waitFor(() => expect(fn).toHaveBeenLastCalledWith(ClientRendezvousFailureReason.Unknown));
});

test("should show error if check code doesn't match", async () => {
jest.spyOn(global.window, "open");
vi.spyOn(global.window, "open");

render(getComponent({ client }));
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({});
jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({
vi.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({});
vi.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({
verificationUri: "mock-verification-uri",
});

Expand All @@ -206,12 +209,12 @@ describe("<LoginWithQR />", () => {

test("reciprocates login", async () => {
const ref = createRef<LoginWithQR>();
jest.spyOn(global.window, "open");
vi.spyOn(global.window, "open");

render(getComponent({ client, ref }));
jest.spyOn(MSC4108SignInWithQR.prototype, "shareSecrets").mockResolvedValue({});
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({});
jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({
vi.spyOn(MSC4108SignInWithQR.prototype, "shareSecrets").mockResolvedValue({});
vi.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({});
vi.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({
verificationUri: "mock-verification-uri",
});

Expand Down Expand Up @@ -242,11 +245,11 @@ describe("<LoginWithQR />", () => {
test("handles errors during protocol negotiation", async () => {
const ref = createRef<LoginWithQR>();
render(getComponent({ client, ref }));
jest.spyOn(MSC4108SignInWithQR.prototype, "cancel").mockResolvedValue();
vi.spyOn(MSC4108SignInWithQR.prototype, "cancel").mockResolvedValue();
const err = new RendezvousError("Unknown Failure", MSC4108FailureReason.UnsupportedProtocol);
// @ts-ignore work-around for lazy mocks
err.code = MSC4108FailureReason.UnsupportedProtocol;
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockRejectedValue(err);
vi.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockRejectedValue(err);
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith(
expect.objectContaining({
Expand All @@ -263,8 +266,8 @@ describe("<LoginWithQR />", () => {

test("handles errors during reciprocation", async () => {
render(getComponent({ client }));
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({});
jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({});
vi.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({});
vi.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({});
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith({
phase: Phase.OutOfBandConfirmation,
Expand All @@ -273,7 +276,7 @@ describe("<LoginWithQR />", () => {
}),
);

jest.spyOn(MSC4108SignInWithQR.prototype, "shareSecrets").mockRejectedValue(
vi.spyOn(MSC4108SignInWithQR.prototype, "shareSecrets").mockRejectedValue(
new HTTPError("Internal Server Error", 500),
);
const onClick = mockedFlow.mock.calls[0][0].onClick;
Expand All @@ -292,9 +295,9 @@ describe("<LoginWithQR />", () => {
test("handles user cancelling during reciprocation", async () => {
const ref = createRef<LoginWithQR>();
render(getComponent({ client, ref }));
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({});
jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({});
jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({});
vi.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({});
vi.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({});
vi.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({});
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith({
phase: Phase.OutOfBandConfirmation,
Expand All @@ -303,7 +306,7 @@ describe("<LoginWithQR />", () => {
}),
);

jest.spyOn(MSC4108SignInWithQR.prototype, "cancel").mockResolvedValue();
vi.spyOn(MSC4108SignInWithQR.prototype, "cancel").mockResolvedValue();
const onClick = mockedFlow.mock.calls[0][0].onClick;
await onClick(Click.Cancel);

Expand All @@ -320,14 +323,14 @@ describe("<LoginWithQR />", () => {
ref?: RefObject<LoginWithQR | null>;
}) => (
<LoginWithQR
onLoggedIn={jest.fn()}
onLoggedIn={vi.fn()}
{...defaultProps}
{...props}
intent={RendezvousIntent.LOGIN_ON_NEW_DEVICE}
/>
);

test("should handle qr login", async () => {
it("should handle qr login", async () => {
fetchMock.get("https://hs/_matrix/client/versions", {
unstable_features: {},
versions: ["v1.1", "v1.5", "v1.6", "v1.8", "v1.9", "v1.15"],
Expand All @@ -341,7 +344,7 @@ describe("<LoginWithQR />", () => {
});

mockPlatformPeg({
getOAuthClientMetadata: jest.fn().mockReturnValue({
getOAuthClientMetadata: vi.fn().mockReturnValue({
client_name: "App name",
client_uri: "https://company",
redirect_uris: ["https://app"],
Expand All @@ -353,7 +356,7 @@ describe("<LoginWithQR />", () => {
const ref = createRef<LoginWithQR>();

render(getComponent({ client, ref }));
jest.spyOn(MSC4108SignInWithQR.prototype, "shareSecrets").mockResolvedValue({
vi.spyOn(MSC4108SignInWithQR.prototype, "shareSecrets").mockResolvedValue({
secrets: {
cross_signing: {
master_key: "mk",
Expand All @@ -362,15 +365,15 @@ describe("<LoginWithQR />", () => {
},
},
});
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({ serverName: "hs" });
jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({
vi.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({ serverName: "hs" });
vi.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({
userCode: "123456",
});
jest.spyOn(MSC4108SignInWithQR.prototype, "completeLoginOnNewDevice").mockResolvedValue({
vi.spyOn(MSC4108SignInWithQR.prototype, "completeLoginOnNewDevice").mockResolvedValue({
access_token: "token",
token_type: "Bearer",
});
jest.spyOn(AutoDiscovery, "findClientConfig").mockResolvedValue({
vi.spyOn(AutoDiscovery, "findClientConfig").mockResolvedValue({
"m.homeserver": { base_url: "https://hs", state: AutoDiscoveryAction.SUCCESS },
"m.identity_server": { state: AutoDiscoveryAction.PROMPT },
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/

// @vitest-environment happy-dom

import React from "react";
import { act, fireEvent, render } from "jest-matrix-react";
import { describe, it, expect, vi } from "vitest";
import { act, fireEvent, render } from "test-utils-rtl";

import CurrentDeviceSection from "../../../../../../src/components/views/settings/devices/CurrentDeviceSection";
import type { ExtendedDevice } from "../../../../../../src/components/views/settings/devices/types.ts";
import CurrentDeviceSection from "./CurrentDeviceSection";
import type { ExtendedDevice } from "./types.ts";

describe("<CurrentDeviceSection />", () => {
const deviceId = "alices_device";
Expand All @@ -26,13 +29,13 @@ describe("<CurrentDeviceSection />", () => {

const defaultProps = {
device: alicesVerifiedDevice,
onVerifyCurrentDevice: jest.fn(),
onSignOutCurrentDevice: jest.fn(),
saveDeviceName: jest.fn(),
onVerifyCurrentDevice: vi.fn(),
onSignOutCurrentDevice: vi.fn(),
saveDeviceName: vi.fn(),
isLoading: false,
isSigningOut: false,
otherSessionsCount: 1,
setPushNotifications: jest.fn(),
setPushNotifications: vi.fn(),
};

const getComponent = (props = {}): React.ReactElement => <CurrentDeviceSection {...defaultProps} {...props} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@
Please see LICENSE files in the repository root for full details.
*/

// @vitest-environment happy-dom

import React from "react";
import { fireEvent, render, type RenderResult } from "jest-matrix-react";
import { describe, it, expect, vi } from "vitest";
import { fireEvent, render, type RenderResult } from "test-utils-rtl";
import { flushPromisesWithFakeTimers } from "test-utils";

import { DeviceDetailHeading } from "../../../../../../src/components/views/settings/devices/DeviceDetailHeading";
import { flushPromisesWithFakeTimers } from "../../../../../test-utils";
import type { ExtendedDevice } from "../../../../../../src/components/views/settings/devices/types.ts";
import { DeviceDetailHeading } from "./DeviceDetailHeading";
import type { ExtendedDevice } from "./types";

jest.useFakeTimers();
vi.useFakeTimers({ shouldAdvanceTime: true });

describe("<DeviceDetailHeading />", () => {
const device: ExtendedDevice = {
Expand All @@ -23,7 +26,7 @@
};
const defaultProps = {
device,
saveDeviceName: jest.fn(),
saveDeviceName: vi.fn(),
};
const getComponent = (props = {}) => <DeviceDetailHeading {...defaultProps} {...props} />;

Expand Down Expand Up @@ -64,11 +67,11 @@
// stop editing
fireEvent.click(getByTestId("device-rename-cancel-cta"));

expect(container.getElementsByClassName("mx_DeviceDetailHeading").length).toBe(1);

Check warning on line 70 in apps/web/src/components/views/settings/devices/DeviceDetailHeading.test.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer "expect(container.getElementsByClassName("mx_DeviceDetailHeading")).toHaveLength(1)" over this generic assertion for better reporting; it works on any object with a numeric length property.

See more on https://sonarcloud.io/project/issues?id=element-web&issues=AaBCejmnI4aEr1Ge7UVA&open=AaBCejmnI4aEr1Ge7UVA&pullRequest=34848
});

it("clicking submit updates device name with edited value", () => {
const saveDeviceName = jest.fn();
const saveDeviceName = vi.fn();
const { getByTestId } = render(getComponent({ saveDeviceName }));

// start editing
Expand Down Expand Up @@ -113,7 +116,7 @@
});

it("displays error when device name fails to save", async () => {
const saveDeviceName = jest.fn().mockRejectedValueOnce("oups").mockResolvedValue({});
const saveDeviceName = vi.fn().mockRejectedValueOnce("oups").mockResolvedValue({});
const { getByTestId, queryByText, findByText, container } = render(getComponent({ saveDeviceName }));

// start editing
Expand Down
Loading
Loading