Replace react-dom tests with react testing-library tests (#10260)

t3chguy/dedup-icons-17oct
Michael Telatynski 2023-03-01 15:59:27 +00:00 committed by GitHub
parent 5398db21ad
commit e5291c195d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 132 additions and 184 deletions

View File

@ -68,8 +68,7 @@ interface IState {
} }
export default class MemberList extends React.Component<IProps, IState> { export default class MemberList extends React.Component<IProps, IState> {
// XXX: exported for tests private readonly showPresence: boolean;
public showPresence = true;
private mounted = false; private mounted = false;
public static contextType = SDKContext; public static contextType = SDKContext;
@ -260,32 +259,6 @@ export default class MemberList extends React.Component<IProps, IState> {
}); });
}; };
/**
* SHOULD ONLY BE USED BY TESTS
*/
public memberString(member: RoomMember): string {
if (!member) {
return "(null)";
} else {
const u = member.user;
return (
"(" +
member.name +
", " +
member.powerLevel +
", " +
(u ? u.lastActiveAgo : "<null>") +
", " +
(u ? u.getLastActiveTs() : "<null>") +
", " +
(u ? u.currentlyActive : "<null>") +
", " +
(u ? u.presence : "<null>") +
")"
);
}
}
public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>, snapshot?: any): void { public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>, snapshot?: any): void {
if (prevProps.searchQuery !== this.props.searchQuery) { if (prevProps.searchQuery !== this.props.searchQuery) {
this.updateListNow(false); this.updateListNow(false);

View File

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { act, renderIntoDocument, Simulate } from "react-dom/test-utils"; import { fireEvent, render } from "@testing-library/react";
import { Alignment } from "../../../../src/components/views/elements/Tooltip"; import { Alignment } from "../../../../src/components/views/elements/Tooltip";
import TooltipTarget from "../../../../src/components/views/elements/TooltipTarget"; import TooltipTarget from "../../../../src/components/views/elements/TooltipTarget";
@ -28,27 +28,19 @@ describe("<TooltipTarget />", () => {
"label": "test label", "label": "test label",
"alignment": Alignment.Left, "alignment": Alignment.Left,
"id": "test id", "id": "test id",
"data-test-id": "test", "data-testid": "test",
}; };
afterEach(() => {
// clean up renderer tooltips
const wrapper = document.querySelector(".mx_Tooltip_wrapper");
while (wrapper?.firstChild) {
wrapper.removeChild(wrapper.lastChild!);
}
});
const getComponent = (props = {}) => { const getComponent = (props = {}) => {
const wrapper = renderIntoDocument<HTMLSpanElement>( const wrapper = render(
// wrap in element so renderIntoDocument can render functional component // wrap in element so renderIntoDocument can render functional component
<span> <span>
<TooltipTarget {...defaultProps} {...props}> <TooltipTarget {...defaultProps} {...props}>
<span>child</span> <span>child</span>
</TooltipTarget> </TooltipTarget>
</span>, </span>,
) as HTMLSpanElement; );
return wrapper.querySelector("[data-test-id=test]"); return wrapper.getByTestId("test");
}; };
const getVisibleTooltip = () => document.querySelector(".mx_Tooltip.mx_Tooltip_visible"); const getVisibleTooltip = () => document.querySelector(".mx_Tooltip.mx_Tooltip_visible");
@ -62,41 +54,29 @@ describe("<TooltipTarget />", () => {
const alignmentKeys = Object.keys(Alignment).filter((o: any) => isNaN(o)); const alignmentKeys = Object.keys(Alignment).filter((o: any) => isNaN(o));
it.each(alignmentKeys)("displays %s aligned tooltip on mouseover", async (alignment: any) => { it.each(alignmentKeys)("displays %s aligned tooltip on mouseover", async (alignment: any) => {
const wrapper = getComponent({ alignment: Alignment[alignment] })!; const wrapper = getComponent({ alignment: Alignment[alignment] })!;
act(() => { fireEvent.mouseOver(wrapper);
Simulate.mouseOver(wrapper);
});
expect(getVisibleTooltip()).toMatchSnapshot(); expect(getVisibleTooltip()).toMatchSnapshot();
}); });
it("hides tooltip on mouseleave", () => { it("hides tooltip on mouseleave", () => {
const wrapper = getComponent()!; const wrapper = getComponent()!;
act(() => { fireEvent.mouseOver(wrapper);
Simulate.mouseOver(wrapper);
});
expect(getVisibleTooltip()).toBeTruthy(); expect(getVisibleTooltip()).toBeTruthy();
act(() => { fireEvent.mouseLeave(wrapper);
Simulate.mouseLeave(wrapper);
});
expect(getVisibleTooltip()).toBeFalsy(); expect(getVisibleTooltip()).toBeFalsy();
}); });
it("displays tooltip on focus", () => { it("displays tooltip on focus", () => {
const wrapper = getComponent()!; const wrapper = getComponent()!;
act(() => { fireEvent.focus(wrapper);
Simulate.focus(wrapper);
});
expect(getVisibleTooltip()).toBeTruthy(); expect(getVisibleTooltip()).toBeTruthy();
}); });
it("hides tooltip on blur", async () => { it("hides tooltip on blur", async () => {
const wrapper = getComponent()!; const wrapper = getComponent()!;
act(() => { fireEvent.focus(wrapper);
Simulate.focus(wrapper);
});
expect(getVisibleTooltip()).toBeTruthy(); expect(getVisibleTooltip()).toBeTruthy();
act(() => { fireEvent.blur(wrapper);
Simulate.blur(wrapper);
});
expect(getVisibleTooltip()).toBeFalsy(); expect(getVisibleTooltip()).toBeFalsy();
}); });
}); });

View File

@ -95,7 +95,7 @@ exports[`<TooltipTarget /> renders container 1`] = `
<div <div
aria-describedby="test id" aria-describedby="test id"
class="test tooltipTargetClassName" class="test tooltipTargetClassName"
data-test-id="test" data-testid="test"
tabindex="0" tabindex="0"
> >
<span> <span>

View File

@ -15,10 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { Component } from "react"; import React from "react";
// eslint-disable-next-line deprecate/import import { act, render, RenderResult } from "@testing-library/react";
import ReactTestUtils from "react-dom/test-utils";
import ReactDOM from "react-dom";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { User } from "matrix-js-sdk/src/models/user"; import { User } from "matrix-js-sdk/src/models/user";
@ -28,7 +26,6 @@ import { MatrixClient, RoomState } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import * as TestUtils from "../../../test-utils"; import * as TestUtils from "../../../test-utils";
import MemberList from "../../../../src/components/views/rooms/MemberList"; import MemberList from "../../../../src/components/views/rooms/MemberList";
import MemberTile from "../../../../src/components/views/rooms/MemberTile";
import { SDKContext } from "../../../../src/contexts/SDKContext"; import { SDKContext } from "../../../../src/contexts/SDKContext";
import { TestSdkContext } from "../../../TestSdkContext"; import { TestSdkContext } from "../../../TestSdkContext";
@ -45,9 +42,8 @@ describe("MemberList", () => {
return room; return room;
} }
let parentDiv: HTMLDivElement;
let client: MatrixClient; let client: MatrixClient;
let root: Component; let root: RenderResult;
let memberListRoom: Room; let memberListRoom: Room;
let memberList: MemberList; let memberList: MemberList;
@ -55,108 +51,40 @@ describe("MemberList", () => {
let moderatorUsers: RoomMember[] = []; let moderatorUsers: RoomMember[] = [];
let defaultUsers: RoomMember[] = []; let defaultUsers: RoomMember[] = [];
beforeEach(function () { function memberString(member: RoomMember): string {
TestUtils.stubClient(); if (!member) {
client = MatrixClientPeg.get(); return "(null)";
client.hasLazyLoadMembersEnabled = () => false; } else {
const u = member.user;
parentDiv = document.createElement("div"); return (
document.body.appendChild(parentDiv); "(" +
member.name +
// Make room ", " +
memberListRoom = createRoom(); member.powerLevel +
expect(memberListRoom.roomId).toBeTruthy(); ", " +
(u ? u.lastActiveAgo : "<null>") +
// Make users ", " +
adminUsers = []; (u ? u.getLastActiveTs() : "<null>") +
moderatorUsers = []; ", " +
defaultUsers = []; (u ? u.currentlyActive : "<null>") +
const usersPerLevel = 2; ", " +
for (let i = 0; i < usersPerLevel; i++) { (u ? u.presence : "<null>") +
const adminUser = new RoomMember(memberListRoom.roomId, `@admin${i}:localhost`); ")"
adminUser.membership = "join"; );
adminUser.powerLevel = 100;
adminUser.user = new User(adminUser.userId);
adminUser.user.currentlyActive = true;
adminUser.user.presence = "online";
adminUser.user.lastPresenceTs = 1000;
adminUser.user.lastActiveAgo = 10;
adminUsers.push(adminUser);
const moderatorUser = new RoomMember(memberListRoom.roomId, `@moderator${i}:localhost`);
moderatorUser.membership = "join";
moderatorUser.powerLevel = 50;
moderatorUser.user = new User(moderatorUser.userId);
moderatorUser.user.currentlyActive = true;
moderatorUser.user.presence = "online";
moderatorUser.user.lastPresenceTs = 1000;
moderatorUser.user.lastActiveAgo = 10;
moderatorUsers.push(moderatorUser);
const defaultUser = new RoomMember(memberListRoom.roomId, `@default${i}:localhost`);
defaultUser.membership = "join";
defaultUser.powerLevel = 0;
defaultUser.user = new User(defaultUser.userId);
defaultUser.user.currentlyActive = true;
defaultUser.user.presence = "online";
defaultUser.user.lastPresenceTs = 1000;
defaultUser.user.lastActiveAgo = 10;
defaultUsers.push(defaultUser);
} }
}
client.getRoom = (roomId) => { function expectOrderedByPresenceAndPowerLevel(memberTiles: NodeListOf<Element>, isPresenceEnabled: boolean) {
if (roomId === memberListRoom.roomId) return memberListRoom;
else return null;
};
memberListRoom.currentState = {
members: {},
getMember: jest.fn(),
getStateEvents: ((eventType, stateKey) =>
stateKey === undefined ? [] : null) as RoomState["getStateEvents"], // ignore 3pid invites
} as unknown as RoomState;
for (const member of [...adminUsers, ...moderatorUsers, ...defaultUsers]) {
memberListRoom.currentState.members[member.userId] = member;
}
const gatherWrappedRef = (r: MemberList) => {
memberList = r;
};
const context = new TestSdkContext();
context.client = client;
root = ReactDOM.render(
<SDKContext.Provider value={context}>
<MemberList
searchQuery=""
onClose={jest.fn()}
onSearchQueryChanged={jest.fn()}
roomId={memberListRoom.roomId}
ref={gatherWrappedRef}
/>
</SDKContext.Provider>,
parentDiv,
) as unknown as Component;
});
afterEach(() => {
if (parentDiv) {
ReactDOM.unmountComponentAtNode(parentDiv);
parentDiv.remove();
}
});
function expectOrderedByPresenceAndPowerLevel(memberTiles: MemberTile[], isPresenceEnabled: boolean) {
let prevMember: RoomMember | undefined; let prevMember: RoomMember | undefined;
for (const tile of memberTiles) { for (const tile of memberTiles) {
const memberA = prevMember; const memberA = prevMember;
const memberB = tile.props.member; const memberB = memberListRoom.currentState.members[tile.getAttribute("title")!.split(" ")[0]];
prevMember = memberB; // just in case an expect fails, set this early prevMember = memberB; // just in case an expect fails, set this early
if (!memberA) { if (!memberA) {
continue; continue;
} }
console.log("COMPARING A VS B:"); console.log("COMPARING A VS B:", memberString(memberA), memberString(memberB));
console.log(memberList.memberString(memberA));
console.log(memberList.memberString(memberB));
const userA = memberA.user!; const userA = memberA.user!;
const userB = memberB.user!; const userB = memberB.user!;
@ -212,7 +140,86 @@ describe("MemberList", () => {
} }
} }
function itDoesOrderMembersCorrectly(enablePresence: boolean) { describe.each([false, true])("does order members correctly (presence %s)", (enablePresence) => {
beforeEach(function () {
TestUtils.stubClient();
client = MatrixClientPeg.get();
client.hasLazyLoadMembersEnabled = () => false;
// Make room
memberListRoom = createRoom();
expect(memberListRoom.roomId).toBeTruthy();
// Make users
adminUsers = [];
moderatorUsers = [];
defaultUsers = [];
const usersPerLevel = 2;
for (let i = 0; i < usersPerLevel; i++) {
const adminUser = new RoomMember(memberListRoom.roomId, `@admin${i}:localhost`);
adminUser.membership = "join";
adminUser.powerLevel = 100;
adminUser.user = new User(adminUser.userId);
adminUser.user.currentlyActive = true;
adminUser.user.presence = "online";
adminUser.user.lastPresenceTs = 1000;
adminUser.user.lastActiveAgo = 10;
adminUsers.push(adminUser);
const moderatorUser = new RoomMember(memberListRoom.roomId, `@moderator${i}:localhost`);
moderatorUser.membership = "join";
moderatorUser.powerLevel = 50;
moderatorUser.user = new User(moderatorUser.userId);
moderatorUser.user.currentlyActive = true;
moderatorUser.user.presence = "online";
moderatorUser.user.lastPresenceTs = 1000;
moderatorUser.user.lastActiveAgo = 10;
moderatorUsers.push(moderatorUser);
const defaultUser = new RoomMember(memberListRoom.roomId, `@default${i}:localhost`);
defaultUser.membership = "join";
defaultUser.powerLevel = 0;
defaultUser.user = new User(defaultUser.userId);
defaultUser.user.currentlyActive = true;
defaultUser.user.presence = "online";
defaultUser.user.lastPresenceTs = 1000;
defaultUser.user.lastActiveAgo = 10;
defaultUsers.push(defaultUser);
}
client.getRoom = (roomId) => {
if (roomId === memberListRoom.roomId) return memberListRoom;
else return null;
};
memberListRoom.currentState = {
members: {},
getMember: jest.fn(),
getStateEvents: ((eventType, stateKey) =>
stateKey === undefined ? [] : null) as RoomState["getStateEvents"], // ignore 3pid invites
} as unknown as RoomState;
for (const member of [...adminUsers, ...moderatorUsers, ...defaultUsers]) {
memberListRoom.currentState.members[member.userId] = member;
}
const gatherWrappedRef = (r: MemberList) => {
memberList = r;
};
const context = new TestSdkContext();
context.client = client;
context.memberListStore.isPresenceEnabled = jest.fn().mockReturnValue(enablePresence);
root = render(
<SDKContext.Provider value={context}>
<MemberList
searchQuery=""
onClose={jest.fn()}
onSearchQueryChanged={jest.fn()}
roomId={memberListRoom.roomId}
ref={gatherWrappedRef}
/>
</SDKContext.Provider>,
);
});
describe("does order members correctly", () => { describe("does order members correctly", () => {
// Note: even if presence is disabled, we still expect that the presence // Note: even if presence is disabled, we still expect that the presence
// tests will pass. All expectOrderedByPresenceAndPowerLevel does is ensure // tests will pass. All expectOrderedByPresenceAndPowerLevel does is ensure
@ -221,7 +228,7 @@ describe("MemberList", () => {
// Each of the 4 tests here is done to prove that the member list can meet // Each of the 4 tests here is done to prove that the member list can meet
// all 4 criteria independently. Together, they should work. // all 4 criteria independently. Together, they should work.
it("by presence state", () => { it("by presence state", async () => {
// Intentionally pick users that will confuse the power level sorting // Intentionally pick users that will confuse the power level sorting
const activeUsers = [defaultUsers[0]]; const activeUsers = [defaultUsers[0]];
const onlineUsers = [adminUsers[0]]; const onlineUsers = [adminUsers[0]];
@ -240,25 +247,23 @@ describe("MemberList", () => {
}); });
// Bypass all the event listeners and skip to the good part // Bypass all the event listeners and skip to the good part
memberList.showPresence = enablePresence; await act(() => memberList.updateListNow(true));
memberList.updateListNow();
const tiles = ReactTestUtils.scryRenderedComponentsWithType(root, MemberTile); const tiles = root.container.querySelectorAll(".mx_EntityTile");
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence); expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
}); });
it("by power level", () => { it("by power level", async () => {
// We already have admin, moderator, and default users so leave them alone // We already have admin, moderator, and default users so leave them alone
// Bypass all the event listeners and skip to the good part // Bypass all the event listeners and skip to the good part
memberList.showPresence = enablePresence; await act(() => memberList.updateListNow(true));
memberList.updateListNow();
const tiles = ReactTestUtils.scryRenderedComponentsWithType(root, MemberTile); const tiles = root.container.querySelectorAll(".mx_EntityTile");
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence); expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
}); });
it("by last active timestamp", () => { it("by last active timestamp", async () => {
// Intentionally pick users that will confuse the power level sorting // Intentionally pick users that will confuse the power level sorting
// lastActiveAgoTs == lastPresenceTs - lastActiveAgo // lastActiveAgoTs == lastPresenceTs - lastActiveAgo
const activeUsers = [defaultUsers[0]]; const activeUsers = [defaultUsers[0]];
@ -281,14 +286,13 @@ describe("MemberList", () => {
}); });
// Bypass all the event listeners and skip to the good part // Bypass all the event listeners and skip to the good part
memberList.showPresence = enablePresence; await act(() => memberList.updateListNow(true));
memberList.updateListNow();
const tiles = ReactTestUtils.scryRenderedComponentsWithType(root, MemberTile); const tiles = root.container.querySelectorAll(".mx_EntityTile");
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence); expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
}); });
it("by name", () => { it("by name", async () => {
// Intentionally put everyone on the same level to force a name comparison // Intentionally put everyone on the same level to force a name comparison
const allUsers = [...adminUsers, ...moderatorUsers, ...defaultUsers]; const allUsers = [...adminUsers, ...moderatorUsers, ...defaultUsers];
allUsers.forEach((u) => { allUsers.forEach((u) => {
@ -300,20 +304,11 @@ describe("MemberList", () => {
}); });
// Bypass all the event listeners and skip to the good part // Bypass all the event listeners and skip to the good part
memberList.showPresence = enablePresence; await act(() => memberList.updateListNow(true));
memberList.updateListNow();
const tiles = ReactTestUtils.scryRenderedComponentsWithType(root, MemberTile); const tiles = root.container.querySelectorAll(".mx_EntityTile");
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence); expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
}); });
}); });
}
describe("when presence is enabled", () => {
itDoesOrderMembersCorrectly(true);
});
describe("when presence is not enabled", () => {
itDoesOrderMembersCorrectly(false);
}); });
}); });