Update tests to prefer RTL over Enzyme (#10247

* Update tests to prefer RTL over Enzyme

* Strict types
This commit is contained in:
Michael Telatyński 2023-02-28 08:58:23 +00:00 committed by GitHub
parent dd6fc124d7
commit f40d15388c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1095 additions and 632 deletions

View file

@ -16,6 +16,7 @@ limitations under the License.
*/
import React, { Component } from "react";
// eslint-disable-next-line deprecate/import
import ReactTestUtils from "react-dom/test-utils";
import ReactDOM from "react-dom";
import { Room } from "matrix-js-sdk/src/models/room";

View file

@ -15,9 +15,7 @@ limitations under the License.
*/
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount, ReactWrapper } from "enzyme";
import { render, screen, act, fireEvent, waitFor, getByRole } from "@testing-library/react";
import { render, screen, act, fireEvent, waitFor, getByRole, RenderResult } from "@testing-library/react";
import { mocked, Mocked } from "jest-mock";
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import { Room } from "matrix-js-sdk/src/models/room";
@ -60,291 +58,7 @@ import WidgetUtils from "../../../../src/utils/WidgetUtils";
import { ElementWidgetActions } from "../../../../src/stores/widgets/ElementWidgetActions";
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../src/MediaDeviceHandler";
describe("RoomHeader (Enzyme)", () => {
it("shows the room avatar in a room with only ourselves", () => {
// When we render a non-DM room with 1 person in it
const room = createRoom({ name: "X Room", isDm: false, userIds: [] });
const rendered = mountHeader(room);
// Then the room's avatar is the initial of its name
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
expect(initial.text()).toEqual("X");
// And there is no image avatar (because it's not set on this room)
const image = findImg(rendered, ".mx_BaseAvatar_image");
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it("shows the room avatar in a room with 2 people", () => {
// When we render a non-DM room with 2 people in it
const room = createRoom({ name: "Y Room", isDm: false, userIds: ["other"] });
const rendered = mountHeader(room);
// Then the room's avatar is the initial of its name
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
expect(initial.text()).toEqual("Y");
// And there is no image avatar (because it's not set on this room)
const image = findImg(rendered, ".mx_BaseAvatar_image");
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it("shows the room avatar in a room with >2 people", () => {
// When we render a non-DM room with 3 people in it
const room = createRoom({ name: "Z Room", isDm: false, userIds: ["other1", "other2"] });
const rendered = mountHeader(room);
// Then the room's avatar is the initial of its name
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
expect(initial.text()).toEqual("Z");
// And there is no image avatar (because it's not set on this room)
const image = findImg(rendered, ".mx_BaseAvatar_image");
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it("shows the room avatar in a DM with only ourselves", () => {
// When we render a non-DM room with 1 person in it
const room = createRoom({ name: "Z Room", isDm: true, userIds: [] });
const rendered = mountHeader(room);
// Then the room's avatar is the initial of its name
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
expect(initial.text()).toEqual("Z");
// And there is no image avatar (because it's not set on this room)
const image = findImg(rendered, ".mx_BaseAvatar_image");
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it("shows the user avatar in a DM with 2 people", () => {
// Note: this is the interesting case - this is the ONLY
// time we should use the user's avatar.
// When we render a DM room with only 2 people in it
const room = createRoom({ name: "Y Room", isDm: true, userIds: ["other"] });
const rendered = mountHeader(room);
// Then we use the other user's avatar as our room's image avatar
const image = findImg(rendered, ".mx_BaseAvatar_image");
expect(image.prop("src")).toEqual("http://this.is.a.url/example.org/other");
// And there is no initial avatar
expect(rendered.find(".mx_BaseAvatar_initial")).toHaveLength(0);
});
it("shows the room avatar in a DM with >2 people", () => {
// When we render a DM room with 3 people in it
const room = createRoom({
name: "Z Room",
isDm: true,
userIds: ["other1", "other2"],
});
const rendered = mountHeader(room);
// Then the room's avatar is the initial of its name
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
expect(initial.text()).toEqual("Z");
// And there is no image avatar (because it's not set on this room)
const image = findImg(rendered, ".mx_BaseAvatar_image");
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it("renders call buttons normally", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: ["other"] });
const wrapper = mountHeader(room);
expect(wrapper.find('[aria-label="Voice call"]').hostNodes()).toHaveLength(1);
expect(wrapper.find('[aria-label="Video call"]').hostNodes()).toHaveLength(1);
});
it("hides call buttons when the room is tombstoned", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
const wrapper = mountHeader(
room,
{},
{
tombstone: mkEvent({
event: true,
type: "m.room.tombstone",
room: room.roomId,
user: "@user1:server",
skey: "",
content: {},
ts: Date.now(),
}),
},
);
expect(wrapper.find('[aria-label="Voice call"]').hostNodes()).toHaveLength(0);
expect(wrapper.find('[aria-label="Video call"]').hostNodes()).toHaveLength(0);
});
it("should render buttons if not passing showButtons (default true)", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
const wrapper = mountHeader(room);
expect(wrapper.find(".mx_RoomHeader_button")).not.toHaveLength(0);
});
it("should not render buttons if passing showButtons = false", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
const wrapper = mountHeader(room, { showButtons: false });
expect(wrapper.find(".mx_RoomHeader_button")).toHaveLength(0);
});
it("should render the room options context menu if not passing enableRoomOptionsMenu (default true)", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
const wrapper = mountHeader(room);
expect(wrapper.find(".mx_RoomHeader_name.mx_AccessibleButton")).toHaveLength(1);
});
it("should not render the room options context menu if passing enableRoomOptionsMenu = false", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
const wrapper = mountHeader(room, { enableRoomOptionsMenu: false });
expect(wrapper.find(".mx_RoomHeader_name.mx_AccessibleButton")).toHaveLength(0);
});
});
interface IRoomCreationInfo {
name: string;
isDm: boolean;
userIds: string[];
}
function createRoom(info: IRoomCreationInfo) {
stubClient();
const client: MatrixClient = MatrixClientPeg.get();
const roomId = "!1234567890:domain";
const userId = client.getUserId()!;
if (info.isDm) {
client.getAccountData = (eventType) => {
expect(eventType).toEqual("m.direct");
return mkDirectEvent(roomId, userId, info.userIds);
};
}
DMRoomMap.makeShared().start();
const room = new Room(roomId, client, userId, {
pendingEventOrdering: PendingEventOrdering.Detached,
});
const otherJoinEvents: MatrixEvent[] = [];
for (const otherUserId of info.userIds) {
otherJoinEvents.push(mkJoinEvent(roomId, otherUserId));
}
room.currentState.setStateEvents([
mkCreationEvent(roomId, userId),
mkNameEvent(roomId, userId, info.name),
mkJoinEvent(roomId, userId),
...otherJoinEvents,
]);
room.recalculate();
return room;
}
function mountHeader(room: Room, propsOverride = {}, roomContext?: Partial<IRoomState>): ReactWrapper {
const props: RoomHeaderProps = {
room,
inRoom: true,
onSearchClick: () => {},
onInviteClick: null,
onForgetClick: () => {},
onAppsClick: () => {},
e2eStatus: E2EStatus.Normal,
appsShown: true,
searchInfo: {
searchId: Math.random(),
promise: new Promise<ISearchResults>(() => {}),
term: "",
scope: SearchScope.Room,
count: 0,
},
viewingCall: false,
activeCall: null,
...propsOverride,
};
return mount(
<RoomContext.Provider value={{ ...roomContext, room } as IRoomState}>
<RoomHeader {...props} />
</RoomContext.Provider>,
);
}
function mkCreationEvent(roomId: string, userId: string): MatrixEvent {
return mkEvent({
event: true,
type: "m.room.create",
room: roomId,
user: userId,
content: {
creator: userId,
room_version: "5",
predecessor: {
room_id: "!prevroom",
event_id: "$someevent",
},
},
});
}
function mkNameEvent(roomId: string, userId: string, name: string): MatrixEvent {
return mkEvent({
event: true,
type: "m.room.name",
room: roomId,
user: userId,
content: { name },
});
}
function mkJoinEvent(roomId: string, userId: string) {
const ret = mkEvent({
event: true,
type: "m.room.member",
room: roomId,
user: userId,
content: {
membership: "join",
avatar_url: "mxc://example.org/" + userId,
},
});
ret.event.state_key = userId;
return ret;
}
function mkDirectEvent(roomId: string, userId: string, otherUsers: string[]): MatrixEvent {
const content: Record<string, string[]> = {};
for (const otherUserId of otherUsers) {
content[otherUserId] = [roomId];
}
return mkEvent({
event: true,
type: "m.direct",
room: roomId,
user: userId,
content,
});
}
function findSpan(wrapper: ReactWrapper, selector: string): ReactWrapper {
const els = wrapper.find(selector).hostNodes();
expect(els).toHaveLength(1);
return els.at(0);
}
function findImg(wrapper: ReactWrapper, selector: string): ReactWrapper {
const els = wrapper.find(selector).hostNodes();
expect(els).toHaveLength(1);
return els.at(0);
}
describe("RoomHeader (React Testing Library)", () => {
describe("RoomHeader", () => {
let client: Mocked<MatrixClient>;
let room: Room;
let alice: RoomMember;
@ -397,6 +111,9 @@ describe("RoomHeader (React Testing Library)", () => {
[MediaDeviceKindEnum.VideoInput]: [],
[MediaDeviceKindEnum.AudioOutput]: [],
});
DMRoomMap.makeShared();
jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(carol.userId);
});
afterEach(async () => {
@ -879,4 +596,274 @@ describe("RoomHeader (React Testing Library)", () => {
expect(screen.queryByRole("button", { name: /invite/i })).toBeNull();
});
it("shows the room avatar in a room with only ourselves", () => {
// When we render a non-DM room with 1 person in it
const room = createRoom({ name: "X Room", isDm: false, userIds: [] });
const rendered = mountHeader(room);
// Then the room's avatar is the initial of its name
const initial = rendered.container.querySelector(".mx_BaseAvatar_initial");
expect(initial).toHaveTextContent("X");
// And there is no image avatar (because it's not set on this room)
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
});
it("shows the room avatar in a room with 2 people", () => {
// When we render a non-DM room with 2 people in it
const room = createRoom({ name: "Y Room", isDm: false, userIds: ["other"] });
const rendered = mountHeader(room);
// Then the room's avatar is the initial of its name
const initial = rendered.container.querySelector(".mx_BaseAvatar_initial");
expect(initial).toHaveTextContent("Y");
// And there is no image avatar (because it's not set on this room)
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
});
it("shows the room avatar in a room with >2 people", () => {
// When we render a non-DM room with 3 people in it
const room = createRoom({ name: "Z Room", isDm: false, userIds: ["other1", "other2"] });
const rendered = mountHeader(room);
// Then the room's avatar is the initial of its name
const initial = rendered.container.querySelector(".mx_BaseAvatar_initial");
expect(initial).toHaveTextContent("Z");
// And there is no image avatar (because it's not set on this room)
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
});
it("shows the room avatar in a DM with only ourselves", () => {
// When we render a non-DM room with 1 person in it
const room = createRoom({ name: "Z Room", isDm: true, userIds: [] });
const rendered = mountHeader(room);
// Then the room's avatar is the initial of its name
const initial = rendered.container.querySelector(".mx_BaseAvatar_initial");
expect(initial).toHaveTextContent("Z");
// And there is no image avatar (because it's not set on this room)
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
});
it("shows the user avatar in a DM with 2 people", () => {
// Note: this is the interesting case - this is the ONLY
// time we should use the user's avatar.
// When we render a DM room with only 2 people in it
const room = createRoom({ name: "Y Room", isDm: true, userIds: ["other"] });
const rendered = mountHeader(room);
// Then we use the other user's avatar as our room's image avatar
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
expect(image).toHaveAttribute("src", "http://this.is.a.url/example.org/other");
// And there is no initial avatar
expect(rendered.container.querySelector(".mx_BaseAvatar_initial")).toBeFalsy();
});
it("shows the room avatar in a DM with >2 people", () => {
// When we render a DM room with 3 people in it
const room = createRoom({
name: "Z Room",
isDm: true,
userIds: ["other1", "other2"],
});
const rendered = mountHeader(room);
// Then the room's avatar is the initial of its name
const initial = rendered.container.querySelector(".mx_BaseAvatar_initial");
expect(initial).toHaveTextContent("Z");
// And there is no image avatar (because it's not set on this room)
const image = rendered.container.querySelector(".mx_BaseAvatar_image");
expect(image).toHaveAttribute("src", "data:image/png;base64,00");
});
it("renders call buttons normally", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: ["other"] });
const wrapper = mountHeader(room);
expect(wrapper.container.querySelector('[aria-label="Voice call"]')).toBeDefined();
expect(wrapper.container.querySelector('[aria-label="Video call"]')).toBeDefined();
});
it("hides call buttons when the room is tombstoned", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
const wrapper = mountHeader(
room,
{},
{
tombstone: mkEvent({
event: true,
type: "m.room.tombstone",
room: room.roomId,
user: "@user1:server",
skey: "",
content: {},
ts: Date.now(),
}),
},
);
expect(wrapper.container.querySelector('[aria-label="Voice call"]')).toBeFalsy();
expect(wrapper.container.querySelector('[aria-label="Video call"]')).toBeFalsy();
});
it("should render buttons if not passing showButtons (default true)", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
const wrapper = mountHeader(room);
expect(wrapper.container.querySelector(".mx_RoomHeader_button")).toBeDefined();
});
it("should not render buttons if passing showButtons = false", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
const wrapper = mountHeader(room, { showButtons: false });
expect(wrapper.container.querySelector(".mx_RoomHeader_button")).toBeFalsy();
});
it("should render the room options context menu if not passing enableRoomOptionsMenu (default true)", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
const wrapper = mountHeader(room);
expect(wrapper.container.querySelector(".mx_RoomHeader_name.mx_AccessibleButton")).toBeDefined();
});
it("should not render the room options context menu if passing enableRoomOptionsMenu = false", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
const wrapper = mountHeader(room, { enableRoomOptionsMenu: false });
expect(wrapper.container.querySelector(".mx_RoomHeader_name.mx_AccessibleButton")).toBeFalsy();
});
});
interface IRoomCreationInfo {
name: string;
isDm: boolean;
userIds: string[];
}
function createRoom(info: IRoomCreationInfo) {
stubClient();
const client: MatrixClient = MatrixClientPeg.get();
const roomId = "!1234567890:domain";
const userId = client.getUserId()!;
if (info.isDm) {
client.getAccountData = (eventType) => {
expect(eventType).toEqual("m.direct");
return mkDirectEvent(roomId, userId, info.userIds);
};
}
DMRoomMap.makeShared().start();
const room = new Room(roomId, client, userId, {
pendingEventOrdering: PendingEventOrdering.Detached,
});
const otherJoinEvents: MatrixEvent[] = [];
for (const otherUserId of info.userIds) {
otherJoinEvents.push(mkJoinEvent(roomId, otherUserId));
}
room.currentState.setStateEvents([
mkCreationEvent(roomId, userId),
mkNameEvent(roomId, userId, info.name),
mkJoinEvent(roomId, userId),
...otherJoinEvents,
]);
room.recalculate();
return room;
}
function mountHeader(room: Room, propsOverride = {}, roomContext?: Partial<IRoomState>): RenderResult {
const props: RoomHeaderProps = {
room,
inRoom: true,
onSearchClick: () => {},
onInviteClick: null,
onForgetClick: () => {},
onAppsClick: () => {},
e2eStatus: E2EStatus.Normal,
appsShown: true,
searchInfo: {
searchId: Math.random(),
promise: new Promise<ISearchResults>(() => {}),
term: "",
scope: SearchScope.Room,
count: 0,
},
viewingCall: false,
activeCall: null,
...propsOverride,
};
return render(
<RoomContext.Provider value={{ ...roomContext, room } as IRoomState}>
<RoomHeader {...props} />
</RoomContext.Provider>,
);
}
function mkCreationEvent(roomId: string, userId: string): MatrixEvent {
return mkEvent({
event: true,
type: "m.room.create",
room: roomId,
user: userId,
content: {
creator: userId,
room_version: "5",
predecessor: {
room_id: "!prevroom",
event_id: "$someevent",
},
},
});
}
function mkNameEvent(roomId: string, userId: string, name: string): MatrixEvent {
return mkEvent({
event: true,
type: "m.room.name",
room: roomId,
user: userId,
content: { name },
});
}
function mkJoinEvent(roomId: string, userId: string) {
const ret = mkEvent({
event: true,
type: "m.room.member",
room: roomId,
user: userId,
content: {
membership: "join",
avatar_url: "mxc://example.org/" + userId,
},
});
ret.event.state_key = userId;
return ret;
}
function mkDirectEvent(roomId: string, userId: string, otherUsers: string[]): MatrixEvent {
const content: Record<string, string[]> = {};
for (const otherUserId of otherUsers) {
content[otherUserId] = [roomId];
}
return mkEvent({
event: true,
type: "m.direct",
room: roomId,
user: userId,
content,
});
}

