mirror of https://github.com/vector-im/riot-web
				
				
				
			Implement new unreachable state and fix broken string ref (#11748)
* Fix string ref issue * Implement unreachable state * Fix eslint failure * Fix i18n * Fix i18n again * Write cypress test * Write jest test * Write more jest tests * Update method name * Use unstable prefix * Always use prefix This is never to going to be in the spec so always use the io.element prefix * Update tests * Remove redundant code from cypress test * Use unstable prefix * Refactor code * Remove supressOnHover prop * Remove sub-text label * Join lines * Remove blank line * Add jsdocpull/28788/head^2
							parent
							
								
									6849afd9fc
								
							
						
					
					
						commit
						90419bdffd
					
				| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2023 The Matrix.org Foundation C.I.C.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
/// <reference types="cypress" />
 | 
			
		||||
import { HomeserverInstance } from "../../plugins/utils/homeserver";
 | 
			
		||||
 | 
			
		||||
describe("Presence tests", () => {
 | 
			
		||||
    let homeserver: HomeserverInstance;
 | 
			
		||||
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
        cy.startHomeserver("default").then((data) => {
 | 
			
		||||
            homeserver = data;
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        cy.stopHomeserver(homeserver);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("renders unreachable presence state correctly", () => {
 | 
			
		||||
        cy.initTestUser(homeserver, "Janet");
 | 
			
		||||
        cy.getBot(homeserver, { displayName: "Bob" }).then((bob) => {
 | 
			
		||||
            cy.intercept("GET", "**/sync*", (req) => {
 | 
			
		||||
                req.continue((res) => {
 | 
			
		||||
                    res.body.presence = {
 | 
			
		||||
                        events: [
 | 
			
		||||
                            {
 | 
			
		||||
                                type: "m.presence",
 | 
			
		||||
                                sender: bob.getUserId(),
 | 
			
		||||
                                content: {
 | 
			
		||||
                                    presence: "io.element.unreachable",
 | 
			
		||||
                                    currently_active: false,
 | 
			
		||||
                                },
 | 
			
		||||
                            },
 | 
			
		||||
                        ],
 | 
			
		||||
                    };
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
            cy.createRoom({ name: "My Room", invite: [bob.getUserId()] }).then((roomId) => {
 | 
			
		||||
                cy.viewRoomById(roomId);
 | 
			
		||||
            });
 | 
			
		||||
            cy.findByRole("button", { name: "Room info" }).click();
 | 
			
		||||
            cy.get(".mx_RightPanel").within(() => {
 | 
			
		||||
                cy.contains("People").click();
 | 
			
		||||
            });
 | 
			
		||||
            cy.get(".mx_EntityTile_unreachable")
 | 
			
		||||
                .should("contain.text", "Bob")
 | 
			
		||||
                .should("contain.text", "User's server unreachable");
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -46,11 +46,11 @@ limitations under the License.
 | 
			
		|||
    background-color: $header-panel-text-primary-color;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_EntityTile .mx_PresenceLabel {
 | 
			
		||||
.mx_EntityTile:not(.mx_EntityTile_unreachable) .mx_PresenceLabel {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_EntityTile:not(.mx_EntityTile_noHover):hover .mx_PresenceLabel {
 | 
			
		||||
.mx_EntityTile:hover .mx_PresenceLabel {
 | 
			
		||||
    display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +106,9 @@ limitations under the License.
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
.mx_EntityTile_unknown .mx_EntityTile_avatar,
 | 
			
		||||
.mx_EntityTile_unknown .mx_EntityTile_name {
 | 
			
		||||
.mx_EntityTile_unknown .mx_EntityTile_name,
 | 
			
		||||
.mx_EntityTile_unreachable .mx_EntityTile_avatar,
 | 
			
		||||
.mx_EntityTile_unreachable .mx_EntityTile_name {
 | 
			
		||||
    opacity: 0.25;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -264,8 +264,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
 | 
			
		|||
                    <BaseAvatar url={require("../../../../res/img/ellipsis.svg").default} name="..." size="36px" />
 | 
			
		||||
                }
 | 
			
		||||
                name={text}
 | 
			
		||||
                presenceState="online"
 | 
			
		||||
                suppressOnHover={true}
 | 
			
		||||
                showPresence={false}
 | 
			
		||||
                onClick={() => setTruncateAt(totalCount)}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,12 +35,13 @@ const PowerLabel: Record<PowerStatus, TranslationKey> = {
 | 
			
		|||
    [PowerStatus.Moderator]: _td("power_level|mod"),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type PresenceState = "offline" | "online" | "unavailable";
 | 
			
		||||
export type PresenceState = "offline" | "online" | "unavailable" | "io.element.unreachable";
 | 
			
		||||
 | 
			
		||||
const PRESENCE_CLASS: Record<PresenceState, string> = {
 | 
			
		||||
    offline: "mx_EntityTile_offline",
 | 
			
		||||
    online: "mx_EntityTile_online",
 | 
			
		||||
    unavailable: "mx_EntityTile_unavailable",
 | 
			
		||||
    "offline": "mx_EntityTile_offline",
 | 
			
		||||
    "online": "mx_EntityTile_online",
 | 
			
		||||
    "unavailable": "mx_EntityTile_unavailable",
 | 
			
		||||
    "io.element.unreachable": "mx_EntityTile_unreachable",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function presenceClassForMember(presenceState?: PresenceState, lastActiveAgo?: number, showPresence?: boolean): string {
 | 
			
		||||
| 
						 | 
				
			
			@ -75,7 +76,6 @@ interface IProps {
 | 
			
		|||
    presenceCurrentlyActive?: boolean;
 | 
			
		||||
    showInviteButton: boolean;
 | 
			
		||||
    onClick(): void;
 | 
			
		||||
    suppressOnHover: boolean;
 | 
			
		||||
    showPresence: boolean;
 | 
			
		||||
    subtextLabel?: string;
 | 
			
		||||
    e2eStatus?: E2EState;
 | 
			
		||||
| 
						 | 
				
			
			@ -93,7 +93,6 @@ export default class EntityTile extends React.PureComponent<IProps, IState> {
 | 
			
		|||
        presenceLastActiveAgo: 0,
 | 
			
		||||
        presenceLastTs: 0,
 | 
			
		||||
        showInviteButton: false,
 | 
			
		||||
        suppressOnHover: false,
 | 
			
		||||
        showPresence: true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -105,10 +104,27 @@ export default class EntityTile extends React.PureComponent<IProps, IState> {
 | 
			
		|||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates the PresenceLabel component if needed
 | 
			
		||||
     * @returns The PresenceLabel component if we need to render it, undefined otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private getPresenceLabel(): JSX.Element | undefined {
 | 
			
		||||
        if (!this.props.showPresence) return;
 | 
			
		||||
        const activeAgo = this.props.presenceLastActiveAgo
 | 
			
		||||
            ? Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)
 | 
			
		||||
            : -1;
 | 
			
		||||
        return (
 | 
			
		||||
            <PresenceLabel
 | 
			
		||||
                activeAgo={activeAgo}
 | 
			
		||||
                currentlyActive={this.props.presenceCurrentlyActive}
 | 
			
		||||
                presenceState={this.props.presenceState}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public render(): React.ReactNode {
 | 
			
		||||
        const mainClassNames: Record<string, boolean> = {
 | 
			
		||||
            mx_EntityTile: true,
 | 
			
		||||
            mx_EntityTile_noHover: !!this.props.suppressOnHover,
 | 
			
		||||
        };
 | 
			
		||||
        if (this.props.className) mainClassNames[this.props.className] = true;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -119,43 +135,13 @@ export default class EntityTile extends React.PureComponent<IProps, IState> {
 | 
			
		|||
        );
 | 
			
		||||
        mainClassNames[presenceClass] = true;
 | 
			
		||||
 | 
			
		||||
        let nameEl;
 | 
			
		||||
        const name = this.props.nameJSX || this.props.name;
 | 
			
		||||
 | 
			
		||||
        if (!this.props.suppressOnHover) {
 | 
			
		||||
            const activeAgo = this.props.presenceLastActiveAgo
 | 
			
		||||
                ? Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)
 | 
			
		||||
                : -1;
 | 
			
		||||
 | 
			
		||||
            let presenceLabel: JSX.Element | undefined;
 | 
			
		||||
            if (this.props.showPresence) {
 | 
			
		||||
                presenceLabel = (
 | 
			
		||||
                    <PresenceLabel
 | 
			
		||||
                        activeAgo={activeAgo}
 | 
			
		||||
                        currentlyActive={this.props.presenceCurrentlyActive}
 | 
			
		||||
                        presenceState={this.props.presenceState}
 | 
			
		||||
                    />
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            if (this.props.subtextLabel) {
 | 
			
		||||
                presenceLabel = <span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span>;
 | 
			
		||||
            }
 | 
			
		||||
            nameEl = (
 | 
			
		||||
                <div className="mx_EntityTile_details">
 | 
			
		||||
                    <div className="mx_EntityTile_name">{name}</div>
 | 
			
		||||
                    {presenceLabel}
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
        } else if (this.props.subtextLabel) {
 | 
			
		||||
            nameEl = (
 | 
			
		||||
                <div className="mx_EntityTile_details">
 | 
			
		||||
                    <div className="mx_EntityTile_name">{name}</div>
 | 
			
		||||
                    <span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            nameEl = <div className="mx_EntityTile_name">{name}</div>;
 | 
			
		||||
        }
 | 
			
		||||
        const nameAndPresence = (
 | 
			
		||||
            <div className="mx_EntityTile_details">
 | 
			
		||||
                <div className="mx_EntityTile_name">{name}</div>
 | 
			
		||||
                {this.getPresenceLabel()}
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let inviteButton;
 | 
			
		||||
        if (this.props.showInviteButton) {
 | 
			
		||||
| 
						 | 
				
			
			@ -198,7 +184,7 @@ export default class EntityTile extends React.PureComponent<IProps, IState> {
 | 
			
		|||
                        {av}
 | 
			
		||||
                        {e2eIcon}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {nameEl}
 | 
			
		||||
                    {nameAndPresence}
 | 
			
		||||
                    {powerLabel}
 | 
			
		||||
                    {inviteButton}
 | 
			
		||||
                </AccessibleButton>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -81,6 +81,7 @@ export default class MemberList extends React.Component<IProps, IState> {
 | 
			
		|||
 | 
			
		||||
    public static contextType = SDKContext;
 | 
			
		||||
    public context!: React.ContextType<typeof SDKContext>;
 | 
			
		||||
    private tiles: Map<string, MemberTile> = new Map();
 | 
			
		||||
 | 
			
		||||
    public constructor(props: IProps, context: React.ContextType<typeof SDKContext>) {
 | 
			
		||||
        super(props);
 | 
			
		||||
| 
						 | 
				
			
			@ -154,7 +155,7 @@ export default class MemberList extends React.Component<IProps, IState> {
 | 
			
		|||
        // Attach a SINGLE listener for global presence changes then locate the
 | 
			
		||||
        // member tile and re-render it. This is more efficient than every tile
 | 
			
		||||
        // ever attaching their own listener.
 | 
			
		||||
        const tile = this.refs[user.userId];
 | 
			
		||||
        const tile = this.tiles.get(user.userId);
 | 
			
		||||
        if (tile) {
 | 
			
		||||
            this.updateList(); // reorder the membership list
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -245,8 +246,7 @@ export default class MemberList extends React.Component<IProps, IState> {
 | 
			
		|||
                    <BaseAvatar url={require("../../../../res/img/ellipsis.svg").default} name="..." size="36px" />
 | 
			
		||||
                }
 | 
			
		||||
                name={text}
 | 
			
		||||
                presenceState="online"
 | 
			
		||||
                suppressOnHover={true}
 | 
			
		||||
                showPresence={false}
 | 
			
		||||
                onClick={onClick}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
| 
						 | 
				
			
			@ -307,14 +307,24 @@ export default class MemberList extends React.Component<IProps, IState> {
 | 
			
		|||
        return members.map((m) => {
 | 
			
		||||
            if (m instanceof RoomMember) {
 | 
			
		||||
                // Is a Matrix invite
 | 
			
		||||
                return <MemberTile key={m.userId} member={m} ref={m.userId} showPresence={this.showPresence} />;
 | 
			
		||||
                return (
 | 
			
		||||
                    <MemberTile
 | 
			
		||||
                        key={m.userId}
 | 
			
		||||
                        member={m}
 | 
			
		||||
                        ref={(tile) => {
 | 
			
		||||
                            if (tile) this.tiles.set(m.userId, tile);
 | 
			
		||||
                            else this.tiles.delete(m.userId);
 | 
			
		||||
                        }}
 | 
			
		||||
                        showPresence={this.showPresence}
 | 
			
		||||
                    />
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                // Is a 3pid invite
 | 
			
		||||
                return (
 | 
			
		||||
                    <EntityTile
 | 
			
		||||
                        key={m.getStateKey()}
 | 
			
		||||
                        name={m.getContent().display_name}
 | 
			
		||||
                        suppressOnHover={true}
 | 
			
		||||
                        showPresence={false}
 | 
			
		||||
                        onClick={() => this.onPending3pidInviteClick(m)}
 | 
			
		||||
                    />
 | 
			
		||||
                );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,6 +44,8 @@ export default class PresenceLabel extends React.Component<IProps> {
 | 
			
		|||
        // the 'active ago' ends up being 0.
 | 
			
		||||
        if (presence && BUSY_PRESENCE_NAME.matches(presence)) return _t("presence|busy");
 | 
			
		||||
 | 
			
		||||
        if (presence === "io.element.unreachable") return _t("presence|unreachable");
 | 
			
		||||
 | 
			
		||||
        if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) {
 | 
			
		||||
            const duration = formatDuration(activeAgo);
 | 
			
		||||
            if (presence === "online") return _t("presence|online_for", { duration: duration });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1740,7 +1740,8 @@
 | 
			
		|||
        "online": "Online",
 | 
			
		||||
        "online_for": "Online for %(duration)s",
 | 
			
		||||
        "unknown": "Unknown",
 | 
			
		||||
        "unknown_for": "Unknown for %(duration)s"
 | 
			
		||||
        "unknown_for": "Unknown for %(duration)s",
 | 
			
		||||
        "unreachable": "User's server unreachable"
 | 
			
		||||
    },
 | 
			
		||||
    "quick_settings": {
 | 
			
		||||
        "all_settings": "All settings",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,8 +16,8 @@ limitations under the License.
 | 
			
		|||
*/
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { act, render, RenderResult } from "@testing-library/react";
 | 
			
		||||
import { Room, MatrixClient, RoomState, RoomMember, User } from "matrix-js-sdk/src/matrix";
 | 
			
		||||
import { act, render, RenderResult, screen } from "@testing-library/react";
 | 
			
		||||
import { Room, MatrixClient, RoomState, RoomMember, User, MatrixEvent } from "matrix-js-sdk/src/matrix";
 | 
			
		||||
import { compare } from "matrix-js-sdk/src/utils";
 | 
			
		||||
 | 
			
		||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
 | 
			
		||||
| 
						 | 
				
			
			@ -137,84 +137,88 @@ describe("MemberList", () => {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function renderMemberList(enablePresence: boolean): void {
 | 
			
		||||
        TestUtils.stubClient();
 | 
			
		||||
        client = MatrixClientPeg.safeGet();
 | 
			
		||||
        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 = User.createUser(adminUser.userId, client);
 | 
			
		||||
            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 = User.createUser(moderatorUser.userId, client);
 | 
			
		||||
            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 = User.createUser(defaultUser.userId, client);
 | 
			
		||||
            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.each([false, true])("does order members correctly (presence %s)", (enablePresence) => {
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            TestUtils.stubClient();
 | 
			
		||||
            client = MatrixClientPeg.safeGet();
 | 
			
		||||
            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>,
 | 
			
		||||
            );
 | 
			
		||||
            renderMemberList(enablePresence);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe("does order members correctly", () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -308,4 +312,24 @@ describe("MemberList", () => {
 | 
			
		|||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe("memberlist is rendered correctly", () => {
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            renderMemberList(true);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("memberlist is re-rendered on unreachable presence event", async () => {
 | 
			
		||||
            defaultUsers[0].user?.setPresenceEvent(
 | 
			
		||||
                new MatrixEvent({
 | 
			
		||||
                    type: "m.presence",
 | 
			
		||||
                    sender: defaultUsers[0].userId,
 | 
			
		||||
                    content: {
 | 
			
		||||
                        presence: "io.element.unreachable",
 | 
			
		||||
                        currently_active: false,
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            );
 | 
			
		||||
            expect(await screen.findByText(/User's server unreachable/)).toBeInTheDocument();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,4 +32,17 @@ describe("<PresenceLabel/>", () => {
 | 
			
		|||
            </DocumentFragment>
 | 
			
		||||
        `);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("should render 'Unreachable' for presence=unreachable", () => {
 | 
			
		||||
        const { asFragment } = render(<PresenceLabel presenceState="io.element.unreachable" />);
 | 
			
		||||
        expect(asFragment()).toMatchInlineSnapshot(`
 | 
			
		||||
            <DocumentFragment>
 | 
			
		||||
              <div
 | 
			
		||||
                class="mx_PresenceLabel"
 | 
			
		||||
              >
 | 
			
		||||
                User's server unreachable
 | 
			
		||||
              </div>
 | 
			
		||||
            </DocumentFragment>
 | 
			
		||||
        `);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue