Update react monorepo to v19 (major) (#28914)

* Update react monorepo to v19

* Import JSX explicitly for React 19 compatibility

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update usages of refs for React 19 compatibility

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update react imports

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Avoid legacy contexts as much as possible

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Avoid deprecated React symbols

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Stash

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update usages of refs for React 19 compatibility

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Switch pillify to use a html-react-parser approach rather than DOM muddling

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate react html parsing

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate react html parsing

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate html parsing

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Memoize the EventContentBody component

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate html parsing

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Simplify

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Discard changes to src/Linkify.tsx

* Discard changes to src/components/views/messages/TextualBody.tsx

* Discard changes to src/settings/handlers/AbstractLocalStorageSettingsHandler.ts

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Prepare for React 19 upgrade

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove stale comment

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
renovate[bot]
2025-04-09 19:03:09 +00:00
committed by GitHub
parent f54fbf7231
commit e1b2e3a101
44 changed files with 300 additions and 290 deletions

View File

@@ -69,9 +69,10 @@
"postinstall": "patch-package" "postinstall": "patch-package"
}, },
"resolutions": { "resolutions": {
"**/pretty-format/react-is": "19.0.0",
"@playwright/test": "1.51.1", "@playwright/test": "1.51.1",
"@types/react": "18.3.18", "@types/react": "19.0.10",
"@types/react-dom": "18.3.5", "@types/react-dom": "19.0.4",
"oidc-client-ts": "3.2.0", "oidc-client-ts": "3.2.0",
"jwt-decode": "4.0.0", "jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001707", "caniuse-lite": "1.0.30001707",
@@ -141,10 +142,10 @@
"posthog-js": "1.157.2", "posthog-js": "1.157.2",
"qrcode": "1.5.4", "qrcode": "1.5.4",
"re-resizable": "6.11.2", "re-resizable": "6.11.2",
"react": "^18.3.1", "react": "^19.0.0",
"react-beautiful-dnd": "^13.1.0", "react-beautiful-dnd": "^13.1.0",
"react-blurhash": "^0.3.0", "react-blurhash": "^0.3.0",
"react-dom": "^18.3.1", "react-dom": "^19.0.0",
"react-focus-lock": "^2.5.1", "react-focus-lock": "^2.5.1",
"react-string-replace": "^1.1.1", "react-string-replace": "^1.1.1",
"react-transition-group": "^4.4.1", "react-transition-group": "^4.4.1",
@@ -211,9 +212,9 @@
"@types/node-fetch": "^2.6.2", "@types/node-fetch": "^2.6.2",
"@types/pako": "^2.0.0", "@types/pako": "^2.0.0",
"@types/qrcode": "^1.3.5", "@types/qrcode": "^1.3.5",
"@types/react": "18.3.18", "@types/react": "19.0.10",
"@types/react-beautiful-dnd": "^13.0.0", "@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "18.3.5", "@types/react-dom": "19.0.4",
"@types/react-transition-group": "^4.4.0", "@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "2.15.0", "@types/sanitize-html": "2.15.0",
"@types/semver": "^7.5.8", "@types/semver": "^7.5.8",

View File

@@ -1,76 +0,0 @@
diff --git a/node_modules/@types/react/index.d.ts b/node_modules/@types/react/index.d.ts
index 6ea73ef..cb51757 100644
--- a/node_modules/@types/react/index.d.ts
+++ b/node_modules/@types/react/index.d.ts
@@ -151,7 +151,7 @@ declare namespace React {
/**
* The current value of the ref.
*/
- readonly current: T | null;
+ current: T;
}
interface DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES {
@@ -186,7 +186,7 @@ declare namespace React {
* @see {@link RefObject}
*/
- type Ref<T> = RefCallback<T> | RefObject<T> | null;
+ type Ref<T> = RefCallback<T> | RefObject<T | null> | null;
/**
* A legacy implementation of refs where you can pass a string to a ref prop.
*
@@ -300,7 +300,7 @@ declare namespace React {
*
* @see {@link https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom React Docs}
*/
- ref?: LegacyRef<T> | undefined;
+ ref?: LegacyRef<T | null> | undefined;
}
/**
@@ -1234,7 +1234,7 @@ declare namespace React {
*
* @see {@link ForwardRefRenderFunction}
*/
- type ForwardedRef<T> = ((instance: T | null) => void) | MutableRefObject<T | null> | null;
+ type ForwardedRef<T> = ((instance: T | null) => void) | RefObject<T | null> | null;
/**
* The type of the function passed to {@link forwardRef}. This is considered different
@@ -1565,7 +1565,7 @@ declare namespace React {
[propertyName: string]: any;
}
- function createRef<T>(): RefObject<T>;
+ function createRef<T>(): RefObject<T | null>;
/**
* The type of the component returned from {@link forwardRef}.
@@ -1989,7 +1989,7 @@ declare namespace React {
* @version 16.8.0
* @see {@link https://react.dev/reference/react/useRef}
*/
- function useRef<T>(initialValue: T): MutableRefObject<T>;
+ function useRef<T>(initialValue: T): RefObject<T>;
// convenience overload for refs given as a ref prop as they typically start with a null value
/**
* `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
@@ -2004,7 +2004,7 @@ declare namespace React {
* @version 16.8.0
* @see {@link https://react.dev/reference/react/useRef}
*/
- function useRef<T>(initialValue: T | null): RefObject<T>;
+ function useRef<T>(initialValue: T | null): RefObject<T | null>;
// convenience overload for potentially undefined initialValue / call with 0 arguments
// has a default to stop it from defaulting to {} instead
/**
@@ -2017,7 +2017,7 @@ declare namespace React {
* @version 16.8.0
* @see {@link https://react.dev/reference/react/useRef}
*/
- function useRef<T = undefined>(initialValue?: undefined): MutableRefObject<T | undefined>;
+ function useRef<T>(initialValue: T | undefined): RefObject<T | undefined>;
/**
* The signature is identical to `useEffect`, but it fires synchronously after all DOM mutations.
* Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside

View File

@@ -0,0 +1,31 @@
diff --git a/node_modules/@types/react/index.d.ts b/node_modules/@types/react/index.d.ts
index 2272032..18bd20a 100644
--- a/node_modules/@types/react/index.d.ts
+++ b/node_modules/@types/react/index.d.ts
@@ -134,7 +134,7 @@ declare namespace React {
props: P,
) => ReactNode | Promise<ReactNode>)
// constructor signature must match React.Component
- | (new(props: P) => Component<any, any>);
+ | (new(props: P, context?: any) => Component<any, any>);
/**
* Created by {@link createRef}, or {@link useRef} when passed `null`.
@@ -941,7 +941,7 @@ declare namespace React {
context: unknown;
// Keep in sync with constructor signature of JSXElementConstructor and ComponentClass.
- constructor(props: P);
+ constructor(props: P, context?: unknown);
// We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
@@ -1113,7 +1113,7 @@ declare namespace React {
*/
interface ComponentClass<P = {}, S = ComponentState> extends StaticLifecycle<P, S> {
// constructor signature must match React.Component
- new(props: P): Component<P, S>;
+ new(props: P, context?: any): Component<P, S>;
/**
* Ignored by React.
* @deprecated Only kept in types for backwards compatibility. Will be removed in a future major release.

View File

@@ -0,0 +1,22 @@
diff --git a/node_modules/react-blurhash/dist/index.d.ts b/node_modules/react-blurhash/dist/index.d.ts
index 3adbd0a..32e8c13 100644
--- a/node_modules/react-blurhash/dist/index.d.ts
+++ b/node_modules/react-blurhash/dist/index.d.ts
@@ -19,7 +19,7 @@ declare class Blurhash extends React.PureComponent<Props$1> {
resolutionY: number;
};
componentDidUpdate(): void;
- render(): JSX.Element;
+ render(): React.JSX.Element;
}
declare type Props = React.CanvasHTMLAttributes<HTMLCanvasElement> & {
@@ -37,7 +37,7 @@ declare class BlurhashCanvas extends React.PureComponent<Props> {
componentDidUpdate(): void;
handleRef: (canvas: HTMLCanvasElement) => void;
draw: () => void;
- render(): JSX.Element;
+ render(): React.JSX.Element;
}
export { Blurhash, BlurhashCanvas };

View File

@@ -43,6 +43,7 @@ test.describe("Pills", () => {
// go back to the message room and try to click on the pill text, as a user would // go back to the message room and try to click on the pill text, as a user would
await app.viewRoomByName(messageRoom); await app.viewRoomByName(messageRoom);
await expect(page).toHaveURL(new RegExp(`/#/room/${messageRoomId}`));
const pillText = page.locator(".mx_EventTile_body .mx_Pill .mx_Pill_text"); const pillText = page.locator(".mx_EventTile_body .mx_Pill .mx_Pill_text");
await expect(pillText).toHaveCSS("pointer-events", "none"); await expect(pillText).toHaveCSS("pointer-events", "none");
await pillText.click({ force: true }); // force is to ensure we bypass pointer-events await pillText.click({ force: true }); // force is to ensure we bypass pointer-events

View File

@@ -18,4 +18,9 @@ declare module "react" {
// Fix lazy types - https://stackoverflow.com/a/71017028 // Fix lazy types - https://stackoverflow.com/a/71017028
function lazy<T extends ComponentType<any>>(factory: () => Promise<{ default: T }>): T; function lazy<T extends ComponentType<any>>(factory: () => Promise<{ default: T }>): T;
// Standardize defaultProps for FunctionComponent so we can write generics assuming `defaultProps` exists on ComponentType
interface FunctionComponent {
defaultProps?: unknown;
}
} }

View File

@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { type JSX, type LegacyRef, type ReactNode } from "react"; import React, { type JSX, type Key, type LegacyRef, type ReactNode } from "react";
import sanitizeHtml, { type IOptions } from "sanitize-html"; import sanitizeHtml, { type IOptions } from "sanitize-html";
import classNames from "classnames"; import classNames from "classnames";
import katex from "katex"; import katex from "katex";
@@ -239,7 +239,7 @@ class HtmlHighlighter extends BaseHighlighter<string> {
const emojiToHtmlSpan = (emoji: string): string => const emojiToHtmlSpan = (emoji: string): string =>
`<span class='mx_Emoji' title='${unicodeToShortcode(emoji)}'>${emoji}</span>`; `<span class='mx_Emoji' title='${unicodeToShortcode(emoji)}'>${emoji}</span>`;
const emojiToJsxSpan = (emoji: string, key: number): JSX.Element => ( const emojiToJsxSpan = (emoji: string, key: Key): JSX.Element => (
<span key={key} className="mx_Emoji" title={unicodeToShortcode(emoji)}> <span key={key} className="mx_Emoji" title={unicodeToShortcode(emoji)}>
{emoji} {emoji}
</span> </span>

View File

@@ -321,7 +321,7 @@ async function attemptOidcNativeLogin(queryParams: QueryDict): Promise<boolean>
} catch (error) { } catch (error) {
logger.error("Failed to login via OIDC", error); logger.error("Failed to login via OIDC", error);
await onFailedDelegatedAuthLogin(getOidcErrorMessage(error as Error)); onFailedDelegatedAuthLogin(getOidcErrorMessage(error as Error));
return false; return false;
} }
} }
@@ -468,7 +468,7 @@ type TryAgainFunction = () => void;
* @param description error description * @param description error description
* @param tryAgain OPTIONAL function to call on try again button from error dialog * @param tryAgain OPTIONAL function to call on try again button from error dialog
*/ */
async function onFailedDelegatedAuthLogin(description: string | ReactNode, tryAgain?: TryAgainFunction): Promise<void> { function onFailedDelegatedAuthLogin(description: string | ReactNode, tryAgain?: TryAgainFunction): void {
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("auth|oidc|error_title"), title: _t("auth|oidc|error_title"),
description, description,

View File

@@ -212,7 +212,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
scrollIntoView, scrollIntoView,
onKeyDown, onKeyDown,
}) => { }) => {
const [state, dispatch] = useReducer<Reducer<IState, Action>>(reducer, { const [state, dispatch] = useReducer<IState, [Action]>(reducer, {
nodes: [], nodes: [],
}); });

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { type ReactElement } from "react"; import { type ReactElement, type RefAttributes, type HTMLAttributes } from "react";
import { type Room } from "matrix-js-sdk/src/matrix"; import { type Room } from "matrix-js-sdk/src/matrix";
import CommandProvider from "./CommandProvider"; import CommandProvider from "./CommandProvider";
@@ -31,7 +31,7 @@ export interface ICompletion {
type?: "at-room" | "command" | "community" | "room" | "user"; type?: "at-room" | "command" | "community" | "room" | "user";
completion: string; completion: string;
completionId?: string; completionId?: string;
component: ReactElement; component: ReactElement<RefAttributes<HTMLElement> & HTMLAttributes<HTMLElement>>;
range: ISelectionRange; range: ISelectionRange;
command?: string; command?: string;
suffix?: string; suffix?: string;

View File

@@ -21,8 +21,6 @@ export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<AutoHideScrollb
// scroll horizontally rather than vertically. This should only be used on components // scroll horizontally rather than vertically. This should only be used on components
// with no vertical scroll opportunity. // with no vertical scroll opportunity.
verticalScrollsHorizontally?: boolean; verticalScrollsHorizontally?: boolean;
children: React.ReactNode;
}; };
interface IState { interface IState {

View File

@@ -165,12 +165,6 @@ interface IProps {
initialScreenAfterLogin?: IScreen; initialScreenAfterLogin?: IScreen;
// displayname, if any, to set on the device when logging in/registering. // displayname, if any, to set on the device when logging in/registering.
defaultDeviceDisplayName?: string; defaultDeviceDisplayName?: string;
// Used by tests, this function is called when session initialisation starts
// with a promise that resolves or rejects once the initialiation process
// has finished, so that tests can wait for this to avoid them executing over
// each other.
initPromiseCallback?: (p: Promise<void>) => void;
} }
interface IState { interface IState {
@@ -291,9 +285,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
*/ */
private startInitSession = (): void => { private startInitSession = (): void => {
const initProm = this.initSession(); const initProm = this.initSession();
if (this.props.initPromiseCallback) {
this.props.initPromiseCallback(initProm);
}
initProm.catch((err) => { initProm.catch((err) => {
// TODO: show an error screen, rather than a spinner of doom // TODO: show an error screen, rather than a spinner of doom
@@ -1002,10 +993,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// Wait for the first sync to complete so that if a room does have an alias, // Wait for the first sync to complete so that if a room does have an alias,
// it would have been retrieved. // it would have been retrieved.
if (!this.firstSyncComplete) { if (!this.firstSyncComplete) {
if (!this.firstSyncPromise) {
logger.warn("Cannot view a room before first sync. room_id:", roomInfo.room_id);
return;
}
await this.firstSyncPromise.promise; await this.firstSyncPromise.promise;
} }
@@ -1116,8 +1103,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
private viewUser(userId: string, subAction: string): void { private viewUser(userId: string, subAction: string): void {
// Wait for the first sync so that `getRoom` gives us a room object if it's // Wait for the first sync so that `getRoom` gives us a room object if it's
// in the sync response // in the sync response
const waitForSync = this.firstSyncPromise ? this.firstSyncPromise.promise : Promise.resolve(); this.firstSyncPromise.promise.then(() => {
waitForSync.then(() => {
if (subAction === "chat") { if (subAction === "chat") {
this.chatCreateOrReuse(userId); this.chatCreateOrReuse(userId);
return; return;
@@ -1480,11 +1466,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
* (useful for setting listeners) * (useful for setting listeners)
*/ */
private onWillStartClient(): void { private onWillStartClient(): void {
// reset the 'have completed first sync' flag, // Reset the 'have completed first sync' flag,
// since we're about to start the client and therefore about // since we're about to start the client and therefore about to do the first sync
// to do the first sync // We resolve the existing promise with the new one to update any existing listeners
if (!this.firstSyncComplete) {
const firstSyncPromise = defer<void>();
this.firstSyncPromise.resolve(firstSyncPromise.promise);
this.firstSyncPromise = firstSyncPromise;
} else {
this.firstSyncPromise = defer();
}
this.firstSyncComplete = false; this.firstSyncComplete = false;
this.firstSyncPromise = defer();
const cli = MatrixClientPeg.safeGet(); const cli = MatrixClientPeg.safeGet();
// Allow the JS SDK to reap timeline events. This reduces the amount of // Allow the JS SDK to reap timeline events. This reduces the amount of

View File

@@ -27,14 +27,14 @@ export class Tab<T extends string> {
* @param {string} id The tab's ID. * @param {string} id The tab's ID.
* @param {string} label The untranslated tab label. * @param {string} label The untranslated tab label.
* @param {string|JSX.Element} icon An SVG element to use for the tab icon. Can also be a string for legacy icons, in which case it is the class for the tab icon. This should be a simple mask. * @param {string|JSX.Element} icon An SVG element to use for the tab icon. Can also be a string for legacy icons, in which case it is the class for the tab icon. This should be a simple mask.
* @param {React.ReactNode} body The JSX for the tab container. * @param {JSX.Element} body The JSX for the tab container.
* @param {string} screenName The screen name to report to Posthog. * @param {string} screenName The screen name to report to Posthog.
*/ */
public constructor( public constructor(
public readonly id: T, public readonly id: T,
public readonly label: TranslationKey, public readonly label: TranslationKey,
public readonly icon: string | JSX.Element | null, public readonly icon: string | JSX.Element | null,
public readonly body: React.ReactNode, public readonly body: JSX.Element,
public readonly screenName?: ScreenName, public readonly screenName?: ScreenName,
) {} ) {}
} }

View File

@@ -6,10 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { createContext, type Dispatch, type ReducerAction, type ReducerState } from "react"; import { createContext, type Dispatch, type Reducer, type ReducerState } from "react";
import type { AuthHeaderReducer } from "./AuthHeaderProvider"; import type { AuthHeaderReducer } from "./AuthHeaderProvider";
type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;
interface AuthHeaderContextType { interface AuthHeaderContextType {
state: ReducerState<AuthHeaderReducer>; state: ReducerState<AuthHeaderReducer>;
dispatch: Dispatch<ReducerAction<AuthHeaderReducer>>; dispatch: Dispatch<ReducerAction<AuthHeaderReducer>>;

View File

@@ -25,7 +25,7 @@ interface AuthHeaderAction {
export type AuthHeaderReducer = Reducer<ComponentProps<typeof AuthHeaderModifier>[], AuthHeaderAction>; export type AuthHeaderReducer = Reducer<ComponentProps<typeof AuthHeaderModifier>[], AuthHeaderAction>;
export function AuthHeaderProvider({ children }: PropsWithChildren): JSX.Element { export function AuthHeaderProvider({ children }: PropsWithChildren): JSX.Element {
const [state, dispatch] = useReducer<AuthHeaderReducer>( const [state, dispatch] = useReducer<ComponentProps<typeof AuthHeaderModifier>[], [AuthHeaderAction]>(
(state: ComponentProps<typeof AuthHeaderModifier>[], action: AuthHeaderAction) => { (state: ComponentProps<typeof AuthHeaderModifier>[], action: AuthHeaderAction) => {
switch (action.type) { switch (action.type) {
case AuthHeaderActionType.Add: case AuthHeaderActionType.Add:

View File

@@ -18,6 +18,7 @@ import { CardContext } from "../right_panel/context";
import UserIdentifierCustomisations from "../../../customisations/UserIdentifier"; import UserIdentifierCustomisations from "../../../customisations/UserIdentifier";
import { useRoomMemberProfile } from "../../../hooks/room/useRoomMemberProfile"; import { useRoomMemberProfile } from "../../../hooks/room/useRoomMemberProfile";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import MatrixClientContext from "../../../contexts/MatrixClientContext.tsx";
interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> { interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> {
member: RoomMember | null; member: RoomMember | null;
@@ -47,6 +48,7 @@ function MemberAvatar(
}: IProps, }: IProps,
ref: Ref<HTMLElement>, ref: Ref<HTMLElement>,
): JSX.Element { ): JSX.Element {
const cli = useContext(MatrixClientContext);
const card = useContext(CardContext); const card = useContext(CardContext);
const member = useRoomMemberProfile({ const member = useRoomMemberProfile({
@@ -60,7 +62,7 @@ function MemberAvatar(
let imageUrl: string | null | undefined; let imageUrl: string | null | undefined;
if (member?.name) { if (member?.name) {
if (member.getMxcAvatarUrl()) { if (member.getMxcAvatarUrl()) {
imageUrl = mediaFromMxc(member.getMxcAvatarUrl() ?? "").getThumbnailOfSourceHttp( imageUrl = mediaFromMxc(member.getMxcAvatarUrl() ?? "", cli).getThumbnailOfSourceHttp(
parseInt(size, 10), parseInt(size, 10),
parseInt(size, 10), parseInt(size, 10),
resizeMethod, resizeMethod,

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { createRef } from "react"; import React, { createRef, type RefObject } from "react";
import { type DialogContent, type DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent"; import { type DialogContent, type DialogProps } from "@matrix-org/react-sdk-module-api/lib/components/DialogContent";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { type ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi"; import { type ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
@@ -27,10 +27,10 @@ interface IState extends IScrollableBaseState {
// nothing special // nothing special
} }
export class ModuleUiDialog<P extends DialogProps, C extends DialogContent<P>> extends ScrollableBaseModal< export class ModuleUiDialog<
IProps<P, C>, P extends DialogProps = DialogProps,
IState C extends DialogContent<P> = DialogContent<P>,
> { > extends ScrollableBaseModal<IProps<P, C>, IState> {
private contentRef = createRef<C>(); private contentRef = createRef<C>();
public constructor(props: IProps<P, C>) { public constructor(props: IProps<P, C>) {
@@ -74,6 +74,11 @@ export class ModuleUiDialog<P extends DialogProps, C extends DialogContent<P>> e
...dialogProps, ...dialogProps,
} as unknown as P; } as unknown as P;
return <div className="mx_ModuleUiDialog">{this.props.contentFactory(contentProps, this.contentRef)}</div>; // XXX: we have to fudge the types here a little as the react-sdk-module-api lacks React 19 support
return (
<div className="mx_ModuleUiDialog">
{this.props.contentFactory(contentProps, this.contentRef as RefObject<C>)}
</div>
);
} }
} }

View File

@@ -135,12 +135,19 @@ const AccessibleButton = forwardRef(function <T extends ElementType = typeof def
placement = "right", placement = "right",
onTooltipOpenChange, onTooltipOpenChange,
disableTooltip, disableTooltip,
role = "button",
tabIndex = 0,
...restProps ...restProps
}: ButtonProps<T>, }: ButtonProps<T>,
ref: Ref<HTMLElementTagNameMap[T]>, ref: Ref<HTMLElementTagNameMap[T]>,
): JSX.Element { ): JSX.Element {
const newProps = restProps as RenderedElementProps<T>; const newProps = {
newProps["aria-label"] = newProps["aria-label"] ?? title; ...restProps,
tabIndex,
role,
"aria-label": restProps["aria-label"] ?? title,
} as RenderedElementProps<T>;
if (disabled) { if (disabled) {
newProps["aria-disabled"] = true; newProps["aria-disabled"] = true;
newProps["disabled"] = true; newProps["disabled"] = true;
@@ -222,10 +229,6 @@ const AccessibleButton = forwardRef(function <T extends ElementType = typeof def
}); });
// Type assertion required due to forwardRef type workaround in react.d.ts // Type assertion required due to forwardRef type workaround in react.d.ts
(AccessibleButton as FunctionComponent).defaultProps = {
role: "button",
tabIndex: 0,
};
(AccessibleButton as FunctionComponent).displayName = "AccessibleButton"; (AccessibleButton as FunctionComponent).displayName = "AccessibleButton";
interface RefProp<T extends ElementType> { interface RefProp<T extends ElementType> {

View File

@@ -6,7 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { type JSX, type ChangeEvent, type ContextType, createRef, type SyntheticEvent } from "react"; import React, {
type JSX,
type ToggleEvent,
type ChangeEvent,
type ContextType,
createRef,
type SyntheticEvent,
} from "react";
import { type MatrixEvent, EventType } from "matrix-js-sdk/src/matrix"; import { type MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { type RoomCanonicalAliasEventContent } from "matrix-js-sdk/src/types"; import { type RoomCanonicalAliasEventContent } from "matrix-js-sdk/src/types";
@@ -278,7 +285,7 @@ export default class AliasSettings extends React.Component<IProps, IState> {
}); });
}; };
private onLocalAliasesToggled = (event: ChangeEvent<HTMLDetailsElement>): void => { private onLocalAliasesToggled = (event: ToggleEvent<HTMLDetailsElement>): void => {
// expanded // expanded
if (event.currentTarget.open) { if (event.currentTarget.open) {
// if local aliases haven't been preloaded yet at component mount // if local aliases haven't been preloaded yet at component mount

View File

@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { type ReactElement, useCallback, useEffect, useState } from "react"; import React, { type JSX, type ReactElement, useCallback, useEffect, useState } from "react";
import { type NonEmptyArray } from "../../../../../@types/common"; import { type NonEmptyArray } from "../../../../../@types/common";
import { _t, getCurrentLanguage } from "../../../../../languageHandler"; import { _t, getCurrentLanguage } from "../../../../../languageHandler";

View File

@@ -198,7 +198,7 @@ export const options: Opts = {
rel: "noreferrer noopener", rel: "noreferrer noopener",
}, },
ignoreTags: ["pre", "code"], ignoreTags: ["a", "pre", "code"],
className: "linkified", className: "linkified",

View File

@@ -9,15 +9,20 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, type ReactNode } from "react"; import React, { type JSX, type ReactNode } from "react";
/** /**
* Joins an array into one value with a joiner. E.g. join(["hello", "world"], " ") -> <span>hello world</span> * Joins an array into one value with a joiner. E.g. join(["hello", "world"], " ") -> <>hello world</>
* @param array the array of element to join * @param array the array of element to join
* @param joiner the string/JSX.Element to join with * @param joiner the string/JSX.Element to join with
* @returns the joined array * @returns the joined array
*/ */
export function jsxJoin(array: ReactNode[], joiner?: string | JSX.Element): JSX.Element { export function jsxJoin(array: ReactNode[], joiner?: string | JSX.Element): JSX.Element {
const newArray: ReactNode[] = []; return (
array.forEach((element, index) => { <>
newArray.push(element, index === array.length - 1 ? null : joiner); {array.map((element, index) => (
}); <React.Fragment key={index}>
return <span>{newArray}</span>; {element}
{index === array.length - 1 ? null : joiner}
</React.Fragment>
))}
</>
);
} }

View File

@@ -10,7 +10,6 @@ import { type IWidget } from "matrix-widget-api";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { type Room } from "matrix-js-sdk/src/matrix"; import { type Room } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../MatrixClientPeg";
import { getCallBehaviourWellKnown } from "../utils/WellKnownUtils"; import { getCallBehaviourWellKnown } from "../utils/WellKnownUtils";
import WidgetUtils from "../utils/WidgetUtils"; import WidgetUtils from "../utils/WidgetUtils";
import { type IStoredLayout, WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore"; import { type IStoredLayout, WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
@@ -37,7 +36,7 @@ function getWidgetBuildUrl(room: Room): string | undefined {
return SdkConfig.get().widget_build_url; return SdkConfig.get().widget_build_url;
} }
const wellKnown = getCallBehaviourWellKnown(MatrixClientPeg.safeGet()); const wellKnown = getCallBehaviourWellKnown(room.client);
if (isDm && wellKnown?.ignore_dm) { if (isDm && wellKnown?.ignore_dm) {
return undefined; return undefined;
} }

View File

@@ -153,7 +153,7 @@ export const mockClientMethodsCrypto = (): Partial<
> => ({ > => ({
isKeyBackupKeyStored: jest.fn(), isKeyBackupKeyStored: jest.fn(),
getCrossSigningCacheCallbacks: jest.fn().mockReturnValue({ getCrossSigningKeyCache: jest.fn() }), getCrossSigningCacheCallbacks: jest.fn().mockReturnValue({ getCrossSigningKeyCache: jest.fn() }),
secretStorage: { hasKey: jest.fn() }, secretStorage: { hasKey: jest.fn(), isStored: jest.fn().mockResolvedValue(null) },
getCrypto: jest.fn().mockReturnValue({ getCrypto: jest.fn().mockReturnValue({
getUserDeviceInfo: jest.fn(), getUserDeviceInfo: jest.fn(),
getCrossSigningStatus: jest.fn().mockResolvedValue({ getCrossSigningStatus: jest.fn().mockResolvedValue({

View File

@@ -7,8 +7,6 @@ exports[`TextForEvent textForJoinRulesEvent() returns correct JSX message when r
<AccessibleButton <AccessibleButton
kind="link_inline" kind="link_inline"
onClick={[Function]} onClick={[Function]}
role="button"
tabIndex={0}
> >
View settings View settings
</AccessibleButton> </AccessibleButton>

View File

@@ -33,6 +33,10 @@ const EMOJI_SHORTCODES = [
// to simply assert that the final completion with the colon is the exact emoji. // to simply assert that the final completion with the colon is the exact emoji.
const TOO_SHORT_EMOJI_SHORTCODE = [{ emojiShortcode: ":o", expectedEmoji: "⭕️" }]; const TOO_SHORT_EMOJI_SHORTCODE = [{ emojiShortcode: ":o", expectedEmoji: "⭕️" }];
interface CompletionComponentProps {
title: string;
}
describe("EmojiProvider", function () { describe("EmojiProvider", function () {
const testRoom = mkStubRoom(undefined, undefined, undefined); const testRoom = mkStubRoom(undefined, undefined, undefined);
stubClient(); stubClient();
@@ -69,8 +73,8 @@ describe("EmojiProvider", function () {
const ep = new EmojiProvider(testRoom); const ep = new EmojiProvider(testRoom);
const completionsList = await ep.getCompletions(":heart", { beginning: true, start: 0, end: 6 }); const completionsList = await ep.getCompletions(":heart", { beginning: true, start: 0, end: 6 });
expect(completionsList[0]?.component?.props.title).toEqual(":heartpulse:"); expect((completionsList[0]?.component?.props as CompletionComponentProps).title).toEqual(":heartpulse:");
expect(completionsList[1]?.component?.props.title).toEqual(":heart_eyes:"); expect((completionsList[1]?.component?.props as CompletionComponentProps).title).toEqual(":heart_eyes:");
}); });
it("Exact match in recently used takes the lead", async function () { it("Exact match in recently used takes the lead", async function () {
@@ -83,8 +87,8 @@ describe("EmojiProvider", function () {
const ep = new EmojiProvider(testRoom); const ep = new EmojiProvider(testRoom);
const completionsList = await ep.getCompletions(":heart", { beginning: true, start: 0, end: 6 }); const completionsList = await ep.getCompletions(":heart", { beginning: true, start: 0, end: 6 });
expect(completionsList[0]?.component?.props.title).toEqual(":heart:"); expect((completionsList[0]?.component?.props as CompletionComponentProps).title).toEqual(":heart:");
expect(completionsList[1]?.component?.props.title).toEqual(":heartpulse:"); expect((completionsList[1]?.component?.props as CompletionComponentProps).title).toEqual(":heartpulse:");
expect(completionsList[2]?.component?.props.title).toEqual(":heart_eyes:"); expect((completionsList[2]?.component?.props as CompletionComponentProps).title).toEqual(":heart_eyes:");
}); });
}); });

View File

@@ -63,12 +63,12 @@ describe("AutocompleteInput", () => {
const input = getEditorInput(); const input = getEditorInput();
act(() => { await act(async () => {
fireEvent.focus(input); fireEvent.focus(input);
fireEvent.change(input, { target: { value: "user" } }); fireEvent.change(input, { target: { value: "user" } });
await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
}); });
await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
expect(screen.getByTestId("autocomplete-matches").childNodes).toHaveLength(mockCompletion.length); expect(screen.getByTestId("autocomplete-matches").childNodes).toHaveLength(mockCompletion.length);
}); });
@@ -152,12 +152,12 @@ describe("AutocompleteInput", () => {
const input = getEditorInput(); const input = getEditorInput();
act(() => { await act(async () => {
fireEvent.focus(input); fireEvent.focus(input);
fireEvent.change(input, { target: { value: "user" } }); fireEvent.change(input, { target: { value: "user" } });
await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
}); });
await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
expect(screen.getAllByTestId("custom-suggestion-element")).toHaveLength(mockCompletion.length); expect(screen.getAllByTestId("custom-suggestion-element")).toHaveLength(mockCompletion.length);
}); });

View File

@@ -118,7 +118,7 @@ describe("<MatrixChat />", () => {
startup: jest.fn(), startup: jest.fn(),
}, },
login: jest.fn(), login: jest.fn(),
loginFlows: jest.fn(), loginFlows: jest.fn().mockResolvedValue({ flows: [] }),
isGuest: jest.fn().mockReturnValue(false), isGuest: jest.fn().mockReturnValue(false),
clearStores: jest.fn(), clearStores: jest.fn(),
setGuest: jest.fn(), setGuest: jest.fn(),
@@ -165,12 +165,9 @@ describe("<MatrixChat />", () => {
isNameResolvable: true, isNameResolvable: true,
warning: "", warning: "",
}; };
let initPromise: Promise<void> | undefined;
let defaultProps: ComponentProps<typeof MatrixChat>; let defaultProps: ComponentProps<typeof MatrixChat>;
const getComponent = (props: Partial<ComponentProps<typeof MatrixChat>> = {}) => { const getComponent = (props: Partial<ComponentProps<typeof MatrixChat>> = {}) => {
// MatrixChat does many questionable things which bomb tests in modern React mode, return render(<MatrixChat {...defaultProps} {...props} />);
// we'll want to refactor and break up MatrixChat before turning off legacyRoot mode
return render(<MatrixChat {...defaultProps} {...props} />, { legacyRoot: true });
}; };
// make test results readable // make test results readable
@@ -220,6 +217,8 @@ describe("<MatrixChat />", () => {
}; };
beforeEach(async () => { beforeEach(async () => {
localStorage.clear();
jest.restoreAllMocks();
defaultProps = { defaultProps = {
config: { config: {
brand: "Test", brand: "Test",
@@ -235,10 +234,8 @@ describe("<MatrixChat />", () => {
onNewScreen: jest.fn(), onNewScreen: jest.fn(),
onTokenLoginCompleted: jest.fn(), onTokenLoginCompleted: jest.fn(),
realQueryParams: {}, realQueryParams: {},
initPromiseCallback: (p: Promise<void>) => (initPromise = p),
}; };
initPromise = undefined;
mockClient = getMockClientWithEventEmitter(getMockClientMethods()); mockClient = getMockClientWithEventEmitter(getMockClientMethods());
jest.spyOn(MatrixJs, "createClient").mockReturnValue(mockClient); jest.spyOn(MatrixJs, "createClient").mockReturnValue(mockClient);
@@ -257,18 +254,9 @@ describe("<MatrixChat />", () => {
}); });
afterEach(async () => { afterEach(async () => {
// Wait for the promise that MatrixChat gives us to complete so that we know
// it's finished running its login code. We either need to do this or make the
// login code abort halfway through once the test finishes testing whatever it
// needs to test. If we do nothing, the login code will just continue running
// and interfere with the subsequent tests.
await initPromise;
// @ts-ignore // @ts-ignore
DMRoomMap.setShared(null); DMRoomMap.setShared(null);
jest.restoreAllMocks();
// emit a loggedOut event so that all of the Store singletons forget about their references to the mock client // emit a loggedOut event so that all of the Store singletons forget about their references to the mock client
// (must be sync otherwise the next test will start before it happens) // (must be sync otherwise the next test will start before it happens)
act(() => defaultDispatcher.dispatch({ action: Action.OnLoggedOut }, true)); act(() => defaultDispatcher.dispatch({ action: Action.OnLoggedOut }, true));
@@ -313,7 +301,6 @@ describe("<MatrixChat />", () => {
state: state, state: state,
}; };
const userId = "@alice:server.org";
const deviceId = "test-device-id"; const deviceId = "test-device-id";
const accessToken = "test-access-token-from-oidc"; const accessToken = "test-access-token-from-oidc";
@@ -335,8 +322,6 @@ describe("<MatrixChat />", () => {
const dialog = await screen.findByRole("dialog"); const dialog = await screen.findByRole("dialog");
expect(within(dialog).getByText(errorMessage)).toBeInTheDocument(); expect(within(dialog).getByText(errorMessage)).toBeInTheDocument();
// just check we're back on welcome page
await expect(screen.findByTestId("mx_welcome_screen")).resolves.toBeInTheDocument();
}; };
beforeEach(() => { beforeEach(() => {
@@ -518,6 +503,9 @@ describe("<MatrixChat />", () => {
it("should set logged in and start MatrixClient", async () => { it("should set logged in and start MatrixClient", async () => {
getComponent({ realQueryParams }); getComponent({ realQueryParams });
defaultDispatcher.dispatch({
action: "will_start_client",
});
// client successfully started // client successfully started
await waitFor(() => await waitFor(() =>
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "client_started" }), expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "client_started" }),
@@ -597,9 +585,7 @@ describe("<MatrixChat />", () => {
// wait for logged in view to load // wait for logged in view to load
await screen.findByLabelText("User menu"); await screen.findByLabelText("User menu");
expect(screen.queryByRole("progressbar")).not.toBeInTheDocument(); await screen.findByRole("heading", { level: 1, name: "Welcome Ernie" });
const h1Element = screen.getByRole("heading", { level: 1 });
expect(h1Element).toHaveTextContent(`Welcome Ernie`);
}); });
describe("clean up drafts", () => { describe("clean up drafts", () => {
@@ -612,9 +598,6 @@ describe("<MatrixChat />", () => {
localStorage.setItem(`mx_cider_state_${roomId}`, "fake_content"); localStorage.setItem(`mx_cider_state_${roomId}`, "fake_content");
mockClient.getRoom.mockImplementation((id) => [room].find((room) => room.roomId === id) || null); mockClient.getRoom.mockImplementation((id) => [room].find((room) => room.roomId === id) || null);
}); });
afterEach(() => {
jest.restoreAllMocks();
});
it("should clean up drafts", async () => { it("should clean up drafts", async () => {
Date.now = jest.fn(() => timestamp); Date.now = jest.fn(() => timestamp);
localStorage.setItem(`mx_cider_state_${roomId}`, "fake_content"); localStorage.setItem(`mx_cider_state_${roomId}`, "fake_content");
@@ -1113,8 +1096,6 @@ describe("<MatrixChat />", () => {
"org.matrix.e2e_cross_signing", "org.matrix.e2e_cross_signing",
); );
await flushPromises();
// logged in // logged in
await screen.findByLabelText("User menu"); await screen.findByLabelText("User menu");
}); });
@@ -1210,7 +1191,6 @@ describe("<MatrixChat />", () => {
}; };
let loginClient!: ReturnType<typeof getMockClientWithEventEmitter>; let loginClient!: ReturnType<typeof getMockClientWithEventEmitter>;
const userId = "@alice:server.org";
const deviceId = "test-device-id"; const deviceId = "test-device-id";
const accessToken = "test-access-token"; const accessToken = "test-access-token";
const clientLoginResponse = { const clientLoginResponse = {
@@ -1304,6 +1284,7 @@ describe("<MatrixChat />", () => {
}, },
); );
}); });
it("should clear storage", async () => { it("should clear storage", async () => {
const localStorageClearSpy = jest.spyOn(localStorage.__proto__, "clear"); const localStorageClearSpy = jest.spyOn(localStorage.__proto__, "clear");
@@ -1349,6 +1330,9 @@ describe("<MatrixChat />", () => {
it("should continue to post login setup when no session is found in local storage", async () => { it("should continue to post login setup when no session is found in local storage", async () => {
getComponent({ realQueryParams }); getComponent({ realQueryParams });
defaultDispatcher.dispatch({
action: "will_start_client",
});
// logged in but waiting for sync screen // logged in but waiting for sync screen
await screen.findByText("Logout"); await screen.findByText("Logout");
@@ -1604,8 +1588,13 @@ describe("<MatrixChat />", () => {
}); });
await flushPromises(); await flushPromises();
mockClient.emit(CryptoEvent.KeyBackupFailed, "error code"); mockClient.emit(CryptoEvent.KeyBackupFailed, "error code");
await waitFor(() => expect(spy).toHaveBeenCalledTimes(1)); await waitFor(() =>
expect((spy.mock.lastCall![0] as any)._payload._result).toEqual(expect.objectContaining({ __test: true })); expect(spy).toHaveBeenCalledWith(
expect.objectContaining({
_payload: expect.objectContaining({ _result: expect.objectContaining({ __test: true }) }),
}),
),
);
}); });
it("should show the recovery method removed dialog", async () => { it("should show the recovery method removed dialog", async () => {
@@ -1622,8 +1611,13 @@ describe("<MatrixChat />", () => {
}); });
await flushPromises(); await flushPromises();
mockClient.emit(CryptoEvent.KeyBackupFailed, "error code"); mockClient.emit(CryptoEvent.KeyBackupFailed, "error code");
await waitFor(() => expect(spy).toHaveBeenCalledTimes(1)); await waitFor(() =>
expect((spy.mock.lastCall![0] as any)._payload._result).toEqual(expect.objectContaining({ __test: true })); expect(spy).toHaveBeenCalledWith(
expect.objectContaining({
_payload: expect.objectContaining({ _result: expect.objectContaining({ __test: true }) }),
}),
),
);
}); });
}); });
}); });

View File

@@ -148,6 +148,8 @@ describe("PipContainer", () => {
WidgetStore.instance.addVirtualWidget(call.widget, room.roomId); WidgetStore.instance.addVirtualWidget(call.widget, room.roomId);
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, { WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
stop: () => {}, stop: () => {},
hasCapability: jest.fn(),
feedStateUpdate: jest.fn().mockResolvedValue(undefined),
} as unknown as ClientWidgetApi); } as unknown as ClientWidgetApi);
await call.start(); await call.start();
@@ -271,7 +273,10 @@ describe("PipContainer", () => {
Parameters<ClientWidgetApi["transport"]["send"]> Parameters<ClientWidgetApi["transport"]["send"]>
>() >()
.mockResolvedValue({}); .mockResolvedValue({});
const mockMessaging = { transport: { send: sendSpy }, stop: () => {} } as unknown as ClientWidgetApi; const mockMessaging = {
transport: { send: sendSpy },
stop: () => {},
} as unknown as ClientWidgetApi;
WidgetMessagingStore.instance.storeMessaging(new Widget(widget), room.roomId, mockMessaging); WidgetMessagingStore.instance.storeMessaging(new Widget(widget), room.roomId, mockMessaging);
await user.click(screen.getByRole("button", { name: "Leave" })); await user.click(screen.getByRole("button", { name: "Leave" }));
expect(sendSpy).toHaveBeenCalledWith(ElementWidgetActions.HangupCall, {}); expect(sendSpy).toHaveBeenCalledWith(ElementWidgetActions.HangupCall, {});

View File

@@ -108,9 +108,7 @@ exports[`MessagePanel should handle lots of membership events quickly 1`] = `
<span <span
class="mx_TextualEvent mx_GenericEventListSummary_summary" class="mx_TextualEvent mx_GenericEventListSummary_summary"
> >
<span> @user:id made no changes 100 times
@user:id made no changes 100 times
</span>
</span> </span>
</div> </div>
</div> </div>

View File

@@ -197,7 +197,7 @@ describe("<TextualBody />", () => {
const { container } = getComponent({ mxEvent: ev }); const { container } = getComponent({ mxEvent: ev });
const content = container.querySelector(".mx_EventTile_body"); const content = container.querySelector(".mx_EventTile_body");
expect(content.innerHTML).toMatchInlineSnapshot( expect(content.innerHTML).toMatchInlineSnapshot(
`"Chat with <bdi><a class="mx_Pill mx_UserPill mx_UserPill_me" href="https://matrix.to/#/@user:example.com"><span aria-label="Profile picture" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" src="mxc://avatar.url/image.png" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Member</span></a></bdi>"`, `"Chat with <bdi><a class="mx_Pill mx_UserPill mx_UserPill_me" href="https://matrix.to/#/@user:example.com"><span aria-label="Profile picture" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px" src="mxc://avatar.url/image.png"></span><span class="mx_Pill_text">Member</span></a></bdi>"`,
); );
}); });

View File

@@ -510,9 +510,9 @@ exports[`<TextualBody /> renders plain-text m.text correctly linkification get a
</div> </div>
`; `;
exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to a message in the same room with the label »Message from Member« 1`] = `"Visit <bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room1:example.com/%event_id%"><span aria-label="Profile picture" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" src="mxc://avatar.url/image.png" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Message from Member</span></a></bdi>"`; exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to a message in the same room with the label »Message from Member« 1`] = `"Visit <bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room1:example.com/%event_id%"><span aria-label="Profile picture" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px" src="mxc://avatar.url/image.png"></span><span class="mx_Pill_text">Message from Member</span></a></bdi>"`;
exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an event in another room with the label »Message in Room 2« 1`] = `"Visit <bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room2:example.com/%event_id%"><span aria-label="Avatar" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" src="mxc://avatar.url/room.png" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Message in Room 2</span></a></bdi>"`; exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an event in another room with the label »Message in Room 2« 1`] = `"Visit <bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room2:example.com/%event_id%"><span aria-label="Avatar" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_1qbcf_8 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" referrerpolicy="no-referrer" class="_image_1qbcf_41" data-type="round" width="16px" height="16px" src="mxc://avatar.url/room.png"></span><span class="mx_Pill_text">Message in Room 2</span></a></bdi>"`;
exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an unknown message in the same room with the label »Message« 1`] = ` exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an unknown message in the same room with the label »Message« 1`] = `
<div <div

View File

@@ -91,7 +91,7 @@ exports[`<PollHistory /> renders a list of active polls when there are polls in
tabindex="0" tabindex="0"
> >
<div <div
aria-labelledby=":rc:" aria-labelledby=":ra:"
class="mx_PollListItem_content" class="mx_PollListItem_content"
> >
<span> <span>
@@ -116,7 +116,7 @@ exports[`<PollHistory /> renders a list of active polls when there are polls in
tabindex="0" tabindex="0"
> >
<div <div
aria-labelledby=":rh:" aria-labelledby=":rf:"
class="mx_PollListItem_content" class="mx_PollListItem_content"
> >
<span> <span>

View File

@@ -467,7 +467,7 @@ describe("<UserInfo />", () => {
await expect(screen.findByRole("button", { name: "Deactivate user" })).resolves.toBeInTheDocument(); await expect(screen.findByRole("button", { name: "Deactivate user" })).resolves.toBeInTheDocument();
if (screen.queryAllByRole("progressbar").length) { if (screen.queryAllByRole("progressbar").length) {
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar")); await act(() => waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar")));
} }
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -19,7 +19,7 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
</p> </p>
</div> </div>
<button <button
aria-labelledby=":r6m:" aria-labelledby=":r6i:"
class="_icon-button_m2erp_8 _subtle-bg_m2erp_29" class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
data-testid="base-card-close-button" data-testid="base-card-close-button"
role="button" role="button"
@@ -305,7 +305,7 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
</p> </p>
</div> </div>
<button <button
aria-labelledby=":r70:" aria-labelledby=":r6s:"
class="_icon-button_m2erp_8 _subtle-bg_m2erp_29" class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
data-testid="base-card-close-button" data-testid="base-card-close-button"
role="button" role="button"
@@ -401,21 +401,13 @@ exports[`<UserInfo /> with crypto enabled should render a deactivate button for
class="mx_Flex mx_UserInfo_verification" class="mx_Flex mx_UserInfo_verification"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
> >
<svg <p
class="_icon_11k6c_18" class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31 mx_UserInfo_verification_unavailable"
fill="currentColor"
height="1em"
style="width: 24px; height: 24px;"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
> >
<path (
clip-rule="evenodd" User verification unavailable
d="M12 4.031a8 8 0 1 0 8 8 1 1 0 0 1 2 0c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10a1 1 0 1 1 0 2" )
fill-rule="evenodd" </p>
/>
</svg>
</div> </div>
</div> </div>
<div <div

View File

@@ -55,7 +55,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);" style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
> >
<svg <svg
aria-labelledby=":r166:" aria-labelledby=":r15i:"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -71,7 +71,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
<button <button
aria-disabled="true" aria-disabled="true"
aria-label="There's no one here to call" aria-label="There's no one here to call"
aria-labelledby=":r16b:" aria-labelledby=":r15n:"
class="_icon-button_m2erp_8" class="_icon-button_m2erp_8"
role="button" role="button"
style="--cpd-icon-button-size: 32px;" style="--cpd-icon-button-size: 32px;"
@@ -96,7 +96,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
</button> </button>
<button <button
aria-label="Threads" aria-label="Threads"
aria-labelledby=":r16g:" aria-labelledby=":r15s:"
class="_icon-button_m2erp_8" class="_icon-button_m2erp_8"
role="button" role="button"
style="--cpd-icon-button-size: 32px;" style="--cpd-icon-button-size: 32px;"
@@ -122,7 +122,7 @@ exports[`RoomHeader dm does not show the face pile for DMs 1`] = `
</button> </button>
<button <button
aria-label="Room info" aria-label="Room info"
aria-labelledby=":r16l:" aria-labelledby=":r161:"
class="_icon-button_m2erp_8" class="_icon-button_m2erp_8"
role="button" role="button"
style="--cpd-icon-button-size: 32px;" style="--cpd-icon-button-size: 32px;"

View File

@@ -84,12 +84,12 @@ describe("<AddPrivilegedUsers />", () => {
// Find some suggestions and select them. // Find some suggestions and select them.
const autocompleteInput = getByTestId("autocomplete-input"); const autocompleteInput = getByTestId("autocomplete-input");
act(() => { await act(async () => {
fireEvent.focus(autocompleteInput); fireEvent.focus(autocompleteInput);
fireEvent.change(autocompleteInput, { target: { value: "u" } }); fireEvent.change(autocompleteInput, { target: { value: "u" } });
await waitFor(() => expect(provider.mock.instances[0].getCompletions).toHaveBeenCalledTimes(1));
}); });
await waitFor(() => expect(provider.mock.instances[0].getCompletions).toHaveBeenCalledTimes(1));
const matchOne = getByTestId("autocomplete-suggestion-item-@user_1:host.local"); const matchOne = getByTestId("autocomplete-suggestion-item-@user_1:host.local");
const matchTwo = getByTestId("autocomplete-suggestion-item-@user_2:host.local"); const matchTwo = getByTestId("autocomplete-suggestion-item-@user_2:host.local");

View File

@@ -41,6 +41,7 @@ import {
flushPromises, flushPromises,
getMockClientWithEventEmitter, getMockClientWithEventEmitter,
mkPusher, mkPusher,
mockClientMethodsCrypto,
mockClientMethodsServer, mockClientMethodsServer,
mockClientMethodsUser, mockClientMethodsUser,
mockPlatformPeg, mockPlatformPeg,
@@ -124,10 +125,11 @@ describe("<SessionManagerTab />", () => {
const mockCrypto = mocked({ const mockCrypto = mocked({
getDeviceVerificationStatus: jest.fn(), getDeviceVerificationStatus: jest.fn(),
getUserDeviceInfo: jest.fn(), getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
requestDeviceVerification: jest.fn().mockResolvedValue(mockVerificationRequest), requestDeviceVerification: jest.fn().mockResolvedValue(mockVerificationRequest),
supportsSecretsForQrLogin: jest.fn().mockReturnValue(false), supportsSecretsForQrLogin: jest.fn().mockReturnValue(false),
isCrossSigningReady: jest.fn().mockReturnValue(true), isCrossSigningReady: jest.fn().mockReturnValue(true),
getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
} as unknown as CryptoApi); } as unknown as CryptoApi);
let mockClient!: MockedObject<MatrixClient>; let mockClient!: MockedObject<MatrixClient>;
@@ -203,6 +205,7 @@ describe("<SessionManagerTab />", () => {
mockClient = getMockClientWithEventEmitter({ mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(aliceId), ...mockClientMethodsUser(aliceId),
...mockClientMethodsServer(), ...mockClientMethodsServer(),
...mockClientMethodsCrypto(),
getCrypto: jest.fn().mockReturnValue(mockCrypto), getCrypto: jest.fn().mockReturnValue(mockCrypto),
getDevices: jest.fn(), getDevices: jest.fn(),
getDeviceId: jest.fn().mockReturnValue(deviceId), getDeviceId: jest.fn().mockReturnValue(deviceId),

View File

@@ -224,7 +224,7 @@ exports[`ThreadsActivityCentre should close the release announcement when the TA
<div <div
data-radix-popper-content-wrapper="" data-radix-popper-content-wrapper=""
dir="ltr" dir="ltr"
style="position: fixed; left: 0px; top: 0px; transform: translate(0px, -8px); min-width: max-content; --radix-popper-available-width: 0px; --radix-popper-available-height: -8px; --radix-popper-anchor-width: 0px; --radix-popper-anchor-height: 0px; --radix-popper-transform-origin: 0% 0px;" style="position: fixed; left: 0px; top: 0px; transform: translate(0px, -8px); min-width: max-content; --radix-popper-transform-origin: 0% 0px; --radix-popper-available-width: 0px; --radix-popper-available-height: -8px; --radix-popper-anchor-width: 0px; --radix-popper-anchor-height: 0px;"
> >
<div <div
aria-labelledby="radix-:r1c:" aria-labelledby="radix-:r1c:"

View File

@@ -1,48 +1,72 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FormattingUtils formatList should return expected sentence in ReactNode when given 2 React children 1`] = ` exports[`FormattingUtils formatList should return expected sentence in ReactNode when given 2 React children 1`] = `
<span> <React.Fragment>
<span> <React.Fragment>
a <span>
</span> a
and </span>
<span> </React.Fragment>
b <React.Fragment>
</span> and
</span> </React.Fragment>
<React.Fragment>
<span>
b
</span>
</React.Fragment>
</React.Fragment>
`; `;
exports[`FormattingUtils formatList should return expected sentence in ReactNode when given more React children 1`] = ` exports[`FormattingUtils formatList should return expected sentence in ReactNode when given more React children 1`] = `
<span> <React.Fragment>
<span> <React.Fragment>
a <span>
</span> a
, </span>
<span> </React.Fragment>
b <React.Fragment>
</span> ,
, </React.Fragment>
<span> <React.Fragment>
c <span>
</span> b
and </span>
<span> </React.Fragment>
d <React.Fragment>
</span> ,
</span> </React.Fragment>
<React.Fragment>
<span>
c
</span>
</React.Fragment>
<React.Fragment>
and
</React.Fragment>
<React.Fragment>
<span>
d
</span>
</React.Fragment>
</React.Fragment>
`; `;
exports[`FormattingUtils formatList should return expected sentence in ReactNode when using itemLimit 1`] = ` exports[`FormattingUtils formatList should return expected sentence in ReactNode when using itemLimit 1`] = `
<span> <span>
<span> <React.Fragment>
<span> <React.Fragment>
a <span>
</span> a
, </span>
<span> ,
b </React.Fragment>
</span> <React.Fragment>
</span> <span>
b
</span>
</React.Fragment>
</React.Fragment>
and 2 others and 2 others
</span> </span>
`; `;

View File

@@ -253,8 +253,9 @@ describe("export", function () {
}, },
setProgressText, setProgressText,
); );
const imageRegex = /<img.+ src="mxc:\/\/test.org" alt="image\.png"\/?>/; expect(renderToString(exporter.getEventTile(mkImageEvent(), true))).toMatch(
expect(imageRegex.test(renderToString(exporter.getEventTile(mkImageEvent(), true)))).toBeTruthy(); /<img.+ alt="image\.png" src="mxc:\/\/test.org"\/?>/,
);
}); });
const invalidExportOptions: [string, IExportOptions][] = [ const invalidExportOptions: [string, IExportOptions][] = [

File diff suppressed because one or more lines are too long

View File

@@ -116,7 +116,7 @@ describe("Permalinks", function () {
it("should gracefully handle invalid MXIDs", () => { it("should gracefully handle invalid MXIDs", () => {
const roomId = "!fake:example.org"; const roomId = "!fake:example.org";
const alice50 = makeMemberWithPL(roomId, "@alice:pl_50:org", 50); const alice50 = makeMemberWithPL(roomId, "@alice:pl-50:org", 50);
const room = mockRoom(roomId, [alice50]); const room = mockRoom(roomId, [alice50]);
const creator = new RoomPermalinkCreator(room); const creator = new RoomPermalinkCreator(room);
creator.load(); creator.load();

View File

@@ -3376,10 +3376,10 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-dom@18.3.5": "@types/react-dom@19.0.4":
version "18.3.5" version "19.0.4"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.5.tgz#45f9f87398c5dcea085b715c58ddcf1faf65f716" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.4.tgz#bedba97f9346bd4c0fe5d39e689713804ec9ac89"
integrity sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q== integrity sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==
"@types/react-redux@^7.1.20": "@types/react-redux@^7.1.20":
version "7.1.34" version "7.1.34"
@@ -3404,12 +3404,11 @@
"@types/prop-types" "*" "@types/prop-types" "*"
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@18.3.18": "@types/react@*", "@types/react@19.0.10":
version "18.3.18" version "19.0.10"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.18.tgz#9b382c4cd32e13e463f97df07c2ee3bbcd26904b" resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.10.tgz#d0c66dafd862474190fe95ce11a68de69ed2b0eb"
integrity sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ== integrity sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==
dependencies: dependencies:
"@types/prop-types" "*"
csstype "^3.0.2" csstype "^3.0.2"
"@types/retry@0.12.0": "@types/retry@0.12.0":
@@ -8936,7 +8935,7 @@ long@^5.2.0:
resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: loose-envify@^1.0.0, loose-envify@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -10856,13 +10855,12 @@ react-clientside-effect@^1.2.7:
dependencies: dependencies:
"@babel/runtime" "^7.12.13" "@babel/runtime" "^7.12.13"
react-dom@^18.3.1: react-dom@^19.0.0:
version "18.3.1" version "19.0.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== integrity sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==
dependencies: dependencies:
loose-envify "^1.1.0" scheduler "^0.25.0"
scheduler "^0.23.2"
react-focus-lock@^2.5.1: react-focus-lock@^2.5.1:
version "2.13.6" version "2.13.6"
@@ -10876,21 +10874,21 @@ react-focus-lock@^2.5.1:
use-callback-ref "^1.3.3" use-callback-ref "^1.3.3"
use-sidecar "^1.1.3" use-sidecar "^1.1.3"
react-is@19.0.0, react-is@^17.0.1, react-is@^18.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.0.0.tgz#d6669fd389ff022a9684f708cf6fa4962d1fea7a"
integrity sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==
react-is@^16.13.1, react-is@^16.7.0: react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^17.0.1, react-is@^17.0.2: react-is@^17.0.2:
version "17.0.2" version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-is@^18.0.0:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
react-lifecycles-compat@^3.0.4: react-lifecycles-compat@^3.0.4:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
@@ -10968,12 +10966,10 @@ react-virtualized@^9.22.5:
prop-types "^15.7.2" prop-types "^15.7.2"
react-lifecycles-compat "^3.0.4" react-lifecycles-compat "^3.0.4"
react@^18.3.1: react@^19.0.0:
version "18.3.1" version "19.0.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==
dependencies:
loose-envify "^1.1.0"
read-cache@^1.0.0: read-cache@^1.0.0:
version "1.0.0" version "1.0.0"
@@ -11411,12 +11407,10 @@ saxes@^6.0.0:
dependencies: dependencies:
xmlchars "^2.2.0" xmlchars "^2.2.0"
scheduler@^0.23.2: scheduler@^0.25.0:
version "0.23.2" version "0.25.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0.tgz#336cd9768e8cceebf52d3c80e3dcf5de23e7e015"
integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== integrity sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==
dependencies:
loose-envify "^1.1.0"
schema-utils@^3.0.0: schema-utils@^3.0.0:
version "3.3.0" version "3.3.0"