View file

@ -14,26 +14,36 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount, ReactWrapper } from "enzyme";
import React, { createRef, RefObject } from "react";
import { render } from "@testing-library/react";
import { MatrixClient, MsgType, Room } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";
import VoiceRecordComposerTile from "../../../../src/components/views/rooms/VoiceRecordComposerTile";
import { VoiceRecording } from "../../../../src/audio/VoiceRecording";
import { doMaybeLocalRoomAction } from "../../../../src/utils/local-room";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { IUpload } from "../../../../src/audio/VoiceMessageRecording";
import { IUpload, VoiceMessageRecording } from "../../../../src/audio/VoiceMessageRecording";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
import { VoiceRecordingStore } from "../../../../src/stores/VoiceRecordingStore";
import { PlaybackClock } from "../../../../src/audio/PlaybackClock";
jest.mock("../../../../src/utils/local-room", () => ({
doMaybeLocalRoomAction: jest.fn(),
}));
jest.mock("../../../../src/stores/VoiceRecordingStore", () => ({
VoiceRecordingStore: {
getVoiceRecordingId: jest.fn().mockReturnValue("voice-recording-id"),
instance: {
getActiveRecording: jest.fn(),
disposeRecording: jest.fn(),
},
},
}));
describe("<VoiceRecordComposerTile/>", () => {
let voiceRecordComposerTile: ReactWrapper<VoiceRecordComposerTile>;
let mockRecorder: VoiceRecording;
let voiceRecordComposerTile: RefObject<VoiceRecordComposerTile>;
let mockRecorder: VoiceMessageRecording;
let mockUpload: IUpload;
let mockClient: MatrixClient;
const roomId = "!room:example.com";
@ -47,26 +57,42 @@ describe("<VoiceRecordComposerTile/>", () => {
const room = {
roomId,
} as unknown as Room;
voiceRecordComposerTile = createRef();
const props = {
room,
ref: voiceRecordComposerTile,
permalinkCreator: new RoomPermalinkCreator(room),
};
mockUpload = {
mxc: "mxc://example.com/voice",
};
mockRecorder = {
on: jest.fn(),
off: jest.fn(),
stop: jest.fn(),
upload: () => Promise.resolve(mockUpload),
durationSeconds: 1337,
contentType: "audio/ogg",
getPlayback: () => ({
on: jest.fn(),
off: jest.fn(),
prepare: jest.fn().mockResolvedValue(void 0),
clockInfo: {
timeSeconds: 0,
liveData: {
onUpdate: jest.fn(),
},
} as unknown as PlaybackClock,
waveform: [1.4, 2.5, 3.6],
waveformData: {
onUpdate: jest.fn(),
},
thumbnailWaveform: [1.4, 2.5, 3.6],
}),
} as unknown as VoiceRecording;
voiceRecordComposerTile = mount(<VoiceRecordComposerTile {...props} />);
voiceRecordComposerTile.setState({
recorder: mockRecorder,
});
} as unknown as VoiceMessageRecording;
mocked(VoiceRecordingStore.instance.getActiveRecording).mockReturnValue(mockRecorder);
render(<VoiceRecordComposerTile {...props} />);
mocked(doMaybeLocalRoomAction).mockImplementation(
<T,>(roomId: string, fn: (actualRoomId: string) => Promise<T>, _client?: MatrixClient) => {
@ -77,7 +103,7 @@ describe("<VoiceRecordComposerTile/>", () => {
describe("send", () => {
it("should send the voice recording", async () => {
await (voiceRecordComposerTile.instance() as VoiceRecordComposerTile).send();
await voiceRecordComposerTile.current!.send();
expect(mockClient.sendMessage).toHaveBeenCalledWith(roomId, {
"body": "Voice message",
"file": undefined,