Migrate to React 18 createRoot API (#28256)

* Migrate to React 18 createRoot API

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>

* 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>

* Discard changes to src/components/views/settings/devices/DeviceDetails.tsx

* 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>

* Attempt to stabilise test

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

* legacyRoot?

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>

* Fix tests

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

* Improve coverage

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

* Update snapshots

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

* Improve coverage

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>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2024-11-20 13:29:23 +00:00 committed by GitHub
parent 48fd330dd9
commit ca33d9165a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 719 additions and 731 deletions

View file

@ -239,7 +239,7 @@ describe("Spotlight Dialog", () => {
});
it("should call getVisibleRooms with MSC3946 dynamic room predecessors", async () => {
render(<SpotlightDialog onFinished={() => null} />, { legacyRoot: false });
render(<SpotlightDialog onFinished={() => null} />);
jest.advanceTimersByTime(200);
await flushPromisesWithFakeTimers();
expect(mockedClient.getVisibleRooms).toHaveBeenCalledWith(true);

View file

@ -13,7 +13,7 @@ import { mocked, MockedObject } from "jest-mock";
import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
import { sleep } from "matrix-js-sdk/src/utils";
import { filterConsole, stubClient } from "../../../../../test-utils";
import { filterConsole, flushPromises, stubClient } from "../../../../../test-utils";
import CreateSecretStorageDialog from "../../../../../../src/async-components/views/dialogs/security/CreateSecretStorageDialog";
describe("CreateSecretStorageDialog", () => {
@ -125,6 +125,7 @@ describe("CreateSecretStorageDialog", () => {
resetFunctionCallLog.push("resetKeyBackup");
});
await flushPromises();
result.getByRole("button", { name: "Continue" }).click();
await result.findByText("Your keys are now being backed up from this device.");

View file

@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { screen, fireEvent, render, waitFor } from "jest-matrix-react";
import { screen, fireEvent, render, waitFor, act } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { Crypto, IMegolmSessionData } from "matrix-js-sdk/src/matrix";
@ -23,12 +23,12 @@ describe("ExportE2eKeysDialog", () => {
expect(asFragment()).toMatchSnapshot();
});
it("should have disabled submit button initially", () => {
it("should have disabled submit button initially", async () => {
const cli = createTestClient();
const onFinished = jest.fn();
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
fireEvent.click(container.querySelector("[type=submit]")!);
expect(screen.getByText("Enter passphrase")).toBeInTheDocument();
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
expect(screen.getByLabelText("Enter passphrase")).toBeInTheDocument();
});
it("should complain about weak passphrases", async () => {
@ -38,7 +38,7 @@ describe("ExportE2eKeysDialog", () => {
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
const input = screen.getByLabelText("Enter passphrase");
await userEvent.type(input, "password");
fireEvent.click(container.querySelector("[type=submit]")!);
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
await expect(screen.findByText("This is a top-10 common password")).resolves.toBeInTheDocument();
});
@ -49,7 +49,7 @@ describe("ExportE2eKeysDialog", () => {
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
await userEvent.type(screen.getByLabelText("Enter passphrase"), "ThisIsAMoreSecurePW123$$");
await userEvent.type(screen.getByLabelText("Confirm passphrase"), "ThisIsAMoreSecurePW124$$");
fireEvent.click(container.querySelector("[type=submit]")!);
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
await expect(screen.findByText("Passphrases must match")).resolves.toBeInTheDocument();
});
@ -74,7 +74,7 @@ describe("ExportE2eKeysDialog", () => {
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={jest.fn()} />);
await userEvent.type(screen.getByLabelText("Enter passphrase"), passphrase);
await userEvent.type(screen.getByLabelText("Confirm passphrase"), passphrase);
fireEvent.click(container.querySelector("[type=submit]")!);
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
// Then it exports keys and encrypts them
await waitFor(() => expect(exportRoomKeysAsJson).toHaveBeenCalled());

View file

@ -10,7 +10,7 @@ import React from "react";
import { Room, MatrixClient } from "matrix-js-sdk/src/matrix";
import { ClientWidgetApi, IWidget, MatrixWidgetType } from "matrix-widget-api";
import { Optional } from "matrix-events-sdk";
import { act, render, RenderResult } from "jest-matrix-react";
import { act, render, RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import {
ApprovalOpts,
@ -29,7 +29,6 @@ import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext
import SettingsStore from "../../../../../src/settings/SettingsStore";
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
import { UPDATE_EVENT } from "../../../../../src/stores/AsyncStore";
import WidgetStore, { IApp } from "../../../../../src/stores/WidgetStore";
import ActiveWidgetStore from "../../../../../src/stores/ActiveWidgetStore";
import AppTile from "../../../../../src/components/views/elements/AppTile";
@ -59,16 +58,6 @@ describe("AppTile", () => {
let app1: IApp;
let app2: IApp;
const waitForRps = (roomId: string) =>
new Promise<void>((resolve) => {
const update = () => {
if (RightPanelStore.instance.currentCardForRoom(roomId).phase !== RightPanelPhases.Widget) return;
RightPanelStore.instance.off(UPDATE_EVENT, update);
resolve();
};
RightPanelStore.instance.on(UPDATE_EVENT, update);
});
beforeAll(async () => {
stubClient();
cli = MatrixClientPeg.safeGet();
@ -160,29 +149,28 @@ describe("AppTile", () => {
/>
</MatrixClientContext.Provider>,
);
// Wait for RPS room 1 updates to fire
const rpsUpdated = waitForRps("r1");
dis.dispatch({
action: Action.ViewRoom,
room_id: "r1",
});
await rpsUpdated;
act(() =>
dis.dispatch({
action: Action.ViewRoom,
room_id: "r1",
}),
);
expect(renderResult.getByText("Example 1")).toBeInTheDocument();
await expect(renderResult.findByText("Example 1")).resolves.toBeInTheDocument();
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(true);
const { container, asFragment } = renderResult;
expect(container.getElementsByClassName("mx_Spinner").length).toBeTruthy();
const { asFragment } = renderResult;
expect(asFragment()).toMatchSnapshot();
// We want to verify that as we change to room 2, we should close the
// right panel and destroy the widget.
// Switch to room 2
dis.dispatch({
action: Action.ViewRoom,
room_id: "r2",
});
act(() =>
dis.dispatch({
action: Action.ViewRoom,
room_id: "r2",
}),
);
renderResult.rerender(
<MatrixClientContext.Provider value={cli}>
@ -233,16 +221,17 @@ describe("AppTile", () => {
/>
</MatrixClientContext.Provider>,
);
// Wait for RPS room 1 updates to fire
const rpsUpdated1 = waitForRps("r1");
dis.dispatch({
action: Action.ViewRoom,
room_id: "r1",
});
await rpsUpdated1;
act(() =>
dis.dispatch({
action: Action.ViewRoom,
room_id: "r1",
}),
);
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(true);
expect(ActiveWidgetStore.instance.isLive("1", "r2")).toBe(false);
await waitFor(() => {
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(true);
expect(ActiveWidgetStore.instance.isLive("1", "r2")).toBe(false);
});
jest.spyOn(SettingsStore, "getValue").mockImplementation((name, roomId) => {
if (name === "RightPanel.phases") {
@ -263,13 +252,13 @@ describe("AppTile", () => {
}
return realGetValue(name, roomId);
});
// Wait for RPS room 2 updates to fire
const rpsUpdated2 = waitForRps("r2");
// Switch to room 2
dis.dispatch({
action: Action.ViewRoom,
room_id: "r2",
});
act(() =>
dis.dispatch({
action: Action.ViewRoom,
room_id: "r2",
}),
);
renderResult.rerender(
<MatrixClientContext.Provider value={cli}>
<RightPanel
@ -279,10 +268,11 @@ describe("AppTile", () => {
/>
</MatrixClientContext.Provider>,
);
await rpsUpdated2;
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(false);
expect(ActiveWidgetStore.instance.isLive("1", "r2")).toBe(true);
await waitFor(() => {
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(false);
expect(ActiveWidgetStore.instance.isLive("1", "r2")).toBe(true);
});
});
it("preserves non-persisted widget on container move", async () => {
@ -345,7 +335,7 @@ describe("AppTile", () => {
let renderResult: RenderResult;
let moveToContainerSpy: jest.SpyInstance<void, [room: Room, widget: IWidget, toContainer: Container]>;
beforeEach(() => {
beforeEach(async () => {
renderResult = render(
<MatrixClientContext.Provider value={cli}>
<AppTile key={app1.id} app={app1} room={r1} />
@ -353,12 +343,12 @@ describe("AppTile", () => {
);
moveToContainerSpy = jest.spyOn(WidgetLayoutStore.instance, "moveToContainer");
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
});
it("should render", () => {
const { container, asFragment } = renderResult;
const { asFragment } = renderResult;
expect(container.querySelector(".mx_Spinner")).toBeFalsy(); // Assert that the spinner is gone
expect(asFragment()).toMatchSnapshot(); // Take a snapshot of the pinned widget
});
@ -459,18 +449,19 @@ describe("AppTile", () => {
describe("for a persistent app", () => {
let renderResult: RenderResult;
beforeEach(() => {
beforeEach(async () => {
renderResult = render(
<MatrixClientContext.Provider value={cli}>
<AppTile key={app1.id} app={app1} fullWidth={true} room={r1} miniMode={true} showMenubar={false} />
</MatrixClientContext.Provider>,
);
await waitForElementToBeRemoved(() => renderResult.queryByRole("progressbar"));
});
it("should render", () => {
const { container, asFragment } = renderResult;
it("should render", async () => {
const { asFragment } = renderResult;
expect(container.querySelector(".mx_Spinner")).toBeFalsy();
expect(asFragment()).toMatchSnapshot();
});
});

View file

@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { act, render, RenderResult, screen } from "jest-matrix-react";
import { render, RenderResult, screen } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { mocked, Mocked } from "jest-mock";
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
@ -214,9 +214,7 @@ describe("<Pill>", () => {
});
// wait for profile query via API
await act(async () => {
await flushPromises();
});
await flushPromises();
expect(renderResult.asFragment()).toMatchSnapshot();
});
@ -228,9 +226,7 @@ describe("<Pill>", () => {
});
// wait for profile query via API
await act(async () => {
await flushPromises();
});
await flushPromises();
expect(renderResult.asFragment()).toMatchSnapshot();
});

View file

@ -60,29 +60,9 @@ exports[`AppTile destroys non-persisted right panel widget on room change 1`] =
id="1"
>
<div
class="mx_AppTileBody mx_AppTileBody--large"
class="mx_AppTile_persistedWrapper"
>
<div
class="mx_AppTileBody_fadeInSpinner"
>
<div
class="mx_Spinner"
>
<div
class="mx_Spinner_Msg"
>
Loading…
</div>
 
<div
aria-label="Loading…"
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 32px; height: 32px;"
/>
</div>
</div>
<div />
</div>
</div>
</div>

View file

@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { createRef } from "react";
import { render, waitFor } from "jest-matrix-react";
import { render, waitFor, act } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import EmojiPicker from "../../../../../src/components/views/emojipicker/EmojiPicker";
@ -27,12 +27,12 @@ describe("EmojiPicker", function () {
// Apply a filter and assert that the HTML has changed
//@ts-ignore private access
ref.current!.onChangeFilter("test");
act(() => ref.current!.onChangeFilter("test"));
expect(beforeHtml).not.toEqual(container.innerHTML);
// Clear the filter and assert that the HTML matches what it was before filtering
//@ts-ignore private access
ref.current!.onChangeFilter("");
act(() => ref.current!.onChangeFilter(""));
await waitFor(() => expect(beforeHtml).toEqual(container.innerHTML));
});
@ -40,7 +40,7 @@ describe("EmojiPicker", function () {
const ep = new EmojiPicker({ onChoose: (str: string) => false, onFinished: jest.fn() });
//@ts-ignore private access
ep.onChangeFilter("heart");
act(() => ep.onChangeFilter("heart"));
//@ts-ignore private access
expect(ep.memoizedDataByCategory["people"][0].shortcodes[0]).toEqual("heart");

View file

@ -139,7 +139,7 @@ describe("<LocationShareMenu />", () => {
const [, onGeolocateCallback] = mocked(mockGeolocate.on).mock.calls.find(([event]) => event === "geolocate")!;
// set the location
onGeolocateCallback(position);
act(() => onGeolocateCallback(position));
};
const setLocationClick = () => {
@ -151,7 +151,7 @@ describe("<LocationShareMenu />", () => {
lngLat: { lng: position.coords.longitude, lat: position.coords.latitude },
} as unknown as maplibregl.MapMouseEvent;
// set the location
onMapClickCallback(event);
act(() => onMapClickCallback(event));
};
const shareTypeLabels: Record<LocationShareType, string> = {

View file

@ -48,6 +48,7 @@ describe("DateSeparator", () => {
<MatrixClientContext.Provider value={mockClient}>
<DateSeparator {...defaultProps} {...props} />
</MatrixClientContext.Provider>,
{ legacyRoot: true },
);
type TestCase = [string, number, string];
@ -264,10 +265,12 @@ describe("DateSeparator", () => {
fireEvent.click(jumpToLastWeekButton);
// Expect error to be shown. We have to wait for the UI to transition.
expect(await screen.findByTestId("jump-to-date-error-content")).toBeInTheDocument();
await expect(screen.findByTestId("jump-to-date-error-content")).resolves.toBeInTheDocument();
// Expect an option to submit debug logs to be shown when a non-network error occurs
expect(await screen.findByTestId("jump-to-date-error-submit-debug-logs-button")).toBeInTheDocument();
await expect(
screen.findByTestId("jump-to-date-error-submit-debug-logs-button"),
).resolves.toBeInTheDocument();
});
[
@ -280,19 +283,20 @@ describe("DateSeparator", () => {
),
].forEach((fakeError) => {
it(`should show error dialog without submit debug logs option when networking error (${fakeError.name}) occurs`, async () => {
// Try to jump to "last week" but we want a network error to occur
mockClient.timestampToEvent.mockRejectedValue(fakeError);
// Render the component
getComponent();
// Open the jump to date context menu
fireEvent.click(screen.getByTestId("jump-to-date-separator-button"));
// Try to jump to "last week" but we want a network error to occur
mockClient.timestampToEvent.mockRejectedValue(fakeError);
const jumpToLastWeekButton = await screen.findByTestId("jump-to-date-last-week");
fireEvent.click(jumpToLastWeekButton);
// Expect error to be shown. We have to wait for the UI to transition.
expect(await screen.findByTestId("jump-to-date-error-content")).toBeInTheDocument();
await expect(screen.findByTestId("jump-to-date-error-content")).resolves.toBeInTheDocument();
// The submit debug logs option should *NOT* be shown for network errors.
//

View file

@ -27,9 +27,9 @@ const renderEncryptionEvent = (client: MatrixClient, event: MatrixEvent) => {
);
};
const checkTexts = (title: string, subTitle: string) => {
screen.getByText(title);
screen.getByText(subTitle);
const checkTexts = async (title: string, subTitle: string) => {
await screen.findByText(title);
await screen.findByText(subTitle);
};
describe("EncryptionEvent", () => {
@ -120,9 +120,9 @@ describe("EncryptionEvent", () => {
renderEncryptionEvent(client, event);
});
it("should show the expected texts", () => {
it("should show the expected texts", async () => {
expect(client.getCrypto()!.isEncryptionEnabledInRoom).toHaveBeenCalledWith(roomId);
checkTexts("Encryption enabled", "Messages in this chat will be end-to-end encrypted.");
await checkTexts("Encryption enabled", "Messages in this chat will be end-to-end encrypted.");
});
});
});

View file

@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { fireEvent, render, RenderResult, waitFor } from "jest-matrix-react";
import { act, fireEvent, render, RenderResult, waitForElementToBeRemoved, waitFor } from "jest-matrix-react";
import {
MatrixEvent,
Relations,
@ -226,7 +226,7 @@ describe("MPollBody", () => {
clickOption(renderResult, "pizza");
// When a new vote from me comes in
await room.processPollEvents([responseEvent("@me:example.com", "wings", 101)]);
await act(() => room.processPollEvents([responseEvent("@me:example.com", "wings", 101)]));
// Then the new vote is counted, not the old one
expect(votesCount(renderResult, "pizza")).toBe("0 votes");
@ -255,7 +255,7 @@ describe("MPollBody", () => {
clickOption(renderResult, "pizza");
// When a new vote from someone else comes in
await room.processPollEvents([responseEvent("@xx:example.com", "wings", 101)]);
await act(() => room.processPollEvents([responseEvent("@xx:example.com", "wings", 101)]));
// Then my vote is still for pizza
// NOTE: the new event does not affect the counts for other people -
@ -596,11 +596,13 @@ describe("MPollBody", () => {
];
const renderResult = await newMPollBody(votes, ends);
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
await waitFor(() => {
expect(endedVotesCount(renderResult, "pizza")).toBe("2 votes");
expect(endedVotesCount(renderResult, "poutine")).toBe("0 votes");
expect(endedVotesCount(renderResult, "italian")).toBe("0 votes");
expect(endedVotesCount(renderResult, "wings")).toBe('<div class="mx_PollOption_winnerIcon"></div>3 votes');
expect(renderResult.getByTestId("totalVotes").innerHTML).toBe("Final result based on 5 votes");
});
});
it("ignores votes that arrived after the first end poll event", async () => {
@ -890,12 +892,14 @@ async function newMPollBody(
room_id: "#myroom:example.com",
content: newPollStart(answers, undefined, disclosed),
});
const result = newMPollBodyFromEvent(mxEvent, relationEvents, endEvents);
// flush promises from loading relations
const prom = newMPollBodyFromEvent(mxEvent, relationEvents, endEvents);
if (waitForResponsesLoad) {
await flushPromises();
const result = await prom;
if (result.queryByTestId("spinner")) {
await waitForElementToBeRemoved(() => result.getByTestId("spinner"));
}
}
return result;
return prom;
}
function getMPollBodyPropsFromEvent(mxEvent: MatrixEvent): IBodyProps {

View file

@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { render, waitFor } from "jest-matrix-react";
import { render, waitFor, waitForElementToBeRemoved } from "jest-matrix-react";
import { EventTimeline, MatrixEvent, Room, M_TEXT } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
@ -127,6 +127,7 @@ describe("<MPollEndBody />", () => {
expect(container).toMatchSnapshot();
await waitFor(() => expect(getByRole("progressbar")).toBeInTheDocument());
await waitForElementToBeRemoved(() => getByRole("progressbar"));
expect(mockClient.fetchRoomEvent).toHaveBeenCalledWith(roomId, pollStartEvent.getId());

View file

@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { act, fireEvent, render } from "jest-matrix-react";
import { fireEvent, render } from "jest-matrix-react";
import { Filter, EventTimeline, Room, MatrixEvent, M_POLL_START } from "matrix-js-sdk/src/matrix";
import { PollHistory } from "../../../../../../src/components/views/polls/pollHistory/PollHistory";
@ -110,7 +110,7 @@ describe("<PollHistory />", () => {
expect(getByText("Loading polls")).toBeInTheDocument();
// flush filter creation request
await act(flushPromises);
await flushPromises();
expect(liveTimeline.getPaginationToken).toHaveBeenCalledWith(EventTimeline.BACKWARDS);
expect(mockClient.paginateEventTimeline).toHaveBeenCalledWith(liveTimeline, { backwards: true });
@ -140,7 +140,7 @@ describe("<PollHistory />", () => {
);
// flush filter creation request
await act(flushPromises);
await flushPromises();
// once per page
expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(3);
@ -175,7 +175,7 @@ describe("<PollHistory />", () => {
it("renders a no polls message when there are no active polls in the room", async () => {
const { getByText } = getComponent();
await act(flushPromises);
await flushPromises();
expect(getByText("There are no active polls in this room")).toBeTruthy();
});
@ -199,7 +199,7 @@ describe("<PollHistory />", () => {
.mockReturnValueOnce("test-pagination-token-3");
const { getByText } = getComponent();
await act(flushPromises);
await flushPromises();
expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(1);
@ -212,7 +212,7 @@ describe("<PollHistory />", () => {
// load more polls button still in UI, with loader
expect(getByText("Load more polls")).toMatchSnapshot();
await act(flushPromises);
await flushPromises();
// no more spinner
expect(getByText("Load more polls")).toMatchSnapshot();

View file

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

View file

@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { fireEvent, render, screen, waitFor, cleanup, act, within } from "jest-matrix-react";
import { fireEvent, render, screen, cleanup, act, within } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { Mocked, mocked } from "jest-mock";
import { Room, User, MatrixClient, RoomMember, MatrixEvent, EventType, Device } from "matrix-js-sdk/src/matrix";
@ -199,6 +199,7 @@ describe("<UserInfo />", () => {
return render(<UserInfo {...defaultProps} {...props} />, {
wrapper: Wrapper,
legacyRoot: true,
});
};
@ -439,7 +440,7 @@ describe("<UserInfo />", () => {
it("renders a device list which can be expanded", async () => {
renderComponent();
await act(flushPromises);
await flushPromises();
// check the button exists with the expected text
const devicesButton = screen.getByRole("button", { name: "1 session" });
@ -459,9 +460,9 @@ describe("<UserInfo />", () => {
verificationRequest,
room: mockRoom,
});
await act(flushPromises);
await flushPromises();
await waitFor(() => expect(screen.getByRole("button", { name: "Verify" })).toBeInTheDocument());
await expect(screen.findByRole("button", { name: "Verify" })).resolves.toBeInTheDocument();
expect(container).toMatchSnapshot();
});
@ -490,7 +491,7 @@ describe("<UserInfo />", () => {
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
renderComponent({ room: mockRoom });
await act(flushPromises);
await flushPromises();
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
const devicesButton = screen.getByRole("button", { name: "1 session" });
@ -538,7 +539,7 @@ describe("<UserInfo />", () => {
} as DeviceVerificationStatus);
renderComponent({ room: mockRoom });
await act(flushPromises);
await flushPromises();
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
const devicesButton = screen.getByRole("button", { name: "1 verified session" });
@ -583,7 +584,7 @@ describe("<UserInfo />", () => {
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, true, true));
renderComponent({ room: mockRoom });
await act(flushPromises);
await flushPromises();
// the dehydrated device should be shown as an unverified device, which means
// there should now be a button with the device id ...
@ -618,7 +619,7 @@ describe("<UserInfo />", () => {
mockCrypto.getUserDeviceInfo.mockResolvedValue(userDeviceMap);
renderComponent({ room: mockRoom });
await act(flushPromises);
await flushPromises();
// check the button exists with the expected text (the dehydrated device shouldn't be counted)
const devicesButton = screen.getByRole("button", { name: "2 sessions" });
@ -653,7 +654,7 @@ describe("<UserInfo />", () => {
room: mockRoom,
});
await waitFor(() => expect(screen.getByRole("button", { name: "Deactivate user" })).toBeInTheDocument());
await expect(screen.findByRole("button", { name: "Deactivate user" })).resolves.toBeInTheDocument();
expect(container).toMatchSnapshot();
});
});
@ -666,7 +667,7 @@ describe("<UserInfo />", () => {
it("renders unverified user info", async () => {
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(false, false, false));
renderComponent({ room: mockRoom });
await act(flushPromises);
await flushPromises();
const userHeading = screen.getByRole("heading", { name: /@user:example.com/ });
@ -677,7 +678,7 @@ describe("<UserInfo />", () => {
it("renders verified user info", async () => {
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, false, false));
renderComponent({ room: mockRoom });
await act(flushPromises);
await flushPromises();
const userHeading = screen.getByRole("heading", { name: /@user:example.com/ });
@ -768,7 +769,7 @@ describe("<DeviceItem />", () => {
it("with unverified user and device, displays button without a label", async () => {
renderComponent();
await act(flushPromises);
await flushPromises();
expect(screen.getByRole("button", { name: device.displayName! })).toBeInTheDocument();
expect(screen.queryByText(/trusted/i)).not.toBeInTheDocument();
@ -776,7 +777,7 @@ describe("<DeviceItem />", () => {
it("with verified user only, displays button with a 'Not trusted' label", async () => {
renderComponent({ isUserVerified: true });
await act(flushPromises);
await flushPromises();
const button = screen.getByRole("button", { name: device.displayName });
expect(button).toHaveTextContent(`${device.displayName}Not trusted`);
@ -785,7 +786,7 @@ describe("<DeviceItem />", () => {
it("with verified device only, displays no button without a label", async () => {
setMockDeviceTrust(true);
renderComponent();
await act(flushPromises);
await flushPromises();
expect(screen.getByText(device.displayName!)).toBeInTheDocument();
expect(screen.queryByText(/trusted/)).not.toBeInTheDocument();
@ -798,7 +799,7 @@ describe("<DeviceItem />", () => {
mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
mockClient.getUserId.mockReturnValueOnce(defaultUserId);
renderComponent();
await act(flushPromises);
await flushPromises();
// set trust to be false for isVerified, true for isCrossSigningVerified
deferred.resolve({
@ -814,7 +815,7 @@ describe("<DeviceItem />", () => {
it("with verified user and device, displays no button and a 'Trusted' label", async () => {
setMockDeviceTrust(true);
renderComponent({ isUserVerified: true });
await act(flushPromises);
await flushPromises();
expect(screen.queryByRole("button")).not.toBeInTheDocument();
expect(screen.getByText(device.displayName!)).toBeInTheDocument();
@ -824,7 +825,7 @@ describe("<DeviceItem />", () => {
it("does not call verifyDevice if client.getUser returns null", async () => {
mockClient.getUser.mockReturnValueOnce(null);
renderComponent();
await act(flushPromises);
await flushPromises();
const button = screen.getByRole("button", { name: device.displayName! });
expect(button).toBeInTheDocument();
@ -839,7 +840,7 @@ describe("<DeviceItem />", () => {
// even more mocking
mockClient.isGuest.mockReturnValueOnce(true);
renderComponent();
await act(flushPromises);
await flushPromises();
const button = screen.getByRole("button", { name: device.displayName! });
expect(button).toBeInTheDocument();
@ -851,7 +852,7 @@ describe("<DeviceItem />", () => {
it("with display name", async () => {
const { container } = renderComponent();
await act(flushPromises);
await flushPromises();
expect(container).toMatchSnapshot();
});
@ -859,7 +860,7 @@ describe("<DeviceItem />", () => {
it("without display name", async () => {
const device = { deviceId: "deviceId" } as Device;
const { container } = renderComponent({ device, userId: defaultUserId });
await act(flushPromises);
await flushPromises();
expect(container).toMatchSnapshot();
});
@ -867,7 +868,7 @@ describe("<DeviceItem />", () => {
it("ambiguous display name", async () => {
const device = { deviceId: "deviceId", ambiguous: true, displayName: "my display name" };
const { container } = renderComponent({ device, userId: defaultUserId });
await act(flushPromises);
await flushPromises();
expect(container).toMatchSnapshot();
});
@ -1033,9 +1034,7 @@ describe("<UserOptionsSection />", () => {
expect(inviteSpy).toHaveBeenCalledWith([member.userId]);
// check that the test error message is displayed
await waitFor(() => {
expect(screen.getByText(mockErrorMessage.message)).toBeInTheDocument();
});
await expect(screen.findByText(mockErrorMessage.message)).resolves.toBeInTheDocument();
});
it("if calling .invite throws something strange, show default error message", async () => {
@ -1048,9 +1047,7 @@ describe("<UserOptionsSection />", () => {
await userEvent.click(inviteButton);
// check that the default test error message is displayed
await waitFor(() => {
expect(screen.getByText(/operation failed/i)).toBeInTheDocument();
});
await expect(screen.findByText(/operation failed/i)).resolves.toBeInTheDocument();
});
it.each([

View file

@ -260,7 +260,7 @@ describe("EventTile", () => {
} as EventEncryptionInfo);
const { container } = getComponent();
await act(flushPromises);
await flushPromises();
const eventTiles = container.getElementsByClassName("mx_EventTile");
expect(eventTiles).toHaveLength(1);
@ -285,7 +285,7 @@ describe("EventTile", () => {
} as EventEncryptionInfo);
const { container } = getComponent();
await act(flushPromises);
await flushPromises();
const eventTiles = container.getElementsByClassName("mx_EventTile");
expect(eventTiles).toHaveLength(1);
@ -314,7 +314,7 @@ describe("EventTile", () => {
} as EventEncryptionInfo);
const { container } = getComponent();
await act(flushPromises);
await flushPromises();
const e2eIcons = container.getElementsByClassName("mx_EventTile_e2eIcon");
expect(e2eIcons).toHaveLength(1);
@ -346,7 +346,7 @@ describe("EventTile", () => {
await mxEvent.attemptDecryption(mockCrypto);
const { container } = getComponent();
await act(flushPromises);
await flushPromises();
const eventTiles = container.getElementsByClassName("mx_EventTile");
expect(eventTiles).toHaveLength(1);
@ -400,7 +400,7 @@ describe("EventTile", () => {
const roomContext = getRoomContext(room, {});
const { container, rerender } = render(<WrappedEventTile roomContext={roomContext} />);
await act(flushPromises);
await flushPromises();
const eventTiles = container.getElementsByClassName("mx_EventTile");
expect(eventTiles).toHaveLength(1);
@ -451,7 +451,7 @@ describe("EventTile", () => {
const roomContext = getRoomContext(room, {});
const { container, rerender } = render(<WrappedEventTile roomContext={roomContext} />);
await act(flushPromises);
await flushPromises();
const eventTiles = container.getElementsByClassName("mx_EventTile");
expect(eventTiles).toHaveLength(1);

View file

@ -8,7 +8,16 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { act, fireEvent, render, RenderResult, screen, waitFor, waitForElementToBeRemoved } from "jest-matrix-react";
import {
act,
fireEvent,
render,
RenderResult,
screen,
waitFor,
waitForElementToBeRemoved,
cleanup,
} from "jest-matrix-react";
import { Room, MatrixClient, RoomState, RoomMember, User, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { mocked, MockedObject } from "jest-mock";
@ -361,6 +370,7 @@ describe("MemberList", () => {
afterEach(() => {
jest.restoreAllMocks();
cleanup();
});
const renderComponent = () => {
@ -397,21 +407,22 @@ describe("MemberList", () => {
jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
jest.spyOn(room, "canInvite").mockReturnValue(false);
renderComponent();
await flushPromises();
const { findByLabelText } = renderComponent();
// button rendered but disabled
expect(screen.getByText("Invite to this room")).toHaveAttribute("aria-disabled", "true");
await expect(findByLabelText("You do not have permission to invite users")).resolves.toHaveAttribute(
"aria-disabled",
"true",
);
});
it("renders enabled invite button when current user is a member and has rights to invite", async () => {
jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
jest.spyOn(room, "canInvite").mockReturnValue(true);
renderComponent();
await flushPromises();
const { findByText } = renderComponent();
expect(screen.getByText("Invite to this room")).not.toBeDisabled();
await expect(findByText("Invite to this room")).resolves.not.toBeDisabled();
});
it("opens room inviter on button click", async () => {

View file

@ -42,17 +42,13 @@ import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/t
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
const openStickerPicker = async (): Promise<void> => {
await act(async () => {
await userEvent.click(screen.getByLabelText("More options"));
await userEvent.click(screen.getByLabelText("Sticker"));
});
await userEvent.click(screen.getByLabelText("More options"));
await userEvent.click(screen.getByLabelText("Sticker"));
};
const startVoiceMessage = async (): Promise<void> => {
await act(async () => {
await userEvent.click(screen.getByLabelText("More options"));
await userEvent.click(screen.getByLabelText("Voice Message"));
});
await userEvent.click(screen.getByLabelText("More options"));
await userEvent.click(screen.getByLabelText("Voice Message"));
};
const setCurrentBroadcastRecording = (room: Room, state: VoiceBroadcastInfoState): void => {
@ -61,7 +57,7 @@ const setCurrentBroadcastRecording = (room: Room, state: VoiceBroadcastInfoState
MatrixClientPeg.safeGet(),
state,
);
SdkContextClass.instance.voiceBroadcastRecordingsStore.setCurrent(recording);
act(() => SdkContextClass.instance.voiceBroadcastRecordingsStore.setCurrent(recording));
};
const expectVoiceMessageRecordingTriggered = (): void => {
@ -97,6 +93,45 @@ describe("MessageComposer", () => {
});
});
it("wysiwyg correctly persists state to and from localStorage", async () => {
const room = mkStubRoom("!roomId:server", "Room 1", cli);
const messageText = "Test Text";
await SettingsStore.setValue("feature_wysiwyg_composer", null, SettingLevel.DEVICE, true);
const { renderResult, rawComponent } = wrapAndRender({ room });
const { unmount } = renderResult;
await flushPromises();
const key = `mx_wysiwyg_state_${room.roomId}`;
await userEvent.click(screen.getByRole("textbox"));
fireEvent.input(screen.getByRole("textbox"), {
data: messageText,
inputType: "insertText",
});
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText));
// Wait for event dispatch to happen
await flushPromises();
// assert there is state persisted
expect(localStorage.getItem(key)).toBeNull();
// ensure the right state was persisted to localStorage
unmount();
// assert the persisted state
expect(JSON.parse(localStorage.getItem(key)!)).toStrictEqual({
content: messageText,
isRichText: true,
});
// ensure the correct state is re-loaded
render(rawComponent);
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText));
}, 10000);
describe("for a Room", () => {
const room = mkStubRoom("!roomId:server", "Room 1", cli);
@ -185,14 +220,12 @@ describe("MessageComposer", () => {
[true, false].forEach((value: boolean) => {
describe(`when ${setting} = ${value}`, () => {
beforeEach(async () => {
SettingsStore.setValue(setting, null, SettingLevel.DEVICE, value);
await act(() => SettingsStore.setValue(setting, null, SettingLevel.DEVICE, value));
wrapAndRender({ room });
await act(async () => {
await userEvent.click(screen.getByLabelText("More options"));
});
await userEvent.click(screen.getByLabelText("More options"));
});
it(`should${value || "not"} display the button`, () => {
it(`should${value ? "" : " not"} display the button`, () => {
if (value) {
// eslint-disable-next-line jest/no-conditional-expect
expect(screen.getByLabelText(buttonLabel)).toBeInTheDocument();
@ -205,15 +238,17 @@ describe("MessageComposer", () => {
describe(`and setting ${setting} to ${!value}`, () => {
beforeEach(async () => {
// simulate settings update
await SettingsStore.setValue(setting, null, SettingLevel.DEVICE, !value);
dis.dispatch(
{
action: Action.SettingUpdated,
settingName: setting,
newValue: !value,
},
true,
);
await act(async () => {
await SettingsStore.setValue(setting, null, SettingLevel.DEVICE, !value);
dis.dispatch(
{
action: Action.SettingUpdated,
settingName: setting,
newValue: !value,
},
true,
);
});
});
it(`should${!value || "not"} display the button`, () => {
@ -273,7 +308,7 @@ describe("MessageComposer", () => {
beforeEach(async () => {
wrapAndRender({ room }, true, true);
await openStickerPicker();
resizeCallback(UI_EVENTS.Resize, {});
act(() => resizeCallback(UI_EVENTS.Resize, {}));
});
it("should close the menu", () => {
@ -295,7 +330,7 @@ describe("MessageComposer", () => {
beforeEach(async () => {
wrapAndRender({ room }, true, false);
await openStickerPicker();
resizeCallback(UI_EVENTS.Resize, {});
act(() => resizeCallback(UI_EVENTS.Resize, {}));
});
it("should close the menu", () => {
@ -443,51 +478,6 @@ describe("MessageComposer", () => {
expect(screen.queryByLabelText("Sticker")).not.toBeInTheDocument();
});
});
it("wysiwyg correctly persists state to and from localStorage", async () => {
const room = mkStubRoom("!roomId:server", "Room 1", cli);
const messageText = "Test Text";
await SettingsStore.setValue("feature_wysiwyg_composer", null, SettingLevel.DEVICE, true);
const { renderResult, rawComponent } = wrapAndRender({ room });
const { unmount, rerender } = renderResult;
await act(async () => {
await flushPromises();
});
const key = `mx_wysiwyg_state_${room.roomId}`;
await act(async () => {
await userEvent.click(screen.getByRole("textbox"));
});
fireEvent.input(screen.getByRole("textbox"), {
data: messageText,
inputType: "insertText",
});
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText));
// Wait for event dispatch to happen
await act(async () => {
await flushPromises();
});
// assert there is state persisted
expect(localStorage.getItem(key)).toBeNull();
// ensure the right state was persisted to localStorage
unmount();
// assert the persisted state
expect(JSON.parse(localStorage.getItem(key)!)).toStrictEqual({
content: messageText,
isRichText: true,
});
// ensure the correct state is re-loaded
rerender(rawComponent);
await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText));
}, 10000);
});
function wrapAndRender(
@ -529,7 +519,7 @@ function wrapAndRender(
);
return {
rawComponent: getRawComponent(props, roomContext, mockClient),
renderResult: render(getRawComponent(props, roomContext, mockClient)),
renderResult: render(getRawComponent(props, roomContext, mockClient), { legacyRoot: true }),
roomContext,
};
}

View file

@ -385,7 +385,7 @@ describe("<SendMessageComposer/>", () => {
it("correctly persists state to and from localStorage", () => {
const props = { replyToEvent: mockEvent };
const { container, unmount, rerender } = getComponent(props);
let { container, unmount } = getComponent(props);
addTextToComposer(container, "Test Text");
@ -402,7 +402,7 @@ describe("<SendMessageComposer/>", () => {
});
// ensure the correct model is re-loaded
rerender(getRawComponent(props));
({ container, unmount } = getComponent(props));
expect(container.textContent).toBe("Test Text");
expect(spyDispatcher).toHaveBeenCalledWith({
action: "reply_to_event",
@ -413,7 +413,7 @@ describe("<SendMessageComposer/>", () => {
// now try with localStorage wiped out
unmount();
localStorage.removeItem(key);
rerender(getRawComponent(props));
({ container } = getComponent(props));
expect(container.textContent).toBe("");
});

View file

@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import "@testing-library/jest-dom";
import React from "react";
import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react";
import { fireEvent, render, screen, waitFor } from "jest-matrix-react";
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
import RoomContext from "../../../../../../src/contexts/RoomContext";
@ -253,9 +253,7 @@ describe("EditWysiwygComposer", () => {
});
// Wait for event dispatch to happen
await act(async () => {
await flushPromises();
});
await flushPromises();
// Then we don't get it because we are disabled
expect(screen.getByRole("textbox")).not.toHaveFocus();

View file

@ -6,7 +6,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 { render, screen, waitFor } from "jest-matrix-react";
import { render, screen, waitFor, cleanup } from "jest-matrix-react";
import { MatrixClient, MatrixError, ThreepidMedium } from "matrix-js-sdk/src/matrix";
import React from "react";
import userEvent from "@testing-library/user-event";
@ -48,54 +48,13 @@ describe("AddRemoveThreepids", () => {
afterEach(() => {
jest.restoreAllMocks();
clearAllModals();
cleanup();
});
const clientProviderWrapper: React.FC = ({ children }: React.PropsWithChildren) => (
<MatrixClientContext.Provider value={client}>{children}</MatrixClientContext.Provider>
);
it("should render a loader while loading", async () => {
render(
<AddRemoveThreepids
mode="hs"
medium={ThreepidMedium.Email}
threepids={[]}
isLoading={true}
onChange={() => {}}
/>,
);
expect(screen.getByLabelText("Loading…")).toBeInTheDocument();
});
it("should render email addresses", async () => {
const { container } = render(
<AddRemoveThreepids
mode="hs"
medium={ThreepidMedium.Email}
threepids={[EMAIL1]}
isLoading={false}
onChange={() => {}}
/>,
);
expect(container).toMatchSnapshot();
});
it("should render phone numbers", async () => {
const { container } = render(
<AddRemoveThreepids
mode="hs"
medium={ThreepidMedium.Phone}
threepids={[PHONE1]}
isLoading={false}
onChange={() => {}}
/>,
);
expect(container).toMatchSnapshot();
});
it("should handle no email addresses", async () => {
const { container } = render(
<AddRemoveThreepids
@ -107,6 +66,7 @@ describe("AddRemoveThreepids", () => {
/>,
);
await expect(screen.findByText("Email Address")).resolves.toBeVisible();
expect(container).toMatchSnapshot();
});
@ -127,7 +87,7 @@ describe("AddRemoveThreepids", () => {
},
);
const input = screen.getByRole("textbox", { name: "Email Address" });
const input = await screen.findByRole("textbox", { name: "Email Address" });
await userEvent.type(input, EMAIL1.address);
const addButton = screen.getByRole("button", { name: "Add" });
await userEvent.click(addButton);
@ -166,7 +126,7 @@ describe("AddRemoveThreepids", () => {
},
);
const input = screen.getByRole("textbox", { name: "Email Address" });
const input = await screen.findByRole("textbox", { name: "Email Address" });
await userEvent.type(input, EMAIL1.address);
const addButton = screen.getByRole("button", { name: "Add" });
await userEvent.click(addButton);
@ -210,7 +170,7 @@ describe("AddRemoveThreepids", () => {
},
);
const countryDropdown = screen.getByRole("button", { name: /Country Dropdown/ });
const countryDropdown = await screen.findByRole("button", { name: /Country Dropdown/ });
await userEvent.click(countryDropdown);
const gbOption = screen.getByRole("option", { name: "🇬🇧 United Kingdom (+44)" });
await userEvent.click(gbOption);
@ -270,7 +230,7 @@ describe("AddRemoveThreepids", () => {
},
);
const removeButton = screen.getByRole("button", { name: /Remove/ });
const removeButton = await screen.findByRole("button", { name: /Remove/ });
await userEvent.click(removeButton);
expect(screen.getByText(`Remove ${EMAIL1.address}?`)).toBeVisible();
@ -297,7 +257,7 @@ describe("AddRemoveThreepids", () => {
},
);
const removeButton = screen.getByRole("button", { name: /Remove/ });
const removeButton = await screen.findByRole("button", { name: /Remove/ });
await userEvent.click(removeButton);
expect(screen.getByText(`Remove ${EMAIL1.address}?`)).toBeVisible();
@ -326,7 +286,7 @@ describe("AddRemoveThreepids", () => {
},
);
const removeButton = screen.getByRole("button", { name: /Remove/ });
const removeButton = await screen.findByRole("button", { name: /Remove/ });
await userEvent.click(removeButton);
expect(screen.getByText(`Remove ${PHONE1.address}?`)).toBeVisible();
@ -357,7 +317,7 @@ describe("AddRemoveThreepids", () => {
},
);
expect(screen.getByText(EMAIL1.address)).toBeVisible();
await expect(screen.findByText(EMAIL1.address)).resolves.toBeVisible();
const shareButton = screen.getByRole("button", { name: /Share/ });
await userEvent.click(shareButton);
@ -408,7 +368,7 @@ describe("AddRemoveThreepids", () => {
},
);
expect(screen.getByText(PHONE1.address)).toBeVisible();
await expect(screen.findByText(PHONE1.address)).resolves.toBeVisible();
const shareButton = screen.getByRole("button", { name: /Share/ });
await userEvent.click(shareButton);
@ -452,7 +412,7 @@ describe("AddRemoveThreepids", () => {
},
);
expect(screen.getByText(EMAIL1.address)).toBeVisible();
await expect(screen.findByText(EMAIL1.address)).resolves.toBeVisible();
const revokeButton = screen.getByRole("button", { name: /Revoke/ });
await userEvent.click(revokeButton);
@ -475,7 +435,7 @@ describe("AddRemoveThreepids", () => {
},
);
expect(screen.getByText(PHONE1.address)).toBeVisible();
await expect(screen.findByText(PHONE1.address)).resolves.toBeVisible();
const revokeButton = screen.getByRole("button", { name: /Revoke/ });
await userEvent.click(revokeButton);
@ -596,4 +556,48 @@ describe("AddRemoveThreepids", () => {
}),
);
});
it("should render a loader while loading", async () => {
render(
<AddRemoveThreepids
mode="hs"
medium={ThreepidMedium.Email}
threepids={[]}
isLoading={true}
onChange={() => {}}
/>,
);
expect(screen.getByLabelText("Loading…")).toBeInTheDocument();
});
it("should render email addresses", async () => {
const { container } = render(
<AddRemoveThreepids
mode="hs"
medium={ThreepidMedium.Email}
threepids={[EMAIL1]}
isLoading={false}
onChange={() => {}}
/>,
);
await expect(screen.findByText(EMAIL1.address)).resolves.toBeVisible();
expect(container).toMatchSnapshot();
});
it("should render phone numbers", async () => {
const { container } = render(
<AddRemoveThreepids
mode="hs"
medium={ThreepidMedium.Phone}
threepids={[PHONE1]}
isLoading={false}
onChange={() => {}}
/>,
);
await expect(screen.findByText(PHONE1.address)).resolves.toBeVisible();
expect(container).toMatchSnapshot();
});
});

View file

@ -11,14 +11,14 @@ exports[`AddRemoveThreepids should handle no email addresses 1`] = `
>
<input
autocomplete="email"
id="mx_Field_3"
id="mx_Field_1"
label="Email Address"
placeholder="Email Address"
type="text"
value=""
/>
<label
for="mx_Field_3"
for="mx_Field_1"
>
Email Address
</label>
@ -61,14 +61,14 @@ exports[`AddRemoveThreepids should render email addresses 1`] = `
>
<input
autocomplete="email"
id="mx_Field_1"
id="mx_Field_14"
label="Email Address"
placeholder="Email Address"
type="text"
value=""
/>
<label
for="mx_Field_1"
for="mx_Field_14"
>
Email Address
</label>
@ -148,14 +148,14 @@ exports[`AddRemoveThreepids should render phone numbers 1`] = `
</span>
<input
autocomplete="tel-national"
id="mx_Field_2"
id="mx_Field_15"
label="Phone Number"
placeholder="Phone Number"
type="text"
value=""
/>
<label
for="mx_Field_2"
for="mx_Field_15"
>
Phone Number
</label>

View file

@ -79,9 +79,7 @@ describe("<LoginWithQR />", () => {
describe("MSC4108", () => {
const getComponent = (props: { client: MatrixClient; onFinished?: () => void }) => (
<React.StrictMode>
<LoginWithQR {...defaultProps} {...props} />
</React.StrictMode>
<LoginWithQR {...defaultProps} {...props} />
);
test("render QR then back", async () => {

View file

@ -277,9 +277,7 @@ describe("<SessionManagerTab />", () => {
mockClient.getDevices.mockRejectedValue({ httpStatus: 404 });
const { container } = render(getComponent());
await act(async () => {
await flushPromises();
});
await flushPromises();
expect(container.getElementsByClassName("mx_Spinner").length).toBeFalsy();
});
@ -302,9 +300,7 @@ describe("<SessionManagerTab />", () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromises();
});
await flushPromises();
expect(mockCrypto.getDeviceVerificationStatus).toHaveBeenCalledTimes(3);
expect(
@ -337,9 +333,7 @@ describe("<SessionManagerTab />", () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromises();
});
await flushPromises();
// twice for each device
expect(mockClient.getAccountData).toHaveBeenCalledTimes(4);
@ -356,9 +350,7 @@ describe("<SessionManagerTab />", () => {
const { getByTestId, queryByTestId } = render(getComponent());
await act(async () => {
await flushPromises();
});
await flushPromises();
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
// application metadata section not rendered
@ -369,9 +361,7 @@ describe("<SessionManagerTab />", () => {
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice] });
const { queryByTestId } = render(getComponent());
await act(async () => {
await flushPromises();
});
await flushPromises();
expect(queryByTestId("other-sessions-section")).toBeFalsy();
});
@ -382,9 +372,7 @@ describe("<SessionManagerTab />", () => {
});
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromises();
});
await flushPromises();
expect(getByTestId("other-sessions-section")).toBeTruthy();
});
@ -395,9 +383,7 @@ describe("<SessionManagerTab />", () => {
});
const { getByTestId, container } = render(getComponent());
await act(async () => {
await flushPromises();
});
await flushPromises();
fireEvent.click(getByTestId("unverified-devices-cta"));
@ -908,7 +894,8 @@ describe("<SessionManagerTab />", () => {
});
it("deletes a device when interactive auth is not required", async () => {
mockClient.deleteMultipleDevices.mockResolvedValue({});
const deferredDeleteMultipleDevices = defer<{}>();
mockClient.deleteMultipleDevices.mockReturnValue(deferredDeleteMultipleDevices.promise);
mockClient.getDevices.mockResolvedValue({
devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice],
});
@ -933,6 +920,7 @@ describe("<SessionManagerTab />", () => {
fireEvent.click(signOutButton);
await confirmSignout(getByTestId);
await prom;
deferredDeleteMultipleDevices.resolve({});
// delete called
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(
@ -991,7 +979,7 @@ describe("<SessionManagerTab />", () => {
const { getByTestId, getByLabelText } = render(getComponent());
await act(flushPromises);
await flushPromises();
// reset mock count after initial load
mockClient.getDevices.mockClear();
@ -1025,7 +1013,7 @@ describe("<SessionManagerTab />", () => {
fireEvent.submit(getByLabelText("Password"));
});
await act(flushPromises);
await flushPromises();
// called again with auth
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith([alicesMobileDevice.device_id], {
@ -1551,7 +1539,7 @@ describe("<SessionManagerTab />", () => {
});
const { getByTestId, container } = render(getComponent());
await act(flushPromises);
await flushPromises();
// filter for inactive sessions
await setFilter(container, DeviceSecurityVariation.Inactive);
@ -1577,9 +1565,7 @@ describe("<SessionManagerTab />", () => {
it("lets you change the pusher state", async () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromises();
});
await flushPromises();
toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
@ -1598,9 +1584,7 @@ describe("<SessionManagerTab />", () => {
it("lets you change the local notification settings state", async () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromises();
});
await flushPromises();
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
@ -1621,9 +1605,7 @@ describe("<SessionManagerTab />", () => {
it("updates the UI when another session changes the local notifications", async () => {
const { getByTestId } = render(getComponent());
await act(async () => {
await flushPromises();
});
await flushPromises();
toggleDeviceDetails(getByTestId, alicesDevice.device_id);

View file

@ -42,14 +42,14 @@ exports[`<AccountUserSettingsTab /> 3pids should display 3pid email addresses an
>
<input
autocomplete="email"
id="mx_Field_9"
id="mx_Field_3"
label="Email Address"
placeholder="Email Address"
type="text"
value=""
/>
<label
for="mx_Field_9"
for="mx_Field_3"
>
Email Address
</label>
@ -145,14 +145,14 @@ exports[`<AccountUserSettingsTab /> 3pids should display 3pid email addresses an
</span>
<input
autocomplete="tel-national"
id="mx_Field_10"
id="mx_Field_4"
label="Phone Number"
placeholder="Phone Number"
type="text"
value=""
/>
<label
for="mx_Field_10"
for="mx_Field_4"
>
Phone Number
</label>

View file

@ -388,7 +388,7 @@ exports[`<SessionManagerTab /> goes to filtered list from security recommendatio
>
<input
aria-label="Select all"
aria-labelledby=":r4e:"
aria-labelledby=":r3s:"
data-testid="device-select-all-checkbox"
id="device-select-all-checkbox"
type="checkbox"

View file

@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { ComponentProps } from "react";
import { mocked, Mocked } from "jest-mock";
import { act, render, RenderResult } from "jest-matrix-react";
import { render, RenderResult } from "jest-matrix-react";
import { TypedEventEmitter, IMyDevice, MatrixClient, Device } from "matrix-js-sdk/src/matrix";
import { VerificationRequest, VerificationRequestEvent } from "matrix-js-sdk/src/crypto-api";
@ -63,9 +63,7 @@ describe("VerificationRequestToast", () => {
otherDeviceId,
});
const result = renderComponent({ request });
await act(async () => {
await flushPromises();
});
await flushPromises();
expect(result.container).toMatchSnapshot();
});
@ -76,9 +74,7 @@ describe("VerificationRequestToast", () => {
otherUserId,
});
const result = renderComponent({ request });
await act(async () => {
await flushPromises();
});
await flushPromises();
expect(result.container).toMatchSnapshot();
});
@ -89,9 +85,7 @@ describe("VerificationRequestToast", () => {
otherUserId,
});
renderComponent({ request, toastKey: "testKey" });
await act(async () => {
await flushPromises();
});
await flushPromises();
const dismiss = jest.spyOn(ToastStore.sharedInstance(), "dismissToast");
Object.defineProperty(request, "accepting", { value: true });