Make tests more resilient for React 18 upgrade (#12861)
* Make tests more resilient for React 18 upgrade 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> * Delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
4e4c5c7768
commit
8285283cc3
35 changed files with 313 additions and 290 deletions
|
@ -1380,7 +1380,6 @@ describe("<MatrixChat />", () => {
|
|||
|
||||
it("while we are checking the sync store", async () => {
|
||||
const rendered = getComponent({});
|
||||
await flushPromises();
|
||||
expect(rendered.getByTestId("spinner")).toBeInTheDocument();
|
||||
|
||||
// now a third session starts
|
||||
|
|
|
@ -128,7 +128,7 @@ describe("<UserMenu>", () => {
|
|||
|
||||
const spy = jest.spyOn(defaultDispatcher, "dispatch");
|
||||
screen.getByRole("button", { name: /User menu/i }).click();
|
||||
screen.getByRole("menuitem", { name: /Sign out/i }).click();
|
||||
(await screen.findByRole("menuitem", { name: /Sign out/i })).click();
|
||||
await waitFor(() => {
|
||||
expect(spy).toHaveBeenCalledWith({ action: "logout" });
|
||||
});
|
||||
|
@ -152,7 +152,7 @@ describe("<UserMenu>", () => {
|
|||
|
||||
const spy = jest.spyOn(defaultDispatcher, "dispatch");
|
||||
screen.getByRole("button", { name: /User menu/i }).click();
|
||||
screen.getByRole("menuitem", { name: /Sign out/i }).click();
|
||||
(await screen.findByRole("menuitem", { name: /Sign out/i })).click();
|
||||
await waitFor(() => {
|
||||
expect(spy).toHaveBeenCalledWith({ action: "logout" });
|
||||
});
|
||||
|
@ -178,7 +178,7 @@ describe("<UserMenu>", () => {
|
|||
|
||||
const spy = jest.spyOn(Modal, "createDialog");
|
||||
screen.getByRole("button", { name: /User menu/i }).click();
|
||||
screen.getByRole("menuitem", { name: /Sign out/i }).click();
|
||||
(await screen.findByRole("menuitem", { name: /Sign out/i })).click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(spy).toHaveBeenCalledWith(LogoutDialog);
|
||||
|
|
|
@ -129,11 +129,11 @@ describe("AccessSecretStorageDialog", () => {
|
|||
expect(screen.getByPlaceholderText("Security Phrase")).toHaveValue(securityKey);
|
||||
await submitDialog();
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
await expect(
|
||||
screen.findByText(
|
||||
"👎 Unable to access secret storage. Please verify that you entered the correct Security Phrase.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
expect(screen.getByPlaceholderText("Security Phrase")).toHaveFocus();
|
||||
});
|
||||
|
|
|
@ -429,7 +429,7 @@ describe("InviteDialog", () => {
|
|||
|
||||
describe("when clicking »Start DM anyway«", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByRole("button", { name: "Start DM anyway", exact: true }));
|
||||
await userEvent.click(screen.getByRole("button", { name: "Start DM anyway" }));
|
||||
});
|
||||
|
||||
it("should start the DM", () => {
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, RenderResult } from "@testing-library/react";
|
||||
import { render, RenderResult, waitForElementToBeRemoved } from "@testing-library/react";
|
||||
import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
@ -39,6 +39,7 @@ describe("<MessageEditHistory />", () => {
|
|||
|
||||
async function renderComponent(): Promise<RenderResult> {
|
||||
const result = render(<MessageEditHistoryDialog mxEvent={event} onFinished={jest.fn()} />);
|
||||
await waitForElementToBeRemoved(() => result.queryByRole("progressbar"));
|
||||
await flushPromises();
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import {
|
||||
EventTimeline,
|
||||
EventType,
|
||||
|
@ -129,7 +129,7 @@ describe("<RoomSettingsDialog />", () => {
|
|||
expect(screen.getByTestId("settings-tab-ROOM_PEOPLE_TAB")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("re-renders on room join rule changes", () => {
|
||||
it("re-renders on room join rule changes", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(setting) => setting === "feature_ask_to_join",
|
||||
);
|
||||
|
@ -142,7 +142,9 @@ describe("<RoomSettingsDialog />", () => {
|
|||
room.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
|
||||
null,
|
||||
);
|
||||
expect(screen.queryByTestId("settings-tab-ROOM_PEOPLE_TAB")).not.toBeInTheDocument();
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByTestId("settings-tab-ROOM_PEOPLE_TAB")).not.toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -57,14 +57,9 @@ jest.mock("../../../../src/settings/SettingsStore", () => ({
|
|||
settingIsOveriddenAtConfigLevel: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../src/SdkConfig", () => ({
|
||||
get: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("<UserSettingsDialog />", () => {
|
||||
const userId = "@alice:server.org";
|
||||
const mockSettingsStore = mocked(SettingsStore);
|
||||
const mockSdkConfig = mocked(SdkConfig);
|
||||
let mockClient!: MockedObject<MatrixClient>;
|
||||
|
||||
let sdkContext: SdkContextClass;
|
||||
|
@ -89,7 +84,8 @@ describe("<UserSettingsDialog />", () => {
|
|||
mockSettingsStore.getValue.mockReturnValue(false);
|
||||
mockSettingsStore.getValueAt.mockReturnValue(false);
|
||||
mockSettingsStore.getFeatureSettingNames.mockReturnValue([]);
|
||||
mockSdkConfig.get.mockReturnValue({ brand: "Test" });
|
||||
SdkConfig.reset();
|
||||
SdkConfig.put({ brand: "Test" });
|
||||
});
|
||||
|
||||
const getActiveTabLabel = (container: Element) =>
|
||||
|
@ -115,6 +111,9 @@ describe("<UserSettingsDialog />", () => {
|
|||
});
|
||||
|
||||
it("renders tabs correctly", () => {
|
||||
SdkConfig.add({
|
||||
show_labs_settings: true,
|
||||
});
|
||||
const { container } = render(getComponent());
|
||||
expect(container.querySelectorAll(".mx_TabbedView_tabLabel")).toMatchSnapshot();
|
||||
});
|
||||
|
@ -181,7 +180,7 @@ describe("<UserSettingsDialog />", () => {
|
|||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Settings: Voice & Video");
|
||||
});
|
||||
|
||||
it("renders with secutity tab selected", () => {
|
||||
it("renders with security tab selected", () => {
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Security }));
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Security & Privacy");
|
||||
|
@ -189,18 +188,8 @@ describe("<UserSettingsDialog />", () => {
|
|||
});
|
||||
|
||||
it("renders with labs tab selected", () => {
|
||||
// @ts-ignore I give up trying to get the types right here
|
||||
// why do we have functions that return different things depending on what they're passed?
|
||||
mockSdkConfig.get.mockImplementation((x) => {
|
||||
const mockConfig = { show_labs_settings: true, brand: "Test" };
|
||||
switch (x) {
|
||||
case "show_labs_settings":
|
||||
case "brand":
|
||||
// @ts-ignore
|
||||
return mockConfig[x];
|
||||
default:
|
||||
return mockConfig;
|
||||
}
|
||||
SdkConfig.add({
|
||||
show_labs_settings: true,
|
||||
});
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Labs }));
|
||||
|
||||
|
@ -223,8 +212,9 @@ describe("<UserSettingsDialog />", () => {
|
|||
});
|
||||
|
||||
it("renders labs tab when show_labs_settings is enabled in config", () => {
|
||||
// @ts-ignore simplified test stub
|
||||
mockSdkConfig.get.mockImplementation((configName) => configName === "show_labs_settings");
|
||||
SdkConfig.add({
|
||||
show_labs_settings: true,
|
||||
});
|
||||
const { getByTestId } = render(getComponent());
|
||||
expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy();
|
||||
});
|
||||
|
@ -238,7 +228,7 @@ describe("<UserSettingsDialog />", () => {
|
|||
expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("watches settings", () => {
|
||||
it("watches settings", async () => {
|
||||
const watchSettingCallbacks: Record<string, CallbackFn> = {};
|
||||
|
||||
mockSettingsStore.watchSetting.mockImplementation((settingName, roomId, callback) => {
|
||||
|
@ -247,7 +237,7 @@ describe("<UserSettingsDialog />", () => {
|
|||
});
|
||||
mockSettingsStore.getValue.mockReturnValue(false);
|
||||
|
||||
const { queryByTestId, unmount } = render(getComponent());
|
||||
const { queryByTestId, findByTestId, unmount } = render(getComponent());
|
||||
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeFalsy();
|
||||
|
||||
expect(mockSettingsStore.watchSetting).toHaveBeenCalledWith("feature_mjolnir", null, expect.anything());
|
||||
|
@ -257,7 +247,7 @@ describe("<UserSettingsDialog />", () => {
|
|||
watchSettingCallbacks["feature_mjolnir"]("feature_mjolnir", "", SettingLevel.ACCOUNT, true, true);
|
||||
|
||||
// tab is rendered now
|
||||
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy();
|
||||
await expect(findByTestId(`settings-tab-${UserTab.Mjolnir}`)).resolves.toBeTruthy();
|
||||
|
||||
unmount();
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
|
||||
import Field from "../../../../src/components/views/elements/Field";
|
||||
|
||||
|
@ -63,12 +63,10 @@ describe("Field", () => {
|
|||
);
|
||||
|
||||
// When invalid
|
||||
await act(async () => {
|
||||
fireEvent.focus(screen.getByRole("textbox"));
|
||||
});
|
||||
fireEvent.focus(screen.getByRole("textbox"));
|
||||
|
||||
// Expect 'alert' role
|
||||
expect(screen.queryByRole("alert")).toBeInTheDocument();
|
||||
await expect(screen.findByRole("alert")).resolves.toBeInTheDocument();
|
||||
|
||||
// Close the feedback is Escape is pressed
|
||||
fireEvent.keyDown(screen.getByRole("textbox"), { key: "Escape" });
|
||||
|
@ -85,12 +83,10 @@ describe("Field", () => {
|
|||
);
|
||||
|
||||
// When valid
|
||||
await act(async () => {
|
||||
fireEvent.focus(screen.getByRole("textbox"));
|
||||
});
|
||||
fireEvent.focus(screen.getByRole("textbox"));
|
||||
|
||||
// Expect 'status' role
|
||||
expect(screen.queryByRole("status")).toBeInTheDocument();
|
||||
await expect(screen.findByRole("status")).resolves.toBeInTheDocument();
|
||||
|
||||
// Close the feedback is Escape is pressed
|
||||
fireEvent.keyDown(screen.getByRole("textbox"), { key: "Escape" });
|
||||
|
@ -108,12 +104,10 @@ describe("Field", () => {
|
|||
);
|
||||
|
||||
// When valid or invalid and 'tooltipContent' set
|
||||
await act(async () => {
|
||||
fireEvent.focus(screen.getByRole("textbox"));
|
||||
});
|
||||
fireEvent.focus(screen.getByRole("textbox"));
|
||||
|
||||
// Expect 'tooltip' role
|
||||
expect(screen.queryByRole("tooltip")).toBeInTheDocument();
|
||||
await expect(screen.findByRole("tooltip")).resolves.toBeInTheDocument();
|
||||
|
||||
// Close the feedback is Escape is pressed
|
||||
fireEvent.keyDown(screen.getByRole("textbox"), { key: "Escape" });
|
||||
|
|
|
@ -32,10 +32,10 @@ describe("<SearchWarning />", () => {
|
|||
});
|
||||
|
||||
it("renders with a logo by default", () => {
|
||||
const { asFragment, queryByRole } = render(
|
||||
const { asFragment, getByRole } = render(
|
||||
<SearchWarning isRoomEncrypted={true} kind={WarningKind.Search} />,
|
||||
);
|
||||
expect(queryByRole("img")).toBeInTheDocument();
|
||||
expect(getByRole("img")).toHaveAttribute("src", "https://logo");
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
|
|
@ -22,7 +22,10 @@ import PlatformPeg from "../../../../src/PlatformPeg";
|
|||
|
||||
describe("<SpellCheckLanguagesDropdown />", () => {
|
||||
it("renders as expected", async () => {
|
||||
const platform: any = { getAvailableSpellCheckLanguages: jest.fn().mockResolvedValue(["en", "de", "qq"]) };
|
||||
const platform: any = {
|
||||
getAvailableSpellCheckLanguages: jest.fn().mockResolvedValue(["en", "de", "qq"]),
|
||||
supportsSetting: jest.fn(),
|
||||
};
|
||||
PlatformPeg.set(platform);
|
||||
|
||||
const { asFragment } = render(
|
||||
|
|
|
@ -211,7 +211,7 @@ describe("<MImageBody/>", () => {
|
|||
it("should generate a thumbnail if one isn't included for animated media", async () => {
|
||||
Object.defineProperty(global.Image.prototype, "src", {
|
||||
set(src) {
|
||||
window.setTimeout(() => this.onload());
|
||||
window.setTimeout(() => this.onload?.());
|
||||
},
|
||||
});
|
||||
Object.defineProperty(global.Image.prototype, "height", {
|
||||
|
|
|
@ -19,6 +19,7 @@ import { fireEvent, render, waitFor } from "@testing-library/react";
|
|||
import { LocationAssetType, ClientEvent, RoomMember, SyncState } from "matrix-js-sdk/src/matrix";
|
||||
import * as maplibregl from "maplibre-gl";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import MLocationBody from "../../../../src/components/views/messages/MLocationBody";
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
|
@ -64,9 +65,11 @@ describe("MLocationBody", () => {
|
|||
});
|
||||
const component = getComponent();
|
||||
|
||||
// simulate error initialising map in maplibregl
|
||||
// @ts-ignore
|
||||
mockMap.emit("error", { status: 404 });
|
||||
sleep(10).then(() => {
|
||||
// simulate error initialising map in maplibregl
|
||||
// @ts-ignore
|
||||
mockMap.emit("error", { status: 404 });
|
||||
});
|
||||
|
||||
return component;
|
||||
};
|
||||
|
@ -100,9 +103,10 @@ describe("MLocationBody", () => {
|
|||
expect(component.container.querySelector(".mx_EventTile_body")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("displays correct fallback content when map_style_url is misconfigured", () => {
|
||||
it("displays correct fallback content when map_style_url is misconfigured", async () => {
|
||||
const component = getMapErrorComponent();
|
||||
expect(component.container.querySelector(".mx_EventTile_body")).toMatchSnapshot();
|
||||
await waitFor(() => expect(component.container.querySelector(".mx_EventTile_body")).toBeTruthy());
|
||||
await waitFor(() => expect(component.container.querySelector(".mx_EventTile_body")).toMatchSnapshot());
|
||||
});
|
||||
|
||||
it("should clear the error on reconnect", () => {
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "@testing-library/react";
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
import { EventTimeline, MatrixEvent, Room, M_TEXT } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
|
@ -129,13 +129,12 @@ describe("<MPollEndBody />", () => {
|
|||
describe("when poll start event does not exist in current timeline", () => {
|
||||
it("fetches the related poll start event and displays a poll tile", async () => {
|
||||
await setupRoomWithEventsTimeline(pollEndEvent);
|
||||
const { container, getByTestId } = getComponent();
|
||||
const { container, getByTestId, getByRole } = getComponent();
|
||||
|
||||
// while fetching event, only icon is shown
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
// flush the fetch event promise
|
||||
await flushPromises();
|
||||
await waitFor(() => expect(getByRole("progressbar")).toBeInTheDocument());
|
||||
|
||||
expect(mockClient.fetchRoomEvent).toHaveBeenCalledWith(roomId, pollStartEvent.getId());
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
getMockClientWithEventEmitter,
|
||||
makePollEndEvent,
|
||||
makePollStartEvent,
|
||||
mockClientMethodsRooms,
|
||||
mockClientMethodsUser,
|
||||
mockIntlDateTimeFormat,
|
||||
setupRoomWithPollEvents,
|
||||
|
@ -41,7 +42,7 @@ describe("<PollHistory />", () => {
|
|||
const roomId = "!room:domain.org";
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
getRoom: jest.fn(),
|
||||
...mockClientMethodsRooms([]),
|
||||
relations: jest.fn(),
|
||||
decryptEventIfNeeded: jest.fn(),
|
||||
getOrCreateFilter: jest.fn(),
|
||||
|
@ -117,7 +118,7 @@ describe("<PollHistory />", () => {
|
|||
expect(getByText("Loading polls")).toBeInTheDocument();
|
||||
|
||||
// flush filter creation request
|
||||
await flushPromises();
|
||||
await act(flushPromises);
|
||||
|
||||
expect(liveTimeline.getPaginationToken).toHaveBeenCalledWith(EventTimeline.BACKWARDS);
|
||||
expect(mockClient.paginateEventTimeline).toHaveBeenCalledWith(liveTimeline, { backwards: true });
|
||||
|
@ -147,7 +148,7 @@ describe("<PollHistory />", () => {
|
|||
);
|
||||
|
||||
// flush filter creation request
|
||||
await flushPromises();
|
||||
await act(flushPromises);
|
||||
// once per page
|
||||
expect(mockClient.paginateEventTimeline).toHaveBeenCalledTimes(3);
|
||||
|
||||
|
@ -182,7 +183,7 @@ describe("<PollHistory />", () => {
|
|||
|
||||
it("renders a no polls message when there are no active polls in the room", async () => {
|
||||
const { getByText } = getComponent();
|
||||
await flushPromises();
|
||||
await act(flushPromises);
|
||||
|
||||
expect(getByText("There are no active polls in this room")).toBeTruthy();
|
||||
});
|
||||
|
|
|
@ -163,7 +163,7 @@ describe("<PollListItemEnded />", () => {
|
|||
await setupRoomWithPollEvents([pollStartEvent], responses, [pollEndEvent], mockClient, room);
|
||||
const poll = room.polls.get(pollId)!;
|
||||
|
||||
const { getByText, queryByText } = getComponent({ event: pollStartEvent, poll });
|
||||
const { getByText, queryByText, findByText } = getComponent({ event: pollStartEvent, poll });
|
||||
// fetch relations
|
||||
await flushPromises();
|
||||
|
||||
|
@ -174,7 +174,7 @@ describe("<PollListItemEnded />", () => {
|
|||
]);
|
||||
|
||||
// updated with more responses
|
||||
expect(getByText("Final result based on 3 votes")).toBeInTheDocument();
|
||||
await expect(findByText("Final result based on 3 votes")).resolves.toBeInTheDocument();
|
||||
expect(getByText("Nissan Silvia S15")).toBeInTheDocument();
|
||||
expect(queryByText("Mitsubishi Lancer Evolution IX")).not.toBeInTheDocument();
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { render } from "@testing-library/react";
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
import {
|
||||
MatrixEvent,
|
||||
MsgType,
|
||||
|
@ -79,21 +79,23 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
|
|||
expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();
|
||||
});
|
||||
|
||||
it("thread notification does change the thread button", () => {
|
||||
it("thread notification does change the thread button", async () => {
|
||||
const { container } = getComponent(room);
|
||||
expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeFalsy();
|
||||
|
||||
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Total, 1);
|
||||
expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeTruthy();
|
||||
expect(isIndicatorOfType(container, "notification")).toBe(true);
|
||||
await waitFor(() => {
|
||||
expect(getThreadButton(container)!.className.includes("mx_LegacyRoomHeader_button--unread")).toBeTruthy();
|
||||
expect(isIndicatorOfType(container, "notification")).toBe(true);
|
||||
});
|
||||
|
||||
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 1);
|
||||
expect(isIndicatorOfType(container, "highlight")).toBe(true);
|
||||
await waitFor(() => expect(isIndicatorOfType(container, "highlight")).toBe(true));
|
||||
|
||||
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Total, 0);
|
||||
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 0);
|
||||
|
||||
expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();
|
||||
await waitFor(() => expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull());
|
||||
});
|
||||
|
||||
it("thread activity does change the thread button", async () => {
|
||||
|
@ -122,7 +124,7 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
|
|||
},
|
||||
});
|
||||
room.addReceipt(receipt);
|
||||
expect(isIndicatorOfType(container, "activity")).toBe(true);
|
||||
await waitFor(() => expect(isIndicatorOfType(container, "activity")).toBe(true));
|
||||
|
||||
// Sending the last event should clear the notification.
|
||||
let event = mkEvent({
|
||||
|
@ -140,7 +142,7 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
|
|||
},
|
||||
});
|
||||
room.addLiveEvents([event]);
|
||||
expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();
|
||||
await waitFor(() => expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull());
|
||||
|
||||
// Mark it as unread again.
|
||||
event = mkEvent({
|
||||
|
@ -158,7 +160,7 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
|
|||
},
|
||||
});
|
||||
room.addLiveEvents([event]);
|
||||
expect(isIndicatorOfType(container, "activity")).toBe(true);
|
||||
await waitFor(() => expect(isIndicatorOfType(container, "activity")).toBe(true));
|
||||
|
||||
// Sending a read receipt on an earlier event shouldn't do anything.
|
||||
receipt = new MatrixEvent({
|
||||
|
@ -173,7 +175,7 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
|
|||
},
|
||||
});
|
||||
room.addReceipt(receipt);
|
||||
expect(isIndicatorOfType(container, "activity")).toBe(true);
|
||||
await waitFor(() => expect(isIndicatorOfType(container, "activity")).toBe(true));
|
||||
|
||||
// Sending a receipt on the latest event should clear the notification.
|
||||
receipt = new MatrixEvent({
|
||||
|
@ -188,6 +190,6 @@ describe("LegacyRoomHeaderButtons-test.tsx", function () {
|
|||
},
|
||||
});
|
||||
room.addReceipt(receipt);
|
||||
expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();
|
||||
await waitFor(() => expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -87,20 +87,17 @@ describe("<PinnedMessagesCard />", () => {
|
|||
};
|
||||
|
||||
const mountPins = async (room: Room): Promise<RenderResult> => {
|
||||
let pins!: RenderResult;
|
||||
await act(async () => {
|
||||
pins = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<PinnedMessagesCard
|
||||
room={room}
|
||||
onClose={jest.fn()}
|
||||
permalinkCreator={new RoomPermalinkCreator(room, room.roomId)}
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
// Wait a tick for state updates
|
||||
await sleep(0);
|
||||
});
|
||||
const pins = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<PinnedMessagesCard
|
||||
room={room}
|
||||
onClose={jest.fn()}
|
||||
permalinkCreator={new RoomPermalinkCreator(room, room.roomId)}
|
||||
/>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
// Wait a tick for state updates
|
||||
await act(() => sleep(0));
|
||||
|
||||
return pins;
|
||||
};
|
||||
|
@ -313,7 +310,6 @@ describe("<PinnedMessagesCard />", () => {
|
|||
it("should show spinner whilst loading", async () => {
|
||||
const room = mkRoom([], [pin1]);
|
||||
mountPins(room);
|
||||
const spinner = await screen.findByTestId("spinner");
|
||||
await waitForElementToBeRemoved(spinner);
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -287,10 +287,10 @@ describe("<UserInfo />", () => {
|
|||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders close button correctly when encryption panel with a pending verification request", () => {
|
||||
it("renders close button correctly when encryption panel with a pending verification request", async () => {
|
||||
renderComponent({ phase: RightPanelPhases.EncryptionPanel, verificationRequest });
|
||||
screen.getByTestId("base-card-close-button").focus();
|
||||
expect(screen.getByRole("tooltip")).toHaveTextContent("Cancel");
|
||||
await expect(screen.findByRole("tooltip", { name: "Cancel" })).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -16,7 +16,15 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, fireEvent, render, RenderResult, screen } from "@testing-library/react";
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
RenderResult,
|
||||
screen,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
} from "@testing-library/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";
|
||||
|
@ -30,6 +38,7 @@ import {
|
|||
filterConsole,
|
||||
flushPromises,
|
||||
getMockClientWithEventEmitter,
|
||||
mockClientMethodsRooms,
|
||||
mockClientMethodsUser,
|
||||
} from "../../../test-utils";
|
||||
import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents";
|
||||
|
@ -358,6 +367,7 @@ describe("MemberList", () => {
|
|||
mocked(shouldShowComponent).mockReturnValue(true);
|
||||
client = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(),
|
||||
...mockClientMethodsRooms(),
|
||||
getRoom: jest.fn(),
|
||||
hasLazyLoadMembersEnabled: jest.fn(),
|
||||
});
|
||||
|
@ -372,7 +382,7 @@ describe("MemberList", () => {
|
|||
const renderComponent = () => {
|
||||
const context = new TestSdkContext();
|
||||
context.client = client;
|
||||
render(
|
||||
return render(
|
||||
<SDKContext.Provider value={context}>
|
||||
<MemberList
|
||||
searchQuery=""
|
||||
|
@ -407,7 +417,10 @@ describe("MemberList", () => {
|
|||
await flushPromises();
|
||||
|
||||
// button rendered but disabled
|
||||
expect(screen.getByText("Invite to this room")).toHaveAttribute("aria-disabled", "true");
|
||||
expect(screen.getByRole("button", { name: "Invite to this room" })).toHaveAttribute(
|
||||
"aria-disabled",
|
||||
"true",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders enabled invite button when current user is a member and has rights to invite", async () => {
|
||||
|
@ -425,10 +438,17 @@ describe("MemberList", () => {
|
|||
jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Join);
|
||||
jest.spyOn(room, "canInvite").mockReturnValue(true);
|
||||
|
||||
renderComponent();
|
||||
await flushPromises();
|
||||
const { getByRole } = renderComponent();
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
|
||||
|
||||
fireEvent.click(screen.getByText("Invite to this room"));
|
||||
await waitFor(() =>
|
||||
expect(getByRole("button", { name: "Invite to this room" })).not.toHaveAttribute(
|
||||
"aria-disabled",
|
||||
"true",
|
||||
),
|
||||
);
|
||||
|
||||
fireEvent.click(getByRole("button", { name: "Invite to this room" }));
|
||||
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
|
||||
action: "view_invite",
|
||||
|
|
|
@ -345,6 +345,7 @@ describe("RoomHeader", () => {
|
|||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue({
|
||||
widget,
|
||||
on: () => {},
|
||||
off: () => {},
|
||||
} as unknown as Call);
|
||||
jest.spyOn(WidgetStore.instance, "getApps").mockReturnValue([widget]);
|
||||
const { container } = render(<RoomHeader room={room} />, getWrapper());
|
||||
|
@ -363,6 +364,7 @@ describe("RoomHeader", () => {
|
|||
jest.spyOn(CallStore.instance, "getCall").mockReturnValue({
|
||||
widget,
|
||||
on: () => {},
|
||||
off: () => {},
|
||||
} as unknown as Call);
|
||||
jest.spyOn(WidgetStore.instance, "getApps").mockReturnValue([widget]);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { MockedObject } from "jest-mock";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
|
||||
import { VideoRoomChatButton } from "../../../../../src/components/views/rooms/RoomHeader/VideoRoomChatButton";
|
||||
import { SDKContext, SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
||||
|
@ -94,7 +94,7 @@ describe("<VideoRoomChatButton />", () => {
|
|||
expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("adds unread marker when room notification state changes to unread", () => {
|
||||
it("adds unread marker when room notification state changes to unread", async () => {
|
||||
const room = makeRoom();
|
||||
// start in read state
|
||||
const notificationState = mockRoomNotificationState(room, NotificationLevel.None);
|
||||
|
@ -108,10 +108,10 @@ describe("<VideoRoomChatButton />", () => {
|
|||
notificationState.emit(NotificationStateEvents.Update);
|
||||
|
||||
// unread marker
|
||||
expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeTruthy();
|
||||
await waitFor(() => expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeTruthy());
|
||||
});
|
||||
|
||||
it("clears unread marker when room notification state changes to read", () => {
|
||||
it("clears unread marker when room notification state changes to read", async () => {
|
||||
const room = makeRoom();
|
||||
// start in unread state
|
||||
const notificationState = mockRoomNotificationState(room, NotificationLevel.Highlight);
|
||||
|
@ -125,6 +125,6 @@ describe("<VideoRoomChatButton />", () => {
|
|||
notificationState.emit(NotificationStateEvents.Update);
|
||||
|
||||
// unread marker cleared
|
||||
expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeFalsy();
|
||||
await waitFor(() => expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeFalsy());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,10 +15,9 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, { ComponentProps } from "react";
|
||||
import { render, fireEvent, RenderResult, waitFor } from "@testing-library/react";
|
||||
import { render, fireEvent, RenderResult, waitFor, waitForElementToBeRemoved } from "@testing-library/react";
|
||||
import { Room, RoomMember, MatrixError, IContent } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { withClientContextRenderOptions, stubClient } from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
|
@ -374,8 +373,7 @@ describe("<RoomPreviewBar />", () => {
|
|||
const onJoinClick = jest.fn();
|
||||
const onRejectClick = jest.fn();
|
||||
const component = getComponent({ ...props, onJoinClick, onRejectClick });
|
||||
await sleep(0);
|
||||
expect(getPrimaryActionButton(component)).toBeTruthy();
|
||||
await waitFor(() => expect(getPrimaryActionButton(component)).toBeTruthy());
|
||||
if (expectSecondaryButton) expect(getSecondaryActionButton(component)).toBeFalsy();
|
||||
fireEvent.click(getPrimaryActionButton(component)!);
|
||||
expect(onJoinClick).toHaveBeenCalled();
|
||||
|
@ -388,7 +386,7 @@ describe("<RoomPreviewBar />", () => {
|
|||
|
||||
it("renders error message", async () => {
|
||||
const component = getComponent({ inviterName, invitedEmail });
|
||||
await sleep(0);
|
||||
await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
|
||||
|
||||
expect(getMessage(component)).toMatchSnapshot();
|
||||
});
|
||||
|
@ -405,7 +403,7 @@ describe("<RoomPreviewBar />", () => {
|
|||
|
||||
it("renders invite message with invited email", async () => {
|
||||
const component = getComponent({ inviterName, invitedEmail });
|
||||
await sleep(0);
|
||||
await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
|
||||
|
||||
expect(getMessage(component)).toMatchSnapshot();
|
||||
});
|
||||
|
@ -421,7 +419,7 @@ describe("<RoomPreviewBar />", () => {
|
|||
|
||||
it("renders invite message with invited email", async () => {
|
||||
const component = getComponent({ inviterName, invitedEmail });
|
||||
await sleep(0);
|
||||
await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
|
||||
|
||||
expect(getMessage(component)).toMatchSnapshot();
|
||||
});
|
||||
|
@ -439,7 +437,7 @@ describe("<RoomPreviewBar />", () => {
|
|||
it("renders email mismatch message when invite email mxid doesnt match", async () => {
|
||||
MatrixClientPeg.safeGet().lookupThreePid = jest.fn().mockReturnValue({ mxid: "not userid" });
|
||||
const component = getComponent({ inviterName, invitedEmail });
|
||||
await sleep(0);
|
||||
await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
|
||||
|
||||
expect(getMessage(component)).toMatchSnapshot();
|
||||
expect(MatrixClientPeg.safeGet().lookupThreePid).toHaveBeenCalledWith(
|
||||
|
@ -453,7 +451,7 @@ describe("<RoomPreviewBar />", () => {
|
|||
it("renders invite message when invite email mxid match", async () => {
|
||||
MatrixClientPeg.safeGet().lookupThreePid = jest.fn().mockReturnValue({ mxid: userId });
|
||||
const component = getComponent({ inviterName, invitedEmail });
|
||||
await sleep(0);
|
||||
await waitForElementToBeRemoved(() => component.queryByRole("progressbar"));
|
||||
|
||||
expect(getMessage(component)).toMatchSnapshot();
|
||||
await testJoinButton({ inviterName, invitedEmail }, false)();
|
||||
|
|
|
@ -30,7 +30,16 @@ import {
|
|||
ThreepidMedium,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { act, fireEvent, getByTestId, render, screen, waitFor, within } from "@testing-library/react";
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
getByTestId,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
within,
|
||||
} from "@testing-library/react";
|
||||
import { mocked } from "jest-mock";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
|
@ -244,7 +253,7 @@ describe("<Notifications />", () => {
|
|||
// get component, wait for async data and force a render
|
||||
const getComponentAndWait = async () => {
|
||||
const component = getComponent();
|
||||
await flushPromises();
|
||||
await waitForElementToBeRemoved(() => component.queryAllByRole("progressbar"));
|
||||
return component;
|
||||
};
|
||||
|
||||
|
@ -527,7 +536,9 @@ describe("<Notifications />", () => {
|
|||
// oneToOneRule is set to 'on'
|
||||
// and is kind: 'underride'
|
||||
const offToggle = screen.getByTestId(section + oneToOneRule.rule_id).querySelector('input[type="radio"]')!;
|
||||
fireEvent.click(offToggle);
|
||||
await act(() => {
|
||||
fireEvent.click(offToggle);
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
|
@ -552,7 +563,9 @@ describe("<Notifications />", () => {
|
|||
// oneToOneRule is set to 'on'
|
||||
// and is kind: 'underride'
|
||||
const offToggle = screen.getByTestId(section + oneToOneRule.rule_id).querySelector('input[type="radio"]')!;
|
||||
fireEvent.click(offToggle);
|
||||
await act(() => {
|
||||
fireEvent.click(offToggle);
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
|
@ -576,7 +589,7 @@ describe("<Notifications />", () => {
|
|||
|
||||
await flushPromises();
|
||||
|
||||
// no error after after successful change
|
||||
// no error after successful change
|
||||
expect(
|
||||
within(oneToOneRuleElement).queryByText(
|
||||
"An error occurred when updating your notification preferences. Please try to toggle your option again.",
|
||||
|
@ -716,7 +729,9 @@ describe("<Notifications />", () => {
|
|||
mockClient.setPushRuleActions.mockRejectedValue("oups");
|
||||
|
||||
const offToggle = oneToOneRuleElement.querySelector('input[type="radio"]')!;
|
||||
fireEvent.click(offToggle);
|
||||
await act(() => {
|
||||
fireEvent.click(offToggle);
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
|
@ -814,7 +829,9 @@ describe("<Notifications />", () => {
|
|||
|
||||
mockClient.setPushRuleEnabled.mockRejectedValueOnce("oups");
|
||||
|
||||
fireEvent.click(within(screen.getByTestId(section + keywordsRuleId)).getByLabelText("Off"));
|
||||
await act(() => {
|
||||
fireEvent.click(within(screen.getByTestId(section + keywordsRuleId)).getByLabelText("Off"));
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, screen, within } from "@testing-library/react";
|
||||
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||
|
@ -99,6 +99,6 @@ describe("SetIntegrationManager", () => {
|
|||
|
||||
expect(logger.error).toHaveBeenCalledWith("Error changing integration manager provisioning");
|
||||
expect(logger.error).toHaveBeenCalledWith("oups");
|
||||
expect(within(integrationSection).getByRole("switch")).not.toBeChecked();
|
||||
await waitFor(() => expect(within(integrationSection).getByRole("switch")).not.toBeChecked());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -108,7 +108,7 @@ describe("<DeviceDetailHeading />", () => {
|
|||
});
|
||||
|
||||
it("toggles out of editing mode when device name is saved successfully", async () => {
|
||||
const { getByTestId } = render(getComponent());
|
||||
const { getByTestId, findByTestId } = render(getComponent());
|
||||
|
||||
// start editing
|
||||
fireEvent.click(getByTestId("device-heading-rename-cta"));
|
||||
|
@ -118,12 +118,12 @@ describe("<DeviceDetailHeading />", () => {
|
|||
await flushPromisesWithFakeTimers();
|
||||
|
||||
// read mode displayed
|
||||
expect(getByTestId("device-detail-heading")).toBeTruthy();
|
||||
await expect(findByTestId("device-detail-heading")).resolves.toBeTruthy();
|
||||
});
|
||||
|
||||
it("displays error when device name fails to save", async () => {
|
||||
const saveDeviceName = jest.fn().mockRejectedValueOnce("oups").mockResolvedValue({});
|
||||
const { getByTestId, queryByText, container } = render(getComponent({ saveDeviceName }));
|
||||
const { getByTestId, queryByText, findByText, container } = render(getComponent({ saveDeviceName }));
|
||||
|
||||
// start editing
|
||||
fireEvent.click(getByTestId("device-heading-rename-cta"));
|
||||
|
@ -136,7 +136,7 @@ describe("<DeviceDetailHeading />", () => {
|
|||
await flushPromisesWithFakeTimers();
|
||||
|
||||
// error message displayed
|
||||
expect(queryByText("Failed to set session name")).toBeTruthy();
|
||||
await expect(findByText("Failed to set session name")).resolves.toBeTruthy();
|
||||
// spinner removed
|
||||
expect(container.getElementsByClassName("mx_Spinner").length).toBeFalsy();
|
||||
|
||||
|
|
|
@ -120,16 +120,15 @@ describe("<FilteredDeviceList />", () => {
|
|||
});
|
||||
|
||||
describe("filtering", () => {
|
||||
const setFilter = async (container: HTMLElement, option: DeviceSecurityVariation | string) =>
|
||||
await act(async () => {
|
||||
const dropdown = container.querySelector('[aria-label="Filter devices"]');
|
||||
const setFilter = async (container: HTMLElement, option: DeviceSecurityVariation | string) => {
|
||||
const dropdown = container.querySelector('[aria-label="Filter devices"]');
|
||||
|
||||
fireEvent.click(dropdown as Element);
|
||||
// tick to let dropdown render
|
||||
await flushPromises();
|
||||
fireEvent.click(dropdown as Element);
|
||||
// tick to let dropdown render
|
||||
await flushPromises();
|
||||
|
||||
fireEvent.click(container.querySelector(`#device-list-filter__${option}`) as Element);
|
||||
});
|
||||
fireEvent.click(container.querySelector(`#device-list-filter__${option}`) as Element);
|
||||
};
|
||||
|
||||
it("does not display filter description when filter is falsy", () => {
|
||||
const { container } = render(getComponent({ filter: undefined }));
|
||||
|
|
|
@ -104,6 +104,7 @@ describe("<EmailAddress/>", () => {
|
|||
"https://fake-url/",
|
||||
),
|
||||
);
|
||||
await expect(screen.findByText("Complete")).resolves.not.toHaveAttribute("aria-disabled", "true");
|
||||
fireEvent.click(screen.getByText("Complete"));
|
||||
|
||||
// Expect error dialog/modal to be shown. We have to wait for the UI to transition.
|
||||
|
@ -120,6 +121,7 @@ describe("<EmailAddress/>", () => {
|
|||
it("Shows error dialog when share completion fails (UserFriendlyError)", async () => {
|
||||
const fakeErrorText = "Fake UserFriendlyError error in test" as TranslationKey;
|
||||
mockClient.bindThreePid.mockRejectedValue(new UserFriendlyError(fakeErrorText));
|
||||
await expect(screen.findByText("Complete")).resolves.not.toHaveAttribute("aria-disabled", "true");
|
||||
fireEvent.click(screen.getByText("Complete"));
|
||||
|
||||
// Expect error dialog/modal to be shown. We have to wait for the UI to transition.
|
||||
|
@ -132,6 +134,7 @@ describe("<EmailAddress/>", () => {
|
|||
it("Shows error dialog when share completion fails (generic error)", async () => {
|
||||
const fakeErrorText = "Fake plain error in test";
|
||||
mockClient.bindThreePid.mockRejectedValue(new Error(fakeErrorText));
|
||||
await expect(screen.findByText("Complete")).resolves.not.toHaveAttribute("aria-disabled", "true");
|
||||
fireEvent.click(screen.getByText("Complete"));
|
||||
|
||||
// Expect error dialog/modal to be shown. We have to wait for the UI to transition.
|
||||
|
|
|
@ -15,7 +15,16 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, fireEvent, render, RenderResult, screen } from "@testing-library/react";
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
RenderResult,
|
||||
screen,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
within,
|
||||
} from "@testing-library/react";
|
||||
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { CryptoApi, DeviceVerificationStatus, VerificationRequest } from "matrix-js-sdk/src/crypto-api";
|
||||
|
@ -146,7 +155,7 @@ describe("<SessionManagerTab />", () => {
|
|||
// open device detail
|
||||
const tile = getByTestId(`device-tile-${deviceId}`);
|
||||
const label = isOpen ? "Hide details" : "Show details";
|
||||
const toggle = tile.querySelector(`[aria-label="${label}"]`) as Element;
|
||||
const toggle = within(tile).getByLabelText(label);
|
||||
fireEvent.click(toggle);
|
||||
};
|
||||
|
||||
|
@ -165,16 +174,14 @@ describe("<SessionManagerTab />", () => {
|
|||
return getByTestId(`device-tile-${deviceId}`);
|
||||
};
|
||||
|
||||
const setFilter = async (container: HTMLElement, option: DeviceSecurityVariation | string) =>
|
||||
await act(async () => {
|
||||
const dropdown = container.querySelector('[aria-label="Filter devices"]');
|
||||
const setFilter = async (container: HTMLElement, option: DeviceSecurityVariation | string) => {
|
||||
const dropdown = within(container).getByLabelText("Filter devices");
|
||||
|
||||
fireEvent.click(dropdown as Element);
|
||||
// tick to let dropdown render
|
||||
await flushPromises();
|
||||
fireEvent.click(dropdown);
|
||||
screen.getByRole("listbox");
|
||||
|
||||
fireEvent.click(container.querySelector(`#device-list-filter__${option}`) as Element);
|
||||
});
|
||||
fireEvent.click(screen.getByTestId(`filter-option-${option}`) as Element);
|
||||
};
|
||||
|
||||
const isDeviceSelected = (
|
||||
getByTestId: ReturnType<typeof render>["getByTestId"],
|
||||
|
@ -920,37 +927,31 @@ describe("<SessionManagerTab />", () => {
|
|||
|
||||
it("deletes a device when interactive auth is not required", async () => {
|
||||
mockClient.deleteMultipleDevices.mockResolvedValue({});
|
||||
mockClient.getDevices
|
||||
.mockResolvedValueOnce({
|
||||
devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice],
|
||||
})
|
||||
// pretend it was really deleted on refresh
|
||||
.mockResolvedValueOnce({
|
||||
devices: [alicesDevice, alicesOlderMobileDevice],
|
||||
});
|
||||
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
mockClient.getDevices.mockResolvedValue({
|
||||
devices: [alicesDevice, alicesMobileDevice, alicesOlderMobileDevice],
|
||||
});
|
||||
|
||||
toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
|
||||
const { getByTestId, findByTestId } = render(getComponent());
|
||||
|
||||
const deviceDetails = getByTestId(`device-detail-${alicesMobileDevice.device_id}`);
|
||||
const signOutButton = deviceDetails.querySelector(
|
||||
'[data-testid="device-detail-sign-out-cta"]',
|
||||
) as Element;
|
||||
fireEvent.click(signOutButton);
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
|
||||
await toggleDeviceDetails(getByTestId, alicesMobileDevice.device_id);
|
||||
|
||||
await confirmSignout(getByTestId);
|
||||
const signOutButton = await within(
|
||||
await findByTestId(`device-detail-${alicesMobileDevice.device_id}`),
|
||||
).findByTestId("device-detail-sign-out-cta");
|
||||
|
||||
// pretend it was really deleted on refresh
|
||||
mockClient.getDevices.mockResolvedValueOnce({
|
||||
devices: [alicesDevice, alicesOlderMobileDevice],
|
||||
});
|
||||
|
||||
// sign out button is disabled with spinner
|
||||
expect(
|
||||
(deviceDetails.querySelector('[data-testid="device-detail-sign-out-cta"]') as Element).getAttribute(
|
||||
"aria-disabled",
|
||||
),
|
||||
).toEqual("true");
|
||||
const prom = waitFor(() => expect(signOutButton).toHaveAttribute("aria-disabled", "true"));
|
||||
|
||||
fireEvent.click(signOutButton);
|
||||
await confirmSignout(getByTestId);
|
||||
await prom;
|
||||
|
||||
// delete called
|
||||
expect(mockClient.deleteMultipleDevices).toHaveBeenCalledWith(
|
||||
[alicesMobileDevice.device_id],
|
||||
|
@ -1008,9 +1009,7 @@ describe("<SessionManagerTab />", () => {
|
|||
|
||||
const { getByTestId, getByLabelText } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await act(flushPromises);
|
||||
|
||||
// reset mock count after initial load
|
||||
mockClient.getDevices.mockClear();
|
||||
|
@ -1570,9 +1569,7 @@ describe("<SessionManagerTab />", () => {
|
|||
});
|
||||
const { getByTestId, container } = render(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
await act(flushPromises);
|
||||
|
||||
// filter for inactive sessions
|
||||
await setFilter(container, DeviceSecurityVariation.Inactive);
|
||||
|
@ -1765,6 +1762,7 @@ describe("<SessionManagerTab />", () => {
|
|||
await flushPromises();
|
||||
|
||||
fireEvent.click(getByText("Show QR code"));
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar"));
|
||||
|
||||
await expect(findByTestId("login-with-qr")).resolves.toBeTruthy();
|
||||
});
|
||||
|
|
|
@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { act } from "@testing-library/react";
|
||||
import { renderHook } from "@testing-library/react-hooks/dom";
|
||||
import { waitFor } from "@testing-library/react";
|
||||
import { IPushRules, MatrixClient, PushRuleKind, RuleId } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useNotificationSettings } from "../../src/hooks/useNotificationSettings";
|
||||
|
@ -68,99 +68,87 @@ describe("useNotificationSettings", () => {
|
|||
});
|
||||
|
||||
it("correctly parses model", async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useNotificationSettings(cli));
|
||||
expect(result.current.model).toEqual(null);
|
||||
await waitForNextUpdate();
|
||||
expect(result.current.model).toEqual(expectedModel);
|
||||
expect(result.current.hasPendingChanges).toBeFalsy();
|
||||
});
|
||||
const { result } = renderHook(() => useNotificationSettings(cli));
|
||||
expect(result.current.model).toEqual(null);
|
||||
await waitFor(() => expect(result.current.model).toEqual(expectedModel));
|
||||
expect(result.current.hasPendingChanges).toBeFalsy();
|
||||
});
|
||||
|
||||
it("correctly generates change calls", async () => {
|
||||
await act(async () => {
|
||||
const addPushRule = jest.fn(cli.addPushRule);
|
||||
cli.addPushRule = addPushRule;
|
||||
const deletePushRule = jest.fn(cli.deletePushRule);
|
||||
cli.deletePushRule = deletePushRule;
|
||||
const setPushRuleEnabled = jest.fn(cli.setPushRuleEnabled);
|
||||
cli.setPushRuleEnabled = setPushRuleEnabled;
|
||||
const setPushRuleActions = jest.fn(cli.setPushRuleActions);
|
||||
cli.setPushRuleActions = setPushRuleActions;
|
||||
const addPushRule = jest.fn(cli.addPushRule);
|
||||
cli.addPushRule = addPushRule;
|
||||
const deletePushRule = jest.fn(cli.deletePushRule);
|
||||
cli.deletePushRule = deletePushRule;
|
||||
const setPushRuleEnabled = jest.fn(cli.setPushRuleEnabled);
|
||||
cli.setPushRuleEnabled = setPushRuleEnabled;
|
||||
const setPushRuleActions = jest.fn(cli.setPushRuleActions);
|
||||
cli.setPushRuleActions = setPushRuleActions;
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(() => useNotificationSettings(cli));
|
||||
expect(result.current.model).toEqual(null);
|
||||
await waitForNextUpdate();
|
||||
expect(result.current.model).toEqual(expectedModel);
|
||||
expect(result.current.hasPendingChanges).toBeFalsy();
|
||||
await result.current.reconcile(DefaultNotificationSettings);
|
||||
await waitForNextUpdate();
|
||||
expect(result.current.hasPendingChanges).toBeFalsy();
|
||||
expect(addPushRule).toHaveBeenCalledTimes(0);
|
||||
expect(deletePushRule).toHaveBeenCalledTimes(9);
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justjann3");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justj4nn3");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justj4nne");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "Janne");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "J4nne");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "Jann3");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "jann3");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "j4nne");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "janne");
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledTimes(6);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.EncryptedMessage,
|
||||
true,
|
||||
);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.Message, true);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.EncryptedDM, true);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.DM, true);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Override,
|
||||
RuleId.SuppressNotices,
|
||||
false,
|
||||
);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Override, RuleId.InviteToSelf, true);
|
||||
expect(setPushRuleActions).toHaveBeenCalledTimes(6);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.EncryptedMessage,
|
||||
StandardActions.ACTION_NOTIFY,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.Message,
|
||||
StandardActions.ACTION_NOTIFY,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.EncryptedDM,
|
||||
StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.DM,
|
||||
StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Override,
|
||||
RuleId.SuppressNotices,
|
||||
StandardActions.ACTION_DONT_NOTIFY,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Override,
|
||||
RuleId.InviteToSelf,
|
||||
StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
|
||||
);
|
||||
});
|
||||
const { result } = renderHook(() => useNotificationSettings(cli));
|
||||
expect(result.current.model).toEqual(null);
|
||||
await waitFor(() => expect(result.current.model).toEqual(expectedModel));
|
||||
expect(result.current.hasPendingChanges).toBeFalsy();
|
||||
await result.current.reconcile(DefaultNotificationSettings);
|
||||
await waitFor(() => expect(result.current.hasPendingChanges).toBeFalsy());
|
||||
expect(addPushRule).toHaveBeenCalledTimes(0);
|
||||
expect(deletePushRule).toHaveBeenCalledTimes(9);
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justjann3");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justj4nn3");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "justj4nne");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "Janne");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "J4nne");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "Jann3");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "jann3");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "j4nne");
|
||||
expect(deletePushRule).toHaveBeenCalledWith("global", PushRuleKind.ContentSpecific, "janne");
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledTimes(6);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.EncryptedMessage,
|
||||
true,
|
||||
);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.Message, true);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.EncryptedDM, true);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Underride, RuleId.DM, true);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Override, RuleId.SuppressNotices, false);
|
||||
expect(setPushRuleEnabled).toHaveBeenCalledWith("global", PushRuleKind.Override, RuleId.InviteToSelf, true);
|
||||
expect(setPushRuleActions).toHaveBeenCalledTimes(6);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.EncryptedMessage,
|
||||
StandardActions.ACTION_NOTIFY,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.Message,
|
||||
StandardActions.ACTION_NOTIFY,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.EncryptedDM,
|
||||
StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Underride,
|
||||
RuleId.DM,
|
||||
StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Override,
|
||||
RuleId.SuppressNotices,
|
||||
StandardActions.ACTION_DONT_NOTIFY,
|
||||
);
|
||||
expect(setPushRuleActions).toHaveBeenCalledWith(
|
||||
"global",
|
||||
PushRuleKind.Override,
|
||||
RuleId.InviteToSelf,
|
||||
StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -81,9 +81,11 @@ describe("useUserOnboardingTasks", () => {
|
|||
});
|
||||
const { result, rerender } = renderHook(() => useUserOnboardingTasks(context.result.current));
|
||||
expect(result.current[4].id).toBe("permission-notifications");
|
||||
await waitFor(() => expect(result.current[4].completed).toBe(false));
|
||||
expect(result.current[4].completed).toBe(false);
|
||||
result.current[4].action!.onClick!({ type: "click" } as any);
|
||||
rerender();
|
||||
await waitFor(() => expect(result.current[4].completed).toBe(true));
|
||||
await waitFor(() => {
|
||||
rerender();
|
||||
expect(result.current[4].completed).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,6 +32,8 @@ import {
|
|||
TranslatedString,
|
||||
UserFriendlyError,
|
||||
TranslationKey,
|
||||
IVariables,
|
||||
Tags,
|
||||
} from "../src/languageHandler";
|
||||
import { stubClient } from "./test-utils";
|
||||
import { setupLanguageMock } from "./setup/setupLanguage";
|
||||
|
@ -214,13 +216,7 @@ describe("languageHandler JSX", function () {
|
|||
const plurals = "common|and_n_others";
|
||||
const variableSub = "slash_command|ignore_dialog_description";
|
||||
|
||||
type TestCase = [
|
||||
string,
|
||||
TranslationKey,
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown> | undefined,
|
||||
TranslatedString,
|
||||
];
|
||||
type TestCase = [string, TranslationKey, IVariables, Tags | undefined, TranslatedString];
|
||||
const testCasesEn: TestCase[] = [
|
||||
// description of the test case, translationString, variables, tags, expected result
|
||||
["translates a basic string", basicString, {}, undefined, "Rooms"],
|
||||
|
|
|
@ -118,6 +118,9 @@ describe("ProxiedApiModule", () => {
|
|||
describe("openDialog", () => {
|
||||
it("should open dialog with a custom title and default options", async () => {
|
||||
class MyDialogContent extends DialogContent {
|
||||
public constructor(props: DialogProps) {
|
||||
super(props);
|
||||
}
|
||||
trySubmit = async () => ({ result: true });
|
||||
render = () => <p>This is my example content.</p>;
|
||||
}
|
||||
|
@ -147,6 +150,9 @@ describe("ProxiedApiModule", () => {
|
|||
|
||||
it("should open dialog with custom options", async () => {
|
||||
class MyDialogContent extends DialogContent {
|
||||
public constructor(props: DialogProps) {
|
||||
super(props);
|
||||
}
|
||||
trySubmit = async () => ({ result: true });
|
||||
render = () => <p>This is my example content.</p>;
|
||||
}
|
||||
|
@ -178,6 +184,9 @@ describe("ProxiedApiModule", () => {
|
|||
|
||||
it("should update the options from the opened dialog", async () => {
|
||||
class MyDialogContent extends DialogContent {
|
||||
public constructor(props: DialogProps) {
|
||||
super(props);
|
||||
}
|
||||
trySubmit = async () => ({ result: true });
|
||||
render = () => {
|
||||
const onClick = () => {
|
||||
|
@ -231,6 +240,9 @@ describe("ProxiedApiModule", () => {
|
|||
|
||||
it("should cancel the dialog from within the dialog", async () => {
|
||||
class MyDialogContent extends DialogContent {
|
||||
public constructor(props: DialogProps) {
|
||||
super(props);
|
||||
}
|
||||
trySubmit = async () => ({ result: true });
|
||||
render = () => (
|
||||
<button type="button" onClick={this.props.cancel}>
|
||||
|
|
|
@ -181,4 +181,5 @@ export const mockClientMethodsCrypto = (): Partial<
|
|||
export const mockClientMethodsRooms = (rooms: Room[] = []): Partial<Record<MethodLikeKeys<MatrixClient>, unknown>> => ({
|
||||
getRooms: jest.fn().mockReturnValue(rooms),
|
||||
getRoom: jest.fn((roomId) => rooms.find((r) => r.roomId === roomId) ?? null),
|
||||
isRoomEncrypted: jest.fn(),
|
||||
});
|
||||
|
|
|
@ -109,10 +109,8 @@ describe("VoiceBroadcastPreRecordingPip", () => {
|
|||
|
||||
describe("and double clicking »Go live«", () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByText("Go live"));
|
||||
await userEvent.click(screen.getByText("Go live"));
|
||||
});
|
||||
await userEvent.click(screen.getByText("Go live"));
|
||||
await userEvent.click(screen.getByText("Go live"));
|
||||
});
|
||||
|
||||
it("should call start once", () => {
|
||||
|
|
|
@ -117,10 +117,8 @@ describe("VoiceBroadcastRecordingPip", () => {
|
|||
|
||||
describe("and selecting another input device", () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByLabelText("Change input device"));
|
||||
await userEvent.click(screen.getByText("Device 1"));
|
||||
});
|
||||
await userEvent.click(screen.getByLabelText("Change input device"));
|
||||
await userEvent.click(screen.getByText("Device 1"));
|
||||
});
|
||||
|
||||
it("should select the device and pause and resume the broadcast", () => {
|
||||
|
@ -199,8 +197,8 @@ describe("VoiceBroadcastRecordingPip", () => {
|
|||
client.emit(ClientEvent.Sync, SyncState.Catchup, SyncState.Error);
|
||||
});
|
||||
|
||||
it("should render a paused recording", () => {
|
||||
expect(screen.getByLabelText("resume voice broadcast")).toBeInTheDocument();
|
||||
it("should render a paused recording", async () => {
|
||||
await expect(screen.findByLabelText("resume voice broadcast")).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue