Merge branch 'develop' into johannes/webpack5-workers

This commit is contained in:
Johannes Marbach 2023-11-13 15:43:50 +01:00
commit ccee4c9cdb
23 changed files with 347 additions and 177 deletions

1
.github/CODEOWNERS vendored
View file

@ -2,3 +2,4 @@
/.github/workflows/** @matrix-org/element-web-app-team /.github/workflows/** @matrix-org/element-web-app-team
/package.json @matrix-org/element-web-app-team /package.json @matrix-org/element-web-app-team
/yarn.lock @matrix-org/element-web-app-team /yarn.lock @matrix-org/element-web-app-team
/src/i18n/strings

1
.github/release-drafter.yml vendored Normal file
View file

@ -0,0 +1 @@
_extends: matrix-org/matrix-js-sdk

View file

@ -78,16 +78,19 @@ jobs:
core.setOutput("author", response.data.author.name); core.setOutput("author", response.data.author.name);
core.setOutput("email", response.data.author.email); core.setOutput("email", response.data.author.email);
# Only run Percy when it is demanded or we are running the daily build # Percy is disabled while we're figuring out https://github.com/vector-im/wat-internal/issues/36
- name: Enable Percy # and https://github.com/vector-im/wat-internal/issues/56. We're hoping to turn it back on or switch
id: percy # to an alternative in the future.
if: | # # Only run Percy when it is demanded or we are running the daily build
github.event.workflow_run.event == 'schedule' || # - name: Enable Percy
( # id: percy
github.event.workflow_run.event == 'merge_group' && # if: |
contains(fromJSON(steps.prdetails.outputs.data).labels.*.name, 'X-Needs-Percy') # github.event.workflow_run.event == 'schedule' ||
) # (
run: echo "value=1" >> $GITHUB_OUTPUT # github.event.workflow_run.event == 'merge_group' &&
# contains(fromJSON(steps.prdetails.outputs.data).labels.*.name, 'X-Needs-Percy')
# )
# run: echo "value=1" >> $GITHUB_OUTPUT
- name: Generate unique ID 💎 - name: Generate unique ID 💎
id: uuid id: uuid

View file

@ -3,8 +3,12 @@
# as an artifact and run integration tests. # as an artifact and run integration tests.
name: Element Web - Build name: Element Web - Build
on: on:
schedule: # We only need the nightly run for Percy which is disabled while we're
- cron: "17 4 * * 1-5" # every weekday at 04:17 UTC # figuring out https://github.com/vector-im/wat-internal/issues/36 and
# https://github.com/vector-im/wat-internal/issues/56. We're hoping to
# turn it back on or switch to an alternative in the future.
# schedule:
# - cron: "17 4 * * 1-5" # every weekday at 04:17 UTC
pull_request: {} pull_request: {}
merge_group: merge_group:
types: [checks_requested] types: [checks_requested]

14
.github/workflows/release-drafter.yml vendored Normal file
View file

@ -0,0 +1,14 @@
name: Release Drafter
on:
push:
branches: [staging]
concurrency: ${{ github.workflow }}
jobs:
draft:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@dabcf3767562210392d862070ed2ef6434b9bc6f # v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
disable-autolabeler: true

13
.github/workflows/release-gitflow.yml vendored Normal file
View file

@ -0,0 +1,13 @@
# Gitflow merge-back master->develop
name: Merge master -> develop
on:
push:
branches: [master]
concurrency: ${{ github.repository }}-${{ github.workflow }}
jobs:
merge:
uses: matrix-org/matrix-js-sdk/.github/workflows/release-gitflow.yml@develop
secrets: inherit
with:
dependencies: |
matrix-js-sdk

View file

@ -1,11 +1,32 @@
name: Release Process name: Release Process
on: on:
release: workflow_dispatch:
types: [published] inputs:
concurrency: ${{ github.workflow }}-${{ github.ref }} mode:
description: What type of release
required: true
default: rc
type: choice
options:
- rc
- final
matrix-js-sdk:
description: JS SDK version to use (current|X.Y.Z)
required: false
default: current
type: string
npm:
description: Publish to npm
required: true
type: boolean
default: true
concurrency: ${{ github.workflow }}
jobs: jobs:
npm: release:
name: Publish uses: matrix-org/matrix-js-sdk/.github/workflows/release-action.yml@develop
uses: matrix-org/matrix-js-sdk/.github/workflows/release-npm.yml@develop secrets: inherit
secrets: with:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} final: ${{ inputs.mode == 'final' }}
npm: ${{ inputs.npm }}
dependencies: |
matrix-js-sdk=${{ inputs.matrix-js-sdk }}

View file

@ -1,3 +1,9 @@
Changes in [3.84.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.84.1) (2023-11-13)
=====================================================================================================
## 🐛 Bug Fixes
* Ensure `setUserCreator` is called when a store is assigned ([\#3867](https://github.com/matrix-org/matrix-js-sdk/pull/3867)). Fixes vector-im/element-web#26520. Contributed by @MidhunSureshR.
Changes in [3.84.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.84.0) (2023-11-07) Changes in [3.84.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.84.0) (2023-11-07)
===================================================================================================== =====================================================================================================

View file

@ -2,7 +2,14 @@
![Tests](https://github.com/matrix-org/matrix-react-sdk/actions/workflows/tests.yml/badge.svg) ![Tests](https://github.com/matrix-org/matrix-react-sdk/actions/workflows/tests.yml/badge.svg)
![Static Analysis](https://github.com/matrix-org/matrix-react-sdk/actions/workflows/static_analysis.yaml/badge.svg) ![Static Analysis](https://github.com/matrix-org/matrix-react-sdk/actions/workflows/static_analysis.yaml/badge.svg)
[![matrix-react-sdk](https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/simple/ppvnzg/develop&style=flat&logo=cypress)](https://dashboard.cypress.io/projects/ppvnzg/runs) [![matrix-react-sdk](https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/simple/ppvnzg/develop&style=flat&logo=cypress)](https://dashboard.cypress.io/projects/ppvnzg/runs)
<!--
Percy is disabled while we're figuring out https://github.com/vector-im/wat-internal/issues/36
and https://github.com/vector-im/wat-internal/issues/56. We're hoping to turn it back on or switch
to an alternative in the future.
[![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](https://percy.io/dfde73bd/matrix-react-sdk) [![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](https://percy.io/dfde73bd/matrix-react-sdk)
-->
[![Localazy](https://img.shields.io/endpoint?url=https%3A%2F%2Fconnect.localazy.com%2Fstatus%2Felement-web%2Fdata%3Fcontent%3Dall%26title%3Dlocalazy%26logo%3Dtrue)](https://localazy.com/p/element-web) [![Localazy](https://img.shields.io/endpoint?url=https%3A%2F%2Fconnect.localazy.com%2Fstatus%2Felement-web%2Fdata%3Fcontent%3Dall%26title%3Dlocalazy%26logo%3Dtrue)](https://localazy.com/p/element-web)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=matrix-react-sdk&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=matrix-react-sdk) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=matrix-react-sdk&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=matrix-react-sdk)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=matrix-react-sdk&metric=coverage)](https://sonarcloud.io/summary/new_code?id=matrix-react-sdk) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=matrix-react-sdk&metric=coverage)](https://sonarcloud.io/summary/new_code?id=matrix-react-sdk)

View file

@ -269,7 +269,7 @@ describe("Cryptography", function () {
// Assert that verified icon is rendered // Assert that verified icon is rendered
cy.findByRole("button", { name: "Room members" }).click(); cy.findByRole("button", { name: "Room members" }).click();
cy.findByRole("button", { name: "Room information" }).click(); cy.findByRole("button", { name: "Room information" }).click();
cy.get(".mx_RoomSummaryCard_e2ee_verified").should("exist"); cy.get('.mx_RoomSummaryCard_badges [data-kind="success"]').should("contain.text", "Encrypted");
// Take a snapshot of RoomSummaryCard with a verified E2EE icon // Take a snapshot of RoomSummaryCard with a verified E2EE icon
cy.get(".mx_RightPanel").percySnapshotElement("RoomSummaryCard - with a verified E2EE icon", { cy.get(".mx_RightPanel").percySnapshotElement("RoomSummaryCard - with a verified E2EE icon", {

View file

@ -134,8 +134,7 @@ describe("Read receipts", () => {
goTo(room1); goTo(room1);
assertStillRead(room2); assertStillRead(room2);
}); });
// XXX: fails because we see a dot instead of an unread number - probably the server and client disagree it("Editing a message after marking as read makes the room unread", () => {
it.skip("Editing a message after marking as read makes the room unread", () => {
// Given the room is marked as read // Given the room is marked as read
goTo(room1); goTo(room1);
receiveMessages(room2, ["Msg1"]); receiveMessages(room2, ["Msg1"]);
@ -146,8 +145,8 @@ describe("Read receipts", () => {
// When a message is edited // When a message is edited
receiveMessages(room2, [editOf("Msg1", "Msg1 Edit1")]); receiveMessages(room2, [editOf("Msg1", "Msg1 Edit1")]);
// Then the room becomes unread // Then the room remains unread
assertUnread(room2, 1); assertStillRead(room2);
}); });
it("Editing a reply after reading it makes the room unread", () => { it("Editing a reply after reading it makes the room unread", () => {
// Given the room is all read // Given the room is all read
@ -178,7 +177,8 @@ describe("Read receipts", () => {
// Then the room remains read // Then the room remains read
assertStillRead(room2); assertStillRead(room2);
}); });
it("A room with an edit is still read after restart", () => { // XXX: fails because flaky: https://github.com/vector-im/element-web/issues/26341
it.skip("A room with an edit is still read after restart", () => {
// Given a message is marked as read // Given a message is marked as read
goTo(room2); goTo(room2);
receiveMessages(room2, ["Msg1"]); receiveMessages(room2, ["Msg1"]);

View file

@ -303,7 +303,8 @@ describe("Read receipts", () => {
assertUnreadThread("Root2"); assertUnreadThread("Root2");
assertUnreadThread("Root3"); assertUnreadThread("Root3");
}); });
it("Looking in thread view to find old threads that were never read makes the room unread", () => { // XXX: fails because flaky: https://github.com/vector-im/element-web/issues/26331
it.skip("Looking in thread view to find old threads that were never read makes the room unread", () => {
// Given lots of messages in threads that are unread // Given lots of messages in threads that are unread
goTo(room1); goTo(room1);
receiveMessages(room2, [ receiveMessages(room2, [

View file

@ -238,6 +238,10 @@ should generally try to adhere to them.
## Screenshot testing with Percy ## Screenshot testing with Percy
**⚠️ Percy is disabled while we're figuring out https://github.com/vector-im/wat-internal/issues/36**
**and https://github.com/vector-im/wat-internal/issues/56. We're hoping to turn it back on or switch**
**to an alternative in the future.**
We also support visual testing via [Percy](https://percy.io). Within many of our We also support visual testing via [Percy](https://percy.io). Within many of our
Cypress tests you can see lines calling `cy.percySnapshot()`. This creates a Cypress tests you can see lines calling `cy.percySnapshot()`. This creates a
screenshot and uses Percy to check whether it has changed from the last time screenshot and uses Percy to check whether it has changed from the last time

View file

@ -1,6 +1,6 @@
{ {
"name": "matrix-react-sdk", "name": "matrix-react-sdk",
"version": "3.84.0", "version": "3.84.1",
"description": "SDK for matrix.org using React", "description": "SDK for matrix.org using React",
"author": "matrix.org", "author": "matrix.org",
"repository": { "repository": {

View file

@ -31,68 +31,12 @@ limitations under the License.
.mx_RoomSummaryCard_roomName { .mx_RoomSummaryCard_roomName {
margin: $spacing-12 0 $spacing-4; margin: $spacing-12 0 $spacing-4;
font-weight: var(--cpd-font-weight-semibold);
font-size: $font-17px;
} }
.mx_RoomSummaryCard_alias { .mx_RoomSummaryCard_alias {
font: var(--cpd-font-body-md-regular);
color: $secondary-content;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.mx_RoomSummaryCard_avatar {
display: flex;
justify-content: center;
align-items: center;
.mx_RoomSummaryCard_e2ee {
display: inline-block;
position: relative;
width: 54px;
height: 54px;
border-radius: 50%;
background-color: #737d8c;
margin-left: -10px; /* overlap */
border: 3px solid $dark-panel-bg-color;
&::before {
content: "";
position: absolute;
top: 13px;
left: 13px;
height: 28px;
width: 28px;
mask-size: cover;
mask-repeat: no-repeat;
mask-position: center;
mask-image: url("$(res)/img/e2e/disabled.svg");
background-color: #ffffff;
}
}
.mx_RoomSummaryCard_e2ee_normal {
background-color: #424446;
&::before {
mask-image: url("$(res)/img/e2e/normal.svg");
}
}
.mx_RoomSummaryCard_e2ee_verified {
background-color: $e2e-verified-color;
&::before {
mask-image: url("$(res)/img/e2e/verified.svg");
}
}
.mx_RoomSummaryCard_e2ee_warning {
background-color: $e2e-warning-color;
&::before {
mask-image: url("$(res)/img/e2e/warning.svg");
}
}
}
.mx_RoomSummaryCard_aboutGroup { .mx_RoomSummaryCard_aboutGroup {
.mx_RoomSummaryCard_Button { .mx_RoomSummaryCard_Button {
padding-left: 44px; padding-left: 44px;
@ -244,6 +188,10 @@ limitations under the License.
} }
} }
.mx_RoomSummaryCard_badges {
margin: var(--cpd-space-4x) 0;
}
.mx_RoomSummaryCard_header { .mx_RoomSummaryCard_header {
padding: 15px 12px; padding: 15px 12px;

View file

@ -16,9 +16,13 @@ limitations under the License.
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react"; import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/matrix"; import { EventType, JoinRule, Room } from "matrix-js-sdk/src/matrix";
import { Tooltip } from "@vector-im/compound-web"; import { Badge, Heading, Text, Tooltip } from "@vector-im/compound-web";
import { Icon as SearchIcon } from "@vector-im/compound-design-tokens/icons/search.svg"; import { Icon as SearchIcon } from "@vector-im/compound-design-tokens/icons/search.svg";
import { Icon as LockIcon } from "@vector-im/compound-design-tokens/icons/lock.svg";
import { Icon as LockOffIcon } from "@vector-im/compound-design-tokens/icons/lock-off.svg";
import { Icon as PublicIcon } from "@vector-im/compound-design-tokens/icons/public.svg";
import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error.svg";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { useIsEncrypted } from "../../../hooks/useIsEncrypted"; import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
@ -34,7 +38,6 @@ import { useEventEmitter } from "../../../hooks/useEventEmitter";
import WidgetUtils from "../../../utils/WidgetUtils"; import WidgetUtils from "../../../utils/WidgetUtils";
import { IntegrationManagers } from "../../../integrations/IntegrationManagers"; import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import TextWithTooltip from "../elements/TextWithTooltip";
import WidgetAvatar from "../avatars/WidgetAvatar"; import WidgetAvatar from "../avatars/WidgetAvatar";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import WidgetStore, { IApp } from "../../../stores/WidgetStore"; import WidgetStore, { IApp } from "../../../stores/WidgetStore";
@ -56,6 +59,8 @@ import PosthogTrackers from "../../../PosthogTrackers";
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
import { PollHistoryDialog } from "../dialogs/PollHistoryDialog"; import { PollHistoryDialog } from "../dialogs/PollHistoryDialog";
import { Flex } from "../../utils/Flex"; import { Flex } from "../../utils/Flex";
import { useAccountData } from "../../../hooks/useAccountData";
import { useRoomState } from "../../../hooks/useRoomState";
interface IProps { interface IProps {
room: Room; room: Room;
@ -307,31 +312,74 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, on
const isVideoRoom = const isVideoRoom =
videoRoomsEnabled && (room.isElementVideoRoom() || (elementCallVideoRoomsEnabled && room.isCallRoom())); videoRoomsEnabled && (room.isElementVideoRoom() || (elementCallVideoRoomsEnabled && room.isCallRoom()));
const roomState = useRoomState(room);
const directRoomsList = useAccountData<Record<string, string[]>>(room.client, EventType.Direct);
const [isDirectMessage, setDirectMessage] = useState(false);
useEffect(() => {
for (const [, dmRoomList] of Object.entries(directRoomsList)) {
if (dmRoomList.includes(room?.roomId ?? "")) {
setDirectMessage(true);
break;
}
}
}, [room, directRoomsList]);
const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || ""; const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || "";
const header = ( const header = (
<header className="mx_RoomSummaryCard_container"> <header className="mx_RoomSummaryCard_container">
<div className="mx_RoomSummaryCard_avatar" role="presentation"> <RoomAvatar room={room} size="80px" viewAvatarOnClick />
<RoomAvatar room={room} size="54px" viewAvatarOnClick />
<TextWithTooltip
tooltip={isRoomEncrypted ? _t("common|encrypted") : _t("common|unencrypted")}
class={classNames("mx_RoomSummaryCard_e2ee", {
mx_RoomSummaryCard_e2ee_normal: isRoomEncrypted,
mx_RoomSummaryCard_e2ee_warning: isRoomEncrypted && e2eStatus === E2EStatus.Warning,
mx_RoomSummaryCard_e2ee_verified: isRoomEncrypted && e2eStatus === E2EStatus.Verified,
})}
/>
</div>
<RoomName room={room}> <RoomName room={room}>
{(name) => ( {(name) => (
<h1 className="mx_RoomSummaryCard_roomName" title={name}> <Heading
as="h1"
size="md"
weight="semibold"
className="mx_RoomSummaryCard_roomName text-primary"
title={name}
>
{name} {name}
</h1> </Heading>
)} )}
</RoomName> </RoomName>
<div className="mx_RoomSummaryCard_alias" title={alias}> <Text
as="div"
size="sm"
weight="semibold"
className="mx_RoomSummaryCard_alias text-secondary"
title={alias}
>
{alias} {alias}
</div> </Text>
<Flex as="section" justify="center" gap="var(--cpd-space-2x)" className="mx_RoomSummaryCard_badges">
{!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && (
<Badge kind="default">
<PublicIcon width="1em" />
{_t("common|public_room")}
</Badge>
)}
{isRoomEncrypted && e2eStatus !== E2EStatus.Warning && (
<Badge kind="success">
<LockIcon width="1em" />
{_t("common|encrypted")}
</Badge>
)}
{!e2eStatus && (
<Badge kind="default">
<LockOffIcon width="1em" />
{_t("common|unencrypted")}
</Badge>
)}
{e2eStatus === E2EStatus.Warning && (
<Badge kind="critical">
<ErrorIcon width="1em" />
{_t("common|not_trusted")}
</Badge>
)}
</Flex>
</header> </header>
); );

View file

@ -1398,6 +1398,7 @@
"element_call_video_rooms": "Element Call video rooms", "element_call_video_rooms": "Element Call video rooms",
"experimental_description": "Feeling experimental? Try out our latest ideas in development. These features are not finalised; they may be unstable, may change, or may be dropped altogether. <a>Learn more</a>.", "experimental_description": "Feeling experimental? Try out our latest ideas in development. These features are not finalised; they may be unstable, may change, or may be dropped altogether. <a>Learn more</a>.",
"experimental_section": "Early previews", "experimental_section": "Early previews",
"feature_disable_call_per_sender_encryption": "Disable per-sender encryption for Element Call",
"feature_wysiwyg_composer_description": "Use rich text instead of Markdown in the message composer.", "feature_wysiwyg_composer_description": "Use rich text instead of Markdown in the message composer.",
"group_calls": "New group call experience", "group_calls": "New group call experience",
"group_developer": "Developer", "group_developer": "Developer",

View file

@ -661,9 +661,11 @@ export class ElementCall extends Call {
analyticsID, analyticsID,
}); });
if (client.isRoomEncrypted(roomId)) params.append("perParticipantE2EE", ""); if (client.isRoomEncrypted(roomId) && !SettingsStore.getValue("feature_disable_call_per_sender_encryption"))
if (SettingsStore.getValue("fallbackICEServerAllowed")) params.append("allowIceFallback", ""); params.append("perParticipantE2EE", "true");
if (SettingsStore.getValue("feature_allow_screen_share_only_mode")) params.append("allowVoipWithNoMedia", ""); if (SettingsStore.getValue("fallbackICEServerAllowed")) params.append("allowIceFallback", "true");
if (SettingsStore.getValue("feature_allow_screen_share_only_mode"))
params.append("allowVoipWithNoMedia", "true");
// Set custom fonts // Set custom fonts
if (SettingsStore.getValue("useSystemFont")) { if (SettingsStore.getValue("useSystemFont")) {

View file

@ -404,6 +404,13 @@ export const SETTINGS: { [setting: string]: ISetting } = {
controller: new ReloadOnChangeController(), controller: new ReloadOnChangeController(),
default: false, default: false,
}, },
"feature_disable_call_per_sender_encryption": {
isFeature: true,
supportedLevels: LEVELS_FEATURE,
labsGroup: LabGroup.VoiceAndVideo,
displayName: _td("labs|feature_disable_call_per_sender_encryption"),
default: false,
},
"feature_allow_screen_share_only_mode": { "feature_allow_screen_share_only_mode": {
isFeature: true, isFeature: true,
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,

View file

@ -15,8 +15,9 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { render, fireEvent } from "@testing-library/react"; import { render, fireEvent, screen } from "@testing-library/react";
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; import { EventType, MatrixEvent, Room, MatrixClient, JoinRule } from "matrix-js-sdk/src/matrix";
import { mocked, MockedObject } from "jest-mock";
import DMRoomMap from "../../../../src/utils/DMRoomMap"; import DMRoomMap from "../../../../src/utils/DMRoomMap";
import RoomSummaryCard from "../../../../src/components/views/right_panel/RoomSummaryCard"; import RoomSummaryCard from "../../../../src/components/views/right_panel/RoomSummaryCard";
@ -28,56 +29,67 @@ import * as settingsHooks from "../../../../src/hooks/useSettings";
import Modal from "../../../../src/Modal"; import Modal from "../../../../src/Modal";
import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore"; import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore";
import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases"; import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases";
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils"; import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils";
import { PollHistoryDialog } from "../../../../src/components/views/dialogs/PollHistoryDialog"; import { PollHistoryDialog } from "../../../../src/components/views/dialogs/PollHistoryDialog";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
import { _t } from "../../../../src/languageHandler"; import { _t } from "../../../../src/languageHandler";
describe("<RoomSummaryCard />", () => { describe("<RoomSummaryCard />", () => {
const userId = "@alice:domain.org"; const userId = "@alice:domain.org";
const mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
isRoomEncrypted: jest.fn(),
getRoom: jest.fn(),
});
const roomId = "!room:domain.org"; const roomId = "!room:domain.org";
const room = new Room(roomId, mockClient, userId); let mockClient!: MockedObject<MatrixClient>;
const roomCreateEvent = new MatrixEvent({ let room!: Room;
type: "m.room.create",
room_id: roomId, const getComponent = (props = {}) => {
sender: userId, const defaultProps = {
content: { room,
creator: userId, onClose: jest.fn(),
room_version: "5", permalinkCreator: new RoomPermalinkCreator(room),
}, };
state_key: "",
}); return render(<RoomSummaryCard {...defaultProps} {...props} />, {
room.currentState.setStateEvents([roomCreateEvent]);
const defaultProps = {
room,
onClose: jest.fn(),
permalinkCreator: new RoomPermalinkCreator(room),
};
const getComponent = (props = {}) =>
render(<RoomSummaryCard {...defaultProps} {...props} />, {
wrapper: ({ children }) => ( wrapper: ({ children }) => (
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider> <MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
), ),
}); });
};
const modalSpy = jest.spyOn(Modal, "createDialog");
const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch");
const rightPanelCardSpy = jest.spyOn(RightPanelStore.instance, "pushCard");
const featureEnabledSpy = jest.spyOn(settingsHooks, "useFeatureEnabled");
beforeEach(() => { beforeEach(() => {
mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
getAccountData: jest.fn(),
isRoomEncrypted: jest.fn(),
getOrCreateFilter: jest.fn().mockResolvedValue({ filterId: 1 }),
getRoom: jest.fn(),
});
room = new Room(roomId, mockClient, userId);
const roomCreateEvent = new MatrixEvent({
type: "m.room.create",
room_id: roomId,
sender: userId,
content: {
creator: userId,
room_version: "5",
},
state_key: "",
});
room.currentState.setStateEvents([roomCreateEvent]);
jest.spyOn(Modal, "createDialog");
jest.spyOn(RightPanelStore.instance, "pushCard");
jest.spyOn(settingsHooks, "useFeatureEnabled").mockReturnValue(false);
jest.spyOn(defaultDispatcher, "dispatch");
jest.clearAllMocks(); jest.clearAllMocks();
DMRoomMap.makeShared(mockClient); DMRoomMap.makeShared(mockClient);
mockClient.getRoom.mockReturnValue(room); mockClient.getRoom.mockReturnValue(room);
jest.spyOn(room, "isElementVideoRoom").mockRestore(); jest.spyOn(room, "isElementVideoRoom").mockRestore();
jest.spyOn(room, "isCallRoom").mockRestore(); jest.spyOn(room, "isCallRoom").mockRestore();
featureEnabledSpy.mockReset().mockReturnValue(false); });
afterEach(() => {
jest.restoreAllMocks();
}); });
it("renders the room summary", () => { it("renders the room summary", () => {
@ -101,7 +113,10 @@ describe("<RoomSummaryCard />", () => {
fireEvent.click(getByText("People")); fireEvent.click(getByText("People"));
expect(rightPanelCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.RoomMemberList }, true); expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith(
{ phase: RightPanelPhases.RoomMemberList },
true,
);
}); });
it("opens room file panel on button click", () => { it("opens room file panel on button click", () => {
@ -109,7 +124,7 @@ describe("<RoomSummaryCard />", () => {
fireEvent.click(getByText("Files")); fireEvent.click(getByText("Files"));
expect(rightPanelCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.FilePanel }, true); expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith({ phase: RightPanelPhases.FilePanel }, true);
}); });
it("opens room export dialog on button click", () => { it("opens room export dialog on button click", () => {
@ -117,7 +132,7 @@ describe("<RoomSummaryCard />", () => {
fireEvent.click(getByText("Export chat")); fireEvent.click(getByText("Export chat"));
expect(modalSpy).toHaveBeenCalledWith(ExportDialog, { room }); expect(Modal.createDialog).toHaveBeenCalledWith(ExportDialog, { room });
}); });
it("opens share room dialog on button click", () => { it("opens share room dialog on button click", () => {
@ -125,7 +140,7 @@ describe("<RoomSummaryCard />", () => {
fireEvent.click(getByText("Share room")); fireEvent.click(getByText("Share room"));
expect(modalSpy).toHaveBeenCalledWith(ShareDialog, { target: room }); expect(Modal.createDialog).toHaveBeenCalledWith(ShareDialog, { target: room });
}); });
it("opens room settings on button click", () => { it("opens room settings on button click", () => {
@ -133,12 +148,12 @@ describe("<RoomSummaryCard />", () => {
fireEvent.click(getByText("Room settings")); fireEvent.click(getByText("Room settings"));
expect(dispatchSpy).toHaveBeenCalledWith({ action: "open_room_settings" }); expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "open_room_settings" });
}); });
describe("pinning", () => { describe("pinning", () => {
it("renders pins options when pinning feature is enabled", () => { it("renders pins options when pinning feature is enabled", () => {
featureEnabledSpy.mockImplementation((feature) => feature === "feature_pinning"); mocked(settingsHooks.useFeatureEnabled).mockImplementation((feature) => feature === "feature_pinning");
const { getByText } = getComponent(); const { getByText } = getComponent();
expect(getByText("Pinned")).toBeInTheDocument(); expect(getByText("Pinned")).toBeInTheDocument();
@ -153,14 +168,15 @@ describe("<RoomSummaryCard />", () => {
}); });
it("opens poll history dialog on button click", () => { it("opens poll history dialog on button click", () => {
const { getByText } = getComponent(); const permalinkCreator = new RoomPermalinkCreator(room);
const { getByText } = getComponent({ permalinkCreator });
fireEvent.click(getByText("Poll history")); fireEvent.click(getByText("Poll history"));
expect(modalSpy).toHaveBeenCalledWith(PollHistoryDialog, { expect(Modal.createDialog).toHaveBeenCalledWith(PollHistoryDialog, {
room, room,
matrixClient: mockClient, matrixClient: mockClient,
permalinkCreator: defaultProps.permalinkCreator, permalinkCreator: permalinkCreator,
}); });
}); });
}); });
@ -168,7 +184,7 @@ describe("<RoomSummaryCard />", () => {
describe("video rooms", () => { describe("video rooms", () => {
it("does not render irrelevant options for element video room", () => { it("does not render irrelevant options for element video room", () => {
jest.spyOn(room, "isElementVideoRoom").mockReturnValue(true); jest.spyOn(room, "isElementVideoRoom").mockReturnValue(true);
featureEnabledSpy.mockImplementation( mocked(settingsHooks.useFeatureEnabled).mockImplementation(
(feature) => feature === "feature_video_rooms" || feature === "feature_pinning", (feature) => feature === "feature_video_rooms" || feature === "feature_pinning",
); );
const { queryByText } = getComponent(); const { queryByText } = getComponent();
@ -181,7 +197,7 @@ describe("<RoomSummaryCard />", () => {
it("does not render irrelevant options for element call room", () => { it("does not render irrelevant options for element call room", () => {
jest.spyOn(room, "isCallRoom").mockReturnValue(true); jest.spyOn(room, "isCallRoom").mockReturnValue(true);
featureEnabledSpy.mockImplementation( mocked(settingsHooks.useFeatureEnabled).mockImplementation(
(feature) => (feature) =>
feature === "feature_element_call_video_rooms" || feature === "feature_element_call_video_rooms" ||
feature === "feature_video_rooms" || feature === "feature_video_rooms" ||
@ -195,4 +211,49 @@ describe("<RoomSummaryCard />", () => {
expect(queryByText("Export chat")).not.toBeInTheDocument(); expect(queryByText("Export chat")).not.toBeInTheDocument();
}); });
}); });
describe("public room label", () => {
beforeEach(() => {
jest.spyOn(room.currentState, "getJoinRule").mockReturnValue(JoinRule.Public);
});
it("does not show public room label for a DM", async () => {
mockClient.getAccountData.mockImplementation(
(eventType) =>
({
[EventType.Direct]: new MatrixEvent({
type: EventType.Direct,
content: {
"@bob:sesame.st": ["some-room-id"],
// this room is a DM with ernie
"@ernie:sesame.st": ["some-other-room-id", room.roomId],
},
}),
}[eventType]),
);
getComponent();
await flushPromises();
expect(screen.queryByText("Public room")).not.toBeInTheDocument();
});
it("does not show public room label for non public room", async () => {
jest.spyOn(room.currentState, "getJoinRule").mockReturnValue(JoinRule.Invite);
getComponent();
await flushPromises();
expect(screen.queryByText("Public room")).not.toBeInTheDocument();
});
it("shows a public room label for a public room", async () => {
jest.spyOn(room.currentState, "getJoinRule").mockReturnValue(JoinRule.Public);
getComponent();
await flushPromises();
expect(screen.queryByText("Public room")).toBeInTheDocument();
});
});
}); });

View file

@ -35,36 +35,40 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
<header <header
class="mx_RoomSummaryCard_container" class="mx_RoomSummaryCard_container"
> >
<div <span
class="mx_RoomSummaryCard_avatar" class="_avatar_1o69u_17 mx_BaseAvatar _avatar-imageless_1o69u_60"
data-color="7"
data-testid="avatar-img"
data-type="round"
role="presentation" role="presentation"
style="--cpd-avatar-size: 80px;"
> >
<span !
class="_avatar_1o69u_17 mx_BaseAvatar _avatar-imageless_1o69u_60" </span>
data-color="7"
data-testid="avatar-img"
data-type="round"
role="presentation"
style="--cpd-avatar-size: 54px;"
>
!
</span>
<div
aria-describedby="mx_TooltipTarget_vY7Q4uEh"
class="mx_TextWithTooltip_target mx_RoomSummaryCard_e2ee"
tabindex="0"
/>
</div>
<h1 <h1
class="mx_RoomSummaryCard_roomName" class="_font-heading-md-semibold_1jx6b_121 mx_RoomSummaryCard_roomName text-primary"
title="!room:domain.org" title="!room:domain.org"
> >
!room:domain.org !room:domain.org
</h1> </h1>
<div <div
class="mx_RoomSummaryCard_alias" class="_font-body-sm-semibold_1jx6b_45 mx_RoomSummaryCard_alias text-secondary"
title="" title=""
/> />
<section
class="mx_Flex mx_RoomSummaryCard_badges"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x);"
>
<span
class="_font-body-sm-medium_1jx6b_50 _badge_qipht_17"
data-kind="default"
>
<div
width="1em"
/>
Not encrypted
</span>
</section>
</header> </header>
<div <div
class="mx_BaseCard_Group mx_RoomSummaryCard_aboutGroup" class="mx_BaseCard_Group mx_RoomSummaryCard_aboutGroup"

View file

@ -925,6 +925,30 @@ describe("ElementCall", () => {
call.destroy(); call.destroy();
expect(destroyPersistentWidgetSpy).toHaveBeenCalled(); expect(destroyPersistentWidgetSpy).toHaveBeenCalled();
}); });
it("the perParticipantE2EE url flag is used in encrypted rooms while respecting the feature_disable_call_per_sender_encryption flag", async () => {
// We destroy the call created in beforeEach because we test the call creation process.
call.destroy();
const addWidgetSpy = jest.spyOn(WidgetStore.instance, "addVirtualWidget");
// If a room is not encrypted we will never add the perParticipantE2EE flag.
client.isRoomEncrypted.mockReturnValue(true);
// should create call with perParticipantE2EE flag
ElementCall.create(room);
expect(addWidgetSpy.mock.calls[0][0].url).toContain("perParticipantE2EE=true");
ElementCall.get(room)?.destroy();
// should create call without perParticipantE2EE flag
enabledSettings.add("feature_disable_call_per_sender_encryption");
await ElementCall.create(room);
enabledSettings.delete("feature_disable_call_per_sender_encryption");
expect(addWidgetSpy.mock.calls[1][0].url).not.toContain("perParticipantE2EE=true");
client.isRoomEncrypted.mockClear();
addWidgetSpy.mockRestore();
});
}); });
describe("instance in a video room", () => { describe("instance in a video room", () => {

View file

@ -1799,10 +1799,10 @@
emojibase "^15.0.0" emojibase "^15.0.0"
emojibase-data "^15.0.0" emojibase-data "^15.0.0"
"@matrix-org/matrix-sdk-crypto-wasm@^2.2.0": "@matrix-org/matrix-sdk-crypto-wasm@^3.0.1":
version "2.2.0" version "3.0.1"
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-2.2.0.tgz#7c60afe01915281a6b71502821bc8e01afbfa70d" resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-3.0.1.tgz#56a0376f8a389264bcf4d5325b378a71f18b7664"
integrity sha512-txmvaTiZpVV0/kWCRcE7tZvRESCEc1ynLJDVh9OUsFlaXfl13c7qdD3E6IJEJ8YiPMIn+PHogdfBZsO84reaMg== integrity sha512-r0PBfUKlLHm67+fpIV21netX5+DujbY2XjJy7JUGJ55oW4XWBNbSf9vElfaQkrdt/iDscL/8I5PoD5lCuVW6zA==
"@matrix-org/matrix-wysiwyg@2.4.1": "@matrix-org/matrix-wysiwyg@2.4.1":
version "2.4.1" version "2.4.1"
@ -7565,11 +7565,11 @@ matrix-events-sdk@0.0.1:
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": "matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
version "30.0.0" version "30.0.1"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/107e28e1145c8b2667701e1f75b9f09b5d2ac3d6" resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/76f993e7ff72768ba1769f6893712ec9a4f4a0c2"
dependencies: dependencies:
"@babel/runtime" "^7.12.5" "@babel/runtime" "^7.12.5"
"@matrix-org/matrix-sdk-crypto-wasm" "^2.2.0" "@matrix-org/matrix-sdk-crypto-wasm" "^3.0.1"
another-json "^0.2.0" another-json "^0.2.0"
bs58 "^5.0.0" bs58 "^5.0.0"
content-type "^1.0.4" content-type "^1.0.4"