Convert enzyme to rtl: UserInfo (#9846)
* converts tests from Enzyme to Rtl * increases test coveragepull/28217/head
parent
a691e634b0
commit
880a86c8b1
|
@ -69,7 +69,7 @@ const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
|
|||
if (onClose) {
|
||||
closeButton = (
|
||||
<AccessibleButton
|
||||
data-test-id="base-card-close-button"
|
||||
data-testid="base-card-close-button"
|
||||
className="mx_BaseCard_close"
|
||||
onClick={onClose}
|
||||
title={closeLabel || _t("Close")}
|
||||
|
|
|
@ -29,6 +29,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
|
|||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||
import { UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import Modal from "../../../Modal";
|
||||
|
@ -84,7 +85,7 @@ export interface IDevice {
|
|||
getDisplayName(): string;
|
||||
}
|
||||
|
||||
const disambiguateDevices = (devices: IDevice[]) => {
|
||||
export const disambiguateDevices = (devices: IDevice[]) => {
|
||||
const names = Object.create(null);
|
||||
for (let i = 0; i < devices.length; i++) {
|
||||
const name = devices[i].getDisplayName();
|
||||
|
@ -94,7 +95,7 @@ const disambiguateDevices = (devices: IDevice[]) => {
|
|||
}
|
||||
for (const name in names) {
|
||||
if (names[name].length > 1) {
|
||||
names[name].forEach((j) => {
|
||||
names[name].forEach((j: number) => {
|
||||
devices[j].ambiguous = true;
|
||||
});
|
||||
}
|
||||
|
@ -149,7 +150,7 @@ function useHasCrossSigningKeys(cli: MatrixClient, member: User, canVerify: bool
|
|||
}, [cli, member, canVerify]);
|
||||
}
|
||||
|
||||
function DeviceItem({ userId, device }: { userId: string; device: IDevice }) {
|
||||
export function DeviceItem({ userId, device }: { userId: string; device: IDevice }) {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const isMe = userId === cli.getUserId();
|
||||
const deviceTrust = cli.checkDeviceTrust(userId, device.deviceId);
|
||||
|
@ -172,7 +173,10 @@ function DeviceItem({ userId, device }: { userId: string; device: IDevice }) {
|
|||
});
|
||||
|
||||
const onDeviceClick = () => {
|
||||
verifyDevice(cli.getUser(userId), device);
|
||||
const user = cli.getUser(userId);
|
||||
if (user) {
|
||||
verifyDevice(user, device);
|
||||
}
|
||||
};
|
||||
|
||||
let deviceName;
|
||||
|
@ -315,7 +319,7 @@ const MessageButton = ({ member }: { member: RoomMember }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const UserOptionsSection: React.FC<{
|
||||
export const UserOptionsSection: React.FC<{
|
||||
member: RoomMember;
|
||||
isIgnored: boolean;
|
||||
canInvite: boolean;
|
||||
|
@ -367,7 +371,8 @@ const UserOptionsSection: React.FC<{
|
|||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
highlighted: true,
|
||||
event_id: room.getEventReadUpTo(member.userId),
|
||||
// this could return null, the default prevents a type error
|
||||
event_id: room?.getEventReadUpTo(member.userId) || undefined,
|
||||
room_id: member.roomId,
|
||||
metricsTrigger: undefined, // room doesn't change
|
||||
});
|
||||
|
@ -402,16 +407,18 @@ const UserOptionsSection: React.FC<{
|
|||
const onInviteUserButton = async (ev: ButtonEvent) => {
|
||||
try {
|
||||
// We use a MultiInviter to re-use the invite logic, even though we're only inviting one user.
|
||||
const inviter = new MultiInviter(roomId);
|
||||
const inviter = new MultiInviter(roomId || "");
|
||||
await inviter.invite([member.userId]).then(() => {
|
||||
if (inviter.getCompletionState(member.userId) !== "invited") {
|
||||
throw new Error(inviter.getErrorText(member.userId));
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
const description = err instanceof Error ? err.message : _t("Operation failed");
|
||||
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Failed to invite"),
|
||||
description: err && err.message ? err.message : _t("Operation failed"),
|
||||
description,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -432,10 +439,7 @@ const UserOptionsSection: React.FC<{
|
|||
</AccessibleButton>
|
||||
);
|
||||
|
||||
let directMessageButton: JSX.Element;
|
||||
if (!isMe) {
|
||||
directMessageButton = <MessageButton member={member} />;
|
||||
}
|
||||
const directMessageButton = isMe ? null : <MessageButton member={member} />;
|
||||
|
||||
return (
|
||||
<div className="mx_UserInfo_container">
|
||||
|
@ -499,16 +503,24 @@ interface IPowerLevelsContent {
|
|||
redact?: number;
|
||||
}
|
||||
|
||||
const isMuted = (member: RoomMember, powerLevelContent: IPowerLevelsContent) => {
|
||||
export const isMuted = (member: RoomMember, powerLevelContent: IPowerLevelsContent) => {
|
||||
if (!powerLevelContent || !member) return false;
|
||||
|
||||
const levelToSend =
|
||||
(powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) ||
|
||||
powerLevelContent.events_default;
|
||||
|
||||
// levelToSend could be undefined as .events_default is optional. Coercing in this case using
|
||||
// Number() would always return false, so this preserves behaviour
|
||||
// FIXME: per the spec, if `events_default` is unset, it defaults to zero. If
|
||||
// the member has a negative powerlevel, this will give an incorrect result.
|
||||
if (levelToSend === undefined) return false;
|
||||
|
||||
return member.powerLevel < levelToSend;
|
||||
};
|
||||
|
||||
const getPowerLevels = (room) => room?.currentState?.getStateEvents(EventType.RoomPowerLevels, "")?.getContent() || {};
|
||||
export const getPowerLevels = (room: Room): IPowerLevelsContent =>
|
||||
room?.currentState?.getStateEvents(EventType.RoomPowerLevels, "")?.getContent() || {};
|
||||
|
||||
export const useRoomPowerLevels = (cli: MatrixClient, room: Room) => {
|
||||
const [powerLevels, setPowerLevels] = useState<IPowerLevelsContent>(getPowerLevels(room));
|
||||
|
@ -538,7 +550,7 @@ interface IBaseProps {
|
|||
stopUpdating(): void;
|
||||
}
|
||||
|
||||
const RoomKickButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBaseRoomProps, "powerLevels">) => {
|
||||
export const RoomKickButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBaseRoomProps, "powerLevels">) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
// check if user can be kicked/disinvited
|
||||
|
@ -566,7 +578,7 @@ const RoomKickButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBas
|
|||
space: room,
|
||||
spaceChildFilter: (child: Room) => {
|
||||
// Return true if the target member is not banned and we have sufficient PL to ban them
|
||||
const myMember = child.getMember(cli.credentials.userId);
|
||||
const myMember = child.getMember(cli.credentials.userId || "");
|
||||
const theirMember = child.getMember(member.userId);
|
||||
return (
|
||||
myMember &&
|
||||
|
@ -648,7 +660,7 @@ const RedactMessagesButton: React.FC<IBaseProps> = ({ member }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const BanToggleButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBaseRoomProps, "powerLevels">) => {
|
||||
export const BanToggleButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBaseRoomProps, "powerLevels">) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
const isBanned = member.membership === "ban";
|
||||
|
@ -674,7 +686,7 @@ const BanToggleButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBa
|
|||
spaceChildFilter: isBanned
|
||||
? (child: Room) => {
|
||||
// Return true if the target member is banned and we have sufficient PL to unban
|
||||
const myMember = child.getMember(cli.credentials.userId);
|
||||
const myMember = child.getMember(cli.credentials.userId || "");
|
||||
const theirMember = child.getMember(member.userId);
|
||||
return (
|
||||
myMember &&
|
||||
|
@ -686,7 +698,7 @@ const BanToggleButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBa
|
|||
}
|
||||
: (child: Room) => {
|
||||
// Return true if the target member isn't banned and we have sufficient PL to ban
|
||||
const myMember = child.getMember(cli.credentials.userId);
|
||||
const myMember = child.getMember(cli.credentials.userId || "");
|
||||
const theirMember = child.getMember(member.userId);
|
||||
return (
|
||||
myMember &&
|
||||
|
@ -835,7 +847,7 @@ const MuteToggleButton: React.FC<IBaseRoomProps> = ({ member, room, powerLevels,
|
|||
);
|
||||
};
|
||||
|
||||
const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
|
||||
export const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
|
||||
room,
|
||||
children,
|
||||
member,
|
||||
|
@ -855,7 +867,7 @@ const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
|
|||
// if these do not exist in the event then they should default to 50 as per the spec
|
||||
const { ban: banPowerLevel = 50, kick: kickPowerLevel = 50, redact: redactPowerLevel = 50 } = powerLevels;
|
||||
|
||||
const me = room.getMember(cli.getUserId());
|
||||
const me = room.getMember(cli.getUserId() || "");
|
||||
if (!me) {
|
||||
// we aren't in the room, so return no admin tooling
|
||||
return <div />;
|
||||
|
@ -879,7 +891,7 @@ const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
|
|||
<BanToggleButton room={room} member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />
|
||||
);
|
||||
}
|
||||
if (!isMe && canAffectUser && me.powerLevel >= editPowerLevel && !room.isSpaceRoom()) {
|
||||
if (!isMe && canAffectUser && me.powerLevel >= Number(editPowerLevel) && !room.isSpaceRoom()) {
|
||||
muteButton = (
|
||||
<MuteToggleButton
|
||||
member={member}
|
||||
|
@ -949,7 +961,7 @@ function useRoomPermissions(cli: MatrixClient, room: Room, user: RoomMember): IR
|
|||
const powerLevels = room?.currentState.getStateEvents(EventType.RoomPowerLevels, "")?.getContent();
|
||||
if (!powerLevels) return;
|
||||
|
||||
const me = room.getMember(cli.getUserId());
|
||||
const me = room.getMember(cli.getUserId() || "");
|
||||
if (!me) return;
|
||||
|
||||
const them = user;
|
||||
|
@ -1006,7 +1018,7 @@ const PowerLevelSection: React.FC<{
|
|||
}
|
||||
};
|
||||
|
||||
const PowerLevelEditor: React.FC<{
|
||||
export const PowerLevelEditor: React.FC<{
|
||||
user: RoomMember;
|
||||
room: Room;
|
||||
roomPermissions: IRoomPermissions;
|
||||
|
@ -1022,8 +1034,13 @@ const PowerLevelEditor: React.FC<{
|
|||
async (powerLevel: number) => {
|
||||
setSelectedPowerLevel(powerLevel);
|
||||
|
||||
const applyPowerChange = (roomId, target, powerLevel, powerLevelEvent) => {
|
||||
return cli.setPowerLevel(roomId, target, parseInt(powerLevel), powerLevelEvent).then(
|
||||
const applyPowerChange = (
|
||||
roomId: string,
|
||||
target: string,
|
||||
powerLevel: number,
|
||||
powerLevelEvent: MatrixEvent,
|
||||
) => {
|
||||
return cli.setPowerLevel(roomId, target, powerLevel, powerLevelEvent).then(
|
||||
function () {
|
||||
// NO-OP; rely on the m.room.member event coming down else we could
|
||||
// get out of sync if we force setState here!
|
||||
|
@ -1046,7 +1063,7 @@ const PowerLevelEditor: React.FC<{
|
|||
if (!powerLevelEvent) return;
|
||||
|
||||
const myUserId = cli.getUserId();
|
||||
const myPower = powerLevelEvent.getContent().users[myUserId];
|
||||
const myPower = powerLevelEvent.getContent().users[myUserId || ""];
|
||||
if (myPower && parseInt(myPower) <= powerLevel && myUserId !== target) {
|
||||
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||
title: _t("Warning!"),
|
||||
|
@ -1085,7 +1102,7 @@ const PowerLevelEditor: React.FC<{
|
|||
return (
|
||||
<div className="mx_UserInfo_profileField">
|
||||
<PowerSelector
|
||||
label={null}
|
||||
label={undefined}
|
||||
value={selectedPowerLevel}
|
||||
maxValue={roomPermissions.modifyLevelMax}
|
||||
usersDefault={powerLevelUsersDefault}
|
||||
|
@ -1099,7 +1116,7 @@ export const useDevices = (userId: string) => {
|
|||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
// undefined means yet to be loaded, null means failed to load, otherwise list of devices
|
||||
const [devices, setDevices] = useState(undefined);
|
||||
const [devices, setDevices] = useState<undefined | null | IDevice[]>(undefined);
|
||||
// Download device lists
|
||||
useEffect(() => {
|
||||
setDevices(undefined);
|
||||
|
@ -1116,8 +1133,8 @@ export const useDevices = (userId: string) => {
|
|||
return;
|
||||
}
|
||||
|
||||
disambiguateDevices(devices);
|
||||
setDevices(devices);
|
||||
disambiguateDevices(devices as IDevice[]);
|
||||
setDevices(devices as IDevice[]);
|
||||
} catch (err) {
|
||||
setDevices(null);
|
||||
}
|
||||
|
@ -1136,17 +1153,17 @@ export const useDevices = (userId: string) => {
|
|||
const updateDevices = async () => {
|
||||
const newDevices = cli.getStoredDevicesForUser(userId);
|
||||
if (cancel) return;
|
||||
setDevices(newDevices);
|
||||
setDevices(newDevices as IDevice[]);
|
||||
};
|
||||
const onDevicesUpdated = (users) => {
|
||||
const onDevicesUpdated = (users: string[]) => {
|
||||
if (!users.includes(userId)) return;
|
||||
updateDevices();
|
||||
};
|
||||
const onDeviceVerificationChanged = (_userId, device) => {
|
||||
const onDeviceVerificationChanged = (_userId: string, deviceId: string) => {
|
||||
if (_userId !== userId) return;
|
||||
updateDevices();
|
||||
};
|
||||
const onUserTrustStatusChanged = (_userId, trustStatus) => {
|
||||
const onUserTrustStatusChanged = (_userId: string, trustLevel: UserTrustLevel) => {
|
||||
if (_userId !== userId) return;
|
||||
updateDevices();
|
||||
};
|
||||
|
@ -1229,9 +1246,11 @@ const BasicUserInfo: React.FC<{
|
|||
logger.error("Failed to deactivate user");
|
||||
logger.error(err);
|
||||
|
||||
const description = err instanceof Error ? err.message : _t("Operation failed");
|
||||
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Failed to deactivate user"),
|
||||
description: err && err.message ? err.message : _t("Operation failed"),
|
||||
description,
|
||||
});
|
||||
}
|
||||
}, [cli, member.userId]);
|
||||
|
@ -1317,12 +1336,12 @@ const BasicUserInfo: React.FC<{
|
|||
const homeserverSupportsCrossSigning = useHomeserverSupportsCrossSigning(cli);
|
||||
|
||||
const userTrust = cryptoEnabled && cli.checkUserTrust(member.userId);
|
||||
const userVerified = cryptoEnabled && userTrust.isCrossSigningVerified();
|
||||
const userVerified = cryptoEnabled && userTrust && userTrust.isCrossSigningVerified();
|
||||
const isMe = member.userId === cli.getUserId();
|
||||
const canVerify =
|
||||
cryptoEnabled && homeserverSupportsCrossSigning && !userVerified && !isMe && devices && devices.length > 0;
|
||||
|
||||
const setUpdating = (updating) => {
|
||||
const setUpdating: SetUpdating = (updating) => {
|
||||
setPendingUpdateCount((count) => count + (updating ? 1 : -1));
|
||||
};
|
||||
const hasCrossSigningKeys = useHasCrossSigningKeys(cli, member as User, canVerify, setUpdating);
|
||||
|
@ -1408,9 +1427,9 @@ const BasicUserInfo: React.FC<{
|
|||
|
||||
export type Member = User | RoomMember;
|
||||
|
||||
const UserInfoHeader: React.FC<{
|
||||
export const UserInfoHeader: React.FC<{
|
||||
member: Member;
|
||||
e2eStatus: E2EStatus;
|
||||
e2eStatus?: E2EStatus;
|
||||
roomId?: string;
|
||||
}> = ({ member, e2eStatus, roomId }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
@ -1427,9 +1446,11 @@ const UserInfoHeader: React.FC<{
|
|||
name: (member as RoomMember).name || (member as User).displayName,
|
||||
};
|
||||
|
||||
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true);
|
||||
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", undefined, true);
|
||||
}, [member]);
|
||||
|
||||
const avatarUrl = (member as User).avatarUrl;
|
||||
|
||||
const avatarElement = (
|
||||
<div className="mx_UserInfo_avatar">
|
||||
<div className="mx_UserInfo_avatar_transition">
|
||||
|
@ -1442,7 +1463,7 @@ const UserInfoHeader: React.FC<{
|
|||
resizeMethod="scale"
|
||||
fallbackUserId={member.userId}
|
||||
onClick={onMemberAvatarClick}
|
||||
urls={(member as User).avatarUrl ? [(member as User).avatarUrl] : undefined}
|
||||
urls={avatarUrl ? [avatarUrl] : undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1475,10 +1496,7 @@ const UserInfoHeader: React.FC<{
|
|||
);
|
||||
}
|
||||
|
||||
let e2eIcon;
|
||||
if (e2eStatus) {
|
||||
e2eIcon = <E2EIcon size={18} status={e2eStatus} isUser={true} />;
|
||||
}
|
||||
const e2eIcon = e2eStatus ? <E2EIcon size={18} status={e2eStatus} isUser={true} /> : null;
|
||||
|
||||
const displayName = (member as RoomMember).rawDisplayName;
|
||||
return (
|
||||
|
@ -1496,7 +1514,7 @@ const UserInfoHeader: React.FC<{
|
|||
</h2>
|
||||
</div>
|
||||
<div className="mx_UserInfo_profile_mxid">
|
||||
{UserIdentifierCustomisations.getDisplayUserIdentifier(member.userId, {
|
||||
{UserIdentifierCustomisations.getDisplayUserIdentifier?.(member.userId, {
|
||||
roomId,
|
||||
withDisplayName: true,
|
||||
})}
|
||||
|
@ -1533,7 +1551,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
|
|||
|
||||
const classes = ["mx_UserInfo"];
|
||||
|
||||
let cardState: IRightPanelCardState;
|
||||
let cardState: IRightPanelCardState = {};
|
||||
// We have no previousPhase for when viewing a UserInfo without a Room at this time
|
||||
if (room && phase === RightPanelPhases.EncryptionPanel) {
|
||||
cardState = { member };
|
||||
|
@ -1551,10 +1569,10 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
|
|||
case RightPanelPhases.SpaceMemberInfo:
|
||||
content = (
|
||||
<BasicUserInfo
|
||||
room={room}
|
||||
room={room as Room}
|
||||
member={member as User}
|
||||
devices={devices}
|
||||
isRoomEncrypted={isRoomEncrypted}
|
||||
devices={devices as IDevice[]}
|
||||
isRoomEncrypted={Boolean(isRoomEncrypted)}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
@ -1565,7 +1583,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
|
|||
{...(props as React.ComponentProps<typeof EncryptionPanel>)}
|
||||
member={member as User | RoomMember}
|
||||
onClose={onEncryptionPanelClose}
|
||||
isRoomEncrypted={isRoomEncrypted}
|
||||
isRoomEncrypted={Boolean(isRoomEncrypted)}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
@ -1582,7 +1600,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
|
|||
let scopeHeader;
|
||||
if (room?.isSpaceRoom()) {
|
||||
scopeHeader = (
|
||||
<div data-test-id="space-header" className="mx_RightPanel_scopeHeader">
|
||||
<div data-testid="space-header" className="mx_RightPanel_scopeHeader">
|
||||
<RoomAvatar room={room} height={32} width={32} />
|
||||
<RoomName room={room} />
|
||||
</div>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue