Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to React 18 createRoot API #28256

Merged
merged 32 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d5672ff
Migrate to React 18 createRoot API
t3chguy Oct 21, 2024
5a13e9e
Iterate
t3chguy Oct 22, 2024
54e083e
Iterate
t3chguy Oct 22, 2024
8ccf0d3
Iterate
t3chguy Oct 22, 2024
d8aba7f
Iterate
t3chguy Oct 22, 2024
e0bef93
Merge branch 'develop' of https://github.com/vector-im/element-web in…
t3chguy Oct 22, 2024
b127ca0
Iterate
t3chguy Oct 22, 2024
028df70
Iterate
t3chguy Oct 22, 2024
77e8469
Iterate
t3chguy Oct 22, 2024
ac0b69a
Iterate
t3chguy Oct 23, 2024
bb250f8
Discard changes to src/components/views/settings/devices/DeviceDetail…
t3chguy Oct 23, 2024
b55dc66
Iterate
t3chguy Oct 23, 2024
252a83f
Merge remote-tracking branch 'origin/t3chguy/react18/createRoot' into…
t3chguy Oct 23, 2024
be1dc35
Iterate
t3chguy Oct 24, 2024
5028951
Merge branch 'develop' of https://github.com/vector-im/element-web in…
t3chguy Oct 24, 2024
3b4fa49
Iterate
t3chguy Oct 24, 2024
a5c63ee
Attempt to stabilise test
t3chguy Oct 24, 2024
d48908a
legacyRoot?
t3chguy Oct 24, 2024
eedbfa8
Iterate
t3chguy Oct 25, 2024
b5c0ba7
Iterate
t3chguy Oct 25, 2024
b1c499a
Merge branch 'develop' of https://github.com/vector-im/element-web in…
t3chguy Oct 25, 2024
fc869ba
Merge branch 'develop' of https://github.com/vector-im/element-web in…
t3chguy Nov 6, 2024
41ca0fd
Merge branch 'develop' of https://github.com/vector-im/element-web in…
t3chguy Nov 11, 2024
2cbef0d
Fix tests
t3chguy Nov 11, 2024
1633a33
Improve coverage
t3chguy Nov 11, 2024
6d85961
Merge branch 'develop' of https://github.com/vector-im/element-web in…
t3chguy Nov 13, 2024
ccd2bf7
Update snapshots
t3chguy Nov 13, 2024
6183ad3
Improve coverage
t3chguy Nov 13, 2024
440cb14
Merge branch 'develop' of https://github.com/vector-im/element-web in…
t3chguy Nov 14, 2024
74e2362
Iterate
t3chguy Nov 18, 2024
0867ec7
Merge branch 'develop' into t3chguy/react18/createRoot
t3chguy Nov 19, 2024
42d60cf
Iterate
t3chguy Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/components/structures/auth/ForgotPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ interface State {
}

export default class ForgotPassword extends React.Component<Props, State> {
private unmounted = false;
private reset: PasswordReset;
private fieldPassword: Field | null = null;
private fieldPasswordConfirm: Field | null = null;
Expand Down Expand Up @@ -108,14 +109,20 @@ export default class ForgotPassword extends React.Component<Props, State> {
}
}

public componentWillUnmount(): void {
this.unmounted = true;
}

private async checkServerLiveliness(serverConfig: ValidatedServerConfig): Promise<void> {
try {
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(serverConfig.hsUrl, serverConfig.isUrl);
if (this.unmounted) return;

this.setState({
serverIsAlive: true,
});
} catch (e: any) {
if (this.unmounted) return;
const { serverIsAlive, serverDeadError } = AutoDiscoveryUtils.authComponentStateForError(
e,
"forgot_password",
Expand Down
14 changes: 8 additions & 6 deletions src/vector/init.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

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

Expand Down Expand Up @@ -93,19 +93,21 @@ export async function loadApp(fragParams: {}): Promise<void> {
function setWindowMatrixChat(matrixChat: MatrixChat): void {
window.matrixChat = matrixChat;
}
ReactDOM.render(await module.loadApp(fragParams, setWindowMatrixChat), document.getElementById("matrixchat"));
const app = await module.loadApp(fragParams, setWindowMatrixChat);
const root = createRoot(document.getElementById("matrixchat")!);
root.render(app);
}

export async function showError(title: string, messages?: string[]): Promise<void> {
const { ErrorView } = await import(
/* webpackChunkName: "error-view" */
"../async-components/structures/ErrorView"
);
ReactDOM.render(
const root = createRoot(document.getElementById("matrixchat")!);
root.render(
<StrictMode>
<ErrorView title={title} messages={messages} />
</StrictMode>,
document.getElementById("matrixchat"),
);
}

Expand All @@ -114,11 +116,11 @@ export async function showIncompatibleBrowser(onAccept: () => void): Promise<voi
/* webpackChunkName: "error-view" */
"../async-components/structures/ErrorView"
);
ReactDOM.render(
const root = createRoot(document.getElementById("matrixchat")!);
root.render(
<StrictMode>
<UnsupportedBrowserView onAccept={onAccept} />
</StrictMode>,
document.getElementById("matrixchat"),
);
}

Expand Down
1 change: 0 additions & 1 deletion test/test-utils/jest-matrix-react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ const wrapWithTooltipProvider = (Wrapper: RenderOptions["wrapper"]) => {

const customRender = (ui: ReactElement, options: RenderOptions = {}) => {
return render(ui, {
legacyRoot: true,
...options,
wrapper: wrapWithTooltipProvider(options?.wrapper) as RenderOptions["wrapper"],
}) as ReturnType<typeof render>;
Expand Down
2 changes: 1 addition & 1 deletion test/test-utils/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export const clearAllModals = async (): Promise<void> => {
// Prevent modals from leaking and polluting other tests
let keepClosingModals = true;
while (keepClosingModals) {
keepClosingModals = Modal.closeCurrentModal();
keepClosingModals = await act(() => Modal.closeCurrentModal());

// Then wait for the screen to update (probably React rerender and async/await).
// Important for tests using Jest fake timers to not get into an infinite loop
Expand Down
14 changes: 7 additions & 7 deletions test/unit-tests/accessibility/RovingTabIndex-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/

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

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

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

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

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

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

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

Expand Down Expand Up @@ -390,7 +390,7 @@ describe("RovingTabIndex", () => {
</RovingTabIndexProvider>,
);

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

await userEvent.keyboard("[ArrowDown]");
Expand Down Expand Up @@ -423,7 +423,7 @@ describe("RovingTabIndex", () => {
</RovingTabIndexProvider>,
);

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

const button = container.querySelectorAll("button")[1];
Expand Down
28 changes: 17 additions & 11 deletions test/unit-tests/components/structures/MatrixChat-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Please see LICENSE files in the repository root for full details.
import "core-js/stable/structured-clone";
import "fake-indexeddb/auto";
import React, { ComponentProps } from "react";
import { fireEvent, render, RenderResult, screen, waitFor, within } from "jest-matrix-react";
import { fireEvent, render, RenderResult, screen, waitFor, within, act } from "jest-matrix-react";
import fetchMock from "fetch-mock-jest";
import { Mocked, mocked } from "jest-mock";
import { ClientEvent, MatrixClient, MatrixEvent, Room, SyncState } from "matrix-js-sdk/src/matrix";
Expand Down Expand Up @@ -163,7 +163,7 @@ describe("<MatrixChat />", () => {
let initPromise: Promise<void> | undefined;
let defaultProps: ComponentProps<typeof MatrixChat>;
const getComponent = (props: Partial<ComponentProps<typeof MatrixChat>> = {}) =>
render(<MatrixChat {...defaultProps} {...props} />);
render(<MatrixChat {...defaultProps} {...props} />, { legacyRoot: true });

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

// let things settle
Expand Down Expand Up @@ -263,7 +263,7 @@ describe("<MatrixChat />", () => {

// 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)
defaultDispatcher.dispatch({ action: Action.OnLoggedOut }, true);
act(() => defaultDispatcher.dispatch({ action: Action.OnLoggedOut }, true));

localStorage.clear();
});
Expand Down Expand Up @@ -328,7 +328,7 @@ describe("<MatrixChat />", () => {

expect(within(dialog).getByText(errorMessage)).toBeInTheDocument();
// just check we're back on welcome page
await expect(await screen.findByTestId("mx_welcome_screen")).toBeInTheDocument();
await expect(screen.findByTestId("mx_welcome_screen")).resolves.toBeInTheDocument();
};

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

// go to login page
defaultDispatcher.dispatch({
action: "start_login",
});
act(() =>
defaultDispatcher.dispatch({
action: "start_login",
}),
);

await flushPromises();

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

await getComponentAndLogin();

bootstrapDeferred.resolve();
act(() => bootstrapDeferred.resolve());

await expect(await screen.findByRole("heading", { name: "You're in", level: 1 })).toBeInTheDocument();
await expect(
screen.findByRole("heading", { name: "You're in", level: 1 }),
).resolves.toBeInTheDocument();
});
});
});
Expand Down Expand Up @@ -1397,7 +1401,9 @@ describe("<MatrixChat />", () => {

function simulateSessionLockClaim() {
localStorage.setItem("react_sdk_session_lock_claimant", "testtest");
window.dispatchEvent(new StorageEvent("storage", { key: "react_sdk_session_lock_claimant" }));
act(() =>
window.dispatchEvent(new StorageEvent("storage", { key: "react_sdk_session_lock_claimant" })),
);
}

it("after a session is restored", async () => {
Expand Down
22 changes: 11 additions & 11 deletions test/unit-tests/components/structures/PipContainer-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ describe("PipContainer", () => {
let voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore;

const actFlushPromises = async () => {
await act(async () => {
await flushPromises();
});
await flushPromises();
};

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

const widget = new Widget(call.widget);
WidgetStore.instance.addVirtualWidget(call.widget, room.roomId);
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
stop: () => {},
} as unknown as ClientWidgetApi);

await act(async () => {
WidgetStore.instance.addVirtualWidget(call.widget, room.roomId);
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, {
stop: () => {},
} as unknown as ClientWidgetApi);

await call.start();
ActiveWidgetStore.instance.setWidgetPersistence(widget.id, room.roomId, true);
});

await fn(call);

cleanup();
call.destroy();
ActiveWidgetStore.instance.destroyPersistentWidget(widget.id, room.roomId);
WidgetStore.instance.removeVirtualWidget(widget.id, room.roomId);
act(() => {
call.destroy();
ActiveWidgetStore.instance.destroyPersistentWidget(widget.id, room.roomId);
WidgetStore.instance.removeVirtualWidget(widget.id, room.roomId);
});
};

const withWidget = async (fn: () => Promise<void>): Promise<void> => {
Expand Down
Loading
Loading