1
0
mirror of https://github.com/matrix-org/matrix-react-sdk.git synced 2025-08-06 10:22:45 +03:00

Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/react18/context-hocs

This commit is contained in:
Michael Telatynski
2024-05-01 15:23:14 +01:00
129 changed files with 2571 additions and 1892 deletions

View File

@@ -388,4 +388,60 @@ describe("DecryptionFailureTracker", function () {
// should track remapped error code
expect(counts["XEDNI_EGASSEM_NWONKNU_MLO"]).toBe(1);
});
it("default error code mapper maps error codes correctly", async () => {
const errorCodes: string[] = [];
// @ts-ignore access to private constructor
const tracker = new DecryptionFailureTracker(
(total: number, errorCode: string) => {
errorCodes.push(errorCode);
},
// @ts-ignore access to private member
DecryptionFailureTracker.instance.errorCodeMapFn,
);
const event1 = await createFailedDecryptionEvent(DecryptionFailureCode.MEGOLM_UNKNOWN_INBOUND_SESSION_ID);
tracker.addVisibleEvent(event1);
tracker.eventDecrypted(event1);
const event2 = await createFailedDecryptionEvent(DecryptionFailureCode.OLM_UNKNOWN_MESSAGE_INDEX);
tracker.addVisibleEvent(event2);
tracker.eventDecrypted(event2);
const event3 = await createFailedDecryptionEvent(DecryptionFailureCode.HISTORICAL_MESSAGE_NO_KEY_BACKUP);
tracker.addVisibleEvent(event3);
tracker.eventDecrypted(event3);
const event4 = await createFailedDecryptionEvent(DecryptionFailureCode.HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED);
tracker.addVisibleEvent(event4);
tracker.eventDecrypted(event4);
const event5 = await createFailedDecryptionEvent(DecryptionFailureCode.HISTORICAL_MESSAGE_WORKING_BACKUP);
tracker.addVisibleEvent(event5);
tracker.eventDecrypted(event5);
const event6 = await createFailedDecryptionEvent(DecryptionFailureCode.HISTORICAL_MESSAGE_USER_NOT_JOINED);
tracker.addVisibleEvent(event6);
tracker.eventDecrypted(event6);
const event7 = await createFailedDecryptionEvent(DecryptionFailureCode.UNKNOWN_ERROR);
tracker.addVisibleEvent(event7);
tracker.eventDecrypted(event7);
// Pretend "now" is Infinity
tracker.checkFailures(Infinity);
tracker.trackFailures();
expect(errorCodes).toEqual([
"OlmKeysNotSentError",
"OlmIndexError",
"HistoricalMessage",
"HistoricalMessage",
"HistoricalMessage",
"ExpectedDueToMembership",
"UnknownError",
]);
});
});

View File

@@ -36,7 +36,7 @@ const getFakePosthog = (): PostHog =>
register: jest.fn(),
get_distinct_id: jest.fn(),
persistence: {
get_user_state: jest.fn(),
get_property: jest.fn(),
},
identifyUser: jest.fn(),
}) as unknown as PostHog;

View File

@@ -20,6 +20,8 @@ import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { SlidingSyncManager } from "../src/SlidingSyncManager";
import { stubClient } from "./test-utils";
import SlidingSyncController from "../src/settings/controllers/SlidingSyncController";
import SettingsStore from "../src/settings/SettingsStore";
jest.mock("matrix-js-sdk/src/sliding-sync");
const MockSlidingSync = <jest.Mock<SlidingSync>>(<unknown>SlidingSync);
@@ -231,4 +233,53 @@ describe("SlidingSyncManager", () => {
);
});
});
describe("checkSupport", () => {
beforeEach(() => {
SlidingSyncController.serverSupportsSlidingSync = false;
jest.spyOn(manager, "getProxyFromWellKnown").mockResolvedValue("proxy");
});
it("shorts out if the server has 'native' sliding sync support", async () => {
jest.spyOn(manager, "nativeSlidingSyncSupport").mockResolvedValue(true);
expect(SlidingSyncController.serverSupportsSlidingSync).toBeFalsy();
await manager.checkSupport(client);
expect(manager.getProxyFromWellKnown).not.toHaveBeenCalled(); // We return earlier
expect(SlidingSyncController.serverSupportsSlidingSync).toBeTruthy();
});
it("tries to find a sliding sync proxy url from the client well-known if there's no 'native' support", async () => {
jest.spyOn(manager, "nativeSlidingSyncSupport").mockResolvedValue(false);
expect(SlidingSyncController.serverSupportsSlidingSync).toBeFalsy();
await manager.checkSupport(client);
expect(manager.getProxyFromWellKnown).toHaveBeenCalled();
expect(SlidingSyncController.serverSupportsSlidingSync).toBeTruthy();
});
});
describe("setup", () => {
beforeEach(() => {
jest.spyOn(manager, "configure");
jest.spyOn(manager, "startSpidering");
});
it("uses the baseUrl as a proxy if no proxy is set in the client well-known and the server has no native support", async () => {
await manager.setup(client);
expect(manager.configure).toHaveBeenCalled();
expect(manager.configure).toHaveBeenCalledWith(client, client.baseUrl);
expect(manager.startSpidering).toHaveBeenCalled();
});
it("uses the proxy declared in the client well-known", async () => {
jest.spyOn(manager, "getProxyFromWellKnown").mockResolvedValue("proxy");
await manager.setup(client);
expect(manager.configure).toHaveBeenCalled();
expect(manager.configure).toHaveBeenCalledWith(client, "proxy");
expect(manager.startSpidering).toHaveBeenCalled();
});
it("uses the legacy `feature_sliding_sync_proxy_url` if it was set", async () => {
jest.spyOn(manager, "getProxyFromWellKnown").mockResolvedValue("proxy");
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
if (name === "feature_sliding_sync_proxy_url") return "legacy-proxy";
});
await manager.setup(client);
expect(manager.configure).toHaveBeenCalled();
expect(manager.configure).toHaveBeenCalledWith(client, "legacy-proxy");
expect(manager.startSpidering).toHaveBeenCalled();
});
});
});

View File

