Skip to content

Commit ca33d91

Browse files
authored
Migrate to React 18 createRoot API (#28256)
* Migrate to React 18 createRoot API Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Discard changes to src/components/views/settings/devices/DeviceDetails.tsx * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Attempt to stabilise test Signed-off-by: Michael Telatynski <[email protected]> * legacyRoot? Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Fix tests Signed-off-by: Michael Telatynski <[email protected]> * Improve coverage Signed-off-by: Michael Telatynski <[email protected]> * Update snapshots Signed-off-by: Michael Telatynski <[email protected]> * Improve coverage Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> --------- Signed-off-by: Michael Telatynski <[email protected]>
1 parent 48fd330 commit ca33d91

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+703
-715
lines changed

src/components/structures/auth/ForgotPassword.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ interface State {
7575
}
7676

7777
export default class ForgotPassword extends React.Component<Props, State> {
78+
private unmounted = false;
7879
private reset: PasswordReset;
7980
private fieldPassword: Field | null = null;
8081
private fieldPasswordConfirm: Field | null = null;
@@ -108,14 +109,20 @@ export default class ForgotPassword extends React.Component<Props, State> {
108109
}
109110
}
110111

112+
public componentWillUnmount(): void {
113+
this.unmounted = true;
114+
}
115+
111116
private async checkServerLiveliness(serverConfig: ValidatedServerConfig): Promise<void> {
112117
try {
113118
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(serverConfig.hsUrl, serverConfig.isUrl);
119+
if (this.unmounted) return;
114120

115121
this.setState({
116122
serverIsAlive: true,
117123
});
118124
} catch (e: any) {
125+
if (this.unmounted) return;
119126
const { serverIsAlive, serverDeadError } = AutoDiscoveryUtils.authComponentStateForError(
120127
e,
121128
"forgot_password",

src/vector/init.tsx

+8-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
88
Please see LICENSE files in the repository root for full details.
99
*/
1010

11-
import * as ReactDOM from "react-dom";
11+
import { createRoot } from "react-dom/client";
1212
import React, { StrictMode } from "react";
1313
import { logger } from "matrix-js-sdk/src/logger";
1414

@@ -93,19 +93,21 @@ export async function loadApp(fragParams: {}): Promise<void> {
9393
function setWindowMatrixChat(matrixChat: MatrixChat): void {
9494
window.matrixChat = matrixChat;
9595
}
96-
ReactDOM.render(await module.loadApp(fragParams, setWindowMatrixChat), document.getElementById("matrixchat"));
96+
const app = await module.loadApp(fragParams, setWindowMatrixChat);
97+
const root = createRoot(document.getElementById("matrixchat")!);
98+
root.render(app);
9799
}
98100

99101
export async function showError(title: string, messages?: string[]): Promise<void> {
100102
const { ErrorView } = await import(
101103
/* webpackChunkName: "error-view" */
102104
"../async-components/structures/ErrorView"
103105
);
104-
ReactDOM.render(
106+
const root = createRoot(document.getElementById("matrixchat")!);
107+
root.render(
105108
<StrictMode>
106109
<ErrorView title={title} messages={messages} />
107110
</StrictMode>,
108-
document.getElementById("matrixchat"),
109111
);
110112
}
111113

@@ -114,11 +116,11 @@ export async function showIncompatibleBrowser(onAccept: () => void): Promise<voi
114116
/* webpackChunkName: "error-view" */
115117
"../async-components/structures/ErrorView"
116118
);
117-
ReactDOM.render(
119+
const root = createRoot(document.getElementById("matrixchat")!);
120+
root.render(
118121
<StrictMode>
119122
<UnsupportedBrowserView onAccept={onAccept} />
120123
</StrictMode>,
121-
document.getElementById("matrixchat"),
122124
);
123125
}
124126

test/test-utils/jest-matrix-react.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ const wrapWithTooltipProvider = (Wrapper: RenderOptions["wrapper"]) => {
2727

2828
const customRender = (ui: ReactElement, options: RenderOptions = {}) => {
2929
return render(ui, {
30-
legacyRoot: true,
3130
...options,
3231
wrapper: wrapWithTooltipProvider(options?.wrapper) as RenderOptions["wrapper"],
3332
}) as ReturnType<typeof render>;

test/test-utils/utilities.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ export const clearAllModals = async (): Promise<void> => {
197197
// Prevent modals from leaking and polluting other tests
198198
let keepClosingModals = true;
199199
while (keepClosingModals) {
200-
keepClosingModals = Modal.closeCurrentModal();
200+
keepClosingModals = await act(() => Modal.closeCurrentModal());
201201

202202
// Then wait for the screen to update (probably React rerender and async/await).
203203
// Important for tests using Jest fake timers to not get into an infinite loop

test/unit-tests/accessibility/RovingTabIndex-test.tsx

+7-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
77
*/
88

99
import React, { HTMLAttributes } from "react";
10-
import { render } from "jest-matrix-react";
10+
import { act, render } from "jest-matrix-react";
1111
import userEvent from "@testing-library/user-event";
1212

1313
import {
@@ -79,15 +79,15 @@ describe("RovingTabIndex", () => {
7979
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
8080

8181
// focus on 2nd button and test it is the only active one
82-
container.querySelectorAll("button")[2].focus();
82+
act(() => container.querySelectorAll("button")[2].focus());
8383
checkTabIndexes(container.querySelectorAll("button"), [-1, -1, 0]);
8484

8585
// focus on 1st button and test it is the only active one
86-
container.querySelectorAll("button")[1].focus();
86+
act(() => container.querySelectorAll("button")[1].focus());
8787
checkTabIndexes(container.querySelectorAll("button"), [-1, 0, -1]);
8888

8989
// check that the active button does not change even on an explicit blur event
90-
container.querySelectorAll("button")[1].blur();
90+
act(() => container.querySelectorAll("button")[1].blur());
9191
checkTabIndexes(container.querySelectorAll("button"), [-1, 0, -1]);
9292

9393
// update the children, it should remain on the same button
@@ -162,7 +162,7 @@ describe("RovingTabIndex", () => {
162162
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
163163

164164
// focus on 2nd button and test it is the only active one
165-
container.querySelectorAll("button")[2].focus();
165+
act(() => container.querySelectorAll("button")[2].focus());
166166
checkTabIndexes(container.querySelectorAll("button"), [-1, -1, 0]);
167167
});
168168

@@ -390,7 +390,7 @@ describe("RovingTabIndex", () => {
390390
</RovingTabIndexProvider>,
391391
);
392392

393-
container.querySelectorAll("button")[0].focus();
393+
act(() => container.querySelectorAll("button")[0].focus());
394394
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
395395

396396
await userEvent.keyboard("[ArrowDown]");
@@ -423,7 +423,7 @@ describe("RovingTabIndex", () => {
423423
</RovingTabIndexProvider>,
424424
);
425425

426-
container.querySelectorAll("button")[0].focus();
426+
act(() => container.querySelectorAll("button")[0].focus());
427427
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
428428

429429
const button = container.querySelectorAll("button")[1];

test/unit-tests/components/structures/MatrixChat-test.tsx

+17-11
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Please see LICENSE files in the repository root for full details.
1111
import "core-js/stable/structured-clone";
1212
import "fake-indexeddb/auto";
1313
import React, { ComponentProps } from "react";
14-
import { fireEvent, render, RenderResult, screen, waitFor, within } from "jest-matrix-react";
14+
import { fireEvent, render, RenderResult, screen, waitFor, within, act } from "jest-matrix-react";
1515
import fetchMock from "fetch-mock-jest";
1616
import { Mocked, mocked } from "jest-mock";
1717
import { ClientEvent, MatrixClient, MatrixEvent, Room, SyncState } from "matrix-js-sdk/src/matrix";
@@ -163,7 +163,7 @@ describe("<MatrixChat />", () => {
163163
let initPromise: Promise<void> | undefined;
164164
let defaultProps: ComponentProps<typeof MatrixChat>;
165165
const getComponent = (props: Partial<ComponentProps<typeof MatrixChat>> = {}) =>
166-
render(<MatrixChat {...defaultProps} {...props} />);
166+
render(<MatrixChat {...defaultProps} {...props} />, { legacyRoot: true });
167167

168168
// make test results readable
169169
filterConsole(
@@ -201,7 +201,7 @@ describe("<MatrixChat />", () => {
201201
// we are logged in, but are still waiting for the /sync to complete
202202
await screen.findByText("Syncing…");
203203
// initial sync
204-
client.emit(ClientEvent.Sync, SyncState.Prepared, null);
204+
await act(() => client.emit(ClientEvent.Sync, SyncState.Prepared, null));
205205
}
206206

207207
// let things settle
@@ -263,7 +263,7 @@ describe("<MatrixChat />", () => {
263263

264264
// emit a loggedOut event so that all of the Store singletons forget about their references to the mock client
265265
// (must be sync otherwise the next test will start before it happens)
266-
defaultDispatcher.dispatch({ action: Action.OnLoggedOut }, true);
266+
act(() => defaultDispatcher.dispatch({ action: Action.OnLoggedOut }, true));
267267

268268
localStorage.clear();
269269
});
@@ -328,7 +328,7 @@ describe("<MatrixChat />", () => {
328328

329329
expect(within(dialog).getByText(errorMessage)).toBeInTheDocument();
330330
// just check we're back on welcome page
331-
await expect(await screen.findByTestId("mx_welcome_screen")).toBeInTheDocument();
331+
await expect(screen.findByTestId("mx_welcome_screen")).resolves.toBeInTheDocument();
332332
};
333333

334334
beforeEach(() => {
@@ -956,9 +956,11 @@ describe("<MatrixChat />", () => {
956956
await screen.findByText("Powered by Matrix");
957957

958958
// go to login page
959-
defaultDispatcher.dispatch({
960-
action: "start_login",
961-
});
959+
act(() =>
960+
defaultDispatcher.dispatch({
961+
action: "start_login",
962+
}),
963+
);
962964

963965
await flushPromises();
964966

@@ -1126,9 +1128,11 @@ describe("<MatrixChat />", () => {
11261128

11271129
await getComponentAndLogin();
11281130

1129-
bootstrapDeferred.resolve();
1131+
act(() => bootstrapDeferred.resolve());
11301132

1131-
await expect(await screen.findByRole("heading", { name: "You're in", level: 1 })).toBeInTheDocument();
1133+
await expect(
1134+
screen.findByRole("heading", { name: "You're in", level: 1 }),
1135+
).resolves.toBeInTheDocument();
11321136
});
11331137
});
11341138
});
@@ -1397,7 +1401,9 @@ describe("<MatrixChat />", () => {
13971401

13981402
function simulateSessionLockClaim() {
13991403
localStorage.setItem("react_sdk_session_lock_claimant", "testtest");
1400-
window.dispatchEvent(new StorageEvent("storage", { key: "react_sdk_session_lock_claimant" }));
1404+
act(() =>
1405+
window.dispatchEvent(new StorageEvent("storage", { key: "react_sdk_session_lock_claimant" })),
1406+
);
14011407
}
14021408

14031409
it("after a session is restored", async () => {

test/unit-tests/components/structures/PipContainer-test.tsx

+11-11
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,7 @@ describe("PipContainer", () => {
8181
let voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore;
8282

8383
const actFlushPromises = async () => {
84-
await act(async () => {
85-
await flushPromises();
86-
});
84+
await flushPromises();
8785
};
8886

8987
beforeEach(async () => {
@@ -165,22 +163,24 @@ describe("PipContainer", () => {
165163
if (!(call instanceof MockedCall)) throw new Error("Failed to create call");
166164

167165
const widget = new Widget(call.widget);
168-
WidgetStore.instance.addVirtualWidget(call.widget, room.roomId);
169-
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
170-
stop: () => {},
171-
} as unknown as ClientWidgetApi);
172-
173166
await act(async () => {
167+
WidgetStore.instance.addVirtualWidget(call.widget, room.roomId);
168+
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
169+
stop: () => {},
170+
} as unknown as ClientWidgetApi);
171+
174172
await call.start();
175173
ActiveWidgetStore.instance.setWidgetPersistence(widget.id, room.roomId, true);
176174
});
177175

178176
await fn(call);
179177

180178
cleanup();
181-
call.destroy();
182-
ActiveWidgetStore.instance.destroyPersistentWidget(widget.id, room.roomId);
183-
WidgetStore.instance.removeVirtualWidget(widget.id, room.roomId);
179+
act(() => {
180+
call.destroy();
181+
ActiveWidgetStore.instance.destroyPersistentWidget(widget.id, room.roomId);
182+
WidgetStore.instance.removeVirtualWidget(widget.id, room.roomId);
183+
});
184184
};
185185

186186
const withWidget = async (fn: () => Promise<void>): Promise<void> => {

0 commit comments

Comments
 (0)