diff --git a/src/components/views/rooms/MemberList.tsx b/src/components/views/rooms/MemberList.tsx index 5656e37c8f..42811142d7 100644 --- a/src/components/views/rooms/MemberList.tsx +++ b/src/components/views/rooms/MemberList.tsx @@ -68,8 +68,7 @@ interface IState { } export default class MemberList extends React.Component { - // 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 { }); }; - /** - * 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 : "") + - ", " + - (u ? u.getLastActiveTs() : "") + - ", " + - (u ? u.currentlyActive : "") + - ", " + - (u ? u.presence : "") + - ")" - ); - } - } - public componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void { if (prevProps.searchQuery !== this.props.searchQuery) { this.updateListNow(false); diff --git a/test/components/views/elements/TooltipTarget-test.tsx b/test/components/views/elements/TooltipTarget-test.tsx index c6fa4d47a0..0823229a90 100644 --- a/test/components/views/elements/TooltipTarget-test.tsx +++ b/test/components/views/elements/TooltipTarget-test.tsx @@ -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("", () => { "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( + const wrapper = render( // wrap in element so renderIntoDocument can render functional component child , - ) 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("", () => { 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(); }); }); diff --git a/test/components/views/elements/__snapshots__/TooltipTarget-test.tsx.snap b/test/components/views/elements/__snapshots__/TooltipTarget-test.tsx.snap index 8221ef4b55..bcfbe5effd 100644 --- a/test/components/views/elements/__snapshots__/TooltipTarget-test.tsx.snap +++ b/test/components/views/elements/__snapshots__/TooltipTarget-test.tsx.snap @@ -95,7 +95,7 @@ exports[` renders container 1`] = `
diff --git a/test/components/views/rooms/MemberList-test.tsx b/test/components/views/rooms/MemberList-test.tsx index efbff422e7..6f364bff2a 100644 --- a/test/components/views/rooms/MemberList-test.tsx +++ b/test/components/views/rooms/MemberList-test.tsx @@ -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 : "") + + ", " + + (u ? u.getLastActiveTs() : "") + + ", " + + (u ? u.currentlyActive : "") + + ", " + + (u ? u.presence : "") + + ")" + ); } + } - 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( - - - , - parentDiv, - ) as unknown as Component; - }); - - afterEach(() => { - if (parentDiv) { - ReactDOM.unmountComponentAtNode(parentDiv); - parentDiv.remove(); - } - }); - - function expectOrderedByPresenceAndPowerLevel(memberTiles: MemberTile[], isPresenceEnabled: boolean) { + function expectOrderedByPresenceAndPowerLevel(memberTiles: NodeListOf, 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( + + + , + ); + }); + 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); }); });