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> {
// XXX: exported for tests
public showPresence = true;
private readonly showPresence: boolean;
private mounted = false;
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 {
if (prevProps.searchQuery !== this.props.searchQuery) {
this.updateListNow(false);

View File

@ -15,7 +15,7 @@ limitations under the License.
*/
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 TooltipTarget from "../../../../src/components/views/elements/TooltipTarget";
@ -28,27 +28,19 @@ describe("<TooltipTarget />", () => {
"label": "test label",
"alignment": Alignment.Left,
"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 wrapper = renderIntoDocument<HTMLSpanElement>(
const wrapper = render(
// wrap in element so renderIntoDocument can render functional component
<span>
<TooltipTarget {...defaultProps} {...props}>
<span>child</span>
</TooltipTarget>
</span>,
) as HTMLSpanElement;
return wrapper.querySelector("[data-test-id=test]");
);
return wrapper.getByTestId("test");
};
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));
it.each(alignmentKeys)("displays %s aligned tooltip on mouseover", async (alignment: any) => {
const wrapper = getComponent({ alignment: Alignment[alignment] })!;
act(() => {
Simulate.mouseOver(wrapper);
});
fireEvent.mouseOver(wrapper);
expect(getVisibleTooltip()).toMatchSnapshot();
});
it("hides tooltip on mouseleave", () => {
const wrapper = getComponent()!;
act(() => {
Simulate.mouseOver(wrapper);
});
fireEvent.mouseOver(wrapper);
expect(getVisibleTooltip()).toBeTruthy();
act(() => {
Simulate.mouseLeave(wrapper);
});
fireEvent.mouseLeave(wrapper);
expect(getVisibleTooltip()).toBeFalsy();
});
it("displays tooltip on focus", () => {
const wrapper = getComponent()!;
act(() => {
Simulate.focus(wrapper);
});
fireEvent.focus(wrapper);
expect(getVisibleTooltip()).toBeTruthy();
});
it("hides tooltip on blur", async () => {
const wrapper = getComponent()!;
act(() => {
Simulate.focus(wrapper);
});
fireEvent.focus(wrapper);
expect(getVisibleTooltip()).toBeTruthy();
act(() => {
Simulate.blur(wrapper);
});
fireEvent.blur(wrapper);
expect(getVisibleTooltip()).toBeFalsy();
});
});

View File

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

View File

@ -15,10 +15,8 @@ See the License for the specific language governing permissions and
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 React from "react";
import { act, render, RenderResult } from "@testing-library/react";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
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 * as TestUtils from "../../../test-utils";
import MemberList from "../../../../src/components/views/rooms/MemberList";
import MemberTile from "../../../../src/components/views/rooms/MemberTile";
import { SDKContext } from "../../../../src/contexts/SDKContext";
import { TestSdkContext } from "../../../TestSdkContext";
@ -45,9 +42,8 @@ describe("MemberList", () => {
return room;
}
let parentDiv: HTMLDivElement;
let client: MatrixClient;
let root: Component;
let root: RenderResult;
let memberListRoom: Room;
let memberList: MemberList;
@ -55,108 +51,40 @@ describe("MemberList", () => {
let moderatorUsers: RoomMember[] = [];
let defaultUsers: RoomMember[] = [];
beforeEach(function () {
TestUtils.stubClient();
client = MatrixClientPeg.get();
client.hasLazyLoadMembersEnabled = () => false;
parentDiv = document.createElement("div");
document.body.appendChild(parentDiv);
// 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);
function 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>") +
")"
);
}
}
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;
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) {
function expectOrderedByPresenceAndPowerLevel(memberTiles: NodeListOf<Element>, isPresenceEnabled: boolean) {
let prevMember: RoomMember | undefined;
for (const tile of memberTiles) {
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
if (!memberA) {
continue;
}
console.log("COMPARING A VS B:");
console.log(memberList.memberString(memberA));
console.log(memberList.memberString(memberB));
console.log("COMPARING A VS B:", memberString(memberA), memberString(memberB));
const userA = memberA.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", () => {
// Note: even if presence is disabled, we still expect that the presence
// 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
// 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
const activeUsers = [defaultUsers[0]];
const onlineUsers = [adminUsers[0]];
@ -240,25 +247,23 @@ describe("MemberList", () => {
});
// Bypass all the event listeners and skip to the good part
memberList.showPresence = enablePresence;
memberList.updateListNow();
await act(() => memberList.updateListNow(true));
const tiles = ReactTestUtils.scryRenderedComponentsWithType(root, MemberTile);
const tiles = root.container.querySelectorAll(".mx_EntityTile");
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
});
it("by power level", () => {
it("by power level", async () => {
// We already have admin, moderator, and default users so leave them alone
// Bypass all the event listeners and skip to the good part
memberList.showPresence = enablePresence;
memberList.updateListNow();
await act(() => memberList.updateListNow(true));
const tiles = ReactTestUtils.scryRenderedComponentsWithType(root, MemberTile);
const tiles = root.container.querySelectorAll(".mx_EntityTile");
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
});
it("by last active timestamp", () => {
it("by last active timestamp", async () => {
// Intentionally pick users that will confuse the power level sorting
// lastActiveAgoTs == lastPresenceTs - lastActiveAgo
const activeUsers = [defaultUsers[0]];
@ -281,14 +286,13 @@ describe("MemberList", () => {
});
// Bypass all the event listeners and skip to the good part
memberList.showPresence = enablePresence;
memberList.updateListNow();
await act(() => memberList.updateListNow(true));
const tiles = ReactTestUtils.scryRenderedComponentsWithType(root, MemberTile);
const tiles = root.container.querySelectorAll(".mx_EntityTile");
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
});
it("by name", () => {
it("by name", async () => {
// Intentionally put everyone on the same level to force a name comparison
const allUsers = [...adminUsers, ...moderatorUsers, ...defaultUsers];
allUsers.forEach((u) => {
@ -300,20 +304,11 @@ describe("MemberList", () => {
});
// Bypass all the event listeners and skip to the good part
memberList.showPresence = enablePresence;
memberList.updateListNow();
await act(() => memberList.updateListNow(true));
const tiles = ReactTestUtils.scryRenderedComponentsWithType(root, MemberTile);
const tiles = root.container.querySelectorAll(".mx_EntityTile");
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
});
});
}
describe("when presence is enabled", () => {
itDoesOrderMembersCorrectly(true);
});
describe("when presence is not enabled", () => {
itDoesOrderMembersCorrectly(false);
});
});