Convert enzyme to rtl: UserInfo (#9846)

* converts tests from Enzyme to Rtl
* increases test coverage
pull/28217/head
alunturner 2023-01-11 10:46:35 +00:00 committed by GitHub
parent a691e634b0
commit 880a86c8b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 960 additions and 144 deletions

View File

@ -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")}

View File

@ -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