Properly type Modal props to ensure useful typescript checking (#10238

* Properly type Modal props to ensure useful typescript checking

* delint

* Iterate

* Iterate

* Fix modal.close loop

* Iterate

* Fix tests

* Add comment

* Fix test
This commit is contained in:
Michael Telatyński 2023-02-28 10:31:48 +00:00 committed by GitHub
parent ae5725b24c
commit 629e5cb01f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
124 changed files with 600 additions and 560 deletions

View file

@ -21,7 +21,7 @@ import { RoomType } from "matrix-js-sdk/src/@types/event";
import { Room } from "matrix-js-sdk/src/matrix";
import InviteDialog from "../../../../src/components/views/dialogs/InviteDialog";
import { KIND_DM, KIND_INVITE } from "../../../../src/components/views/dialogs/InviteDialogTypes";
import { InviteKind } from "../../../../src/components/views/dialogs/InviteDialogTypes";
import { getMockClientWithEventEmitter, mkMembership, mkMessage, mkRoomCreateEvent } from "../../../test-utils";
import DMRoomMap from "../../../../src/utils/DMRoomMap";
import SdkConfig from "../../../../src/SdkConfig";
@ -109,20 +109,20 @@ describe("InviteDialog", () => {
room.isSpaceRoom = jest.fn().mockReturnValue(true);
room.getType = jest.fn().mockReturnValue(RoomType.Space);
room.name = "Space";
render(<InviteDialog kind={KIND_INVITE} roomId={roomId} onFinished={jest.fn()} />);
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
expect(screen.queryByText("Invite to Space")).toBeTruthy();
});
it("should label with room name", () => {
render(<InviteDialog kind={KIND_INVITE} roomId={roomId} onFinished={jest.fn()} />);
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
expect(screen.getByText(`Invite to ${roomId}`)).toBeInTheDocument();
});
it("should suggest valid MXIDs even if unknown", async () => {
render(
<InviteDialog
kind={KIND_INVITE}
kind={InviteKind.Invite}
roomId={roomId}
onFinished={jest.fn()}
initialText="@localpart:server.tld"
@ -135,7 +135,7 @@ describe("InviteDialog", () => {
it("should not suggest invalid MXIDs", () => {
render(
<InviteDialog
kind={KIND_INVITE}
kind={InviteKind.Invite}
roomId={roomId}
onFinished={jest.fn()}
initialText="@localpart:server:tld"
@ -145,9 +145,9 @@ describe("InviteDialog", () => {
expect(screen.queryByText("@localpart:server:tld")).toBeFalsy();
});
it.each([[KIND_DM], [KIND_INVITE]] as [typeof KIND_DM | typeof KIND_INVITE][])(
it.each([[InviteKind.Dm], [InviteKind.Invite]] as [typeof InviteKind.Dm | typeof InviteKind.Invite][])(
"should lookup inputs which look like email addresses (%s)",
async (kind: typeof KIND_DM | typeof KIND_INVITE) => {
async (kind: typeof InviteKind.Dm | typeof InviteKind.Invite) => {
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
mockClient.lookupThreePid.mockResolvedValue({
address: aliceEmail,
@ -162,7 +162,7 @@ describe("InviteDialog", () => {
render(
<InviteDialog
kind={kind}
roomId={kind === KIND_INVITE ? roomId : ""}
roomId={kind === InviteKind.Invite ? roomId : ""}
onFinished={jest.fn()}
initialText={aliceEmail}
/>,
@ -182,7 +182,12 @@ describe("InviteDialog", () => {
mockClient.lookupThreePid.mockResolvedValue({});
render(
<InviteDialog kind={KIND_INVITE} roomId={roomId} onFinished={jest.fn()} initialText="foobar@email.com" />,
<InviteDialog
kind={InviteKind.Invite}
roomId={roomId}
onFinished={jest.fn()}
initialText="foobar@email.com"
/>,
);
await screen.findByText("foobar@email.com");
@ -193,7 +198,7 @@ describe("InviteDialog", () => {
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
mockClient.lookupThreePid.mockResolvedValue({});
render(<InviteDialog kind={KIND_INVITE} roomId={roomId} onFinished={jest.fn()} />);
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
const input = screen.getByTestId("invite-dialog-input");
input.focus();

View file

@ -22,7 +22,6 @@ import SettingsStore, { CallbackFn } from "../../../../src/settings/SettingsStor
import SdkConfig from "../../../../src/SdkConfig";
import { UserTab } from "../../../../src/components/views/dialogs/UserTab";
import UserSettingsDialog from "../../../../src/components/views/dialogs/UserSettingsDialog";
import { IDialogProps } from "../../../../src/components/views/dialogs/IDialogProps";
import {
getMockClientWithEventEmitter,
mockClientMethodsUser,
@ -62,7 +61,7 @@ describe("<UserSettingsDialog />", () => {
});
const defaultProps = { onFinished: jest.fn() };
const getComponent = (props: Partial<IDialogProps & { initialTabId?: UserTab }> = {}): ReactElement => (
const getComponent = (props: Partial<typeof defaultProps & { initialTabId?: UserTab }> = {}): ReactElement => (
<UserSettingsDialog {...defaultProps} {...props} />
);

View file

@ -21,13 +21,13 @@ import { mocked } from "jest-mock";
import { Room, User, MatrixClient, RoomMember, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
import { Phase, VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import { DeviceTrustLevel, UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import UserInfo, {
BanToggleButton,
DeviceItem,
disambiguateDevices,
getPowerLevels,
IDevice,
isMuted,
PowerLevelEditor,
RoomAdminToolsContainer,
@ -80,6 +80,22 @@ const mockRoom = mocked({
getEventReadUpTo: jest.fn(),
} as unknown as Room);
const mockSpace = mocked({
roomId: "!fkfk",
getType: jest.fn().mockReturnValue("m.space"),
isSpaceRoom: jest.fn().mockReturnValue(true),
getMember: jest.fn().mockReturnValue(undefined),
getMxcAvatarUrl: jest.fn().mockReturnValue("mock-avatar-url"),
name: "test room",
on: jest.fn(),
off: jest.fn(),
currentState: {
getStateEvents: jest.fn(),
on: jest.fn(),
},
getEventReadUpTo: jest.fn(),
} as unknown as Room);
const mockClient = mocked({
getUser: jest.fn(),
isGuest: jest.fn().mockReturnValue(false),
@ -258,7 +274,7 @@ describe("<UserInfoHeader />", () => {
});
describe("<DeviceItem />", () => {
const device: IDevice = { deviceId: "deviceId", getDisplayName: () => "deviceName" };
const device = { deviceId: "deviceId", getDisplayName: () => "deviceName" } as DeviceInfo;
const defaultProps = {
userId: defaultUserId,
device,
@ -722,14 +738,14 @@ describe("<RoomKickButton />", () => {
it("clicking the kick button calls Modal.createDialog with the correct arguments", async () => {
createDialogSpy.mockReturnValueOnce({ finished: Promise.resolve([]), close: jest.fn() });
renderComponent({ member: memberWithInviteMembership });
renderComponent({ room: mockSpace, member: memberWithInviteMembership });
await userEvent.click(screen.getByText(/disinvite from/i));
// check the last call arguments and the presence of the spaceChildFilter callback
expect(createDialogSpy).toHaveBeenLastCalledWith(
expect.any(Function),
expect.objectContaining({ spaceChildFilter: expect.any(Function) }),
undefined,
"mx_ConfirmSpaceUserActionDialog_wrapper",
);
// test the spaceChildFilter callback
@ -806,14 +822,14 @@ describe("<BanToggleButton />", () => {
it("clicking the ban or unban button calls Modal.createDialog with the correct arguments if user is not banned", async () => {
createDialogSpy.mockReturnValueOnce({ finished: Promise.resolve([]), close: jest.fn() });
renderComponent();
renderComponent({ room: mockSpace });
await userEvent.click(screen.getByText(/ban from/i));
// check the last call arguments and the presence of the spaceChildFilter callback
expect(createDialogSpy).toHaveBeenLastCalledWith(
expect.any(Function),
expect.objectContaining({ spaceChildFilter: expect.any(Function) }),
undefined,
"mx_ConfirmSpaceUserActionDialog_wrapper",
);
// test the spaceChildFilter callback
@ -844,14 +860,14 @@ describe("<BanToggleButton />", () => {
it("clicking the ban or unban button calls Modal.createDialog with the correct arguments if user _is_ banned", async () => {
createDialogSpy.mockReturnValueOnce({ finished: Promise.resolve([]), close: jest.fn() });
renderComponent({ member: memberWithBanMembership });
renderComponent({ room: mockSpace, member: memberWithBanMembership });
await userEvent.click(screen.getByText(/ban from/i));
// check the last call arguments and the presence of the spaceChildFilter callback
expect(createDialogSpy).toHaveBeenLastCalledWith(
expect.any(Function),
expect.objectContaining({ spaceChildFilter: expect.any(Function) }),
undefined,
"mx_ConfirmSpaceUserActionDialog_wrapper",
);
// test the spaceChildFilter callback
@ -955,9 +971,9 @@ describe("<RoomAdminToolsContainer />", () => {
describe("disambiguateDevices", () => {
it("does not add ambiguous key to unique names", () => {
const initialDevices = [
{ deviceId: "id1", getDisplayName: () => "name1" },
{ deviceId: "id2", getDisplayName: () => "name2" },
{ deviceId: "id3", getDisplayName: () => "name3" },
{ deviceId: "id1", getDisplayName: () => "name1" } as DeviceInfo,
{ deviceId: "id2", getDisplayName: () => "name2" } as DeviceInfo,
{ deviceId: "id3", getDisplayName: () => "name3" } as DeviceInfo,
];
disambiguateDevices(initialDevices);
@ -969,14 +985,14 @@ describe("disambiguateDevices", () => {
it("adds ambiguous key to all ids with non-unique names", () => {
const uniqueNameDevices = [
{ deviceId: "id3", getDisplayName: () => "name3" },
{ deviceId: "id4", getDisplayName: () => "name4" },
{ deviceId: "id6", getDisplayName: () => "name6" },
{ deviceId: "id3", getDisplayName: () => "name3" } as DeviceInfo,
{ deviceId: "id4", getDisplayName: () => "name4" } as DeviceInfo,
{ deviceId: "id6", getDisplayName: () => "name6" } as DeviceInfo,
];
const nonUniqueNameDevices = [
{ deviceId: "id1", getDisplayName: () => "nonUnique" },
{ deviceId: "id2", getDisplayName: () => "nonUnique" },
{ deviceId: "id5", getDisplayName: () => "nonUnique" },
{ deviceId: "id1", getDisplayName: () => "nonUnique" } as DeviceInfo,
{ deviceId: "id2", getDisplayName: () => "nonUnique" } as DeviceInfo,
{ deviceId: "id5", getDisplayName: () => "nonUnique" } as DeviceInfo,
];
const initialDevices = [...uniqueNameDevices, ...nonUniqueNameDevices];
disambiguateDevices(initialDevices);

View file

@ -38,12 +38,12 @@ describe("LinkModal", () => {
isForward: true,
};
const customRender = (isTextEnabled: boolean, onClose: () => void, isEditing = false) => {
const customRender = (isTextEnabled: boolean, onFinished: () => void, isEditing = false) => {
return render(
<LinkModal
composer={formattingFunctions}
isTextEnabled={isTextEnabled}
onClose={onClose}
onFinished={onFinished}
composerContext={{ selection: defaultValue }}
isEditing={isEditing}
/>,
@ -60,8 +60,8 @@ describe("LinkModal", () => {
it("Should create a link", async () => {
// When
const onClose = jest.fn();
customRender(false, onClose);
const onFinished = jest.fn();
customRender(false, onFinished);
// Then
expect(screen.getByLabelText("Link")).toBeTruthy();
@ -84,7 +84,7 @@ describe("LinkModal", () => {
// Then
await waitFor(() => {
expect(selectionSpy).toHaveBeenCalledWith(defaultValue);
expect(onClose).toBeCalledTimes(1);
expect(onFinished).toBeCalledTimes(1);
});
// Then
@ -93,8 +93,8 @@ describe("LinkModal", () => {
it("Should create a link with text", async () => {
// When
const onClose = jest.fn();
customRender(true, onClose);
const onFinished = jest.fn();
customRender(true, onFinished);
// Then
expect(screen.getByLabelText("Text")).toBeTruthy();
@ -127,7 +127,7 @@ describe("LinkModal", () => {
// Then
await waitFor(() => {
expect(selectionSpy).toHaveBeenCalledWith(defaultValue);
expect(onClose).toBeCalledTimes(1);
expect(onFinished).toBeCalledTimes(1);
});
// Then
@ -136,13 +136,13 @@ describe("LinkModal", () => {
it("Should remove the link", async () => {
// When
const onClose = jest.fn();
customRender(true, onClose, true);
const onFinished = jest.fn();
customRender(true, onFinished, true);
await userEvent.click(screen.getByText("Remove"));
// Then
expect(formattingFunctions.removeLinks).toHaveBeenCalledTimes(1);
expect(onClose).toBeCalledTimes(1);
expect(onFinished).toBeCalledTimes(1);
});
it("Should display the link in editing", async () => {

View file

@ -28,7 +28,7 @@ describe("deleteDevices()", () => {
deleteMultipleDevices: jest.fn(),
});
const modalSpy = jest.spyOn(Modal, "createDialog");
const modalSpy = jest.spyOn(Modal, "createDialog") as jest.SpyInstance;
const interactiveAuthError = { httpStatus: 401, data: { flows: [] as UIAFlow[] } };

View file

@ -922,7 +922,7 @@ describe("ElementCall", () => {
const sourceId = "source_id";
jest.spyOn(Modal, "createDialog").mockReturnValue({
finished: new Promise((r) => r([sourceId])),
} as IHandle<any[]>);
} as IHandle<any>);
jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(true);
await call.connect();
@ -950,7 +950,7 @@ describe("ElementCall", () => {
it("sends ScreenshareStop if we couldn't get a source id", async () => {
jest.spyOn(Modal, "createDialog").mockReturnValue({
finished: new Promise((r) => r([null])),
} as IHandle<any[]>);
} as IHandle<any>);
jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(true);
await call.connect();

View file

@ -18,10 +18,11 @@ import { mocked } from "jest-mock";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
import Modal, { ModalManager } from "../../src/Modal";
import Modal, { ComponentType, ComponentProps } from "../../src/Modal";
import SettingsStore from "../../src/settings/SettingsStore";
import MultiInviter, { CompletionStates } from "../../src/utils/MultiInviter";
import * as TestUtilsMatrix from "../test-utils";
import AskInviteAnywayDialog from "../../src/components/views/dialogs/AskInviteAnywayDialog";
const ROOMID = "!room:server";
@ -56,9 +57,11 @@ const mockPromptBeforeInviteUnknownUsers = (value: boolean) => {
};
const mockCreateTrackedDialog = (callbackName: "onInviteAnyways" | "onGiveUp") => {
mocked(Modal.createDialog).mockImplementation((...rest: Parameters<ModalManager["createDialog"]>): any => {
rest[1]![callbackName]();
});
mocked(Modal.createDialog).mockImplementation(
(Element: ComponentType, props?: ComponentProps<ComponentType>): any => {
(props as ComponentProps<typeof AskInviteAnywayDialog>)[callbackName]();
},
);
};
const expectAllInvitedResult = (result: CompletionStates) => {