Fix React hydration issues (#32958)

* Add more playwright axe tests to settings dialogs

* Add utility to jest setupTests to detect React hydration errors

* Iterate jest utility

* Fix axe issue heading-order

* Fix div-in-p issues

* Fix setupTests.ts

* Fix heading order

* Make types happier

* Fix hydration issues of thead containing text nodes

* Update tests

* Fix form-in-form React hydration issues

* Fix li-in-li React hydration issues

* Fix checked in form without onChange React hydration issue

* Fix styling bleeding from _common.pcss

* Update snapshots

* Fix more remaining issues

* Remove _common.pcss h2 rule altogether

* Fix test

* Update snapshots

* Iterate

* Iterate

* Update snapshots

* Simplify diff

* Test

* Update screenshots

* Update screenshot
This commit is contained in:
Michael Telatynski 2026-04-16 14:35:40 +01:00 committed by GitHub
parent d7f5546294
commit 30f442208a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 2652 additions and 2579 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -834,18 +834,6 @@ legend {
}
}
@define-mixin ButtonResetDefault {
appearance: none;
background: none;
border: none;
padding: 0;
margin: 0;
font-size: inherit;
font-family: inherit;
line-height: inherit;
cursor: pointer;
}
@define-mixin LegacyCallButton {
box-sizing: border-box;
font-weight: var(--cpd-font-weight-semibold);

View File

@ -13,8 +13,7 @@ Please see LICENSE files in the repository root for full details.
top: 0;
}
.mx_ShareDialogButtons_button {
@mixin ButtonResetDefault;
button.mx_ShareDialogButtons_button {
height: 24px;
width: 24px;
border-radius: 50%;

View File

@ -9,6 +9,19 @@ Please see LICENSE files in the repository root for full details.
.mx_AccessibleButton {
cursor: pointer;
&:where(button) {
/* Clear default button styling */
appearance: none;
background: none;
border: none;
padding: 0;
margin: 0;
font-size: inherit;
font-family: inherit;
line-height: inherit;
box-sizing: content-box;
}
&.mx_AccessibleButton_disabled {
cursor: not-allowed;

View File

@ -28,7 +28,6 @@ Please see LICENSE files in the repository root for full details.
/* using em here to adapt to the local font size */
width: 1em;
height: 1em;
cursor: pointer;
padding-left: 12px;
padding-right: 10px;
display: block;

View File

@ -12,12 +12,6 @@ Please see LICENSE files in the repository root for full details.
gap: 32px;
display: flex;
flex-direction: column;
> form {
gap: 32px;
display: flex;
flex-direction: column;
}
}
.mx_SettingsSubsection_description {

View File

@ -14,12 +14,13 @@ Please see LICENSE files in the repository root for full details.
color: $links;
}
form:not(.mx_EncryptionUserSettingsTab form) {
form {
display: flex;
flex-direction: column;
gap: $spacing-8;
gap: var(--cpd-space-3x);
flex-grow: 1;
}
// never want full width buttons
// event when other content is 100% width
.mx_AccessibleButton {

View File

@ -184,6 +184,10 @@ const Tile: React.FC<ITileProps> = ({
aria-labelledby={checkboxLabelId}
checked={!!selected}
tabIndex={-1}
onChange={(e) => {
e.stopPropagation();
onToggleClick();
}}
/>
);
} else {
@ -311,9 +315,9 @@ const Tile: React.FC<ITileProps> = ({
};
childSection = (
<div className="mx_SpaceHierarchy_subspace_children" onKeyDown={onChildrenKeyDown} role="group">
<ul className="mx_SpaceHierarchy_subspace_children" onKeyDown={onChildrenKeyDown} role="group">
{children}
</div>
</ul>
);
}

View File

@ -12,7 +12,7 @@ import React, { type JSX, type ReactNode } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { sleep } from "matrix-js-sdk/src/utils";
import { LockSolidIcon, CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Button } from "@vector-im/compound-web";
import { Button, Form } from "@vector-im/compound-web";
import { _t, _td } from "../../../languageHandler";
import Modal from "../../../Modal";
@ -380,7 +380,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
<>
<LockSolidIcon className="mx_AuthBody_lockIcon" />
<h1>{_t("auth|reset_password_title")}</h1>
<form onSubmit={this.onSubmitForm}>
<Form.Root onSubmit={this.onSubmitForm}>
<fieldset disabled={this.state.phase === Phase.ResettingPassword}>
<div className="mx_AuthBody_fieldRow">
<PassphraseField
@ -413,6 +413,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
<StyledCheckbox
onChange={() => this.setState({ logoutDevices: !this.state.logoutDevices })}
checked={this.state.logoutDevices}
formWrap={false}
>
{_t("auth|reset_password|sign_out_other_devices")}
</StyledCheckbox>
@ -422,7 +423,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
{submitButtonChild}
</Button>
</fieldset>
</form>
</Form.Root>
</>
);
}

View File

@ -459,8 +459,8 @@ export class EmailIdentityAuthEntry extends React.Component<
{
a: (text: string) => (
<Fragment>
<AccessibleButton kind="link_inline" onClick={null} disabled>
{text} <Spinner size={14} />
<AccessibleButton element="a" kind="link_inline" onClick={null} disabled>
{text} <Spinner as="span" size={14} />
</AccessibleButton>
</Fragment>
),
@ -475,6 +475,7 @@ export class EmailIdentityAuthEntry extends React.Component<
{
a: (text: string) => (
<AccessibleButton
element="a"
kind="link_inline"
title={
this.state.requested ? _t("auth|uia|email_resent") : _t("action|resend")

View File

@ -104,7 +104,12 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
return (
<div className="mx_WidgetCapabilitiesPromptDialog_cap" key={cap + i}>
<StyledCheckbox checked={isChecked} onChange={() => this.onToggle(cap)} description={text.byline}>
<StyledCheckbox
checked={isChecked}
onChange={() => this.onToggle(cap)}
description={text.byline}
formWrap={false}
>
{text.primary}
</StyledCheckbox>
</div>

View File

@ -110,7 +110,11 @@ function KeyStorage(): JSX.Element {
return (
<table aria-label={_t("devtools|crypto|key_storage")}>
<thead>{_t("devtools|crypto|key_storage")}</thead>
<thead>
<tr>
<th colSpan={2}>{_t("devtools|crypto|key_storage")}</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">{_t("devtools|crypto|key_backup_latest_version")}</th>
@ -212,7 +216,11 @@ function CrossSigning(): JSX.Element {
return (
<table aria-label={_t("devtools|crypto|cross_signing")}>
<thead>{_t("devtools|crypto|cross_signing")}</thead>
<thead>
<tr>
<th colSpan={2}>{_t("devtools|crypto|cross_signing")}</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">{_t("devtools|crypto|cross_signing_status")}</th>
@ -303,7 +311,11 @@ function Session(): JSX.Element {
return (
<table aria-label={_t("devtools|crypto|session")}>
<thead>{_t("devtools|crypto|session")}</thead>
<thead>
<tr>
<th colSpan={2}>{_t("devtools|crypto|session")}</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">{_t("devtools|crypto|device_id")}</th>

View File

@ -152,46 +152,49 @@ const AccessibleButton = function AccessibleButton<T extends ElementType = typeo
} else {
newProps.onClick = onClick ?? undefined;
}
// We need to consume enter onKeyDown and space onKeyUp
// otherwise we are risking also activating other keyboard focusable elements
// that might receive focus as a result of the AccessibleButtonClick action
// It's because we are using html buttons at a few places e.g. inside dialogs
// And divs which we report as role button to assistive technologies.
// Browsers handle space and enter key presses differently and we are only adjusting to the
// inconsistencies here
newProps.onKeyDown = (e: KeyboardEvent<never>) => {
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
case KeyBindingAction.Enter:
e.stopPropagation();
e.preventDefault();
return onClick?.(e);
case KeyBindingAction.Space:
e.stopPropagation();
e.preventDefault();
break;
default:
onKeyDown?.(e);
}
};
newProps.onKeyUp = (e: KeyboardEvent<never>) => {
const action = getKeyBindingsManager().getAccessibilityAction(e);
if (element !== "button") {
// We need to consume enter onKeyDown and space onKeyUp
// otherwise we are risking also activating other keyboard focusable elements
// that might receive focus as a result of the AccessibleButtonClick action
// It's because we are using html buttons at a few places e.g. inside dialogs
// And divs which we report as role button to assistive technologies.
// Browsers handle space and enter key presses differently and we are only adjusting to the
// inconsistencies here
newProps.onKeyDown = (e: KeyboardEvent<never>) => {
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
case KeyBindingAction.Enter:
e.stopPropagation();
e.preventDefault();
break;
case KeyBindingAction.Space:
e.stopPropagation();
e.preventDefault();
return onClick?.(e);
default:
onKeyUp?.(e);
break;
}
};
switch (action) {
case KeyBindingAction.Enter:
e.stopPropagation();
e.preventDefault();
return onClick?.(e);
case KeyBindingAction.Space:
e.stopPropagation();
e.preventDefault();
break;
default:
onKeyDown?.(e);
}
};
newProps.onKeyUp = (e: KeyboardEvent<never>) => {
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
case KeyBindingAction.Enter:
e.stopPropagation();
e.preventDefault();
break;
case KeyBindingAction.Space:
e.stopPropagation();
e.preventDefault();
return onClick?.(e);
default:
onKeyUp?.(e);
break;
}
};
}
}
// Pass through the ref - used for keyboard shortcut access to some buttons

View File

@ -44,6 +44,7 @@ export const CopyTextButton: React.FC<Pick<IProps, "getTextToCopy" | "className"
return (
<AccessibleButton
element="button"
title={tooltip ?? _t("action|copy")}
onClick={onCopyClickInternal}
className={className}
@ -62,12 +63,12 @@ const CopyableText: React.FC<IProps> = ({ children, getTextToCopy, border = true
});
return (
<div className={combinedClassName} {...props}>
<span className={combinedClassName} {...props}>
{children}
<CopyTextButton getTextToCopy={getTextToCopy} className="mx_CopyableText_copyButton">
<CopyIcon />
</CopyTextButton>
</div>
</span>
);
};

View File

@ -13,7 +13,7 @@ import Modal from "../../../Modal";
import InfoDialog from "../dialogs/InfoDialog";
import AccessibleButton, { type ButtonProps } from "./AccessibleButton";
type Props = Omit<ButtonProps<"div">, "element" | "kind" | "onClick" | "className"> & {
type Props = Omit<ButtonProps<"button">, "element" | "kind" | "onClick" | "className"> & {
title: string;
description: string | React.ReactNode;
};
@ -29,7 +29,13 @@ const LearnMore: React.FC<Props> = ({ title, description, ...rest }) => {
};
return (
<AccessibleButton {...rest} kind="link_inline" onClick={onClick} className="mx_LearnMore_button">
<AccessibleButton
{...rest}
element="button"
kind="link_inline"
onClick={onClick}
className="mx_LearnMore_button"
>
{_t("action|learn_more")}
</AccessibleButton>
);

View File

@ -15,6 +15,11 @@ interface IProps {
size?: number;
message?: string;
onFinished: any; // XXX: Spinner pretends to be a dialog so it must accept an onFinished, but it never calls it
/**
* Whether to render the content in a div or span.
* @default "div"
*/
as?: "span" | "div";
}
export default class Spinner extends React.PureComponent<IProps> {
@ -23,16 +28,16 @@ export default class Spinner extends React.PureComponent<IProps> {
};
public render(): React.ReactNode {
const { size, message } = this.props;
const { size, message, as: Component = "div" } = this.props;
return (
<div className="mx_Spinner">
<Component className="mx_Spinner">
{message && (
<React.Fragment>
<div className="mx_Spinner_Msg">{message}</div>&nbsp;
</React.Fragment>
)}
<InlineSpinner size={size} aria-label={_t("common|loading")} role="progressbar" data-testid="spinner" />
</div>
</Component>
);
}
}

View File

@ -14,6 +14,7 @@ interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
inputRef?: Ref<HTMLInputElement>;
id?: string;
description?: ReactNode;
formWrap?: boolean;
}
const StyledCheckbox: React.FC<IProps> = ({
@ -22,30 +23,36 @@ const StyledCheckbox: React.FC<IProps> = ({
className,
inputRef,
description,
formWrap = true,
...otherProps
}) => {
const id = initialId || "checkbox_" + secureRandomString(10);
const name = useId();
const descriptionId = useId();
return (
<Form.Root>
<InlineField
className={className}
name={name}
control={
<CheckboxInput
ref={inputRef}
aria-describedby={description ? descriptionId : undefined}
id={id}
{...otherProps}
/>
}
>
{label && <Label htmlFor={id}>{label}</Label>}
{description && <HelpMessage id={descriptionId}>{description}</HelpMessage>}
</InlineField>
</Form.Root>
const field = (
<InlineField
className={className}
name={name}
control={
<CheckboxInput
ref={inputRef}
aria-describedby={description ? descriptionId : undefined}
id={id}
{...otherProps}
/>
}
>
{label && <Label htmlFor={id}>{label}</Label>}
{description && <HelpMessage id={descriptionId}>{description}</HelpMessage>}
</InlineField>
);
if (formWrap) {
return <Form.Root>{field}</Form.Root>;
}
return field;
};
export default StyledCheckbox;

View File

@ -255,7 +255,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
} else {
body = (
<p>
<Spinner />
<Spinner as="span" />
</p>
);
}

View File

@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type ChangeEventHandler } from "react";
import { JoinRule, Visibility } from "matrix-js-sdk/src/matrix";
import { SettingsToggleInput } from "@vector-im/compound-web";
import { Form, SettingsToggleInput } from "@vector-im/compound-web";
import { logger } from "matrix-js-sdk/src/logger";
import { _t } from "../../../languageHandler";
@ -16,6 +16,7 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
import DirectoryCustomisations from "../../../customisations/Directory";
import Modal from "../../../Modal";
import ErrorDialog from "../dialogs/ErrorDialog";
import { onSubmitPreventDefault } from "../../../utils/form.ts";
interface IProps {
roomId: string;
@ -90,16 +91,18 @@ export default class RoomPublishSetting extends React.PureComponent<IProps, ISta
const enabled = canSetCanonicalAlias && (isRoomPublishable || this.state.isRoomPublished);
return (
<SettingsToggleInput
name="room-publish"
checked={this.state.isRoomPublished}
onChange={this.onRoomPublishChange}
disabled={!enabled || this.state.busy}
disabledMessage={disabledMessage}
label={_t("room_settings|general|publish_toggle", {
domain: client.getDomain(),
})}
/>
<Form.Root onSubmit={onSubmitPreventDefault}>
<SettingsToggleInput
name="room-publish"
checked={this.state.isRoomPublished}
onChange={this.onRoomPublishChange}
disabled={!enabled || this.state.busy}
disabledMessage={disabledMessage}
label={_t("room_settings|general|publish_toggle", {
domain: client.getDomain(),
})}
/>
</Form.Root>
);
}
}

View File

@ -220,7 +220,12 @@ export default class EventIndexPanel extends React.Component<EmptyObject, IState
: _t("error|unknown")}
</code>
<p>
<AccessibleButton key="delete" kind="danger" onClick={this.confirmEventStoreReset}>
<AccessibleButton
element="button"
key="delete"
kind="danger"
onClick={this.confirmEventStoreReset}
>
{_t("action|reset")}
</AccessibleButton>
</p>

View File

@ -52,6 +52,7 @@ import { SettingsSubsectionHeading } from "./shared/SettingsSubsectionHeading";
import { SettingsSubsection } from "./shared/SettingsSubsection";
import { doesRoomHaveUnreadMessages } from "../../../Unread";
import SettingsFlag from "../elements/SettingsFlag";
import { onSubmitPreventDefault } from "../../../utils/form.ts";
// TODO: this "view" component still has far too much application logic in it,
// which should be factored out to other files.
@ -651,7 +652,7 @@ export default class Notifications extends React.PureComponent<EmptyObject, ISta
// If all the rules are inhibited, don't show anything.
if (this.isInhibited) {
return masterSwitch;
return <Form.Root onSubmit={onSubmitPreventDefault}>{masterSwitch}</Form.Root>;
}
const emailSwitches = (this.state.threepids || [])
@ -669,19 +670,21 @@ export default class Notifications extends React.PureComponent<EmptyObject, ISta
return (
<SettingsSubsection>
{masterSwitch}
<Form.Root onSubmit={onSubmitPreventDefault}>
{masterSwitch}
<SettingsFlag name="deviceNotificationsEnabled" level={SettingLevel.DEVICE} />
<SettingsFlag name="deviceNotificationsEnabled" level={SettingLevel.DEVICE} />
{this.state.deviceNotificationsEnabled && (
<>
<SettingsFlag name="notificationsEnabled" level={SettingLevel.DEVICE} />
<SettingsFlag name="notificationBodyEnabled" level={SettingLevel.DEVICE} />
<SettingsFlag name="audioNotificationsEnabled" level={SettingLevel.DEVICE} />
</>
)}
{this.state.deviceNotificationsEnabled && (
<>
<SettingsFlag name="notificationsEnabled" level={SettingLevel.DEVICE} />
<SettingsFlag name="notificationBodyEnabled" level={SettingLevel.DEVICE} />
<SettingsFlag name="audioNotificationsEnabled" level={SettingLevel.DEVICE} />
</>
)}
{emailSwitches}
{emailSwitches}
</Form.Root>
</SettingsSubsection>
);
}

View File

@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, useState } from "react";
import { SettingsToggleInput } from "@vector-im/compound-web";
import { Form, SettingsToggleInput } from "@vector-im/compound-web";
import NewAndImprovedIcon from "../../../../../res/img/element-icons/new-and-improved.svg";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
@ -33,6 +33,7 @@ import { SettingsSubsection } from "../shared/SettingsSubsection";
import { NotificationPusherSettings } from "./NotificationPusherSettings";
import SettingsFlag from "../../elements/SettingsFlag";
import { SettingsSubsectionHeading } from "../shared/SettingsSubsectionHeading";
import { onSubmitPreventDefault } from "../../../../utils/form.ts";
enum NotificationDefaultLevels {
AllMessages = "all_messages",
@ -111,7 +112,7 @@ export default function NotificationSettings2(): JSX.Element {
</SettingsBanner>
)}
<SettingsSection>
<div className="mx_SettingsSubsection_content mx_NotificationSettings2_flags">
<Form.Root className="mx_SettingsSubsection_content" onSubmit={onSubmitPreventDefault}>
<SettingsToggleInput
name="enable_notifications_account"
label={_t("settings|notifications|enable_notifications_account")}
@ -131,7 +132,7 @@ export default function NotificationSettings2(): JSX.Element {
level={SettingLevel.DEVICE}
/>
<SettingsFlag name="audioNotificationsEnabled" level={SettingLevel.DEVICE} />
</div>
</Form.Root>
<SettingsSubsection
heading={
<SettingsSubsectionHeading
@ -346,8 +347,10 @@ export default function NotificationSettings2(): JSX.Element {
placeholder={_t("notifications|keyword_new")}
/>
<SettingsFlag name="Notifications.showbold" level={SettingLevel.DEVICE} />
<SettingsFlag name="Notifications.tac_only_notifications" level={SettingLevel.DEVICE} />
<Form.Root onSubmit={onSubmitPreventDefault}>
<SettingsFlag name="Notifications.showbold" level={SettingLevel.DEVICE} />
<SettingsFlag name="Notifications.tac_only_notifications" level={SettingLevel.DEVICE} />
</Form.Root>
</SettingsSubsection>
<NotificationPusherSettings />
<SettingsSubsection heading={_t("settings|notifications|quick_actions_section")}>

View File

@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
import React, { type ContextType } from "react";
import { type Room } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { Form } from "@vector-im/compound-web";
import { _t } from "../../../../../languageHandler";
import RoomProfileSettings from "../../../room_settings/RoomProfileSettings";
@ -72,32 +71,25 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
return (
<SettingsTab data-testid="General">
<Form.Root
onSubmit={(evt) => {
evt.preventDefault();
evt.stopPropagation();
}}
>
<SettingsSection heading={_t("common|general")}>
<RoomProfileSettings roomId={room.roomId} />
</SettingsSection>
<SettingsSection heading={_t("common|general")}>
<RoomProfileSettings roomId={room.roomId} />
</SettingsSection>
<SettingsSection heading={_t("room_settings|general|aliases_section")}>
<AliasSettings
roomId={room.roomId}
canSetCanonicalAlias={canSetCanonical}
canSetAliases={canSetAliases}
canonicalAliasEvent={canonicalAliasEv}
/>
</SettingsSection>
<SettingsSection heading={_t("room_settings|general|aliases_section")}>
<AliasSettings
roomId={room.roomId}
canSetCanonicalAlias={canSetCanonical}
canSetAliases={canSetAliases}
canonicalAliasEvent={canonicalAliasEv}
/>
</SettingsSection>
<SettingsSection heading={_t("room_settings|general|other_section")}>
<SettingsSubsection heading={_t("common|moderation_and_safety")} legacy={false}>
<MediaPreviewAccountSettings roomId={room.roomId} />
</SettingsSubsection>
{leaveSection}
</SettingsSection>
</Form.Root>
<SettingsSection heading={_t("room_settings|general|other_section")}>
<SettingsSubsection heading={_t("common|moderation_and_safety")} legacy={false}>
<MediaPreviewAccountSettings roomId={room.roomId} />
</SettingsSubsection>
{leaveSection}
</SettingsSection>
</SettingsTab>
);
}

View File

@ -137,6 +137,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
{
a: (sub) => (
<AccessibleButton
element="a"
kind="link_inline"
onClick={() => {
dialog.close();
@ -334,6 +335,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
{_t("room_settings|security|encrypted_room_public_confirm_description_2", undefined, {
a: (sub) => (
<AccessibleButton
element="a"
kind="link_inline"
onClick={(): void => {
dialog.close();

View File

@ -7,7 +7,6 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { Form } from "@vector-im/compound-web";
import { Features } from "../../../../../settings/Settings";
import SettingsStore from "../../../../../settings/SettingsStore";
@ -22,20 +21,13 @@ export default class NotificationUserSettingsTab extends React.Component {
return (
<SettingsTab>
<Form.Root
onSubmit={(evt) => {
evt.preventDefault();
evt.stopPropagation();
}}
>
{newNotificationSettingsEnabled ? (
<NotificationSettings2 />
) : (
<SettingsSection>
<Notifications />
</SettingsSection>
)}
</Form.Root>
{newNotificationSettingsEnabled ? (
<NotificationSettings2 />
) : (
<SettingsSection>
<Notifications />
</SettingsSection>
)}
</SettingsTab>
);
}

View File

@ -184,14 +184,7 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
</Form.Root>
</SettingsFieldset>
<Form.Root
onSubmit={(evt) => {
evt.preventDefault();
evt.stopPropagation();
}}
>
{addressesSection}
</Form.Root>
{addressesSection}
</SettingsSection>
</SettingsTab>
);

View File

@ -0,0 +1,17 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import type React from "react";
/**
* onSubmit handler which calls preventDefault and stopPropagation on the event
* @param e submit event
*/
export function onSubmitPreventDefault(e: SubmitEvent | React.SubmitEvent): void {
e.preventDefault();
e.stopPropagation();
}

View File

@ -66,3 +66,28 @@ if (env["GITHUB_ACTIONS"] !== undefined) {
require("./setup/setupManualMocks"); // must be first
require("./setup/setupLanguage");
require("./setup/setupConfig");
// Utility to check for React errors during the tests
// Fails tests on errors like the following:
// In HTML, <div> cannot be a descendant of <p>.
// In HTML, <form> cannot be a descendant of <form>.
// In HTML, text nodes cannot be a child of <thead>.
// This will cause a hydration error.
// You provided a `checked` prop to a form field without an `onChange` handler.
let errors: any[] = [];
beforeEach(() => {
errors = [];
const originalError = console.error;
jest.spyOn(console, "error").mockImplementation((...args) => {
if (/validateDOMNesting|Hydration failed|hydration error|prop to a form field without an/i.test(args[0])) {
errors.push(args[0]);
}
originalError.call(console, ...args);
});
});
afterEach(() => {
mocked(console.error).mockRestore?.();
if (errors.length > 0) {
throw new Error("Test failed due to React hydration errors in the console.");
}
});

View File

@ -378,114 +378,115 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
</svg>
</div>
</div>
<div
<ul
class="mx_SpaceHierarchy_subspace_children"
role="group"
/>
</li>
<li
aria-labelledby="_r_k_"
class="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
>
<div
class="mx_AccessibleButton mx_SpaceHierarchy_roomTile"
role="button"
tabindex="-1"
>
<div
class="mx_SpaceHierarchy_roomTile_item"
<li
aria-labelledby="_r_k_"
class="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
>
<div
class="mx_SpaceHierarchy_roomTile_avatar"
>
<span
class="_avatar_7h2br_8 mx_BaseAvatar _avatar-imageless_7h2br_55"
data-color="2"
data-testid="avatar-img"
data-type="round"
role="presentation"
style="--cpd-avatar-size: 20px;"
>
N
</span>
</div>
<div
class="mx_SpaceHierarchy_roomTile_name"
>
<span
id="_r_k_"
>
Nested room
</span>
</div>
<div
class="mx_SpaceHierarchy_roomTile_info"
>
3 members
</div>
</div>
<div
class="mx_SpaceHierarchy_actions"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
class="mx_AccessibleButton mx_SpaceHierarchy_roomTile"
role="button"
tabindex="-1"
>
Join
</div>
<span
aria-labelledby="_r_l_"
tabindex="0"
>
<form
class="_root_19upo_16"
<div
class="mx_SpaceHierarchy_roomTile_item"
>
<div
class="_inline-field_19upo_32"
class="mx_SpaceHierarchy_roomTile_avatar"
>
<div
class="_inline-field-control_19upo_44"
<span
class="_avatar_7h2br_8 mx_BaseAvatar _avatar-imageless_7h2br_55"
data-color="2"
data-testid="avatar-img"
data-type="round"
role="presentation"
style="--cpd-avatar-size: 20px;"
>
N
</span>
</div>
<div
class="mx_SpaceHierarchy_roomTile_name"
>
<span
id="_r_k_"
>
Nested room
</span>
</div>
<div
class="mx_SpaceHierarchy_roomTile_info"
>
3 members
</div>
</div>
<div
class="mx_SpaceHierarchy_actions"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="-1"
>
Join
</div>
<span
aria-labelledby="_r_l_"
tabindex="0"
>
<form
class="_root_19upo_16"
>
<div
class="_container_153f2_10"
class="_inline-field_19upo_32"
>
<input
aria-labelledby="_r_k_"
class="_input_153f2_18"
disabled=""
id="checkbox_RD7nyrA2oh"
role="presentation"
tabindex="-1"
type="checkbox"
/>
<div
class="_ui_153f2_19"
class="_inline-field-control_19upo_44"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
<div
class="_container_153f2_10"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
<input
aria-labelledby="_r_k_"
class="_input_153f2_18"
disabled=""
id="checkbox_RD7nyrA2oh"
role="presentation"
tabindex="-1"
type="checkbox"
/>
</svg>
<div
class="_ui_153f2_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
<div
class="_inline-field-body_19upo_38"
/>
</div>
</div>
<div
class="_inline-field-body_19upo_38"
/>
</div>
</form>
</span>
</div>
</div>
</form>
</span>
</div>
</div>
</li>
</ul>
</li>
<li
aria-labelledby="_r_t_"
@ -967,7 +968,7 @@ exports[`SpaceHierarchy <SpaceHierarchy /> should not render cycles 1`] = `
</svg>
</div>
</div>
<div
<ul
class="mx_SpaceHierarchy_subspace_children"
role="group"
/>

View File

@ -19,14 +19,14 @@ exports[`<EmailIdentityAuthEntry/> should render 1`] = `
>
<span>
Did not receive it?
<div
<a
aria-label="Resend"
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Resend it
</div>
</a>
</span>
</p>
</div>

View File

@ -54,10 +54,10 @@ exports[`<BeaconListItem /> when a beacon is live and has locations renders beac
/>
</svg>
</a>
<div
<span
class="mx_CopyableText mx_ShareLatestLocation_copy"
>
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -77,8 +77,8 @@ exports[`<BeaconListItem /> when a beacon is live and has locations renders beac
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</div>
</div>
<span

View File

@ -94,10 +94,10 @@ exports[`<DialogSidebar /> renders sidebar correctly with beacons 1`] = `
/>
</svg>
</a>
<div
<span
class="mx_CopyableText mx_ShareLatestLocation_copy"
>
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -117,8 +117,8 @@ exports[`<DialogSidebar /> renders sidebar correctly with beacons 1`] = `
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</div>
</div>
<span

View File

@ -25,10 +25,10 @@ exports[`<ShareLatestLocation /> renders share buttons when there is a location
/>
</svg>
</a>
<div
<span
class="mx_CopyableText mx_ShareLatestLocation_copy"
>
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -48,7 +48,7 @@ exports[`<ShareLatestLocation /> renders share buttons when there is a location
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</DocumentFragment>
`;

View File

@ -29,11 +29,11 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = `
>
Toolbox
</div>
<div
<span
class="mx_CopyableText mx_DevTools_label_right"
>
Room ID: !id
<div
<button
aria-describedby="_r_2_"
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
@ -54,8 +54,8 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = `
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
<div
class="mx_DevTools_label_bottom"
/>

View File

@ -5,7 +5,13 @@ exports[`<Crypto /> <CrossSigning /> should display when the cross-signing data
aria-label="Cross-signing"
>
<thead>
Cross-signing
<tr>
<th
colspan="2"
>
Cross-signing
</th>
</tr>
</thead>
<tbody>
<tr>
@ -77,7 +83,13 @@ exports[`<Crypto /> <CrossSigning /> should display when the cross-signing data
aria-label="Cross-signing"
>
<thead>
Cross-signing
<tr>
<th
colspan="2"
>
Cross-signing
</th>
</tr>
</thead>
<tbody>
<tr>
@ -149,7 +161,13 @@ exports[`<Crypto /> <KeyStorage /> should display when the key storage data are
aria-label="Key Storage"
>
<thead>
Key Storage
<tr>
<th
colspan="2"
>
Key Storage
</th>
</tr>
</thead>
<tbody>
<tr>
@ -221,7 +239,13 @@ exports[`<Crypto /> <KeyStorage /> should display when the key storage data are
aria-label="Key Storage"
>
<thead>
Key Storage
<tr>
<th
colspan="2"
>
Key Storage
</th>
</tr>
</thead>
<tbody>
<tr>

View File

@ -7,11 +7,11 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
>
<ul>
<li>
<div
<span
class="mx_CopyableText"
>
User ID: @alice:example.com
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -31,15 +31,15 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</li>
<li>
<div
<span
class="mx_CopyableText"
>
Device ID: SIGNED
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -59,8 +59,8 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</li>
<li>
Displayname:
@ -95,11 +95,11 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
Device keys
<ul>
<li>
<div
<span
class="mx_CopyableText"
>
ed25519: an_ed25519_public_key
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -119,15 +119,15 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</li>
<li>
<div
<span
class="mx_CopyableText"
>
curve25519: a_curve25519_public_key
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -147,8 +147,8 @@ exports[`<Users /> should render a single device - signed by owner 1`] = `
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</li>
</ul>
</li>
@ -171,11 +171,11 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
>
<ul>
<li>
<div
<span
class="mx_CopyableText"
>
User ID: @alice:example.com
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -195,15 +195,15 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</li>
<li>
<div
<span
class="mx_CopyableText"
>
Device ID: UNSIGNED
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -223,8 +223,8 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</li>
<li>
Displayname:
@ -259,11 +259,11 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
Device keys
<ul>
<li>
<div
<span
class="mx_CopyableText"
>
ed25519: an_ed25519_public_key
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -283,15 +283,15 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</li>
<li>
<div
<span
class="mx_CopyableText"
>
curve25519: a_curve25519_public_key
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -311,8 +311,8 @@ exports[`<Users /> should render a single device - unsigned 1`] = `
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</li>
</ul>
</li>
@ -335,11 +335,11 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
>
<ul>
<li>
<div
<span
class="mx_CopyableText"
>
User ID: @alice:example.com
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -359,15 +359,15 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</li>
<li>
<div
<span
class="mx_CopyableText"
>
Device ID: VERIFIED
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -387,8 +387,8 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</li>
<li>
Displayname:
@ -425,11 +425,11 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
Device keys
<ul>
<li>
<div
<span
class="mx_CopyableText"
>
ed25519: an_ed25519_public_key
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -449,15 +449,15 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</li>
<li>
<div
<span
class="mx_CopyableText"
>
curve25519: a_curve25519_public_key
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -477,8 +477,8 @@ exports[`<Users /> should render a single device - verified by cross-signing 1`]
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</li>
</ul>
</li>
@ -501,11 +501,11 @@ exports[`<Users /> should render a single user 1`] = `
>
<ul>
<li>
<div
<span
class="mx_CopyableText"
>
User ID: @alice:example.com
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -525,8 +525,8 @@ exports[`<Users /> should render a single user 1`] = `
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</li>
<li>
Membership: join

View File

@ -2,13 +2,13 @@
exports[`<LearnMore /> renders button 1`] = `
<div>
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
data-testid="testid"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</div>
`;

View File

@ -59,7 +59,7 @@ exports[`<TextualBody /> renders formatted m.text correctly linkification is not
</code>
</div>
</pre>
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_EventTile_button mx_EventTile_copyButton"
role="button"
@ -79,7 +79,7 @@ exports[`<TextualBody /> renders formatted m.text correctly linkification is not
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</button>
</div>
@ -278,7 +278,7 @@ exports[`<TextualBody /> renders formatted m.text correctly pills do not appear
</code>
</div>
</pre>
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_EventTile_button mx_EventTile_copyButton"
role="button"
@ -298,7 +298,7 @@ exports[`<TextualBody /> renders formatted m.text correctly pills do not appear
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</button>
</div>
@ -482,7 +482,7 @@ num_sqrt = num **
/>
</svg>
</span>
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_EventTile_button mx_EventTile_copyButton mx_EventTile_buttonBottom"
role="button"
@ -502,7 +502,7 @@ num_sqrt = num **
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</button>
</div>

View File

@ -99,11 +99,11 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
<p
class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
>
<div
<span
class="mx_CopyableText"
>
customUserIdentifier
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -123,8 +123,8 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</p>
</div>
<div
@ -401,11 +401,11 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
<p
class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
>
<div
<span
class="mx_CopyableText"
>
customUserIdentifier
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -425,8 +425,8 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</p>
</div>
<div

View File

@ -48,11 +48,11 @@ exports[`<UserInfoHeaderView /> renders custom user identifiers in the header 1`
<p
class="_typography_6v6n8_153 _font-body-sm-semibold_6v6n8_36 mx_UserInfo_profile_mxid"
>
<div
<span
class="mx_CopyableText"
>
customUserIdentifier
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -72,8 +72,8 @@ exports[`<UserInfoHeaderView /> renders custom user identifiers in the header 1`
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</p>
</div>
<div

View File

@ -37,7 +37,6 @@ import {
import { mocked } from "jest-mock";
import userEvent from "@testing-library/user-event";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import { Form } from "@vector-im/compound-web";
import Notifications from "../../../../../src/components/views/settings/Notifications";
import SettingsStore from "../../../../../src/settings/SettingsStore";
@ -249,12 +248,7 @@ const pushRules: IPushRules = {
const flushPromises = async () => await new Promise((resolve) => window.setTimeout(resolve));
describe("<Notifications />", () => {
const getComponent = () =>
render(
<Form.Root>
<Notifications />
</Form.Root>,
);
const getComponent = () => render(<Notifications />);
// get component, wait for async data and force a render
const getComponentAndWait = async () => {

View File

@ -42,74 +42,74 @@ exports[`<Notifications /> main notification switches renders only enable notifi
</span>
</div>
</div>
<form
class="_root_19upo_16"
</form>
<form
class="_root_19upo_16"
>
<div
class="_inline-field_19upo_32"
>
<div
class="_inline-field_19upo_32"
class="_inline-field-control_19upo_44"
>
<div
class="_inline-field-control_19upo_44"
class="_container_udcm8_10"
>
<input
checked=""
class="_input_udcm8_24"
id="mx_SettingsFlag_testid_0"
role="switch"
type="checkbox"
/>
<div
class="_container_udcm8_10"
>
<input
checked=""
class="_input_udcm8_24"
id="mx_SettingsFlag_testid_0"
role="switch"
type="checkbox"
/>
<div
class="_ui_udcm8_34"
/>
</div>
</div>
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="mx_SettingsFlag_testid_0"
>
Show all activity in the room list (dots or number of unread messages)
</label>
class="_ui_udcm8_34"
/>
</div>
</div>
<div
class="_inline-field_19upo_32"
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="mx_SettingsFlag_testid_0"
>
Show all activity in the room list (dots or number of unread messages)
</label>
</div>
</div>
<div
class="_inline-field_19upo_32"
>
<div
class="_inline-field-control_19upo_44"
>
<div
class="_inline-field-control_19upo_44"
class="_container_udcm8_10"
>
<input
checked=""
class="_input_udcm8_24"
id="mx_SettingsFlag_testid_1"
role="switch"
type="checkbox"
/>
<div
class="_container_udcm8_10"
>
<input
checked=""
class="_input_udcm8_24"
id="mx_SettingsFlag_testid_1"
role="switch"
type="checkbox"
/>
<div
class="_ui_udcm8_34"
/>
</div>
</div>
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="mx_SettingsFlag_testid_1"
>
Only show notifications in the thread activity centre
</label>
class="_ui_udcm8_34"
/>
</div>
</div>
</form>
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="mx_SettingsFlag_testid_1"
>
Only show notifications in the thread activity centre
</label>
</div>
</div>
</form>
</div>
`;

View File

@ -57,13 +57,13 @@ HTMLCollection [
class="mx_DeviceSecurityCard_description"
>
Verify your current session for enhanced secure messaging.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
<div
class="mx_DeviceSecurityCard_actions"
@ -334,13 +334,13 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
class="mx_DeviceSecurityCard_description"
>
Verify your current session for enhanced secure messaging.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
<div
class="mx_DeviceSecurityCard_actions"
@ -521,13 +521,13 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
class="mx_DeviceSecurityCard_description"
>
Verify your current session for enhanced secure messaging.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
<div
class="mx_DeviceSecurityCard_actions"

View File

@ -37,13 +37,13 @@ exports[`<DeviceDetailHeading /> displays name edit form on rename button click
id="device-rename-description-123"
>
Please be aware that session names are also visible to people you communicate with.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</span>
</div>
<div

View File

@ -59,13 +59,13 @@ exports[`<DeviceDetails /> renders a verified device 1`] = `
class="mx_DeviceSecurityCard_description"
>
This session is ready for secure messaging.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
</div>
</div>
@ -175,13 +175,13 @@ exports[`<DeviceDetails /> renders device with metadata 1`] = `
class="mx_DeviceSecurityCard_description"
>
Verify or remove this session for best security and reliability.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
</div>
</div>
@ -391,13 +391,13 @@ exports[`<DeviceDetails /> renders device without metadata 1`] = `
class="mx_DeviceSecurityCard_description"
>
Verify or remove this session for best security and reliability.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
</div>
</div>

View File

@ -34,13 +34,13 @@ exports[`<DeviceVerificationStatusCard /> renders a verified device 1`] = `
class="mx_DeviceSecurityCard_description"
>
This session is ready for secure messaging.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
</div>
</div>
@ -79,13 +79,13 @@ exports[`<DeviceVerificationStatusCard /> renders an unverifiable device 1`] = `
class="mx_DeviceSecurityCard_description"
>
This session doesn't support encryption and thus can't be verified.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
</div>
</div>
@ -124,13 +124,13 @@ exports[`<DeviceVerificationStatusCard /> renders an unverified device 1`] = `
class="mx_DeviceSecurityCard_description"
>
Verify or remove this session for best security and reliability.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
<div
class="mx_DeviceSecurityCard_actions"

View File

@ -39,13 +39,13 @@ HTMLCollection [
>
<span>
Consider removing old sessions (90 days or older) you don't use anymore.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</span>
</p>
</div>
@ -90,13 +90,13 @@ HTMLCollection [
>
<span>
Verify your sessions for enhanced secure messaging or remove from those you don't recognize or use anymore.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</span>
</p>
</div>
@ -143,13 +143,13 @@ HTMLCollection [
>
<span>
For best security, remove any session that you don't recognize or use anymore.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</span>
</p>
</div>

View File

@ -57,13 +57,13 @@ exports[`<SecurityRecommendations /> renders both cards when user has both unver
class="mx_DeviceSecurityCard_description"
>
Verify your sessions for enhanced secure messaging or remove from those you don't recognize or use anymore.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
<div
class="mx_DeviceSecurityCard_actions"
@ -105,13 +105,13 @@ exports[`<SecurityRecommendations /> renders both cards when user has both unver
class="mx_DeviceSecurityCard_description"
>
Consider removing old sessions (90 days or older) you don't use anymore.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
<div
class="mx_DeviceSecurityCard_actions"
@ -189,13 +189,13 @@ exports[`<SecurityRecommendations /> renders inactive devices section when user
class="mx_DeviceSecurityCard_description"
>
Verify your sessions for enhanced secure messaging or remove from those you don't recognize or use anymore.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
<div
class="mx_DeviceSecurityCard_actions"
@ -237,13 +237,13 @@ exports[`<SecurityRecommendations /> renders inactive devices section when user
class="mx_DeviceSecurityCard_description"
>
Consider removing old sessions (90 days or older) you don't use anymore.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
<div
class="mx_DeviceSecurityCard_actions"
@ -321,13 +321,13 @@ exports[`<SecurityRecommendations /> renders unverified devices section when use
class="mx_DeviceSecurityCard_description"
>
Verify your sessions for enhanced secure messaging or remove from those you don't recognize or use anymore.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
<div
class="mx_DeviceSecurityCard_actions"
@ -369,13 +369,13 @@ exports[`<SecurityRecommendations /> renders unverified devices section when use
class="mx_DeviceSecurityCard_description"
>
Consider removing old sessions (90 days or older) you don't use anymore.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
<div
class="mx_DeviceSecurityCard_actions"

View File

@ -18,7 +18,6 @@ import {
RuleId,
} from "matrix-js-sdk/src/matrix";
import React from "react";
import { Form } from "@vector-im/compound-web";
import NotificationSettings2 from "../../../../../../src/components/views/settings/notifications/NotificationSettings2";
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
@ -94,9 +93,7 @@ describe("<Notifications />", () => {
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -109,9 +106,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(async () => {
@ -172,9 +167,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -190,9 +183,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -230,9 +221,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -267,9 +256,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -298,9 +285,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -325,9 +310,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -349,9 +332,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -379,9 +360,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -405,9 +384,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -429,9 +406,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -459,9 +434,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -485,9 +458,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -514,9 +485,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -535,9 +504,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -648,9 +615,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -709,9 +674,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const screen = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await act(waitForUpdate);
@ -731,9 +694,7 @@ describe("<Notifications />", () => {
const { container } = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await waitForUpdate();
@ -760,9 +721,7 @@ describe("<Notifications />", () => {
const user = userEvent.setup();
const { container } = render(
<MatrixClientContext.Provider value={cli}>
<Form.Root>
<NotificationSettings2 />
</Form.Root>
<NotificationSettings2 />
</MatrixClientContext.Provider>,
);
await waitForUpdate();

View File

@ -38,11 +38,11 @@ exports[`AdvancedRoomSettingsTab should render as expected 1`] = `
<span>
Internal room ID
</span>
<div
<span
class="mx_CopyableText mx_CopyableText_border"
>
!room:example.com
<div
<button
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
@ -62,8 +62,8 @@ exports[`AdvancedRoomSettingsTab should render as expected 1`] = `
d="M8 10a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2zm2 0v9h9v-9z"
/>
</svg>
</div>
</div>
</button>
</span>
</div>
</div>
</div>

View File

@ -186,7 +186,7 @@ exports[`<SecurityRoomSettingsTab /> join rule warns when trying to make an encr
<span>
To avoid these issues, create a
<div
<a
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
@ -194,7 +194,7 @@ exports[`<SecurityRoomSettingsTab /> join rule warns when trying to make an encr
new public room
</div>
</a>
for the conversation you plan to have.
</span>

View File

@ -32,13 +32,13 @@ HTMLCollection [
class="mx_DeviceSecurityCard_description"
>
This session doesn't support encryption and thus can't be verified.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
</div>
</div>,
@ -238,13 +238,13 @@ exports[`<SessionManagerTab /> current session section renders current session s
class="mx_DeviceSecurityCard_description"
>
Your current session is ready for secure messaging.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
</div>
</div>
@ -411,13 +411,13 @@ exports[`<SessionManagerTab /> current session section renders current session s
class="mx_DeviceSecurityCard_description"
>
Verify your current session for enhanced secure messaging.
<div
<button
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</button>
</p>
<div
class="mx_DeviceSecurityCard_actions"

View File

@ -167,75 +167,75 @@ exports[`<SpaceSettingsVisibilityTab /> for a public space renders addresses sec
</form>
</div>
</fieldset>
<form
class="_root_19upo_16"
<div
class="mx_SettingsSection"
>
<div
class="mx_SettingsSection"
<h3
class="mx_Heading_h4"
>
<h3
class="mx_Heading_h4"
Address
</h3>
<div
class="mx_SettingsSection_subSections"
>
<fieldset
class="mx_SettingsFieldset"
data-testid="published-address-fieldset"
>
Address
</h3>
<div
class="mx_SettingsSection_subSections"
>
<fieldset
class="mx_SettingsFieldset"
data-testid="published-address-fieldset"
<legend
class="mx_SettingsFieldset_legend"
>
Published Addresses
</legend>
<div
class="mx_SettingsFieldset_description"
>
<legend
class="mx_SettingsFieldset_legend"
>
Published Addresses
</legend>
<div
class="mx_SettingsFieldset_description"
class="mx_SettingsSubsection_text"
>
<div
class="mx_SettingsSubsection_text"
>
Published addresses can be used by anyone on any server to join your space. To publish an address, it needs to be set as a local address first.
</div>
Published addresses can be used by anyone on any server to join your space. To publish an address, it needs to be set as a local address first.
</div>
</div>
<div
class="mx_SettingsFieldset_content"
>
<div
class="mx_SettingsFieldset_content"
class="mx_Field mx_Field_select"
>
<div
class="mx_Field mx_Field_select"
<select
disabled=""
id="canonicalAlias"
label="Main address"
placeholder="Main address"
type="text"
>
<select
disabled=""
id="canonicalAlias"
label="Main address"
placeholder="Main address"
type="text"
<option
value=""
>
<option
value=""
>
not specified
</option>
</select>
<label
for="canonicalAlias"
>
Main address
</label>
<svg
class="mx_Field_select_chevron"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
not specified
</option>
</select>
<label
for="canonicalAlias"
>
Main address
</label>
<svg
class="mx_Field_select_chevron"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 14.95q-.2 0-.375-.062a.9.9 0 0 1-.325-.213l-4.6-4.6a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l3.9 3.9 3.9-3.9a.95.95 0 0 1 .7-.275q.425 0 .7.275a.95.95 0 0 1 .275.7.95.95 0 0 1-.275.7l-4.6 4.6q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
<form
class="_root_19upo_16"
>
<div
class="_inline-field_19upo_32"
>
@ -274,59 +274,65 @@ exports[`<SpaceSettingsVisibilityTab /> for a public space renders addresses sec
</span>
</div>
</div>
<datalist
id="mx_AliasSettings_altRecommendations"
/>
</form>
<datalist
id="mx_AliasSettings_altRecommendations"
/>
<div
class="mx_EditableItemList"
id="roomAltAliases"
>
<div
class="mx_EditableItemList_label"
>
No other published addresses yet, add one below
</div>
<ul />
<div />
</div>
</div>
</fieldset>
<fieldset
class="mx_SettingsFieldset"
data-testid="local-address-fieldset"
>
<legend
class="mx_SettingsFieldset_legend"
>
Local Addresses
</legend>
<div
class="mx_SettingsFieldset_description"
>
<div
class="mx_SettingsSubsection_text"
>
Set addresses for this space so users can find this space through your homeserver (matrix.org)
</div>
</div>
<div
class="mx_SettingsFieldset_content"
>
<details>
<summary
class="mx_AliasSettings_localAddresses"
>
Show more
</summary>
<div
class="mx_EditableItemList"
id="roomAltAliases"
id="roomAliases"
>
<div
class="mx_EditableItemList_label"
>
No other published addresses yet, add one below
This space has no local addresses
</div>
<ul />
<div />
</div>
</div>
</fieldset>
<fieldset
class="mx_SettingsFieldset"
data-testid="local-address-fieldset"
>
<legend
class="mx_SettingsFieldset_legend"
>
Local Addresses
</legend>
<div
class="mx_SettingsFieldset_description"
>
<div
class="mx_SettingsSubsection_text"
>
Set addresses for this space so users can find this space through your homeserver (matrix.org)
</div>
</div>
<div
class="mx_SettingsFieldset_content"
>
<details>
<summary
class="mx_AliasSettings_localAddresses"
<form
autocomplete="off"
class="mx_EditableItemList_newItem"
novalidate=""
>
Show more
</summary>
<div
class="mx_EditableItemList"
id="roomAliases"
>
<div
class="mx_EditableItemList_label"
>
This space has no local addresses
</div>
<div
class="mx_Field mx_Field_input mx_RoomAliasField mx_Field_labelAlwaysTopLeft"
>
@ -368,13 +374,13 @@ exports[`<SpaceSettingsVisibilityTab /> for a public space renders addresses sec
>
Add
</div>
</div>
</details>
</div>
</fieldset>
</div>
</form>
</div>
</details>
</div>
</fieldset>
</div>
</form>
</div>
</div>
</div>
</div>
@ -528,9 +534,6 @@ exports[`<SpaceSettingsVisibilityTab /> renders container 1`] = `
</form>
</div>
</fieldset>
<form
class="_root_19upo_16"
/>
</div>
</div>
</div>

View File

@ -0,0 +1,18 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { onSubmitPreventDefault } from "../../../src/utils/form.ts";
describe("onSubmitPreventDefault", () => {
it("should preventDefault", () => {
const event = new SubmitEvent("submit");
const spy = jest.spyOn(event, "preventDefault");
onSubmitPreventDefault(event);
expect(spy).toHaveBeenCalled();
});
});