mirror of
https://github.com/element-hq/element-web
synced 2024-11-25 10:45:51 +03:00
Add customisation point for mxid display (#7595)
* add wrapping component for hiding UI Signed-off-by: Kerry Archibald <kerrya@element.io> * add Setting Signed-off-by: Kerry Archibald <kerrya@element.io> * apply setting to profile settings, user menu, invite dialog, userinfo Signed-off-by: Kerry Archibald <kerrya@element.io> * hide mxids in user autocomplete * remove mxids from title in memeber list and timeline Signed-off-by: Kerry Archibald <kerrya@element.io> * hide mxid in ConfirmUserActionDialog Signed-off-by: Kerry Archibald <kerrya@element.io> * use name in power level event message when displayMxids is falsy Signed-off-by: Kerry Archibald <kerrya@element.io> * add customisation point for mxid display Signed-off-by: Kerry Archibald <kerrya@element.io> * use userid customisation Signed-off-by: Kerry Archibald <kerrya@element.io> * use customisation in sender profile Signed-off-by: Kerry Archibald <kerrya@element.io> * hide profile settings mxid if falsy Signed-off-by: Kerry Archibald <kerrya@element.io> * rename and move to components Signed-off-by: Kerry Archibald <kerrya@element.io> * remove change to UIFeature.ts Signed-off-by: Kerry Archibald <kerrya@element.io> * improvements from pr Signed-off-by: Kerry Archibald <kerrya@element.io> * lint fix Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
parent
b481fc069e
commit
502b805164
15 changed files with 154 additions and 23 deletions
|
@ -44,8 +44,8 @@ limitations under the License.
|
|||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_hostingSignup {
|
||||
margin-left: 20px;
|
||||
.mx_ProfileSettings_userId {
|
||||
margin-right: $spacing-20;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatarUpload {
|
||||
|
|
|
@ -44,6 +44,7 @@ import { MatrixClientPeg } from "./MatrixClientPeg";
|
|||
import { ROOM_SECURITY_TAB } from "./components/views/dialogs/RoomSettingsDialog";
|
||||
import AccessibleButton from './components/views/elements/AccessibleButton';
|
||||
import RightPanelStore from './stores/right-panel/RightPanelStore';
|
||||
import UserIdentifierCustomisations from './customisations/UserIdentifier';
|
||||
|
||||
export function getSenderName(event: MatrixEvent): string {
|
||||
return event.sender?.name ?? event.getSender() ?? _t("Someone");
|
||||
|
@ -499,6 +500,7 @@ function textForPowerEvent(event: MatrixEvent): () => string | null {
|
|||
if (users.indexOf(userId) === -1) users.push(userId);
|
||||
},
|
||||
);
|
||||
|
||||
const diffs = [];
|
||||
users.forEach((userId) => {
|
||||
// Previous power level
|
||||
|
@ -513,18 +515,20 @@ function textForPowerEvent(event: MatrixEvent): () => string | null {
|
|||
}
|
||||
if (from === previousUserDefault && to === currentUserDefault) { return; }
|
||||
if (to !== from) {
|
||||
diffs.push({ userId, from, to });
|
||||
const name = UserIdentifierCustomisations.getDisplayUserIdentifier(userId, { roomId: event.getRoomId() });
|
||||
diffs.push({ userId, name, from, to });
|
||||
}
|
||||
});
|
||||
if (!diffs.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// XXX: This is also surely broken for i18n
|
||||
return () => _t('%(senderName)s changed the power level of %(powerLevelDiffText)s.', {
|
||||
senderName,
|
||||
powerLevelDiffText: diffs.map(diff =>
|
||||
_t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', {
|
||||
userId: diff.userId,
|
||||
userId: diff.name,
|
||||
fromPowerLevel: Roles.textualPowerLevel(diff.from, previousUserDefault),
|
||||
toPowerLevel: Roles.textualPowerLevel(diff.to, currentUserDefault),
|
||||
}),
|
||||
|
|
|
@ -34,6 +34,7 @@ import { makeUserPermalink } from "../utils/permalinks/Permalinks";
|
|||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||
import MemberAvatar from '../components/views/avatars/MemberAvatar';
|
||||
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||
import UserIdentifierCustomisations from '../customisations/UserIdentifier';
|
||||
|
||||
const USER_REGEX = /\B@\S*/g;
|
||||
|
||||
|
@ -127,6 +128,9 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
// Don't include the '@' in our search query - it's only used as a way to trigger completion
|
||||
const query = fullMatch.startsWith('@') ? fullMatch.substring(1) : fullMatch;
|
||||
completions = this.matcher.match(query, limit).map((user) => {
|
||||
const description = UserIdentifierCustomisations.getDisplayUserIdentifier(
|
||||
user.userId, { roomId: this.room.roomId, withDisplayName: true },
|
||||
);
|
||||
const displayName = (user.name || user.userId || '');
|
||||
return {
|
||||
// Length of completion should equal length of text in decorator. draft-js
|
||||
|
@ -137,7 +141,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
suffix: (selection.beginning && range.start === 0) ? ': ' : ' ',
|
||||
href: makeUserPermalink(user.userId),
|
||||
component: (
|
||||
<PillCompletion title={displayName} description={user.userId}>
|
||||
<PillCompletion title={displayName} description={description}>
|
||||
<MemberAvatar member={user} width={24} height={24} />
|
||||
</PillCompletion>
|
||||
),
|
||||
|
|
|
@ -59,6 +59,7 @@ import { UPDATE_SELECTED_SPACE } from "../../stores/spaces";
|
|||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload";
|
||||
import UserIdentifierCustomisations from "../../customisations/UserIdentifier";
|
||||
|
||||
const CustomStatusSection = () => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
@ -499,7 +500,8 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
{ OwnProfileStore.instance.displayName }
|
||||
</span>
|
||||
<span className="mx_UserMenu_contextMenu_userId">
|
||||
{ MatrixClientPeg.get().getUserId() }
|
||||
{ UserIdentifierCustomisations.getDisplayUserIdentifier(
|
||||
MatrixClientPeg.get().getUserId(), { withDisplayName: true }) }
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import BaseAvatar from "./BaseAvatar";
|
|||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import { CardContext } from '../right_panel/BaseCard';
|
||||
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
|
||||
|
||||
interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> {
|
||||
member: RoomMember;
|
||||
|
@ -70,6 +71,9 @@ export default class MemberAvatar extends React.Component<IProps, IState> {
|
|||
private static getState(props: IProps): IState {
|
||||
if (props.member?.name) {
|
||||
let imageUrl = null;
|
||||
const userTitle = UserIdentifierCustomisations.getDisplayUserIdentifier(
|
||||
props.member.userId, { roomId: props.member?.roomId },
|
||||
);
|
||||
if (props.member.getMxcAvatarUrl()) {
|
||||
imageUrl = mediaFromMxc(props.member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(
|
||||
props.width,
|
||||
|
@ -79,7 +83,7 @@ export default class MemberAvatar extends React.Component<IProps, IState> {
|
|||
}
|
||||
return {
|
||||
name: props.member.name,
|
||||
title: props.title || props.member.userId,
|
||||
title: props.title || userTitle,
|
||||
imageUrl: imageUrl,
|
||||
};
|
||||
} else if (props.fallbackUserId) {
|
||||
|
|
|
@ -67,6 +67,7 @@ const ConfirmSpaceUserActionDialog: React.FC<IProps> = ({
|
|||
onFinished(success, reason, roomsToLeave);
|
||||
}}
|
||||
className="mx_ConfirmSpaceUserActionDialog"
|
||||
roomId={space.roomId}
|
||||
>
|
||||
{ warning }
|
||||
<SpaceChildrenPicker
|
||||
|
|
|
@ -28,6 +28,7 @@ import BaseAvatar from '../avatars/BaseAvatar';
|
|||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
import Field from '../elements/Field';
|
||||
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
|
||||
|
||||
interface IProps {
|
||||
// matrix-js-sdk (room) member object. Supply either this or 'groupMember'
|
||||
|
@ -46,6 +47,7 @@ interface IProps {
|
|||
danger?: boolean;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
roomId?: string;
|
||||
onFinished: (success: boolean, reason?: string) => void;
|
||||
}
|
||||
|
||||
|
@ -126,6 +128,10 @@ export default class ConfirmUserActionDialog extends React.Component<IProps, ISt
|
|||
avatar = <BaseAvatar name={name} url={httpAvatarUrl} width={48} height={48} />;
|
||||
}
|
||||
|
||||
const displayUserIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier(
|
||||
userId, { roomId: this.props.roomId, withDisplayName: true },
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className={classNames("mx_ConfirmUserActionDialog", this.props.className)}
|
||||
|
@ -139,7 +145,7 @@ export default class ConfirmUserActionDialog extends React.Component<IProps, ISt
|
|||
{ avatar }
|
||||
</div>
|
||||
<div className="mx_ConfirmUserActionDialog_name">{ name }</div>
|
||||
<div className="mx_ConfirmUserActionDialog_userId">{ userId }</div>
|
||||
<div className="mx_ConfirmUserActionDialog_userId">{ displayUserIdentifier }</div>
|
||||
</div>
|
||||
|
||||
{ reasonBox }
|
||||
|
|
|
@ -72,6 +72,7 @@ import BaseDialog from "./BaseDialog";
|
|||
import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
|
||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
||||
import CallHandler from "../../../CallHandler";
|
||||
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
|
||||
|
||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||
/* eslint-disable camelcase */
|
||||
|
@ -329,9 +330,13 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
|||
</span>
|
||||
);
|
||||
|
||||
const userIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier(
|
||||
this.props.member.userId, { withDisplayName: true },
|
||||
);
|
||||
|
||||
const caption = (this.props.member as ThreepidMember).isEmail
|
||||
? _t("Invite by email")
|
||||
: this.highlightName(this.props.member.userId);
|
||||
: this.highlightName(userIdentifier);
|
||||
|
||||
return (
|
||||
<div className='mx_InviteDialog_roomTile' onClick={this.onClick}>
|
||||
|
|
|
@ -23,6 +23,7 @@ import FlairStore from '../../../stores/FlairStore';
|
|||
import { getUserNameColorClass } from '../../../utils/FormattingUtils';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import UserIdentifier from '../../../customisations/UserIdentifier';
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
|
@ -116,7 +117,9 @@ export default class SenderProfile extends React.Component<IProps, IState> {
|
|||
if (disambiguate) {
|
||||
mxidElement = (
|
||||
<span className="mx_SenderProfile_mxid">
|
||||
{ mxid }
|
||||
{ UserIdentifier.getDisplayUserIdentifier(
|
||||
mxid, { withDisplayName: true, roomId: mxEvent.getRoomId() },
|
||||
) }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -77,6 +77,7 @@ import { TimelineRenderingType } from "../../../contexts/RoomContext";
|
|||
import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
|
||||
import { IRightPanelCardState } from '../../../stores/right-panel/RightPanelStoreIPanelState';
|
||||
import { useUserStatusMessage } from "../../../hooks/useUserStatusMessage";
|
||||
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
|
||||
|
||||
export interface IDevice {
|
||||
deviceId: string;
|
||||
|
@ -1517,7 +1518,8 @@ export type Member = User | RoomMember | GroupMember;
|
|||
const UserInfoHeader: React.FC<{
|
||||
member: Member;
|
||||
e2eStatus: E2EStatus;
|
||||
}> = ({ member, e2eStatus }) => {
|
||||
roomId: string;
|
||||
}> = ({ member, e2eStatus, roomId }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const statusMessage = useUserStatusMessage(member);
|
||||
|
||||
|
@ -1604,7 +1606,7 @@ const UserInfoHeader: React.FC<{
|
|||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div>{ member.userId }</div>
|
||||
<div>{ UserIdentifierCustomisations.getDisplayUserIdentifier(member.userId, { roomId, withDisplayName: true }) }</div>
|
||||
<div className="mx_UserInfo_profileStatus">
|
||||
{ presenceLabel }
|
||||
{ statusLabel }
|
||||
|
@ -1708,7 +1710,7 @@ const UserInfo: React.FC<IProps> = ({
|
|||
|
||||
const header = <React.Fragment>
|
||||
{ scopeHeader }
|
||||
<UserInfoHeader member={member} e2eStatus={e2eStatus} />
|
||||
<UserInfoHeader member={member} e2eStatus={e2eStatus} roomId={room.roomId} />
|
||||
</React.Fragment>;
|
||||
return <BaseCard
|
||||
className={classes.join(" ")}
|
||||
|
|
|
@ -29,6 +29,7 @@ import { Action } from "../../../dispatcher/actions";
|
|||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import EntityTile, { PowerStatus } from "./EntityTile";
|
||||
import MemberAvatar from "./../avatars/MemberAvatar";
|
||||
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
|
||||
|
||||
interface IProps {
|
||||
member: RoomMember;
|
||||
|
@ -209,9 +210,11 @@ export default class MemberTile extends React.Component<IProps, IState> {
|
|||
|
||||
private getPowerLabel(): string {
|
||||
return _t("%(userName)s (power %(powerLevelNumber)s)", {
|
||||
userName: this.props.member.userId,
|
||||
userName: UserIdentifierCustomisations.getDisplayUserIdentifier(
|
||||
this.props.member.userId, { roomId: this.props.member.roomId },
|
||||
),
|
||||
powerLevelNumber: this.props.member.powerLevel,
|
||||
});
|
||||
}).trim();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -34,9 +34,9 @@ import InviteReason from "../elements/InviteReason";
|
|||
import { IOOBData } from "../../../stores/ThreepidInviteStore";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
|
||||
const MemberEventHtmlReasonField = "io.element.html_reason";
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import { mediaFromMxc } from "../../../customisations/Media";
|
|||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import AvatarSetting from './AvatarSetting';
|
||||
import ExternalLink from '../elements/ExternalLink';
|
||||
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
|
||||
|
||||
interface IState {
|
||||
userId?: string;
|
||||
|
@ -162,7 +163,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
|
|||
const hostingSignupLink = getHostingLink('user-settings');
|
||||
let hostingSignup = null;
|
||||
if (hostingSignupLink) {
|
||||
hostingSignup = <span className="mx_ProfileSettings_hostingSignup">
|
||||
hostingSignup = <span>
|
||||
{ _t(
|
||||
"<a>Upgrade</a> to your own domain", {},
|
||||
{
|
||||
|
@ -174,6 +175,10 @@ export default class ProfileSettings extends React.Component<{}, IState> {
|
|||
</span>;
|
||||
}
|
||||
|
||||
const userIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier(
|
||||
this.state.userId, { withDisplayName: true },
|
||||
);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={this.saveProfile}
|
||||
|
@ -199,7 +204,9 @@ export default class ProfileSettings extends React.Component<{}, IState> {
|
|||
onChange={this.onDisplayNameChanged}
|
||||
/>
|
||||
<p>
|
||||
{ this.state.userId }
|
||||
{ userIdentifier && <span className="mx_ProfileSettings_userId">
|
||||
{ userIdentifier }
|
||||
</span> }
|
||||
{ hostingSignup }
|
||||
</p>
|
||||
</div>
|
||||
|
|
41
src/customisations/UserIdentifier.ts
Normal file
41
src/customisations/UserIdentifier.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Copyright 2022 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Customise display of the user identifier
|
||||
* hide userId for guests, display 3pid
|
||||
*
|
||||
* Set withDisplayName to true when user identifier will be displayed alongside user name
|
||||
*/
|
||||
function getDisplayUserIdentifier(
|
||||
userId: string,
|
||||
{ roomId, withDisplayName }: { roomId?: string, withDisplayName?: boolean },
|
||||
): string | null {
|
||||
return userId;
|
||||
}
|
||||
|
||||
// This interface summarises all available customisation points and also marks
|
||||
// them all as optional. This allows customisers to only define and export the
|
||||
// customisations they need while still maintaining type safety.
|
||||
export interface IUserIdentifierCustomisations {
|
||||
getDisplayUserIdentifier?: typeof getDisplayUserIdentifier;
|
||||
}
|
||||
|
||||
// A real customisation module will define and export one or more of the
|
||||
// customisation points that make up `IUserIdentifierCustomisations`.
|
||||
export default {
|
||||
getDisplayUserIdentifier,
|
||||
} as IUserIdentifierCustomisations;
|
|
@ -5,7 +5,14 @@ import renderer from 'react-test-renderer';
|
|||
|
||||
import { getSenderName, textForEvent } from "../src/TextForEvent";
|
||||
import SettingsStore from "../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../src/settings/SettingLevel";
|
||||
import { createTestClient } from './test-utils';
|
||||
import { MatrixClientPeg } from '../src/MatrixClientPeg';
|
||||
import UserIdentifierCustomisations from '../src/customisations/UserIdentifier';
|
||||
|
||||
jest.mock("../src/settings/SettingsStore");
|
||||
jest.mock('../src/customisations/UserIdentifier', () => ({
|
||||
getDisplayUserIdentifier: jest.fn().mockImplementation(userId => userId),
|
||||
}));
|
||||
|
||||
function mockPinnedEvent(
|
||||
pinnedMessageIds?: string[],
|
||||
|
@ -67,7 +74,10 @@ describe('TextForEvent', () => {
|
|||
});
|
||||
|
||||
describe("TextForPinnedEvent", () => {
|
||||
SettingsStore.setValue("feature_pinning", null, SettingLevel.DEVICE, true);
|
||||
beforeAll(() => {
|
||||
// enable feature_pinning setting
|
||||
(SettingsStore.getValue as jest.Mock).mockImplementation(feature => feature === 'feature_pinning');
|
||||
});
|
||||
|
||||
it("mentions message when a single message was pinned, with no previously pinned messages", () => {
|
||||
const event = mockPinnedEvent(['message-1']);
|
||||
|
@ -141,6 +151,11 @@ describe('TextForEvent', () => {
|
|||
});
|
||||
|
||||
describe("textForPowerEvent()", () => {
|
||||
let mockClient;
|
||||
const mockRoom = {
|
||||
getMember: jest.fn(),
|
||||
};
|
||||
|
||||
const userA = {
|
||||
id: '@a',
|
||||
name: 'Alice',
|
||||
|
@ -175,7 +190,23 @@ describe('TextForEvent', () => {
|
|||
},
|
||||
});
|
||||
|
||||
it("returns empty string when no users have changed power level", () => {
|
||||
beforeAll(() => {
|
||||
mockClient = createTestClient();
|
||||
MatrixClientPeg.get = () => mockClient;
|
||||
mockClient.getRoom.mockClear().mockReturnValue(mockRoom);
|
||||
mockRoom.getMember.mockClear().mockImplementation(
|
||||
userId => [userA, userB, userC].find(u => u.id === userId),
|
||||
);
|
||||
(SettingsStore.getValue as jest.Mock).mockReturnValue(true);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
(UserIdentifierCustomisations.getDisplayUserIdentifier as jest.Mock)
|
||||
.mockClear()
|
||||
.mockImplementation(userId => userId);
|
||||
});
|
||||
|
||||
it("returns falsy when no users have changed power level", () => {
|
||||
const event = mockPowerEvent({
|
||||
users: {
|
||||
[userA.id]: 100,
|
||||
|
@ -187,7 +218,7 @@ describe('TextForEvent', () => {
|
|||
expect(textForEvent(event)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("returns empty string when users power levels have been changed by default settings", () => {
|
||||
it("returns false when users power levels have been changed by default settings", () => {
|
||||
const event = mockPowerEvent({
|
||||
usersDefault: 100,
|
||||
prevDefault: 50,
|
||||
|
@ -257,6 +288,24 @@ describe('TextForEvent', () => {
|
|||
"@a changed the power level of @b from Moderator to Admin, @c from Custom (101) to Moderator.";
|
||||
expect(textForEvent(event)).toEqual(expectedText);
|
||||
});
|
||||
|
||||
it("uses userIdentifier customisation", () => {
|
||||
(UserIdentifierCustomisations.getDisplayUserIdentifier as jest.Mock)
|
||||
.mockImplementation(userId => 'customised ' + userId);
|
||||
const event = mockPowerEvent({
|
||||
users: {
|
||||
[userB.id]: 100,
|
||||
},
|
||||
prevUsers: {
|
||||
[userB.id]: 50,
|
||||
},
|
||||
});
|
||||
// uses customised user id
|
||||
const expectedText = "@a changed the power level of customised @b from Moderator to Admin.";
|
||||
expect(textForEvent(event)).toEqual(expectedText);
|
||||
expect(UserIdentifierCustomisations.getDisplayUserIdentifier)
|
||||
.toHaveBeenCalledWith(userB.id, { roomId: event.getRoomId() });
|
||||
});
|
||||
});
|
||||
|
||||
describe("textForCanonicalAliasEvent()", () => {
|
||||
|
|
Loading…
Reference in a new issue