@@ -61,6 +61,7 @@ import SettingsStore from "../../../src/settings/SettingsStore";
import { SettingLevel } from "../../../src/settings/SettingLevel";
import { MatrixClientPeg as peg } from "../../../src/MatrixClientPeg";
import DMRoomMap from "../../../src/utils/DMRoomMap";
import { ReleaseAnnouncementStore } from "../../../src/stores/ReleaseAnnouncementStore";
jest.mock("matrix-js-sdk/src/oidc/authorize", () => ({
completeAuthorizationCodeGrant: jest.fn(),
@@ -627,6 +628,12 @@ describe("<MatrixChat />", () => {
(id) => [room, spaceRoom].find((room) => room.roomId === id) || null,
);
jest.spyOn(spaceRoom, "isSpaceRoom").mockReturnValue(true);
jest.spyOn(ReleaseAnnouncementStore.instance, "getReleaseAnnouncement").mockReturnValue(null);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe("leave_room", () => {

View File

@@ -397,6 +397,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
<button
aria-label="Bold"
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold"
data-state="closed"
role="button"
tabindex="0"
type="button"
@@ -404,6 +405,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
<button
aria-label="Italics"
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic"
data-state="closed"
role="button"
tabindex="-1"
type="button"
@@ -411,6 +413,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
<button
aria-label="Strikethrough"
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough"
data-state="closed"
role="button"
tabindex="-1"
type="button"
@@ -418,6 +421,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
<button
aria-label="Code block"
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode"
data-state="closed"
role="button"
tabindex="-1"
type="button"
@@ -425,6 +429,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
<button
aria-label="Quote"
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote"
data-state="closed"
role="button"
tabindex="-1"
type="button"
@@ -432,6 +437,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
<button
aria-label="Insert link"
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink"
data-state="closed"
role="button"
tabindex="-1"
type="button"
@@ -644,6 +650,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
<button
aria-label="Bold"
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold"
data-state="closed"
role="button"
tabindex="0"
type="button"
@@ -651,6 +658,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
<button
aria-label="Italics"
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic"
data-state="closed"
role="button"
tabindex="-1"
type="button"
@@ -658,6 +666,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
<button
aria-label="Strikethrough"
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough"
data-state="closed"
role="button"
tabindex="-1"
type="button"
@@ -665,6 +674,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
<button
aria-label="Code block"
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode"
data-state="closed"
role="button"
tabindex="-1"
type="button"
@@ -672,6 +682,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
<button
aria-label="Quote"
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote"
data-state="closed"
role="button"
tabindex="-1"
type="button"
@@ -679,6 +690,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
<button
aria-label="Insert link"
class="mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink"
data-state="closed"
role="button"
tabindex="-1"
type="button"

View File

@@ -12,7 +12,6 @@ exports[`<UserMenu> <UserMenu> when video broadcast when rendered should render
class="mx_AccessibleButton mx_UserMenu_contextMenuButton"
role="button"
tabindex="0"
title="User menu"
>
<div
class="mx_UserMenu_userAvatar"

View File

@@ -48,6 +48,7 @@ exports[`<BeaconListItem /> when a beacon is live and has locations renders beac
<div
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
data-state="closed"
role="button"
tabindex="0"
/>

View File

@@ -14,11 +14,12 @@ exports[`<DialogSidebar /> renders sidebar correctly with beacons 1`] = `
View list
</h4>
<div
aria-label="Close sidebar"
class="mx_AccessibleButton mx_DialogSidebar_closeButton"
data-state="closed"
data-testid="dialog-sidebar-close"
role="button"
tabindex="0"
title="Close sidebar"
>
<div
class="mx_DialogSidebar_closeButtonIcon"
@@ -81,6 +82,7 @@ exports[`<DialogSidebar /> renders sidebar correctly with beacons 1`] = `
<div
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
data-state="closed"
role="button"
tabindex="0"
/>
@@ -113,11 +115,12 @@ exports[`<DialogSidebar /> renders sidebar correctly without beacons 1`] = `
View list
</h4>
<div
aria-label="Close sidebar"
class="mx_AccessibleButton mx_DialogSidebar_closeButton"
data-state="closed"
data-testid="dialog-sidebar-close"
role="button"
tabindex="0"
title="Close sidebar"
>
<div
class="mx_DialogSidebar_closeButtonIcon"

View File

@@ -3,10 +3,11 @@
exports[`<LeftPanelLiveShareWarning /> when user has live location monitor renders correctly when minimized 1`] = `
<DocumentFragment>
<div
aria-label="You are sharing your live location"
class="mx_AccessibleButton mx_LeftPanelLiveShareWarning mx_LeftPanelLiveShareWarning__minimized"
data-state="closed"
role="button"
tabindex="0"
title="You are sharing your live location"
>
<div
height="10"

View File

@@ -109,11 +109,12 @@ exports[`<RoomLiveShareWarning /> when user has live beacons and geolocation is
Retry
</button>
<button
aria-label="Stop and close"
class="mx_AccessibleButton mx_RoomLiveShareWarning_closeButton"
data-state="closed"
data-testid="room-live-share-wire-error-close-button"
role="button"
tabindex="0"
title="Stop and close"
>
<div
class="mx_RoomLiveShareWarning_closeButtonIcon"

View File

@@ -19,6 +19,7 @@ exports[`<ShareLatestLocation /> renders share buttons when there is a location
<div
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
data-state="closed"
role="button"
tabindex="0"
/>

View File

@@ -152,14 +152,17 @@ describe("<UserSettingsDialog />", () => {
watchSettingCallbacks[settingName] = callback;
return `mock-watcher-id-${settingName}`;
});
mockSettingsStore.getValue.mockReturnValue(false);
const { queryByTestId, unmount } = render(getComponent());
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeFalsy();
expect(mockSettingsStore.watchSetting.mock.calls[0][0]).toEqual("feature_mjolnir");
expect(mockSettingsStore.watchSetting).toHaveBeenCalledWith("feature_mjolnir", null, expect.anything());
// call the watch setting callback
mockSettingsStore.getValue.mockReturnValue(true);
watchSettingCallbacks["feature_mjolnir"]("feature_mjolnir", "", SettingLevel.ACCOUNT, true, true);
// tab is rendered now
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy();

View File

@@ -41,6 +41,7 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = `
<div
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
data-state="closed"
role="button"
tabindex="0"
/>

View File

@@ -377,12 +377,12 @@ describe("AppTile", () => {
});
it("clicking 'minimise' should send the widget to the right", async () => {
await userEvent.click(renderResult.getByTitle("Minimise"));
await userEvent.click(renderResult.getByLabelText("Minimise"));
expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, Container.Right);
});
it("clicking 'maximise' should send the widget to the center", async () => {
await userEvent.click(renderResult.getByTitle("Maximise"));
await userEvent.click(renderResult.getByLabelText("Maximise"));
expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, Container.Center);
});
@@ -435,7 +435,7 @@ describe("AppTile", () => {
});
it("clicking 'un-maximise' should send the widget to the top", async () => {
await userEvent.click(renderResult.getByTitle("Un-maximise"));
await userEvent.click(renderResult.getByLabelText("Un-maximise"));
expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, Container.Top);
});
});
@@ -461,7 +461,7 @@ describe("AppTile", () => {
});
it("should display the »Popout widget« button", () => {
expect(renderResult.getByTitle("Popout widget")).toBeInTheDocument();
expect(renderResult.getByLabelText("Popout widget")).toBeInTheDocument();
});
});
});

View File

@@ -0,0 +1,29 @@
/*
*
* Copyright 2024 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*/
import React from "react";
import { render } from "@testing-library/react";
import ImageView from "../../../../src/components/views/elements/ImageView";
describe("<ImageView />", () => {
it("renders correctly", () => {
const { container } = render(<ImageView src="https://example.com/image.png" onFinished={jest.fn()} />);
expect(container).toMatchSnapshot();
});
});

View File

@@ -13,11 +13,12 @@ exports[`AppTile destroys non-persisted right panel widget on room change 1`] =
class="mx_BaseCard_header"
>
<div
aria-label="Close"
class="mx_AccessibleButton mx_BaseCard_close"
data-state="closed"
data-testid="base-card-close-button"
role="button"
tabindex="0"
title="Close"
/>
<div
class="mx_BaseCard_headerProp"
@@ -37,7 +38,6 @@ exports[`AppTile destroys non-persisted right panel widget on room change 1`] =
class="mx_AccessibleButton mx_BaseCard_header_title_button--option"
role="button"
tabindex="0"
title="Options"
/>
</div>
</div>
@@ -136,20 +136,22 @@ exports[`AppTile for a pinned widget should render 1`] = `
class="mx_AppTileMenuBar_widgets"
>
<div
aria-label="Un-maximise"
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
data-state="closed"
role="button"
tabindex="0"
title="Un-maximise"
>
<div
class="mx_Icon mx_Icon_12"
/>
</div>
<div
aria-label="Minimise"
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
data-state="closed"
role="button"
tabindex="0"
title="Minimise"
>
<div
class="mx_Icon mx_Icon_12"
@@ -162,7 +164,6 @@ exports[`AppTile for a pinned widget should render 1`] = `
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
role="button"
tabindex="0"
title="Options"
>
<div
class="mx_Icon mx_Icon_12"
@@ -223,20 +224,22 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
class="mx_AppTileMenuBar_widgets"
>
<div
aria-label="Un-maximise"
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
data-state="closed"
role="button"
tabindex="0"
title="Un-maximise"
>
<div
class="mx_Icon mx_Icon_12"
/>
</div>
<div
aria-label="Minimise"
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
data-state="closed"
role="button"
tabindex="0"
title="Minimise"
>
<div
class="mx_Icon mx_Icon_12"
@@ -249,7 +252,6 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
role="button"
tabindex="0"
title="Options"
>
<div
class="mx_Icon mx_Icon_12"
@@ -377,20 +379,22 @@ exports[`AppTile preserves non-persisted widget on container move 1`] = `
class="mx_AppTileMenuBar_widgets"
>
<div
aria-label="Maximise"
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
data-state="closed"
role="button"
tabindex="0"
title="Maximise"
>
<div
class="mx_Icon mx_Icon_12"
/>
</div>
<div
aria-label="Minimise"
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
data-state="closed"
role="button"
tabindex="0"
title="Minimise"
>
<div
class="mx_Icon mx_Icon_12"
@@ -403,7 +407,6 @@ exports[`AppTile preserves non-persisted widget on container move 1`] = `
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
role="button"
tabindex="0"
title="Options"
>
<div
class="mx_Icon mx_Icon_12"

View File

@@ -0,0 +1,88 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ImageView /> renders correctly 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-label="Image view"
class="mx_ImageView"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_ImageView_panel"
>
<div />
<div
class="mx_ImageView_toolbar"
>
<div
aria-describedby="floating-ui-2"
aria-label="Zoom out"
class="mx_AccessibleButton mx_ImageView_button mx_ImageView_button_zoomOut"
data-state="open"
role="button"
tabindex="0"
/>
<div
aria-label="Zoom in"
class="mx_AccessibleButton mx_ImageView_button mx_ImageView_button_zoomIn"
data-state="closed"
role="button"
tabindex="0"
/>
<div
aria-label="Rotate Left"
class="mx_AccessibleButton mx_ImageView_button mx_ImageView_button_rotateCCW"
data-state="closed"
role="button"
tabindex="0"
/>
<div
aria-label="Rotate Right"
class="mx_AccessibleButton mx_ImageView_button mx_ImageView_button_rotateCW"
data-state="closed"
role="button"
tabindex="0"
/>
<div
aria-label="Download"
class="mx_AccessibleButton mx_ImageView_button mx_ImageView_button_download"
data-state="closed"
role="button"
tabindex="0"
/>
<div
aria-label="Close"
class="mx_AccessibleButton mx_ImageView_button mx_ImageView_button_close"
data-state="closed"
role="button"
tabindex="0"
/>
</div>
</div>
<div
class="mx_ImageView_image_wrapper"
>
<img
class="mx_ImageView_image "
draggable="true"
src="https://example.com/image.png"
style="transform: translateX(0px)
translateY(0px)
scale(0)
rotate(0deg); cursor: zoom-out;"
/>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
`;

View File

@@ -23,22 +23,24 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
class="mx_ZoomButtons"
>
<div
aria-label="Zoom in"
class="mx_AccessibleButton mx_ZoomButtons_button"
data-state="closed"
data-testid="map-zoom-in-button"
role="button"
tabindex="0"
title="Zoom in"
>
<div
class="mx_ZoomButtons_icon"
/>
</div>
<div
aria-label="Zoom out"
class="mx_AccessibleButton mx_ZoomButtons_button"
data-state="closed"
data-testid="map-zoom-out-button"
role="button"
tabindex="0"
title="Zoom out"
>
<div
class="mx_ZoomButtons_icon"

View File

@@ -6,22 +6,24 @@ exports[`<ZoomButtons /> renders buttons 1`] = `
class="mx_ZoomButtons"
>
<div
aria-label="Zoom in"
class="mx_AccessibleButton mx_ZoomButtons_button"
data-state="closed"
data-testid="map-zoom-in-button"
role="button"
tabindex="0"
title="Zoom in"
>
<div
class="mx_ZoomButtons_icon"
/>
</div>
<div
aria-label="Zoom out"
class="mx_AccessibleButton mx_ZoomButtons_button"
data-state="closed"
data-testid="map-zoom-out-button"
role="button"
tabindex="0"
title="Zoom out"
>
<div
class="mx_ZoomButtons_icon"

View File

@@ -17,13 +17,20 @@
import React from "react";
import { render } from "@testing-library/react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { mkDecryptionFailureMatrixEvent } from "matrix-js-sdk/src/testing";
import { DecryptionFailureCode } from "matrix-js-sdk/src/crypto-api";
import { mkEvent } from "../../../test-utils";
import { DecryptionFailureBody } from "../../../../src/components/views/messages/DecryptionFailureBody";
import { LocalDeviceVerificationStateContext } from "../../../../src/contexts/LocalDeviceVerificationStateContext";
describe("DecryptionFailureBody", () => {
function customRender(event: MatrixEvent) {
return render(<DecryptionFailureBody mxEvent={event} />);
function customRender(event: MatrixEvent, localDeviceVerified: boolean = false) {
return render(
<LocalDeviceVerificationStateContext.Provider value={localDeviceVerified}>
<DecryptionFailureBody mxEvent={event} />
</LocalDeviceVerificationStateContext.Provider>,
);
}
it(`Should display "Unable to decrypt message"`, () => {
@@ -60,4 +67,51 @@ describe("DecryptionFailureBody", () => {
// Then
expect(container).toMatchSnapshot();
});
it("should handle historical messages with no key backup", async () => {
// When
const event = await mkDecryptionFailureMatrixEvent({
code: DecryptionFailureCode.HISTORICAL_MESSAGE_NO_KEY_BACKUP,
msg: "No backup",
roomId: "fakeroom",
sender: "fakesender",
});
const { container } = customRender(event);
// Then
expect(container).toHaveTextContent("Historical messages are not available on this device");
});
it.each([true, false])(
"should handle historical messages when there is a backup and device verification is %s",
async (verified) => {
// When
const event = await mkDecryptionFailureMatrixEvent({
code: DecryptionFailureCode.HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED,
msg: "Failure",
roomId: "fakeroom",
sender: "fakesender",
});
const { container } = customRender(event, verified);
// Then
expect(container).toHaveTextContent(
verified ? "Unable to decrypt" : "You need to verify this device for access to historical messages",
);
},
);
it("should handle undecryptable pre-join messages", async () => {
// When
const event = await mkDecryptionFailureMatrixEvent({
code: DecryptionFailureCode.HISTORICAL_MESSAGE_USER_NOT_JOINED,
msg: "Not joined",
roomId: "fakeroom",
sender: "fakesender",
});
const { container } = customRender(event);
// Then
expect(container).toHaveTextContent("You don't have access to this message");
});
});

View File

@@ -293,7 +293,8 @@ describe("<UserInfo />", () => {
it("renders close button correctly when encryption panel with a pending verification request", () => {
renderComponent({ phase: RightPanelPhases.EncryptionPanel, verificationRequest });
expect(screen.getByTestId("base-card-close-button")).toHaveAttribute("title", "Cancel");
screen.getByTestId("base-card-close-button").focus();
expect(screen.getByRole("tooltip")).toHaveTextContent("Cancel");
});
});
@@ -387,11 +388,8 @@ describe("<UserInfo />", () => {
// click it
await userEvent.click(devicesButton);
// there should now be a button with the device id ...
const deviceButton = screen.getByRole("button", { description: "d1" });
// ... which should contain the device name
expect(within(deviceButton).getByText("my device")).toBeInTheDocument();
// there should now be a button with the device id which should contain the device name
expect(screen.getByRole("button", { name: "my device" })).toBeInTheDocument();
});
it("renders <BasicUserInfo />", async () => {
@@ -444,10 +442,10 @@ describe("<UserInfo />", () => {
});
// there should now be a button with the non-dehydrated device ID
expect(screen.getByRole("button", { description: "d1" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "my device" })).toBeInTheDocument();
// but not for the dehydrated device ID
expect(screen.queryByRole("button", { description: "d2" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "dehydrated device" })).not.toBeInTheDocument();
// there should be a line saying that the user has "Offline device" enabled
expect(screen.getByText("Offline device enabled")).toBeInTheDocument();
@@ -530,7 +528,7 @@ describe("<UserInfo />", () => {
// the dehydrated device should be shown as an unverified device, which means
// there should now be a button with the device id ...
const deviceButton = screen.getByRole("button", { description: "d2" });
const deviceButton = screen.getByRole("button", { name: "dehydrated device" });
// ... which should contain the device name
expect(within(deviceButton).getByText("dehydrated device")).toBeInTheDocument();
@@ -572,13 +570,15 @@ describe("<UserInfo />", () => {
});
// the dehydrated devices should be shown as an unverified device, which means
// there should now be a button with the first dehydrated device id ...
const device1Button = screen.getByRole("button", { description: "d1" });
// there should now be a button with the first dehydrated device...
const device1Button = screen.getByRole("button", { name: "dehydrated device 1" });
expect(device1Button).toBeVisible();
// ... which should contain the device name
expect(within(device1Button).getByText("dehydrated device 1")).toBeInTheDocument();
// and a button with the second dehydrated device id ...
const device2Button = screen.getByRole("button", { description: "d2" });
// and a button with the second dehydrated device...
const device2Button = screen.getByRole("button", { name: "dehydrated device 2" });
expect(device2Button).toBeVisible();
// ... which should contain the device name
expect(within(device2Button).getByText("dehydrated device 2")).toBeInTheDocument();
@@ -707,7 +707,8 @@ describe("<DeviceItem />", () => {
renderComponent({ isUserVerified: true });
await act(flushPromises);
expect(screen.getByRole("button", { name: `${device.displayName} Not trusted` })).toBeInTheDocument();
const button = screen.getByRole("button", { name: device.displayName });
expect(button).toHaveTextContent(`${device.displayName}Not trusted`);
});
it("with verified device only, displays no button without a label", async () => {

View File

@@ -25,11 +25,12 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
/>
</button>
<div
aria-label="Close"
class="mx_AccessibleButton mx_BaseCard_close"
data-state="closed"
data-testid="base-card-close-button"
role="button"
tabindex="0"
title="Close"
/>
</header>
<header

View File

@@ -3,10 +3,11 @@
exports[`<DeviceItem /> ambiguous display name 1`] = `
<div>
<div
aria-label="my display name (deviceId)"
class="mx_AccessibleButton mx_UserInfo_device mx_UserInfo_device_unverified"
data-state="closed"
role="button"
tabindex="0"
title="deviceId"
>
<div
class="mx_E2EIcon mx_E2EIcon_normal"
@@ -26,10 +27,11 @@ exports[`<DeviceItem /> ambiguous display name 1`] = `
exports[`<DeviceItem /> with display name 1`] = `
<div>
<div
aria-label="deviceName"
class="mx_AccessibleButton mx_UserInfo_device mx_UserInfo_device_unverified"
data-state="closed"
role="button"
tabindex="0"
title="deviceId"
>
<div
class="mx_E2EIcon mx_E2EIcon_normal"
@@ -49,10 +51,11 @@ exports[`<DeviceItem /> with display name 1`] = `
exports[`<DeviceItem /> without display name 1`] = `
<div>
<div
aria-label="deviceId"
class="mx_AccessibleButton mx_UserInfo_device mx_UserInfo_device_unverified"
data-state="closed"
role="button"
tabindex="0"
title="deviceId"
>
<div
class="mx_E2EIcon mx_E2EIcon_normal"
@@ -78,11 +81,12 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
class="mx_BaseCard_header"
>
<div
aria-label="Close"
class="mx_AccessibleButton mx_BaseCard_close"
data-state="closed"
data-testid="base-card-close-button"
role="button"
tabindex="0"
title="Close"
/>
<div
class="mx_BaseCard_headerProp"

View File

@@ -11,6 +11,7 @@ exports[`EventTileThreadToolbar renders 1`] = `
<div
aria-label="View in room"
class="mx_AccessibleButton mx_MessageActionBar_iconButton"
data-state="closed"
role="button"
tabindex="0"
>
@@ -19,6 +20,7 @@ exports[`EventTileThreadToolbar renders 1`] = `
<div
aria-label="Copy link to thread"
class="mx_AccessibleButton mx_MessageActionBar_iconButton"
data-state="closed"
role="button"
tabindex="-1"
>

View File

@@ -92,7 +92,7 @@ describe("MemberList", () => {
let prevMember: RoomMember | undefined;
for (const tile of memberTiles) {
const memberA = prevMember;
const memberB = memberListRoom.currentState.members[tile.getAttribute("title")!.split(" ")[0]];
const memberB = memberListRoom.currentState.members[tile.getAttribute("aria-label")!.split(" ")[0]];
prevMember = memberB; // just in case an expect fails, set this early
if (!memberA) {
continue;

View File

@@ -4,10 +4,11 @@ exports[`MemberTile should display an verified E2EIcon when the e2E status = Ver
<div>
<div>
<div
aria-label="@userId:matrix.org (power 0)"
class="mx_AccessibleButton mx_EntityTile mx_EntityTile_offline_neveractive"
data-state="closed"
role="button"
tabindex="0"
title="@userId:matrix.org (power 0)"
>
<div
class="mx_EntityTile_avatar"
@@ -57,10 +58,11 @@ exports[`MemberTile should display an warning E2EIcon when the e2E status = Warn
<div>
<div>
<div
aria-label="@userId:matrix.org (power 0)"
class="mx_AccessibleButton mx_EntityTile mx_EntityTile_offline_neveractive"
data-state="closed"
role="button"
tabindex="0"
title="@userId:matrix.org (power 0)"
>
<div
class="mx_EntityTile_avatar"
@@ -110,10 +112,11 @@ exports[`MemberTile should not display an E2EIcon when the e2E status = normal 1
<div>
<div>
<div
aria-label="@userId:matrix.org (power 0)"
class="mx_AccessibleButton mx_EntityTile mx_EntityTile_offline_neveractive"
data-state="closed"
role="button"
tabindex="0"
title="@userId:matrix.org (power 0)"
>
<div
class="mx_EntityTile_avatar"

View File

@@ -20,7 +20,8 @@ import React from "react";
import { MSC3906Rendezvous, RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous";
import { HTTPError, LoginTokenPostResponse } from "matrix-js-sdk/src/matrix";
import LoginWithQR, { Click, Mode, Phase } from "../../../../../src/components/views/auth/LoginWithQR";
import LoginWithQR from "../../../../../src/components/views/auth/LoginWithQR";
import { Click, Mode, Phase } from "../../../../../src/components/views/auth/LoginWithQR-types";
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
jest.mock("matrix-js-sdk/src/rendezvous");

View File

@@ -19,12 +19,8 @@ import React from "react";
import { RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous";
import LoginWithQRFlow from "../../../../../src/components/views/auth/LoginWithQRFlow";
import {
Click,
Phase,
LoginWithQRFailureReason,
FailureReason,
} from "../../../../../src/components/views/auth/LoginWithQR";
import { LoginWithQRFailureReason, FailureReason } from "../../../../../src/components/views/auth/LoginWithQR";
import { Click, Phase } from "../../../../../src/components/views/auth/LoginWithQR-types";
describe("<LoginWithQRFlow />", () => {
const onClick = jest.fn();

View File

@@ -139,7 +139,9 @@ exports[`<CurrentDeviceSection /> handles when device is falsy 1`] = `
aria-disabled="true"
aria-expanded="false"
aria-haspopup="true"
aria-label="Options"
class="mx_AccessibleButton mx_AccessibleButton_disabled"
data-state="closed"
data-testid="current-session-menu"
disabled=""
role="button"
@@ -174,7 +176,9 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
<div
aria-expanded="false"
aria-haspopup="true"
aria-label="Options"
class="mx_AccessibleButton"
data-state="closed"
data-testid="current-session-menu"
role="button"
tabindex="0"
@@ -317,7 +321,9 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
<div
aria-expanded="false"
aria-haspopup="true"
aria-label="Options"
class="mx_AccessibleButton"
data-state="closed"
data-testid="current-session-menu"
role="button"
tabindex="0"

View File

@@ -3,19 +3,28 @@
exports[`<LoginWithQRFlow /> errors renders data_mismatch 1`] = `
<div>
<div
class="mx_LoginWithQR"
class="mx_LoginWithQR mx_LoginWithQR_error"
data-testid="login-with-qr"
>
<div
class="mx_LoginWithQR_centreTitle"
/>
<div
class="mx_LoginWithQR_main"
>
<div
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
>
<div
width="32px"
/>
</div>
<h1
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Something went wrong!
</h1>
<p
data-testid="cancellation-message"
>
The request was cancelled.
An unexpected error occurred. The request to connect your other device has been cancelled.
</p>
</div>
<div
@@ -45,19 +54,28 @@ exports[`<LoginWithQRFlow /> errors renders data_mismatch 1`] = `
exports[`<LoginWithQRFlow /> errors renders expired 1`] = `
<div>
<div
class="mx_LoginWithQR"
class="mx_LoginWithQR mx_LoginWithQR_error"
data-testid="login-with-qr"
>
<div
class="mx_LoginWithQR_centreTitle"
/>
<div
class="mx_LoginWithQR_main"
>
<div
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
>
<div
width="32px"
/>
</div>
<h1
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
The sign in was not completed in time
</h1>
<p
data-testid="cancellation-message"
>
The linking wasn't completed in the required time.
Sign in expired. Please try again.
</p>
</div>
<div
@@ -87,19 +105,28 @@ exports[`<LoginWithQRFlow /> errors renders expired 1`] = `
exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 1`] = `
<div>
<div
class="mx_LoginWithQR"
class="mx_LoginWithQR mx_LoginWithQR_error"
data-testid="login-with-qr"
>
<div
class="mx_LoginWithQR_centreTitle"
/>
<div
class="mx_LoginWithQR_main"
>
<div
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
>
<div
width="32px"
/>
</div>
<h1
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Other device not compatible
</h1>
<p
data-testid="cancellation-message"
>
The homeserver doesn't support signing in another device.
This device does not support signing in to the other device with a QR code.
</p>
</div>
<div
@@ -129,20 +156,42 @@ exports[`<LoginWithQRFlow /> errors renders homeserver_lacks_support 1`] = `
exports[`<LoginWithQRFlow /> errors renders invalid_code 1`] = `
<div>
<div
class="mx_LoginWithQR"
class="mx_LoginWithQR mx_LoginWithQR_error"
data-testid="login-with-qr"
>
<div
class="mx_LoginWithQR_centreTitle"
/>
<div
class="mx_LoginWithQR_main"
>
<p
<div
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
>
<div
width="32px"
/>
</div>
<h1
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Connection not secure
</h1>
A secure connection could not be made to the new device. Your existing devices are still safe and you don't need to worry about them.
<h2
class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83"
data-testid="cancellation-message"
>
The scanned code is invalid.
</p>
Now what?
</h2>
<ol>
<li>
Try signing in to the other device again with a QR code in case this was a network problem
</li>
<li>
If you encounter the same problem, try a different wifi network or use your mobile data instead of wifi
</li>
<li>
If that doesn't work, sign in manually
</li>
</ol>
</div>
<div
class="mx_LoginWithQR_buttons"
@@ -171,19 +220,28 @@ exports[`<LoginWithQRFlow /> errors renders invalid_code 1`] = `
exports[`<LoginWithQRFlow /> errors renders other_device_already_signed_in 1`] = `
<div>
<div
class="mx_LoginWithQR"
class="mx_LoginWithQR mx_LoginWithQR_error"
data-testid="login-with-qr"
>
<div
class="mx_LoginWithQR_centreTitle"
/>
<div
class="mx_LoginWithQR_main"
>
<div
class="mx_LoginWithQR_icon"
>
<div
width="32px"
/>
</div>
<h1
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Your other device is already signed in
</h1>
<p
data-testid="cancellation-message"
>
The other device is already signed in.
You dont need to do anything else.
</p>
</div>
<div
@@ -213,19 +271,28 @@ exports[`<LoginWithQRFlow /> errors renders other_device_already_signed_in 1`] =
exports[`<LoginWithQRFlow /> errors renders other_device_not_signed_in 1`] = `
<div>
<div
class="mx_LoginWithQR"
class="mx_LoginWithQR mx_LoginWithQR_error"
data-testid="login-with-qr"
>
<div
class="mx_LoginWithQR_centreTitle"
/>
<div
class="mx_LoginWithQR_main"
>
<div
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
>
<div
width="32px"
/>
</div>
<h1
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Something went wrong!
</h1>
<p
data-testid="cancellation-message"
>
The other device isn't signed in.
An unexpected error occurred. The request to connect your other device has been cancelled.
</p>
</div>
<div
@@ -255,15 +322,24 @@ exports[`<LoginWithQRFlow /> errors renders other_device_not_signed_in 1`] = `
exports[`<LoginWithQRFlow /> errors renders rate_limited 1`] = `
<div>
<div
class="mx_LoginWithQR"
class="mx_LoginWithQR mx_LoginWithQR_error"
data-testid="login-with-qr"
>
<div
class="mx_LoginWithQR_centreTitle"
/>
<div
class="mx_LoginWithQR_main"
>
<div
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
>
<div
width="32px"
/>
</div>
<h1
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Something went wrong!
</h1>
<p
data-testid="cancellation-message"
>
@@ -297,19 +373,28 @@ exports[`<LoginWithQRFlow /> errors renders rate_limited 1`] = `
exports[`<LoginWithQRFlow /> errors renders unknown 1`] = `
<div>
<div
class="mx_LoginWithQR"
class="mx_LoginWithQR mx_LoginWithQR_error"
data-testid="login-with-qr"
>
<div
class="mx_LoginWithQR_centreTitle"
/>
<div
class="mx_LoginWithQR_main"
>
<div
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
>
<div
width="32px"
/>
</div>
<h1
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Something went wrong!
</h1>
<p
data-testid="cancellation-message"
>
An unexpected error occurred.
An unexpected error occurred. The request to connect your other device has been cancelled.
</p>
</div>
<div
@@ -339,19 +424,28 @@ exports[`<LoginWithQRFlow /> errors renders unknown 1`] = `
exports[`<LoginWithQRFlow /> errors renders unsupported_algorithm 1`] = `
<div>
<div
class="mx_LoginWithQR"
class="mx_LoginWithQR mx_LoginWithQR_error"
data-testid="login-with-qr"
>
<div
class="mx_LoginWithQR_centreTitle"
/>
<div
class="mx_LoginWithQR_main"
>
<div
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
>
<div
width="32px"
/>
</div>
<h1
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Other device not compatible
</h1>
<p
data-testid="cancellation-message"
>
Linking with this device is not supported.
This device does not support signing in to the other device with a QR code.
</p>
</div>
<div
@@ -381,19 +475,28 @@ exports[`<LoginWithQRFlow /> errors renders unsupported_algorithm 1`] = `
exports[`<LoginWithQRFlow /> errors renders unsupported_transport 1`] = `
<div>
<div
class="mx_LoginWithQR"
class="mx_LoginWithQR mx_LoginWithQR_error"
data-testid="login-with-qr"
>
<div
class="mx_LoginWithQR_centreTitle"
/>
<div
class="mx_LoginWithQR_main"
>
<div
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
>
<div
width="32px"
/>
</div>
<h1
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Other device not compatible
</h1>
<p
data-testid="cancellation-message"
>
The request was cancelled.
This device does not support signing in to the other device with a QR code.
</p>
</div>
<div
@@ -423,19 +526,28 @@ exports[`<LoginWithQRFlow /> errors renders unsupported_transport 1`] = `
exports[`<LoginWithQRFlow /> errors renders user_cancelled 1`] = `
<div>
<div
class="mx_LoginWithQR"
class="mx_LoginWithQR mx_LoginWithQR_error"
data-testid="login-with-qr"
>
<div
class="mx_LoginWithQR_centreTitle"
/>
<div
class="mx_LoginWithQR_main"
>
<div
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
>
<div
width="32px"
/>
</div>
<h1
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Sign in request cancelled
</h1>
<p
data-testid="cancellation-message"
>
The request was cancelled.
The sign in was cancelled on the other device.
</p>
</div>
<div
@@ -465,19 +577,28 @@ exports[`<LoginWithQRFlow /> errors renders user_cancelled 1`] = `
exports[`<LoginWithQRFlow /> errors renders user_declined 1`] = `
<div>
<div
class="mx_LoginWithQR"
class="mx_LoginWithQR mx_LoginWithQR_error"
data-testid="login-with-qr"
>
<div
class="mx_LoginWithQR_centreTitle"
/>
<div
class="mx_LoginWithQR_main"
>
<div
class="mx_LoginWithQR_icon mx_LoginWithQR_icon--critical"
>
<div
width="32px"
/>
</div>
<h1
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Sign in declined
</h1>
<p
data-testid="cancellation-message"
>
The request was declined on the other device.
You declined the request from your other device to sign in.
</p>
</div>
<div
@@ -511,33 +632,32 @@ exports[`<LoginWithQRFlow /> renders QR code 1`] = `
data-testid="login-with-qr"
>
<div
class=""
class="mx_LoginWithQR_heading"
>
<div
class="mx_LoginWithQR_heading"
aria-label="Back"
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
data-state="closed"
data-testid="back-button"
role="button"
tabindex="0"
>
<div
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
data-testid="back-button"
role="button"
tabindex="0"
title="Back"
>
<div />
</div>
<div
class="mx_LoginWithQR_breadcrumbs"
>
Sessions
/
Link new device
</div>
<div />
</div>
<div
class="mx_LoginWithQR_breadcrumbs"
>
Sessions
/
Link new device
</div>
</div>
<div
class="mx_LoginWithQR_main"
>
<h1>
<h1
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Scan the QR code with another device
</h1>
<div
@@ -561,7 +681,7 @@ exports[`<LoginWithQRFlow /> renders QR code 1`] = `
<span>
Select "
<b>
Scan QR code
Sign in with QR code
</b>
"
</span>
@@ -570,7 +690,7 @@ exports[`<LoginWithQRFlow /> renders QR code 1`] = `
Point the camera at the QR code shown here
</li>
<li>
Follow the remaining instructions to verify your other device
Follow the instructions to link your other device
</li>
</ol>
</div>
@@ -587,9 +707,6 @@ exports[`<LoginWithQRFlow /> renders code when connected 1`] = `
class="mx_LoginWithQR"
data-testid="login-with-qr"
>
<div
class=""
/>
<div
class="mx_LoginWithQR_main"
>
@@ -615,14 +732,6 @@ exports[`<LoginWithQRFlow /> renders code when connected 1`] = `
<div
class="mx_LoginWithQR_buttons"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
data-testid="decline-login-button"
role="button"
tabindex="0"
>
Cancel
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="approve-login-button"
@@ -631,6 +740,14 @@ exports[`<LoginWithQRFlow /> renders code when connected 1`] = `
>
Approve
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
data-testid="decline-login-button"
role="button"
tabindex="0"
>
Cancel
</div>
</div>
</div>
</div>
@@ -643,27 +760,24 @@ exports[`<LoginWithQRFlow /> renders spinner while connecting 1`] = `
data-testid="login-with-qr"
>
<div
class=""
class="mx_LoginWithQR_heading"
>
<div
class="mx_LoginWithQR_heading"
aria-label="Back"
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
data-state="closed"
data-testid="back-button"
role="button"
tabindex="0"
>
<div
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
data-testid="back-button"
role="button"
tabindex="0"
title="Back"
>
<div />
</div>
<div
class="mx_LoginWithQR_breadcrumbs"
>
Sessions
/
Link new device
</div>
<div />
</div>
<div
class="mx_LoginWithQR_breadcrumbs"
>
Sessions
/
Link new device
</div>
</div>
<div
@@ -713,27 +827,24 @@ exports[`<LoginWithQRFlow /> renders spinner while loading 1`] = `
data-testid="login-with-qr"
>
<div
class=""
class="mx_LoginWithQR_heading"
>
<div
class="mx_LoginWithQR_heading"
aria-label="Back"
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
data-state="closed"
data-testid="back-button"
role="button"
tabindex="0"
>
<div
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
data-testid="back-button"
role="button"
tabindex="0"
title="Back"
>
<div />
</div>
<div
class="mx_LoginWithQR_breadcrumbs"
>
Sessions
/
Link new device
</div>
<div />
</div>
<div
class="mx_LoginWithQR_breadcrumbs"
>
Sessions
/
Link new device
</div>
</div>
<div
@@ -771,27 +882,24 @@ exports[`<LoginWithQRFlow /> renders spinner while signing in 1`] = `
data-testid="login-with-qr"
>
<div
class=""
class="mx_LoginWithQR_heading"
>
<div
class="mx_LoginWithQR_heading"
aria-label="Back"
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
data-state="closed"
data-testid="back-button"
role="button"
tabindex="0"
>
<div
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
data-testid="back-button"
role="button"
tabindex="0"
title="Back"
>
<div />
</div>
<div
class="mx_LoginWithQR_breadcrumbs"
>
Sessions
/
Link new device
</div>
<div />
</div>
<div
class="mx_LoginWithQR_breadcrumbs"
>
Sessions
/
Link new device
</div>
</div>
<div
@@ -841,27 +949,24 @@ exports[`<LoginWithQRFlow /> renders spinner while verifying 1`] = `
data-testid="login-with-qr"
>
<div
class="mx_LoginWithQR_centreTitle"
class="mx_LoginWithQR_heading"
>
<div
class="mx_LoginWithQR_heading"
aria-label="Back"
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
data-state="closed"
data-testid="back-button"
role="button"
tabindex="0"
>
<div
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
data-testid="back-button"
role="button"
tabindex="0"
title="Back"
>
<div />
</div>
<div
class="mx_LoginWithQR_breadcrumbs"
>
Sessions
/
Link new device
</div>
<div />
</div>
<div
class="mx_LoginWithQR_breadcrumbs"
>
Sessions
/
Link new device
</div>
</div>
<div
@@ -902,27 +1007,24 @@ exports[`<LoginWithQRFlow /> renders spinner whilst QR generating 1`] = `
data-testid="login-with-qr"
>
<div
class=""
class="mx_LoginWithQR_heading"
>
<div
class="mx_LoginWithQR_heading"
aria-label="Back"
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
data-state="closed"
data-testid="back-button"
role="button"
tabindex="0"
>
<div
class="mx_AccessibleButton mx_LoginWithQR_BackButton"
data-testid="back-button"
role="button"
tabindex="0"
title="Back"
>
<div />
</div>
<div
class="mx_LoginWithQR_breadcrumbs"
>
Sessions
/
Link new device
</div>
<div />
</div>
<div
class="mx_LoginWithQR_breadcrumbs"
>
Sessions
/
Link new device
</div>
</div>
<div

View File

@@ -45,6 +45,7 @@ exports[`AdvancedRoomSettingsTab should render as expected 1`] = `
<div
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
data-state="closed"
role="button"
tabindex="0"
/>

View File

@@ -68,10 +68,11 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests fully 1`]
</div>
</div>
<div
aria-label="Deny"
class="mx_AccessibleButton mx_PeopleRoomSettingsTab_action mx_AccessibleButton_hasKind mx_AccessibleButton_kind_icon_primary_outline"
data-state="closed"
role="button"
tabindex="0"
title="Deny"
>
<div
height="18"
@@ -79,10 +80,11 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests fully 1`]
/>
</div>
<div
aria-label="Approve"
class="mx_AccessibleButton mx_PeopleRoomSettingsTab_action mx_AccessibleButton_hasKind mx_AccessibleButton_kind_icon_primary"
data-state="closed"
role="button"
tabindex="0"
title="Approve"
>
<div
height="18"
@@ -135,10 +137,11 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests reduced 1`
</span>
</div>
<div
aria-label="Deny"
class="mx_AccessibleButton mx_PeopleRoomSettingsTab_action mx_AccessibleButton_hasKind mx_AccessibleButton_kind_icon_primary_outline"
data-state="closed"
role="button"
tabindex="0"
title="Deny"
>
<div
height="18"
@@ -146,10 +149,11 @@ exports[`PeopleRoomSettingsTab with requests to join renders requests reduced 1`
/>
</div>
<div
aria-label="Approve"
class="mx_AccessibleButton mx_PeopleRoomSettingsTab_action mx_AccessibleButton_hasKind mx_AccessibleButton_kind_icon_primary"
data-state="closed"
role="button"
tabindex="0"
title="Approve"
>
<div
height="18"

View File

@@ -15,7 +15,8 @@ limitations under the License.
*/
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import LabsUserSettingsTab from "../../../../../../src/components/views/settings/tabs/user/LabsUserSettingsTab";
import SettingsStore from "../../../../../../src/settings/SettingsStore";
@@ -60,7 +61,7 @@ describe("<LabsUserSettingsTab />", () => {
// non-beta labs section
expect(screen.getByText("Early previews")).toBeInTheDocument();
const labsSections = container.getElementsByClassName("mx_SettingsSubsection");
expect(labsSections).toHaveLength(11);
expect(labsSections).toHaveLength(10);
});
describe("Rust crypto setting", () => {
@@ -113,12 +114,14 @@ describe("<LabsUserSettingsTab />", () => {
expect(toggle.getAttribute("aria-checked")).toEqual("true");
// Hover over the toggle to make it show the tooltip
fireEvent.mouseOver(toggle);
await userEvent.hover(toggle);
const tooltip = rendered.getByRole("tooltip");
expect(tooltip).toHaveTextContent(
"Once enabled, Rust cryptography can only be disabled by logging out and in again",
);
await waitFor(() => {
const tooltip = screen.getByRole("tooltip");
expect(tooltip).toHaveTextContent(
"Once enabled, Rust cryptography can only be disabled by logging out and in again",
);
});
});
});
@@ -150,12 +153,14 @@ describe("<LabsUserSettingsTab />", () => {
expect(toggle.getAttribute("aria-checked")).toEqual("true");
// Hover over the toggle to make it show the tooltip
fireEvent.mouseOver(toggle);
await userEvent.hover(toggle);
const tooltip = rendered.getByRole("tooltip");
expect(tooltip).toHaveTextContent(
"Rust cryptography cannot be disabled on this deployment of BrandedClient",
);
await waitFor(() => {
const tooltip = rendered.getByRole("tooltip");
expect(tooltip).toHaveTextContent(
"Rust cryptography cannot be disabled on this deployment of BrandedClient",
);
});
});
});
});

View File

@@ -289,6 +289,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
aria-disabled="true"
aria-label="Send read receipts"
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
data-state="closed"
id="mx_SettingsFlag_GQvdMWe954DV"
role="switch"
tabindex="0"

View File

@@ -84,7 +84,9 @@ exports[`<SessionManagerTab /> current session section renders current session s
<div
aria-expanded="false"
aria-haspopup="true"
aria-label="Options"
class="mx_AccessibleButton"
data-state="closed"
data-testid="current-session-menu"
role="button"
tabindex="0"
@@ -213,7 +215,9 @@ exports[`<SessionManagerTab /> current session section renders current session s
<div
aria-expanded="false"
aria-haspopup="true"
aria-label="Options"
class="mx_AccessibleButton"
data-state="closed"
data-testid="current-session-menu"
role="button"
tabindex="0"

View File

@@ -16,7 +16,6 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
class="mx_AccessibleButton mx_UserMenu_contextMenuButton"
role="button"
tabindex="0"
title="User menu"
>
<div
class="mx_UserMenu_userAvatar"
@@ -226,6 +225,48 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
</div>
</li>
</ul>
<div
class="mx_ThreadsActivityCentre_container"
>
<button
aria-controls="floating-ui-7"
aria-expanded="true"
aria-haspopup="dialog"
aria-label="Threads"
class="_icon-button_16nk7_17 mx_ThreadsActivityCentreButton"
data-state="closed"
role="button"
style="--cpd-icon-button-size: 32px;"
tabindex="0"
>
<div
class="_indicator-icon_133tf_26"
style="--cpd-icon-button-size: 100%;"
>
<div
class="mx_ThreadsActivityCentreButton_Icon"
/>
</div>
</button>
<span
data-floating-ui-focus-guard=""
data-type="outside"
role="button"
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
tabindex="0"
/>
<span
aria-owns="undefined"
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
/>
<span
data-floating-ui-focus-guard=""
data-type="outside"
role="button"
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
tabindex="0"
/>
</div>
<div
aria-expanded="false"
aria-label="Quick settings"

View File

@@ -0,0 +1,35 @@
/*
Copyright 2024 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import PosthogTrackers from "../../../src/PosthogTrackers";
import AnalyticsController from "../../../src/settings/controllers/AnalyticsController";
import { SettingLevel } from "../../../src/settings/SettingLevel";
describe("AnalyticsController", () => {
afterEach(() => {
jest.restoreAllMocks();
});
it("Tracks a Posthog interaction on change", () => {
const trackInteractionSpy = jest.spyOn(PosthogTrackers, "trackInteraction");
const controller = new AnalyticsController("WebSettingsNotificationsTACOnlyNotificationsToggle");
controller.onChange(SettingLevel.DEVICE, null, false);
expect(trackInteractionSpy).toHaveBeenCalledWith("WebSettingsNotificationsTACOnlyNotificationsToggle");
});
});

View File

@@ -15,7 +15,16 @@ limitations under the License.
*/
import { Mocked, mocked } from "jest-mock";
import { EventTimeline, EventType, MatrixClient, MatrixEvent, RelationType, Room } from "matrix-js-sdk/src/matrix";
import {
EventStatus,
EventTimeline,
EventType,
MatrixClient,
MatrixEvent,
PendingEventOrdering,
RelationType,
Room,
} from "matrix-js-sdk/src/matrix";
import { MessagePreviewStore } from "../../../src/stores/room-list/MessagePreviewStore";
import { mkEvent, mkMessage, mkReaction, setupAsyncStoreWithClient, stubClient } from "../../test-utils";
@@ -25,6 +34,7 @@ import { mkThread } from "../../test-utils/threads";
describe("MessagePreviewStore", () => {
let client: Mocked<MatrixClient>;
let room: Room;
let nonRenderedRoom: Room;
let store: MessagePreviewStore;
async function addEvent(
@@ -46,9 +56,35 @@ describe("MessagePreviewStore", () => {
}
}
async function addPendingEvent(
store: MessagePreviewStore,
room: Room,
event: MatrixEvent,
fireAction = true,
): Promise<void> {
room.addPendingEvent(event, "txid");
if (fireAction) {
// @ts-ignore private access
await store.onLocalEchoUpdated(event, room);
}
}
async function updatePendingEvent(event: MatrixEvent, eventStatus: EventStatus, fireAction = true): Promise<void> {
room.updatePendingEvent(event, eventStatus);
if (fireAction) {
// @ts-ignore private access
await store.onLocalEchoUpdated(event, room);
}
}
beforeEach(async () => {
client = mocked(stubClient());
room = new Room("!roomId:server", client, client.getSafeUserId());
room = new Room("!roomId:server", client, client.getSafeUserId(), {
pendingEventOrdering: PendingEventOrdering.Detached,
});
nonRenderedRoom = new Room("!roomId2:server", client, client.getSafeUserId(), {
pendingEventOrdering: PendingEventOrdering.Detached,
});
mocked(client.getRoom).mockReturnValue(room);
store = MessagePreviewStore.testInstance();
@@ -286,4 +322,63 @@ describe("MessagePreviewStore", () => {
expect(preview?.isThreadReply).toBe(false);
expect(preview?.text).toContain("You reacted 🙃 to root event message");
});
it("should handle local echos correctly", async () => {
const firstMessage = mkMessage({
user: "@sender:server",
event: true,
room: room.roomId,
msg: "First message",
});
await addEvent(store, room, firstMessage);
expect((await store.getPreviewForRoom(room, DefaultTagID.Untagged))?.text).toMatchInlineSnapshot(
`"@sender:server: First message"`,
);
const secondMessage = mkMessage({
user: "@sender:server",
event: true,
room: room.roomId,
msg: "Second message",
});
secondMessage.status = EventStatus.NOT_SENT;
await addPendingEvent(store, room, secondMessage);
expect((await store.getPreviewForRoom(room, DefaultTagID.Untagged))?.text).toMatchInlineSnapshot(
`"@sender:server: Second message"`,
);
await updatePendingEvent(secondMessage, EventStatus.CANCELLED);
expect((await store.getPreviewForRoom(room, DefaultTagID.Untagged))?.text).toMatchInlineSnapshot(
`"@sender:server: First message"`,
);
});
it("should not generate previews for rooms not rendered", async () => {
const firstMessage = mkMessage({
user: "@sender:server",
event: true,
room: nonRenderedRoom.roomId,
msg: "First message",
});
await addEvent(store, room, firstMessage);
const secondMessage = mkMessage({
user: "@sender:server",
event: true,
room: nonRenderedRoom.roomId,
msg: "Second message",
});
secondMessage.status = EventStatus.NOT_SENT;
await addPendingEvent(store, room, secondMessage);
// @ts-ignore private access
expect(store.previews.has(nonRenderedRoom.roomId)).toBeFalsy();
});
});

View File

@@ -622,6 +622,7 @@ export function mkStubRoom(
getType: jest.fn().mockReturnValue(undefined),
getUnfilteredTimelineSet: jest.fn(),
getUnreadNotificationCount: jest.fn(() => 0),
getRoomUnreadNotificationCount: jest.fn().mockReturnValue(0),
getVersion: jest.fn().mockReturnValue("1"),
hasMembershipState: () => false,
isElementVideoRoom: jest.fn().mockReturnValue(false),