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

This commit is contained in:
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,14 +51,101 @@ describe("MemberList", () => {
let moderatorUsers: RoomMember[] = []; let moderatorUsers: RoomMember[] = [];
let defaultUsers: RoomMember[] = []; let defaultUsers: RoomMember[] = [];
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>") +
")"
);
}
}
function expectOrderedByPresenceAndPowerLevel(memberTiles: NodeListOf<Element>, isPresenceEnabled: boolean) {
let prevMember: RoomMember | undefined;
for (const tile of memberTiles) {
const memberA = prevMember;
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:", memberString(memberA), memberString(memberB));
const userA = memberA.user!;
const userB = memberB.user!;
let groupChange = false;
if (isPresenceEnabled) {
const convertPresence = (p: string) => (p === "unavailable" ? "online" : p);
const presenceIndex = (p: string) => {
const order = ["active", "online", "offline"];
const idx = order.indexOf(convertPresence(p));
return idx === -1 ? order.length : idx; // unknown states at the end
};
const idxA = presenceIndex(userA.currentlyActive ? "active" : userA.presence);
const idxB = presenceIndex(userB.currentlyActive ? "active" : userB.presence);
console.log("Comparing presence groups...");
expect(idxB).toBeGreaterThanOrEqual(idxA);
groupChange = idxA !== idxB;
} else {
console.log("Skipped presence groups");
}
if (!groupChange) {
console.log("Comparing power levels...");
expect(memberA.powerLevel).toBeGreaterThanOrEqual(memberB.powerLevel);
groupChange = memberA.powerLevel !== memberB.powerLevel;
} else {
console.log("Skipping power level check due to group change");
}
if (!groupChange) {
if (isPresenceEnabled) {
console.log("Comparing last active timestamp...");
expect(userB.getLastActiveTs()).toBeLessThanOrEqual(userA.getLastActiveTs());
groupChange = userA.getLastActiveTs() !== userB.getLastActiveTs();
} else {
console.log("Skipping last active timestamp");
}
} else {
console.log("Skipping last active timestamp check due to group change");
}
if (!groupChange) {
const nameA = memberA.name[0] === "@" ? memberA.name.slice(1) : memberA.name;
const nameB = memberB.name[0] === "@" ? memberB.name.slice(1) : memberB.name;
const nameCompare = compare(nameB, nameA);
console.log("Comparing name");
expect(nameCompare).toBeGreaterThanOrEqual(0);
} else {
console.log("Skipping name check due to group change");
}
}
}
describe.each([false, true])("does order members correctly (presence %s)", (enablePresence) => {
beforeEach(function () { beforeEach(function () {
TestUtils.stubClient(); TestUtils.stubClient();
client = MatrixClientPeg.get(); client = MatrixClientPeg.get();
client.hasLazyLoadMembersEnabled = () => false; client.hasLazyLoadMembersEnabled = () => false;
parentDiv = document.createElement("div");
document.body.appendChild(parentDiv);
// Make room // Make room
memberListRoom = createRoom(); memberListRoom = createRoom();
expect(memberListRoom.roomId).toBeTruthy(); expect(memberListRoom.roomId).toBeTruthy();
@ -123,7 +206,8 @@ describe("MemberList", () => {
}; };
const context = new TestSdkContext(); const context = new TestSdkContext();
context.client = client; context.client = client;
root = ReactDOM.render( context.memberListStore.isPresenceEnabled = jest.fn().mockReturnValue(enablePresence);
root = render(
<SDKContext.Provider value={context}> <SDKContext.Provider value={context}>
<MemberList <MemberList
searchQuery="" searchQuery=""
@ -133,86 +217,9 @@ describe("MemberList", () => {
ref={gatherWrappedRef} ref={gatherWrappedRef}
/> />
</SDKContext.Provider>, </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;
for (const tile of memberTiles) {
const memberA = prevMember;
const memberB = tile.props.member;
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));
const userA = memberA.user!;
const userB = memberB.user!;
let groupChange = false;
if (isPresenceEnabled) {
const convertPresence = (p: string) => (p === "unavailable" ? "online" : p);
const presenceIndex = (p: string) => {
const order = ["active", "online", "offline"];
const idx = order.indexOf(convertPresence(p));
return idx === -1 ? order.length : idx; // unknown states at the end
};
const idxA = presenceIndex(userA.currentlyActive ? "active" : userA.presence);
const idxB = presenceIndex(userB.currentlyActive ? "active" : userB.presence);
console.log("Comparing presence groups...");
expect(idxB).toBeGreaterThanOrEqual(idxA);
groupChange = idxA !== idxB;
} else {
console.log("Skipped presence groups");
}
if (!groupChange) {
console.log("Comparing power levels...");
expect(memberA.powerLevel).toBeGreaterThanOrEqual(memberB.powerLevel);
groupChange = memberA.powerLevel !== memberB.powerLevel;
} else {
console.log("Skipping power level check due to group change");
}
if (!groupChange) {
if (isPresenceEnabled) {
console.log("Comparing last active timestamp...");
expect(userB.getLastActiveTs()).toBeLessThanOrEqual(userA.getLastActiveTs());
groupChange = userA.getLastActiveTs() !== userB.getLastActiveTs();
} else {
console.log("Skipping last active timestamp");
}
} else {
console.log("Skipping last active timestamp check due to group change");
}
if (!groupChange) {
const nameA = memberA.name[0] === "@" ? memberA.name.slice(1) : memberA.name;
const nameB = memberB.name[0] === "@" ? memberB.name.slice(1) : memberB.name;
const nameCompare = compare(nameB, nameA);
console.log("Comparing name");
expect(nameCompare).toBeGreaterThanOrEqual(0);
} else {
console.log("Skipping name check due to group change");
}
}
}
function itDoesOrderMembersCorrectly(enablePresence: boolean) {
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);
}); });
}); });