From f78b8dd1531b0f3b81d58613f6de0aa69d0ac23e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 18:22:52 +0000 Subject: [PATCH 001/222] Update dependency @vector-im/compound-design-tokens to v4 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d3b898b2..d0a0a202 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "@use-gesture/react": "^10.2.11", - "@vector-im/compound-design-tokens": "^3.0.0", + "@vector-im/compound-design-tokens": "^4.0.0", "@vector-im/compound-web": "^7.2.0", "@vitejs/plugin-basic-ssl": "^1.0.1", "@vitejs/plugin-react": "^4.0.1", diff --git a/yarn.lock b/yarn.lock index ffc3ceca..eb8c59da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5017,9 +5017,9 @@ __metadata: languageName: node linkType: hard -"@vector-im/compound-design-tokens@npm:^3.0.0": - version: 3.0.1 - resolution: "@vector-im/compound-design-tokens@npm:3.0.1" +"@vector-im/compound-design-tokens@npm:^4.0.0": + version: 4.0.1 + resolution: "@vector-im/compound-design-tokens@npm:4.0.1" peerDependencies: "@types/react": "*" react: ^17 || ^18 || ^19.0.0 @@ -5028,7 +5028,7 @@ __metadata: optional: true react: optional: true - checksum: 10c0/afb941fd4efc91c8ccea8751d2da401379c3ababe6326943ccef0aecfe7691ef39860bc2f877fe7fc35b7887f483d46881656da10e3deb0ee94ea32c3c3cbb31 + checksum: 10c0/4788109e22f09f80c003913a978a0efcf45a2f2820cfa21216ebb4c15ad71ddb7f976e5c65f3b627ac44f6a759dfca9b48561a005dd76cb5891272f786c30289 languageName: node linkType: hard @@ -6895,7 +6895,7 @@ __metadata: "@typescript-eslint/eslint-plugin": "npm:^8.0.0" "@typescript-eslint/parser": "npm:^8.0.0" "@use-gesture/react": "npm:^10.2.11" - "@vector-im/compound-design-tokens": "npm:^3.0.0" + "@vector-im/compound-design-tokens": "npm:^4.0.0" "@vector-im/compound-web": "npm:^7.2.0" "@vitejs/plugin-basic-ssl": "npm:^1.0.1" "@vitejs/plugin-react": "npm:^4.0.1" From b969857583bee2dac8db8ada0c8b49adddfd1c6c Mon Sep 17 00:00:00 2001 From: fkwp Date: Thu, 22 May 2025 12:49:37 +0200 Subject: [PATCH 002/222] Allow the jwt service to be running locallay rather than part of the docker-compose. This helps with developing the lk-jwt-service --- backend/dev_nginx.conf | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/dev_nginx.conf b/backend/dev_nginx.conf index 44fef8a5..af370d2e 100644 --- a/backend/dev_nginx.conf +++ b/backend/dev_nginx.conf @@ -43,6 +43,11 @@ server { # MatrixRTC reverse proxy # - MatrixRTC Authorization Service # - LiveKit SFU websocket signaling connection +upstream jwt-auth-services { + server auth-server:8080; + server host.docker.internal:8080; +} + server { listen 80; listen [::]:80; @@ -63,7 +68,8 @@ server { proxy_set_header X-Forwarded-Proto $scheme; # JWT Service running at port 8080 - proxy_pass http://auth-server:8080/; + proxy_pass http://jwt-auth-services/; + } location ^~ /livekit/sfu/ { From d585f6232e4b530212231710f0cc2d98125221fd Mon Sep 17 00:00:00 2001 From: fkwp Date: Tue, 27 May 2025 16:30:06 +0200 Subject: [PATCH 003/222] change JWT port ot 6080 to not conflict with the playwright tests. --- backend/dev_nginx.conf | 4 ++-- dev-backend-docker-compose.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/dev_nginx.conf b/backend/dev_nginx.conf index 44fef8a5..ea890a30 100644 --- a/backend/dev_nginx.conf +++ b/backend/dev_nginx.conf @@ -62,8 +62,8 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - # JWT Service running at port 8080 - proxy_pass http://auth-server:8080/; + # JWT Service running at port 6080 + proxy_pass http://auth-server:6080/; } location ^~ /livekit/sfu/ { diff --git a/dev-backend-docker-compose.yml b/dev-backend-docker-compose.yml index da3c3530..d711c41a 100644 --- a/dev-backend-docker-compose.yml +++ b/dev-backend-docker-compose.yml @@ -6,7 +6,7 @@ services: image: ghcr.io/element-hq/lk-jwt-service:latest-ci hostname: auth-server environment: - - LIVEKIT_JWT_PORT=8080 + - LIVEKIT_JWT_PORT=6080 - LIVEKIT_URL=wss://matrix-rtc.m.localhost/livekit/sfu - LIVEKIT_KEY=devkey - LIVEKIT_SECRET=secret @@ -18,7 +18,7 @@ services: condition: on-failure ports: # HOST_PORT:CONTAINER_PORT - - 8080:8080 + - 6080:6080 networks: - ecbackend From 8953936d3d399bae76bed7abf1f1a627f806532a Mon Sep 17 00:00:00 2001 From: fkwp Date: Tue, 27 May 2025 17:53:06 +0200 Subject: [PATCH 004/222] fix docker compose playwright override --- .github/workflows/test.yaml | 2 +- package.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2ef4dc04..30934f71 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -46,7 +46,7 @@ jobs: run: yarn playwright install --with-deps - name: Run backend components run: | - docker compose -f playwright-backend-docker-compose.yml up -d + docker compose -f playwright-backend-docker-compose.yml -f playwright-backend-docker-compose.override.yml up -d docker ps - name: Copy config file run: cp config/config.devenv.json public/config.json diff --git a/package.json b/package.json index 97c4f737..e8ccdd95 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "test": "vitest", "test:coverage": "vitest --coverage", "backend": "docker-compose -f dev-backend-docker-compose.yml up", + "backend-playwright": "docker-compose -f playwright-backend-docker-compose.yml -f playwright-backend-docker-compose.override.yml up", "test:playwright": "playwright test", "test:playwright:open": "yarn test:playwright --ui", "links:enable": "mv .links.disabled.yaml .links.yaml & touch .links.yaml", From 99fc7162e207abc73ba6f4107d2759bffcbda4fd Mon Sep 17 00:00:00 2001 From: fkwp Date: Tue, 27 May 2025 18:04:34 +0200 Subject: [PATCH 005/222] fix widget fixture --- playwright/fixtures/widget-user.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playwright/fixtures/widget-user.ts b/playwright/fixtures/widget-user.ts index d1412bd8..b25602eb 100644 --- a/playwright/fixtures/widget-user.ts +++ b/playwright/fixtures/widget-user.ts @@ -30,8 +30,8 @@ const PASSWORD = "foobarbaz1!"; const CONFIG_JSON = { default_server_config: { "m.homeserver": { - base_url: "http://synapse.localhost:8008", - server_name: "synapse.localhost", + base_url: "https://synapse.m.localhost", + server_name: "synapse.m.localhost", }, }, From e4fd630f376faa9cd80698f491614ad236c53d23 Mon Sep 17 00:00:00 2001 From: fkwp Date: Tue, 27 May 2025 19:03:53 +0200 Subject: [PATCH 006/222] fix element web fixture --- playwright/fixtures/widget-user.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/playwright/fixtures/widget-user.ts b/playwright/fixtures/widget-user.ts index b25602eb..46973f83 100644 --- a/playwright/fixtures/widget-user.ts +++ b/playwright/fixtures/widget-user.ts @@ -99,6 +99,13 @@ export const widgetTest = test.extend({ .getByRole("textbox", { name: "Confirm password" }) .fill(PASSWORD); await ewPage1.getByRole("button", { name: "Register" }).click(); + await expect( + ewPage1.getByRole("button", { name: "Continue" }), + ).toBeVisible(); + await ewPage1 + .getByRole("textbox", { name: "Password", exact: true }) + .fill(PASSWORD); + await ewPage1.getByRole("button", { name: "Continue" }).click(); await expect( ewPage1.getByRole("heading", { name: `Welcome ${userA}` }), ).toBeVisible(); @@ -127,6 +134,13 @@ export const widgetTest = test.extend({ .getByRole("textbox", { name: "Confirm password" }) .fill(PASSWORD); await ewPage2.getByRole("button", { name: "Register" }).click(); + await expect( + ewPage2.getByRole("button", { name: "Continue" }), + ).toBeVisible(); + await ewPage2 + .getByRole("textbox", { name: "Password", exact: true }) + .fill(PASSWORD); + await ewPage2.getByRole("button", { name: "Continue" }).click(); await expect( ewPage2.getByRole("heading", { name: `Welcome ${userB}` }), ).toBeVisible(); From ebc714b73ff0813f134808e7d75305c5aea49fc7 Mon Sep 17 00:00:00 2001 From: fkwp Date: Tue, 27 May 2025 20:06:28 +0200 Subject: [PATCH 007/222] force to pull the latest docker images --- .github/workflows/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 30934f71..518226fb 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -46,6 +46,7 @@ jobs: run: yarn playwright install --with-deps - name: Run backend components run: | + docker compose -f playwright-backend-docker-compose.yml -f playwright-backend-docker-compose.override.yml pull docker compose -f playwright-backend-docker-compose.yml -f playwright-backend-docker-compose.override.yml up -d docker ps - name: Copy config file From e757f7af6b12f4165acadb5c9cb67aed20a67c64 Mon Sep 17 00:00:00 2001 From: Robin Date: Wed, 28 May 2025 17:00:57 -0400 Subject: [PATCH 008/222] Run the 'Prevent blocked' check whenever a PR branch is updated Because we're now requiring the 'Prevent blocked' check to pass before merging a PR, GitHub Actions now expects it to be associated with the latest Git ref of the PR's branch whenever the branch is updated. Therefore we need to re-run the workflow on the 'synchronize' event. --- .github/workflows/blocked.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/blocked.yaml b/.github/workflows/blocked.yaml index e016e707..f3c99b3e 100644 --- a/.github/workflows/blocked.yaml +++ b/.github/workflows/blocked.yaml @@ -1,7 +1,7 @@ name: Prevent blocked on: pull_request_target: - types: [opened, labeled, unlabeled] + types: [opened, labeled, unlabeled, synchronize] jobs: prevent-blocked: name: Prevent blocked From 07a4244c057332c61d458a17d0ee1994fe10457c Mon Sep 17 00:00:00 2001 From: Robin Date: Wed, 28 May 2025 18:04:29 -0400 Subject: [PATCH 009/222] Upgrade Compound Web to v7.12.0 --- package.json | 2 +- src/FullScreenView.tsx | 4 +- src/__snapshots__/Modal.test.tsx.snap | 4 +- src/__snapshots__/Toast.test.tsx.snap | 2 +- .../ReactionToggleButton.test.tsx.snap | 20 +++--- src/room/GroupCallErrorBoundary.tsx | 4 +- .../GroupCallErrorBoundary.test.tsx.snap | 62 +++++++++---------- src/tile/MediaView.tsx | 4 +- yarn.lock | 12 ++-- 9 files changed, 57 insertions(+), 57 deletions(-) diff --git a/package.json b/package.json index d0a0a202..48bc3044 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@typescript-eslint/parser": "^8.0.0", "@use-gesture/react": "^10.2.11", "@vector-im/compound-design-tokens": "^4.0.0", - "@vector-im/compound-web": "^7.2.0", + "@vector-im/compound-web": "^7.12.0", "@vitejs/plugin-basic-ssl": "^1.0.1", "@vitejs/plugin-react": "^4.0.1", "@vitest/coverage-v8": "^3.0.0", diff --git a/src/FullScreenView.tsx b/src/FullScreenView.tsx index c8655229..4f2f8f1e 100644 --- a/src/FullScreenView.tsx +++ b/src/FullScreenView.tsx @@ -10,7 +10,7 @@ import classNames from "classnames"; import { useTranslation } from "react-i18next"; import * as Sentry from "@sentry/react"; import { logger } from "matrix-js-sdk/src/logger"; -import { ErrorIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; +import { ErrorSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { Header, HeaderLogo, LeftNav, RightNav } from "./Header"; import styles from "./FullScreenView.module.css"; @@ -67,7 +67,7 @@ export const ErrorPage = ({ error, widget }: ErrorPageProps): ReactElement => { ) : (

My modal diff --git a/src/__snapshots__/Toast.test.tsx.snap b/src/__snapshots__/Toast.test.tsx.snap index d1c4d075..60e8d41b 100644 --- a/src/__snapshots__/Toast.test.tsx.snap +++ b/src/__snapshots__/Toast.test.tsx.snap @@ -12,7 +12,7 @@ exports[`Toast > renders 1`] = ` type="button" >

Hello world! diff --git a/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap b/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap index ff0b182c..556d6edb 100644 --- a/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap +++ b/src/button/__snapshots__/ReactionToggleButton.test.tsx.snap @@ -10,7 +10,7 @@ exports[`Can close reaction dialog 1`] = ` aria-expanded="true" aria-haspopup="true" aria-labelledby=":rb5:" - class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59" + class="_button_vczzf_8 _has-icon_vczzf_57 _icon-only_vczzf_50" data-kind="primary" data-size="lg" role="button" @@ -26,7 +26,7 @@ exports[`Can close reaction dialog 1`] = ` > @@ -44,7 +44,7 @@ exports[`Can fully expand emoji picker 1`] = ` aria-expanded="true" aria-haspopup="true" aria-labelledby=":r7m:" - class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59" + class="_button_vczzf_8 _has-icon_vczzf_57 _icon-only_vczzf_50" data-kind="primary" data-size="lg" role="button" @@ -60,7 +60,7 @@ exports[`Can fully expand emoji picker 1`] = ` > @@ -75,7 +75,7 @@ exports[`Can lower hand 1`] = ` aria-expanded="false" aria-haspopup="true" aria-labelledby=":r36:" - class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59" + class="_button_vczzf_8 _has-icon_vczzf_57 _icon-only_vczzf_50" data-kind="secondary" data-size="lg" role="button" @@ -91,7 +91,7 @@ exports[`Can lower hand 1`] = ` > @@ -109,7 +109,7 @@ exports[`Can open menu 1`] = ` aria-expanded="true" aria-haspopup="true" aria-labelledby=":r0:" - class="_button_i91xf_17 _has-icon_i91xf_66 _icon-only_i91xf_59" + class="_button_vczzf_8 _has-icon_vczzf_57 _icon-only_vczzf_50" data-kind="primary" data-size="lg" role="button" @@ -125,7 +125,7 @@ exports[`Can open menu 1`] = ` > @@ -140,7 +140,7 @@ exports[`Can raise hand 1`] = ` aria-expanded="false" aria-haspopup="true" aria-labelledby=":r1j:" - class="_button_i91xf_17 raisedButton _has-icon_i91xf_66 _icon-only_i91xf_59" + class="_button_vczzf_8 raisedButton _has-icon_vczzf_57 _icon-only_vczzf_50" data-kind="primary" data-size="lg" role="button" @@ -155,7 +155,7 @@ exports[`Can raise hand 1`] = ` xmlns="http://www.w3.org/2000/svg" > diff --git a/src/room/GroupCallErrorBoundary.tsx b/src/room/GroupCallErrorBoundary.tsx index 170718d6..b5f195c1 100644 --- a/src/room/GroupCallErrorBoundary.tsx +++ b/src/room/GroupCallErrorBoundary.tsx @@ -16,7 +16,7 @@ import { } from "react"; import { Trans, useTranslation } from "react-i18next"; import { - ErrorIcon, + ErrorSolidIcon, HostIcon, OfflineIcon, WebBrowserIcon, @@ -63,7 +63,7 @@ const ErrorPage: FC = ({ icon = WebBrowserIcon; break; default: - icon = ErrorIcon; + icon = ErrorSolidIcon; } const actions: { label: string; onClick: () => void }[] = []; diff --git a/src/room/__snapshots__/GroupCallErrorBoundary.test.tsx.snap b/src/room/__snapshots__/GroupCallErrorBoundary.test.tsx.snap index 5aab22a2..6fd1ab40 100644 --- a/src/room/__snapshots__/GroupCallErrorBoundary.test.tsx.snap +++ b/src/room/__snapshots__/GroupCallErrorBoundary.test.tsx.snap @@ -108,7 +108,7 @@ exports[`ConnectionLostError: Action handling should reset error state 1`] = ` class="error" >

Connection lost

@@ -135,7 +135,7 @@ exports[`ConnectionLostError: Action handling should reset error state 1`] = ` Reconnect +
Value is {value}
+ + ); + }; + + render(); + screen.getByText("Value is 1"); + await user.click(screen.getByText("Change value")); + screen.getByText("Value is 2"); +}); + +test("useTypedEventEmitterState reacts to changes made by an effect mounted on the same render", () => { + const emitter = new TestEmitter(); + + const Test: FC = () => { + useEffect(() => emitter.setState(2), []); + const value = useTypedEventEmitterState( + emitter, + "change", + emitter.getState, + ); + return `Value is ${value}`; + }; + + render(); + screen.getByText("Value is 2"); +}); + +test("useTypedEventEmitterState reacts to changes in getState", async () => { + const user = userEvent.setup(); + const emitter = new TestEmitter(); + + const Test: FC = () => { + const [fn, setFn] = useState(() => emitter.getState); + const value = useTypedEventEmitterState(emitter, "change", fn); + return ( + <> + +
Value is {value}
+ + ); + }; + + render(); + screen.getByText("Value is 1"); + await user.click(screen.getByText("Change getState")); + screen.getByText("Value is -1"); +}); diff --git a/src/useEvents.ts b/src/useEvents.ts index c19145eb..3495cc57 100644 --- a/src/useEvents.ts +++ b/src/useEvents.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { useEffect } from "react"; +import { useCallback, useEffect, useSyncExternalStore } from "react"; import type { Listener, @@ -13,7 +13,9 @@ import type { TypedEventEmitter, } from "matrix-js-sdk/lib/models/typed-event-emitter"; -// Shortcut for registering a listener on an EventTarget +/** + * Shortcut for registering a listener on an EventTarget. + */ export function useEventTarget( target: EventTarget | null | undefined, eventType: string, @@ -33,7 +35,9 @@ export function useEventTarget( }, [target, eventType, listener, options]); } -// Shortcut for registering a listener on a TypedEventEmitter +/** + * Shortcut for registering a listener on a TypedEventEmitter. + */ export function useTypedEventEmitter< Events extends string, Arguments extends ListenerMap, @@ -50,3 +54,33 @@ export function useTypedEventEmitter< }; }, [emitter, eventType, listener]); } + +/** + * Reactively tracks a value which is recalculated whenever the provided event + * emitter emits an event. This is useful for bridging state from matrix-js-sdk + * into React. + */ +export function useTypedEventEmitterState< + Events extends string, + Arguments extends ListenerMap, + T extends Events, + State, +>( + emitter: TypedEventEmitter, + eventType: T, + getState: () => State, +): State { + const subscribe = useCallback( + (onChange: () => void) => { + emitter.on(eventType, onChange as Listener); + return (): void => { + emitter.off(eventType, onChange as Listener); + }; + }, + [emitter, eventType], + ); + // See the React docs for useSyncExternalStore; given that we're trying to + // bridge state from an external source into React, using this hook is exactly + // what React recommends. + return useSyncExternalStore(subscribe, getState); +} diff --git a/src/useLocalStorage.test.tsx b/src/useLocalStorage.test.tsx new file mode 100644 index 00000000..6e6c8c26 --- /dev/null +++ b/src/useLocalStorage.test.tsx @@ -0,0 +1,51 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { test } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { type FC, useEffect, useState } from "react"; +import userEvent from "@testing-library/user-event"; + +import { + setLocalStorageItemReactive, + useLocalStorage, +} from "./useLocalStorage"; + +test("useLocalStorage reacts to changes made by an effect mounted on the same render", () => { + localStorage.clear(); + const Test: FC = () => { + useEffect(() => setLocalStorageItemReactive("my-value", "Hello!"), []); + const [myValue] = useLocalStorage("my-value"); + return myValue; + }; + render(); + screen.getByText("Hello!"); +}); + +test("useLocalStorage reacts to key changes", async () => { + localStorage.clear(); + localStorage.setItem("value-1", "1"); + localStorage.setItem("value-2", "2"); + + const Test: FC = () => { + const [key, setKey] = useState("value-1"); + const [value] = useLocalStorage(key); + if (key !== `value-${value}`) throw new Error("Value is out of sync"); + return ( + <> + +
Value is: {value}
+ + ); + }; + const user = userEvent.setup(); + render(); + + screen.getByText("Value is: 1"); + await user.click(screen.getByRole("button", { name: "Switch keys" })); + screen.getByText("Value is: 2"); +}); diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index 43e828bf..dcf00a42 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -5,50 +5,44 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import EventEmitter from "events"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback } from "react"; +import { TypedEventEmitter } from "matrix-js-sdk"; + +import { useTypedEventEmitterState } from "./useEvents"; type LocalStorageItem = ReturnType; // Bus to notify other useLocalStorage consumers when an item is changed -export const localStorageBus = new EventEmitter(); +export const localStorageBus = new TypedEventEmitter< + string, + { [key: string]: () => void } +>(); /** * Like useState, but reads from and persists the value to localStorage * This hook will not update when we write to localStorage.setItem(key, value) directly. * For the hook to react either use the returned setter or `setLocalStorageItemReactive`. */ -export const useLocalStorage = ( +export function useLocalStorage( key: string, -): [LocalStorageItem, (value: string) => void] => { - const [value, setValue] = useState(() => - localStorage.getItem(key), +): [LocalStorageItem, (value: string) => void] { + const value = useTypedEventEmitterState( + localStorageBus, + key, + useCallback(() => localStorage.getItem(key), [key]), + ); + const setValue = useCallback( + (newValue: string) => setLocalStorageItemReactive(key, newValue), + [key], ); - useEffect(() => { - localStorageBus.on(key, setValue); - return (): void => { - localStorageBus.off(key, setValue); - }; - }, [key, setValue]); - - return [ - value, - useCallback( - (newValue: string) => { - setValue(newValue); - localStorage.setItem(key, newValue); - localStorageBus.emit(key, newValue); - }, - [key, setValue], - ), - ]; -}; + return [value, setValue]; +} export const setLocalStorageItemReactive = ( key: string, value: string, ): void => { localStorage.setItem(key, value); - localStorageBus.emit(key, value); + localStorageBus.emit(key); }; diff --git a/src/useMatrixRTCSessionJoinState.ts b/src/useMatrixRTCSessionJoinState.ts index 5e7ea110..2f6ccf25 100644 --- a/src/useMatrixRTCSessionJoinState.ts +++ b/src/useMatrixRTCSessionJoinState.ts @@ -10,33 +10,31 @@ import { type MatrixRTCSession, MatrixRTCSessionEvent, } from "matrix-js-sdk/lib/matrixrtc"; -import { useEffect, useState } from "react"; +import { TypedEventEmitter } from "matrix-js-sdk"; +import { useCallback, useEffect } from "react"; + +import { useTypedEventEmitterState } from "./useEvents"; + +const dummySession = new TypedEventEmitter(); export function useMatrixRTCSessionJoinState( rtcSession: MatrixRTCSession | undefined, ): boolean { - const [, setNumUpdates] = useState(0); + // React doesn't allow you to run a hook conditionally, so we have to plug in + // a dummy event emitter in case there is no rtcSession yet + const isJoined = useTypedEventEmitterState( + rtcSession ?? dummySession, + MatrixRTCSessionEvent.JoinStateChanged, + useCallback(() => rtcSession?.isJoined() ?? false, [rtcSession]), + ); useEffect(() => { - if (rtcSession !== undefined) { - const onJoinStateChanged = (isJoined: boolean): void => { - logger.info( - `Session in room ${rtcSession.room.roomId} changed to ${ - isJoined ? "joined" : "left" - }`, - ); - setNumUpdates((n) => n + 1); // Force an update - }; - rtcSession.on(MatrixRTCSessionEvent.JoinStateChanged, onJoinStateChanged); + logger.info( + `Session in room ${rtcSession?.room.roomId} changed to ${ + isJoined ? "joined" : "left" + }`, + ); + }, [rtcSession, isJoined]); - return (): void => { - rtcSession.off( - MatrixRTCSessionEvent.JoinStateChanged, - onJoinStateChanged, - ); - }; - } - }, [rtcSession]); - - return rtcSession?.isJoined() ?? false; + return isJoined; } diff --git a/src/useMatrixRTCSessionMemberships.ts b/src/useMatrixRTCSessionMemberships.ts index 25b790d2..0dba6b15 100644 --- a/src/useMatrixRTCSessionMemberships.ts +++ b/src/useMatrixRTCSessionMemberships.ts @@ -5,39 +5,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { logger } from "matrix-js-sdk/lib/logger"; import { type CallMembership, type MatrixRTCSession, MatrixRTCSessionEvent, } from "matrix-js-sdk/lib/matrixrtc"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback } from "react"; + +import { useTypedEventEmitterState } from "./useEvents"; export function useMatrixRTCSessionMemberships( rtcSession: MatrixRTCSession, ): CallMembership[] { - const [memberships, setMemberships] = useState(rtcSession.memberships); - - const onMembershipsChanged = useCallback(() => { - logger.info( - `Memberships changed for call in room ${rtcSession.room.roomId} (${rtcSession.memberships.length} members)`, - ); - setMemberships(rtcSession.memberships); - }, [rtcSession]); - - useEffect(() => { - rtcSession.on( - MatrixRTCSessionEvent.MembershipsChanged, - onMembershipsChanged, - ); - - return (): void => { - rtcSession.off( - MatrixRTCSessionEvent.MembershipsChanged, - onMembershipsChanged, - ); - }; - }, [rtcSession, onMembershipsChanged]); - - return memberships; + return useTypedEventEmitterState( + rtcSession, + MatrixRTCSessionEvent.MembershipsChanged, + useCallback(() => rtcSession.memberships, [rtcSession]), + ); } diff --git a/src/useMediaQuery.ts b/src/useMediaQuery.ts index ce73cb9c..1ea9196d 100644 --- a/src/useMediaQuery.ts +++ b/src/useMediaQuery.ts @@ -1,13 +1,11 @@ /* -Copyright 2023, 2024 New Vector Ltd. +Copyright 2023-2025 New Vector Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { useCallback, useMemo, useState } from "react"; - -import { useEventTarget } from "./useEvents"; +import { useCallback, useMemo, useSyncExternalStore } from "react"; /** * React hook that tracks whether the given media query matches. @@ -15,14 +13,13 @@ import { useEventTarget } from "./useEvents"; export function useMediaQuery(query: string): boolean { const mediaQuery = useMemo(() => window.matchMedia(query), [query]); - const [numChanges, setNumChanges] = useState(0); - useEventTarget( - mediaQuery, - "change", - useCallback(() => setNumChanges((n) => n + 1), [setNumChanges]), + const subscribe = useCallback( + (onChange: () => void) => { + mediaQuery.addEventListener("change", onChange); + return (): void => mediaQuery.removeEventListener("change", onChange); + }, + [mediaQuery], ); - - // We want any change to the update counter to trigger an update here - // eslint-disable-next-line react-hooks/exhaustive-deps - return useMemo(() => mediaQuery.matches, [mediaQuery, numChanges]); + const getState = useCallback(() => mediaQuery.matches, [mediaQuery]); + return useSyncExternalStore(subscribe, getState); } diff --git a/src/utils/test.ts b/src/utils/test.ts index 51ed1ed2..f142b0d4 100644 --- a/src/utils/test.ts +++ b/src/utils/test.ts @@ -108,7 +108,7 @@ interface EmitterMock { removeListener: () => T; } -function mockEmitter(): EmitterMock { +export function mockEmitter(): EmitterMock { return { on(): T { return this as T; From b005f36ac7ecbdf3d3e2a3f49632495b0ac6ba2f Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 5 Jun 2025 14:36:43 +0200 Subject: [PATCH 036/222] custom compare logic --- src/livekit/MediaDevicesContext.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/livekit/MediaDevicesContext.tsx b/src/livekit/MediaDevicesContext.tsx index c836f82b..0de794bc 100644 --- a/src/livekit/MediaDevicesContext.tsx +++ b/src/livekit/MediaDevicesContext.tsx @@ -20,7 +20,6 @@ import { createMediaDeviceObserver } from "@livekit/components-core"; import { combineLatest, distinctUntilChanged, map, startWith } from "rxjs"; import { useObservable, useObservableEagerState } from "observable-hooks"; import { logger } from "matrix-js-sdk/lib/logger"; -import { deepCompare } from "matrix-js-sdk/lib/utils"; import { useSetting, @@ -149,7 +148,14 @@ function useMediaDeviceHandle( // time of writing, we are seeing mobile Safari firing spurious // 'devicechange' events (where no change has actually occurred) when // we call MediaDevices.getUserMedia. So, filter by deep equality. - distinctUntilChanged(deepCompare), + distinctUntilChanged((prev, current) => { + if (prev.length !== current.length) return true; + if (prev.length === 0) return false; + return !current.every( + (d, i) => + d.deviceId === prev[i].deviceId && d.label === prev[i].label, + ); + }), ), [kind, requestPermissions], ); From 34724b7a8ce468a5f615beee2c68ed9ad5ed9ee6 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 5 Jun 2025 14:39:06 +0200 Subject: [PATCH 037/222] revert "custom compare logic" --- src/livekit/MediaDevicesContext.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/livekit/MediaDevicesContext.tsx b/src/livekit/MediaDevicesContext.tsx index 0de794bc..c836f82b 100644 --- a/src/livekit/MediaDevicesContext.tsx +++ b/src/livekit/MediaDevicesContext.tsx @@ -20,6 +20,7 @@ import { createMediaDeviceObserver } from "@livekit/components-core"; import { combineLatest, distinctUntilChanged, map, startWith } from "rxjs"; import { useObservable, useObservableEagerState } from "observable-hooks"; import { logger } from "matrix-js-sdk/lib/logger"; +import { deepCompare } from "matrix-js-sdk/lib/utils"; import { useSetting, @@ -148,14 +149,7 @@ function useMediaDeviceHandle( // time of writing, we are seeing mobile Safari firing spurious // 'devicechange' events (where no change has actually occurred) when // we call MediaDevices.getUserMedia. So, filter by deep equality. - distinctUntilChanged((prev, current) => { - if (prev.length !== current.length) return true; - if (prev.length === 0) return false; - return !current.every( - (d, i) => - d.deviceId === prev[i].deviceId && d.label === prev[i].label, - ); - }), + distinctUntilChanged(deepCompare), ), [kind, requestPermissions], ); From 97d548ee3a2d9d4be1bc740afad66b4a0d79bdb8 Mon Sep 17 00:00:00 2001 From: fkwp Date: Thu, 5 Jun 2025 23:51:02 +0200 Subject: [PATCH 038/222] add build_mode input and use it in run commands --- .github/workflows/build-element-call.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-element-call.yaml b/.github/workflows/build-element-call.yaml index fc5eee02..a3996ce6 100644 --- a/.github/workflows/build-element-call.yaml +++ b/.github/workflows/build-element-call.yaml @@ -9,6 +9,11 @@ on: type: string # This would ideally be a `choice` type, but that isn't supported yet description: The package type to be built. Must be one of 'full' or 'embedded' required: true + build_mode: + type: string # This would ideally be a `choice` type, but that isn't supported yet + description: The build mode for vite. Must be either 'development' or 'production' + required: true + default: production secrets: SENTRY_ORG: required: true @@ -39,7 +44,7 @@ jobs: run: "yarn install --immutable" - name: Build full version if: ${{ inputs.package == 'full' }} - run: "yarn run build:full" + run: ${{ format('yarn run build:full:{0}', inputs.build_mode) }} env: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} @@ -50,7 +55,7 @@ jobs: NODE_OPTIONS: "--max-old-space-size=4096" - name: Build embedded if: ${{ inputs.package == 'embedded' }} - run: "yarn run build:embedded" + run: ${{ format('yarn run build:embedded:{0}', inputs.build_mode) }} env: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} From 775e110ca363ccfc98f507336997645431949bca Mon Sep 17 00:00:00 2001 From: fkwp Date: Thu, 5 Jun 2025 23:58:24 +0200 Subject: [PATCH 039/222] Add build_mode to inputs of build_full_element_call. derive build_mode from PR label 'development build' --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 47f956c7..e2816432 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -14,6 +14,7 @@ jobs: with: package: full vite_app_version: ${{ github.event.release.tag_name || github.sha }} + build_mode: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'development build') && 'development' || 'production' }} secrets: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} From b88bce522006136e33da42616c0d8aafb5a78803 Mon Sep 17 00:00:00 2001 From: fkwp Date: Thu, 5 Jun 2025 23:58:51 +0200 Subject: [PATCH 040/222] add production and developt build scripts to package.json --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 89a7e5ca..9d253fd2 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,11 @@ "dev:embedded": "vite --config vite-embedded.config.js", "build": "yarn build:full", "build:full": "NODE_OPTIONS=--max-old-space-size=16384 vite build", + "build:full:production": "yarn build:full", "build:full:development": "NODE_OPTIONS=--max-old-space-size=16384 NODE_ENV=development vite build --mode development", "build:embedded": "yarn build:full --config vite-embedded.config.js", + "build:embedded:production": "build:embedded", + "build:embedded:development": "NODE_ENV=development yarn build:full --config vite-embedded.config.js --mode development", "serve": "vite preview", "prettier:check": "prettier -c .", "prettier:format": "prettier -w .", From e07c7c864b33820ea1237cc46c5ffd07b6356413 Mon Sep 17 00:00:00 2001 From: fkwp Date: Fri, 6 Jun 2025 00:02:19 +0200 Subject: [PATCH 041/222] prettier --- vite.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite.config.js b/vite.config.js index 08ed54bf..5fe3a99b 100644 --- a/vite.config.js +++ b/vite.config.js @@ -89,7 +89,7 @@ export default defineConfig(({ mode, packageType }) => { }, }, build: { - minify: mode === 'production' ? true : false, + minify: mode === "production" ? true : false, sourcemap: true, rollupOptions: { output: { From 31185ae3653ac65e39e4988c9abc498a8a3f87cd Mon Sep 17 00:00:00 2001 From: fkwp Date: Fri, 6 Jun 2025 00:13:49 +0200 Subject: [PATCH 042/222] make build_mode optional --- .github/workflows/build-element-call.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-element-call.yaml b/.github/workflows/build-element-call.yaml index a3996ce6..6a65b74d 100644 --- a/.github/workflows/build-element-call.yaml +++ b/.github/workflows/build-element-call.yaml @@ -12,7 +12,7 @@ on: build_mode: type: string # This would ideally be a `choice` type, but that isn't supported yet description: The build mode for vite. Must be either 'development' or 'production' - required: true + required: false default: production secrets: SENTRY_ORG: From d47e579994982c0de62f57757c96022cec07033c Mon Sep 17 00:00:00 2001 From: fkwp Date: Fri, 6 Jun 2025 00:20:11 +0200 Subject: [PATCH 043/222] add build_mode also to build_embedded_element_call --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e2816432..6aa5fae6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -62,6 +62,7 @@ jobs: with: package: embedded vite_app_version: ${{ github.event.release.tag_name || github.sha }} + build_mode: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'development build') && 'development' || 'production' }} secrets: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} From 6f9adfe3e81668fc33e288bf40d430ffdd1d52f9 Mon Sep 17 00:00:00 2001 From: fkwp Date: Fri, 6 Jun 2025 00:21:23 +0200 Subject: [PATCH 044/222] fix: add misssing yarn --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9d253fd2..a1e698bf 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build:full:production": "yarn build:full", "build:full:development": "NODE_OPTIONS=--max-old-space-size=16384 NODE_ENV=development vite build --mode development", "build:embedded": "yarn build:full --config vite-embedded.config.js", - "build:embedded:production": "build:embedded", + "build:embedded:production": "yarn build:embedded", "build:embedded:development": "NODE_ENV=development yarn build:full --config vite-embedded.config.js --mode development", "serve": "vite preview", "prettier:check": "prettier -c .", From 13fac57b012d374c7e9de389788ab77423c369ea Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 6 Jun 2025 12:04:57 -0400 Subject: [PATCH 045/222] Build Docker image on slim base (#3204) * Build Docker image on slim base * Run Playwright tests against Docker container For Playwright end-to-end tests in CI, instead of running a development webserver with `yarn dev`, build and deploy a Docker container for Element Call and use that as the webserver to test against. * Shut down playwright webserver gracefully When using a containerized webserver, this stops the container once tests finish. * Increase Playwright timeout in CI --------- Co-authored-by: fkwp --- .github/workflows/test.yaml | 6 ++--- Dockerfile | 2 +- playwright.config.ts | 14 ++++++++--- playwright/fixtures/widget-user.ts | 31 +++++++++++++++++-------- scripts/playwright-webserver-command.sh | 10 ++++++++ 5 files changed, 46 insertions(+), 17 deletions(-) create mode 100755 scripts/playwright-webserver-command.sh diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 518226fb..f532cda6 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -30,7 +30,7 @@ jobs: fail_ci_if_error: true playwright: name: Run end-to-end tests - timeout-minutes: 10 + timeout-minutes: 30 runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 @@ -49,9 +49,9 @@ jobs: docker compose -f playwright-backend-docker-compose.yml -f playwright-backend-docker-compose.override.yml pull docker compose -f playwright-backend-docker-compose.yml -f playwright-backend-docker-compose.override.yml up -d docker ps - - name: Copy config file - run: cp config/config.devenv.json public/config.json - name: Run Playwright tests + env: + USE_DOCKER: 1 run: yarn playwright test - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 if: ${{ !cancelled() }} diff --git a/Dockerfile b/Dockerfile index c919d0c9..bf34c6c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ COPY ./dist /dist WORKDIR /dist/assets RUN gzip -k ../index.html *.js *.map *.css *.wasm *-app-*.json -FROM nginxinc/nginx-unprivileged:alpine +FROM nginxinc/nginx-unprivileged:alpine-slim COPY --from=builder ./dist /app diff --git a/playwright.config.ts b/playwright.config.ts index cdb8ec23..7a8ee530 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -7,6 +7,10 @@ Please see LICENSE in the repository root for full details. import { defineConfig, devices } from "@playwright/test"; +const baseURL = process.env.USE_DOCKER + ? "http://localhost:8080" + : "https://localhost:3000"; + /** * See https://playwright.dev/docs/test-configuration. */ @@ -25,7 +29,7 @@ export default defineConfig({ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: "https://localhost:3000", + baseURL, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", @@ -73,9 +77,13 @@ export default defineConfig({ /* Run your local dev server before starting the tests */ webServer: { - command: "yarn dev", - url: "https://localhost:3000", + command: "./scripts/playwright-webserver-command.sh", + url: baseURL, reuseExistingServer: !process.env.CI, ignoreHTTPSErrors: true, + gracefulShutdown: { + signal: "SIGTERM", + timeout: 500, + }, }, }); diff --git a/playwright/fixtures/widget-user.ts b/playwright/fixtures/widget-user.ts index 9caef91d..3ccb2ab2 100644 --- a/playwright/fixtures/widget-user.ts +++ b/playwright/fixtures/widget-user.ts @@ -69,16 +69,27 @@ const CONFIG_JSON = { * Set the Element Call URL in the dev tool settings using `window.mxSettingsStore` via `page.evaluate`. * @param page */ -async function setDevToolElementCallDevUrl(page: Page): Promise { - await page.evaluate(() => { - window.mxSettingsStore.setValue( - "Developer.elementCallUrl", - null, - "device", - "https://localhost:3000/room", - ); - }); -} +const setDevToolElementCallDevUrl = process.env.USE_DOCKER + ? async (page: Page): Promise => { + await page.evaluate(() => { + window.mxSettingsStore.setValue( + "Developer.elementCallUrl", + null, + "device", + "http://localhost:8080/room", + ); + }); + } + : async (page: Page): Promise => { + await page.evaluate(() => { + window.mxSettingsStore.setValue( + "Developer.elementCallUrl", + null, + "device", + "https://localhost:3000/room", + ); + }); + }; /** * Registers a new user and returns page, clientHandle and mxId. diff --git a/scripts/playwright-webserver-command.sh b/scripts/playwright-webserver-command.sh new file mode 100755 index 00000000..8c00909b --- /dev/null +++ b/scripts/playwright-webserver-command.sh @@ -0,0 +1,10 @@ +#!/bin/sh +if [ -n "$USE_DOCKER" ]; then + set -ex + yarn build + docker build -t element-call:testing . + exec docker run --rm --name element-call-testing -p 8080:8080 -v ./config/config.devenv.json:/app/config.json:ro,Z element-call:testing +else + cp config/config.devenv.json public/config.json + exec yarn dev +fi From 0ee11af3c50250f7608e8d067cba13cf4c1ff768 Mon Sep 17 00:00:00 2001 From: Robin Date: Mon, 9 Jun 2025 11:47:47 -0400 Subject: [PATCH 046/222] Upgrade react-spring to v10 For React 19 compatibility. --- package.json | 2 +- yarn.lock | 82 ++++++++++++++++++++++++++-------------------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index d6bc9230..4afde12b 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-slider": "^1.1.2", "@radix-ui/react-visually-hidden": "^1.0.3", - "@react-spring/web": "^9.4.4", + "@react-spring/web": "^10.0.0", "@sentry/react": "^8.0.0", "@sentry/vite-plugin": "^3.0.0", "@stylistic/eslint-plugin": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index 5a1b437c..8a16160d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4415,69 +4415,69 @@ __metadata: languageName: node linkType: hard -"@react-spring/animated@npm:~9.7.5": - version: 9.7.5 - resolution: "@react-spring/animated@npm:9.7.5" +"@react-spring/animated@npm:~10.0.1": + version: 10.0.1 + resolution: "@react-spring/animated@npm:10.0.1" dependencies: - "@react-spring/shared": "npm:~9.7.5" - "@react-spring/types": "npm:~9.7.5" + "@react-spring/shared": "npm:~10.0.1" + "@react-spring/types": "npm:~10.0.1" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10c0/f8c2473c60f39a878c7dd0fdfcfcdbc720521e1506aa3f63c9de64780694a0a73d5ccc535a5ccec3520ddb70a71cf43b038b32c18e99531522da5388c510ecd7 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/aaccd4a8b0280ac846d463b253ad8f092ee4afc9dbedc8e77616adf5399ffec755344f09fdd8487cadaf815840dff84d354d1143579c27c2fcd6937549b5fc40 languageName: node linkType: hard -"@react-spring/core@npm:~9.7.5": - version: 9.7.5 - resolution: "@react-spring/core@npm:9.7.5" +"@react-spring/core@npm:~10.0.1": + version: 10.0.1 + resolution: "@react-spring/core@npm:10.0.1" dependencies: - "@react-spring/animated": "npm:~9.7.5" - "@react-spring/shared": "npm:~9.7.5" - "@react-spring/types": "npm:~9.7.5" + "@react-spring/animated": "npm:~10.0.1" + "@react-spring/shared": "npm:~10.0.1" + "@react-spring/types": "npm:~10.0.1" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10c0/5bfd83dfe248cd91889f215f015d908c7714ef445740fd5afa054b27ebc7d5a456abf6c309e2459d9b5b436e78d6fda16b62b9601f96352e9130552c02270830 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/059b122dda4138e5e7e461abd49350921e326735ca9a1d8aa19b1fdbae0937661b5f71af6fe82fd8f59e8db5549627849b38cc3f7ef2ec7ee9c93c3d6225174f languageName: node linkType: hard -"@react-spring/rafz@npm:~9.7.5": - version: 9.7.5 - resolution: "@react-spring/rafz@npm:9.7.5" - checksum: 10c0/8bdad180feaa9a0e870a513043a5e98a4e9b7292a9f887575b7e6fadab2677825bc894b7ff16c38511b35bfe6cc1072df5851c5fee64448d67551559578ca759 +"@react-spring/rafz@npm:~10.0.1": + version: 10.0.1 + resolution: "@react-spring/rafz@npm:10.0.1" + checksum: 10c0/cba76f143d3a06f79dd0c09f7aefd17df9cca9b2c1ef7f9103255e5351326f4a42a5a1366f731a78f74380d96ba683bcc2a49312ed1e4b9e9e249e72c9ff68cb languageName: node linkType: hard -"@react-spring/shared@npm:~9.7.5": - version: 9.7.5 - resolution: "@react-spring/shared@npm:9.7.5" +"@react-spring/shared@npm:~10.0.1": + version: 10.0.1 + resolution: "@react-spring/shared@npm:10.0.1" dependencies: - "@react-spring/rafz": "npm:~9.7.5" - "@react-spring/types": "npm:~9.7.5" + "@react-spring/rafz": "npm:~10.0.1" + "@react-spring/types": "npm:~10.0.1" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10c0/0207eacccdedd918a2fc55e78356ce937f445ce27ad9abd5d3accba8f9701a39349b55115641dc2b39bb9d3a155b058c185b411d292dc8cc5686bfa56f73b94f + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/f056aaa018b3744afd8244e8eea24534d32f92fad9ace815b80e159b296fb5db148e2c9bd840ad9a5188e7a3c0778fd564b8af9ae02cd500e019a228398fb3cf languageName: node linkType: hard -"@react-spring/types@npm:~9.7.5": - version: 9.7.5 - resolution: "@react-spring/types@npm:9.7.5" - checksum: 10c0/85c05121853cacb64f7cf63a4855e9044635e1231f70371cd7b8c78bc10be6f4dd7c68f592f92a2607e8bb68051540989b4677a2ccb525dba937f5cd95dc8bc1 +"@react-spring/types@npm:~10.0.1": + version: 10.0.1 + resolution: "@react-spring/types@npm:10.0.1" + checksum: 10c0/260890f9c156dc69b77c846510017156d8c0a07cce70edc7c108e57b0cf4122b26a15e724b191481a51b2c914296de9e81d56618b2c339339d4b221930691baa languageName: node linkType: hard -"@react-spring/web@npm:^9.4.4": - version: 9.7.5 - resolution: "@react-spring/web@npm:9.7.5" +"@react-spring/web@npm:^10.0.0": + version: 10.0.1 + resolution: "@react-spring/web@npm:10.0.1" dependencies: - "@react-spring/animated": "npm:~9.7.5" - "@react-spring/core": "npm:~9.7.5" - "@react-spring/shared": "npm:~9.7.5" - "@react-spring/types": "npm:~9.7.5" + "@react-spring/animated": "npm:~10.0.1" + "@react-spring/core": "npm:~10.0.1" + "@react-spring/shared": "npm:~10.0.1" + "@react-spring/types": "npm:~10.0.1" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10c0/bcd1e052e1b16341a12a19bf4515f153ca09d1fa86ff7752a5d02d7c4db58e8baf80e6283e64411f1e388c65340dce2254b013083426806b5dbae38bd151e53e + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/a0c788c9fd881ccb834feb22fc0694e74e59f7b76e498f0096f5b65e2c9812513955bf45ee27d7c5348f56a7bba7c5a6961d4be663728bb2172fe5aa6b6bdfc4 languageName: node linkType: hard @@ -7473,7 +7473,7 @@ __metadata: "@radix-ui/react-dialog": "npm:^1.0.4" "@radix-ui/react-slider": "npm:^1.1.2" "@radix-ui/react-visually-hidden": "npm:^1.0.3" - "@react-spring/web": "npm:^9.4.4" + "@react-spring/web": "npm:^10.0.0" "@sentry/react": "npm:^8.0.0" "@sentry/vite-plugin": "npm:^3.0.0" "@stylistic/eslint-plugin": "npm:^3.0.0" From 8704b44b78f29111061fb6b60647ed892afe0ae0 Mon Sep 17 00:00:00 2001 From: Robin Date: Mon, 9 Jun 2025 11:48:01 -0400 Subject: [PATCH 047/222] Update test snapshots for React 19 changes --- src/__snapshots__/Modal.test.tsx.snap | 12 ++++++------ src/__snapshots__/Toast.test.tsx.snap | 6 +++--- .../__snapshots__/ReactionToggleButton.test.tsx.snap | 10 +++++----- src/livekit/TrackProcessorContext.tsx | 9 ++++++++- src/room/__snapshots__/InCallView.test.tsx.snap | 12 ++++++------ 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/__snapshots__/Modal.test.tsx.snap b/src/__snapshots__/Modal.test.tsx.snap index 8262eb9b..92d837d1 100644 --- a/src/__snapshots__/Modal.test.tsx.snap +++ b/src/__snapshots__/Modal.test.tsx.snap @@ -2,10 +2,10 @@ exports[`the content is rendered when the modal is open 1`] = ` ); - }), + }, }); diff --git a/src/grid/OneOnOneLayout.tsx b/src/grid/OneOnOneLayout.tsx index 5f9256fd..8e1bffbe 100644 --- a/src/grid/OneOnOneLayout.tsx +++ b/src/grid/OneOnOneLayout.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { forwardRef, useCallback, useMemo } from "react"; +import { type ReactNode, useCallback, useMemo } from "react"; import { useObservableEagerState } from "observable-hooks"; import classNames from "classnames"; @@ -24,12 +24,12 @@ export const makeOneOnOneLayout: CallLayout = ({ }) => ({ scrollingOnTop: false, - fixed: forwardRef(function OneOnOneLayoutFixed(_props, ref) { + fixed: function OneOnOneLayoutFixed({ ref }): ReactNode { useUpdateLayout(); return
; - }), + }, - scrolling: forwardRef(function OneOnOneLayoutScrolling({ model, Slot }, ref) { + scrolling: function OneOnOneLayoutScrolling({ ref, model, Slot }): ReactNode { useUpdateLayout(); const { width, height } = useObservableEagerState(minBounds$); const pipAlignmentValue = useObservableEagerState(pipAlignment$); @@ -66,5 +66,5 @@ export const makeOneOnOneLayout: CallLayout = ({
); - }), + }, }); diff --git a/src/grid/SpotlightExpandedLayout.tsx b/src/grid/SpotlightExpandedLayout.tsx index aa6b30ae..88271752 100644 --- a/src/grid/SpotlightExpandedLayout.tsx +++ b/src/grid/SpotlightExpandedLayout.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { forwardRef, useCallback } from "react"; +import { type ReactNode, useCallback } from "react"; import { useObservableEagerState } from "observable-hooks"; import { type SpotlightExpandedLayout as SpotlightExpandedLayoutModel } from "../state/CallViewModel"; @@ -22,10 +22,11 @@ export const makeSpotlightExpandedLayout: CallLayout< > = ({ pipAlignment$ }) => ({ scrollingOnTop: true, - fixed: forwardRef(function SpotlightExpandedLayoutFixed( - { model, Slot }, + fixed: function SpotlightExpandedLayoutFixed({ ref, - ) { + model, + Slot, + }): ReactNode { useUpdateLayout(); return ( @@ -37,12 +38,13 @@ export const makeSpotlightExpandedLayout: CallLayout< /> ); - }), + }, - scrolling: forwardRef(function SpotlightExpandedLayoutScrolling( - { model, Slot }, + scrolling: function SpotlightExpandedLayoutScrolling({ ref, - ) { + model, + Slot, + }): ReactNode { useUpdateLayout(); const pipAlignmentValue = useObservableEagerState(pipAlignment$); @@ -69,5 +71,5 @@ export const makeSpotlightExpandedLayout: CallLayout< )} ); - }), + }, }); diff --git a/src/grid/SpotlightLandscapeLayout.tsx b/src/grid/SpotlightLandscapeLayout.tsx index 99b9a82a..96343296 100644 --- a/src/grid/SpotlightLandscapeLayout.tsx +++ b/src/grid/SpotlightLandscapeLayout.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { forwardRef } from "react"; +import { type ReactNode } from "react"; import { useObservableEagerState } from "observable-hooks"; import classNames from "classnames"; @@ -24,10 +24,11 @@ export const makeSpotlightLandscapeLayout: CallLayout< > = ({ minBounds$ }) => ({ scrollingOnTop: false, - fixed: forwardRef(function SpotlightLandscapeLayoutFixed( - { model, Slot }, + fixed: function SpotlightLandscapeLayoutFixed({ ref, - ) { + model, + Slot, + }): ReactNode { useUpdateLayout(); useObservableEagerState(minBounds$); @@ -43,12 +44,13 @@ export const makeSpotlightLandscapeLayout: CallLayout<
); - }), + }, - scrolling: forwardRef(function SpotlightLandscapeLayoutScrolling( - { model, Slot }, + scrolling: function SpotlightLandscapeLayoutScrolling({ ref, - ) { + model, + Slot, + }): ReactNode { useUpdateLayout(); useVisibleTiles(model.setVisibleTiles); useObservableEagerState(minBounds$); @@ -69,5 +71,5 @@ export const makeSpotlightLandscapeLayout: CallLayout< ); - }), + }, }); diff --git a/src/grid/SpotlightPortraitLayout.tsx b/src/grid/SpotlightPortraitLayout.tsx index 3e27c461..3b4de6a1 100644 --- a/src/grid/SpotlightPortraitLayout.tsx +++ b/src/grid/SpotlightPortraitLayout.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { type CSSProperties, forwardRef } from "react"; +import { type ReactNode, type CSSProperties } from "react"; import { useObservableEagerState } from "observable-hooks"; import classNames from "classnames"; @@ -30,10 +30,11 @@ export const makeSpotlightPortraitLayout: CallLayout< > = ({ minBounds$ }) => ({ scrollingOnTop: false, - fixed: forwardRef(function SpotlightPortraitLayoutFixed( - { model, Slot }, + fixed: function SpotlightPortraitLayoutFixed({ ref, - ) { + model, + Slot, + }): ReactNode { useUpdateLayout(); return ( @@ -47,12 +48,13 @@ export const makeSpotlightPortraitLayout: CallLayout< ); - }), + }, - scrolling: forwardRef(function SpotlightPortraitLayoutScrolling( - { model, Slot }, + scrolling: function SpotlightPortraitLayoutScrolling({ ref, - ) { + model, + Slot, + }): ReactNode { useUpdateLayout(); useVisibleTiles(model.setVisibleTiles); const { width } = useObservableEagerState(minBounds$); @@ -90,5 +92,5 @@ export const makeSpotlightPortraitLayout: CallLayout< ); - }), + }, }); diff --git a/src/input/Input.tsx b/src/input/Input.tsx index 82b96109..1f6d35e8 100644 --- a/src/input/Input.tsx +++ b/src/input/Input.tsx @@ -9,10 +9,10 @@ import { type ChangeEvent, type FC, type ForwardedRef, - forwardRef, type ReactNode, useId, type JSX, + type Ref, } from "react"; import classNames from "classnames"; @@ -54,6 +54,7 @@ function Field({ children, className }: FieldProps): JSX.Element { } interface InputFieldProps { + ref?: Ref; label?: string; type: string; prefix?: string; @@ -78,88 +79,81 @@ interface InputFieldProps { onChange?: (event: ChangeEvent) => void; } -export const InputField = forwardRef< - HTMLInputElement | HTMLTextAreaElement, - InputFieldProps ->( - ( - { - id, - label, - className, - type, - checked, - prefix, - suffix, - description, - disabled, - min, - ...rest - }, - ref, - ) => { - const descriptionId = useId(); +export const InputField: FC = ({ + ref, + id, + label, + className, + type, + checked, + prefix, + suffix, + description, + disabled, + min, + ...rest +}) => { + const descriptionId = useId(); - return ( - - {prefix && {prefix}} - {type === "textarea" ? ( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore -