Remove all usages of UNSAFE_* React methods (#9583)
This commit is contained in:
parent
38dbe8ed33
commit
590b845f3f
33 changed files with 585 additions and 413 deletions
|
@ -133,8 +133,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
public componentDidMount() {
|
||||||
UNSAFE_componentWillMount() { // eslint-disable-line @typescript-eslint/naming-convention, camelcase
|
|
||||||
this.authLogic.attemptAuth().then((result) => {
|
this.authLogic.attemptAuth().then((result) => {
|
||||||
const extra = {
|
const extra = {
|
||||||
emailSid: this.authLogic.getEmailSid(),
|
emailSid: this.authLogic.getEmailSid(),
|
||||||
|
|
|
@ -403,12 +403,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.setState({ pendingInitialSync: false });
|
this.setState({ pendingInitialSync: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle stage
|
public setState<K extends keyof IState>(
|
||||||
// eslint-disable-next-line
|
state: ((
|
||||||
UNSAFE_componentWillUpdate(props, state) {
|
prevState: Readonly<IState>,
|
||||||
if (this.shouldTrackPageChange(this.state, state)) {
|
props: Readonly<IProps>,
|
||||||
|
) => (Pick<IState, K> | IState | null)) | (Pick<IState, K> | IState | null),
|
||||||
|
callback?: () => void,
|
||||||
|
): void {
|
||||||
|
if (this.shouldTrackPageChange(this.state, { ...this.state, ...state })) {
|
||||||
this.startPageChangeTimer();
|
this.startPageChangeTimer();
|
||||||
}
|
}
|
||||||
|
super.setState<K>(state, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
|
|
|
@ -299,22 +299,17 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
cli.on(ClientEvent.Sync, this.onSync);
|
cli.on(ClientEvent.Sync, this.onSync);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Move into constructor
|
public componentDidMount() {
|
||||||
// eslint-disable-next-line
|
|
||||||
UNSAFE_componentWillMount() {
|
|
||||||
if (this.props.manageReadReceipts) {
|
if (this.props.manageReadReceipts) {
|
||||||
this.updateReadReceiptOnUserActivity();
|
this.updateReadReceiptOnUserActivity();
|
||||||
}
|
}
|
||||||
if (this.props.manageReadMarkers) {
|
if (this.props.manageReadMarkers) {
|
||||||
this.updateReadMarkerOnUserActivity();
|
this.updateReadMarkerOnUserActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initTimeline(this.props);
|
this.initTimeline(this.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
public componentDidUpdate(newProps) {
|
||||||
// eslint-disable-next-line
|
|
||||||
UNSAFE_componentWillReceiveProps(newProps) {
|
|
||||||
if (newProps.timelineSet !== this.props.timelineSet) {
|
if (newProps.timelineSet !== this.props.timelineSet) {
|
||||||
// throw new Error("changing timelineSet on a TimelinePanel is not supported");
|
// throw new Error("changing timelineSet on a TimelinePanel is not supported");
|
||||||
|
|
||||||
|
@ -334,10 +329,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
const differentHighlightedEventId = newProps.highlightedEventId != this.props.highlightedEventId;
|
const differentHighlightedEventId = newProps.highlightedEventId != this.props.highlightedEventId;
|
||||||
const differentAvoidJump = newProps.eventScrollIntoView && !this.props.eventScrollIntoView;
|
const differentAvoidJump = newProps.eventScrollIntoView && !this.props.eventScrollIntoView;
|
||||||
if (differentEventId || differentHighlightedEventId || differentAvoidJump) {
|
if (differentEventId || differentHighlightedEventId || differentAvoidJump) {
|
||||||
logger.log("TimelinePanel switching to " +
|
logger.log(`TimelinePanel switching to eventId ${newProps.eventId} (was ${this.props.eventId}), ` +
|
||||||
"eventId " + newProps.eventId + " (was " + this.props.eventId + "), " +
|
`scrollIntoView: ${newProps.eventScrollIntoView} (was ${this.props.eventScrollIntoView})`);
|
||||||
"scrollIntoView: " + newProps.eventScrollIntoView + " (was " + this.props.eventScrollIntoView + ")");
|
this.initTimeline(newProps);
|
||||||
return this.initTimeline(newProps);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,17 +113,16 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
|
||||||
this.checkServerCapabilities(this.props.serverConfig);
|
this.checkServerCapabilities(this.props.serverConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
public componentDidUpdate(prevProps: Readonly<IProps>) {
|
||||||
// eslint-disable-next-line
|
if (prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
|
||||||
public UNSAFE_componentWillReceiveProps(newProps: IProps): void {
|
prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl
|
||||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
) {
|
||||||
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
|
||||||
|
|
||||||
// Do a liveliness check on the new URLs
|
// Do a liveliness check on the new URLs
|
||||||
this.checkServerLiveliness(newProps.serverConfig);
|
this.checkServerLiveliness(this.props.serverConfig);
|
||||||
|
|
||||||
// Do capabilities check on new URLs
|
// Do capabilities check on new URLs
|
||||||
this.checkServerCapabilities(newProps.serverConfig);
|
this.checkServerCapabilities(this.props.serverConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkServerLiveliness(serverConfig): Promise<void> {
|
private async checkServerLiveliness(serverConfig): Promise<void> {
|
||||||
|
|
|
@ -144,9 +144,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
public componentDidMount() {
|
||||||
// eslint-disable-next-line
|
|
||||||
UNSAFE_componentWillMount() {
|
|
||||||
this.initLoginLogic(this.props.serverConfig);
|
this.initLoginLogic(this.props.serverConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,14 +152,13 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
this.unmounted = true;
|
this.unmounted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
public componentDidUpdate(prevProps) {
|
||||||
// eslint-disable-next-line
|
if (prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
|
||||||
UNSAFE_componentWillReceiveProps(newProps) {
|
prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl
|
||||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
) {
|
||||||
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
|
||||||
|
|
||||||
// Ensure that we end up actually logging in to the right place
|
// Ensure that we end up actually logging in to the right place
|
||||||
this.initLoginLogic(newProps.serverConfig);
|
this.initLoginLogic(this.props.serverConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isBusy = () => this.state.busy || this.props.busy;
|
isBusy = () => this.state.busy || this.props.busy;
|
||||||
|
@ -369,7 +366,8 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
let isDefaultServer = false;
|
let isDefaultServer = false;
|
||||||
if (this.props.serverConfig.isDefault
|
if (this.props.serverConfig.isDefault
|
||||||
&& hsUrl === this.props.serverConfig.hsUrl
|
&& hsUrl === this.props.serverConfig.hsUrl
|
||||||
&& isUrl === this.props.serverConfig.isUrl) {
|
&& isUrl === this.props.serverConfig.isUrl
|
||||||
|
) {
|
||||||
isDefaultServer = true;
|
isDefaultServer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -165,13 +165,13 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
|
||||||
// eslint-disable-next-line
|
|
||||||
UNSAFE_componentWillReceiveProps(newProps) {
|
|
||||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
|
||||||
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
|
||||||
|
|
||||||
this.replaceClient(newProps.serverConfig);
|
public componentDidUpdate(prevProps) {
|
||||||
|
if (prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
|
||||||
|
prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl
|
||||||
|
) {
|
||||||
|
this.replaceClient(this.props.serverConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async replaceClient(serverConfig: ValidatedServerConfig) {
|
private async replaceClient(serverConfig: ValidatedServerConfig) {
|
||||||
|
|
|
@ -49,17 +49,21 @@ export default function MemberAvatar({
|
||||||
height,
|
height,
|
||||||
resizeMethod = 'crop',
|
resizeMethod = 'crop',
|
||||||
viewUserOnClick,
|
viewUserOnClick,
|
||||||
|
forceHistorical,
|
||||||
|
fallbackUserId,
|
||||||
|
hideTitle,
|
||||||
|
member: propsMember,
|
||||||
...props
|
...props
|
||||||
}: IProps) {
|
}: IProps) {
|
||||||
const card = useContext(CardContext);
|
const card = useContext(CardContext);
|
||||||
|
|
||||||
const member = useRoomMemberProfile({
|
const member = useRoomMemberProfile({
|
||||||
userId: props.member?.userId,
|
userId: propsMember?.userId,
|
||||||
member: props.member,
|
member: propsMember,
|
||||||
forceHistorical: props.forceHistorical,
|
forceHistorical: forceHistorical,
|
||||||
});
|
});
|
||||||
|
|
||||||
const name = member?.name ?? props.fallbackUserId;
|
const name = member?.name ?? fallbackUserId;
|
||||||
let title: string | undefined = props.title;
|
let title: string | undefined = props.title;
|
||||||
let imageUrl: string | undefined;
|
let imageUrl: string | undefined;
|
||||||
if (member?.name) {
|
if (member?.name) {
|
||||||
|
@ -74,7 +78,7 @@ export default function MemberAvatar({
|
||||||
if (!title) {
|
if (!title) {
|
||||||
title = UserIdentifierCustomisations.getDisplayUserIdentifier(
|
title = UserIdentifierCustomisations.getDisplayUserIdentifier(
|
||||||
member?.userId ?? "", { roomId: member?.roomId ?? "" },
|
member?.userId ?? "", { roomId: member?.roomId ?? "" },
|
||||||
) ?? props.fallbackUserId;
|
) ?? fallbackUserId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,13 +88,13 @@ export default function MemberAvatar({
|
||||||
height={height}
|
height={height}
|
||||||
resizeMethod={resizeMethod}
|
resizeMethod={resizeMethod}
|
||||||
name={name ?? ""}
|
name={name ?? ""}
|
||||||
title={props.hideTitle ? undefined : title}
|
title={hideTitle ? undefined : title}
|
||||||
idName={member?.userId ?? props.fallbackUserId}
|
idName={member?.userId ?? fallbackUserId}
|
||||||
url={imageUrl}
|
url={imageUrl}
|
||||||
onClick={viewUserOnClick ? () => {
|
onClick={viewUserOnClick ? () => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: Action.ViewUser,
|
action: Action.ViewUser,
|
||||||
member: props.member,
|
member: propsMember,
|
||||||
push: card.isCard,
|
push: card.isCard,
|
||||||
});
|
});
|
||||||
} : props.onClick}
|
} : props.onClick}
|
||||||
|
|
|
@ -99,7 +99,6 @@ interface IState {
|
||||||
isUserProfileReady: boolean;
|
isUserProfileReady: boolean;
|
||||||
error: Error;
|
error: Error;
|
||||||
menuDisplayed: boolean;
|
menuDisplayed: boolean;
|
||||||
widgetPageTitle: string;
|
|
||||||
requiresClient: boolean;
|
requiresClient: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +228,6 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||||
isUserProfileReady: OwnProfileStore.instance.isProfileInfoFetched,
|
isUserProfileReady: OwnProfileStore.instance.isProfileInfoFetched,
|
||||||
error: null,
|
error: null,
|
||||||
menuDisplayed: false,
|
menuDisplayed: false,
|
||||||
widgetPageTitle: this.props.widgetPageTitle,
|
|
||||||
requiresClient: this.determineInitialRequiresClientState(),
|
requiresClient: this.determineInitialRequiresClientState(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -351,21 +349,13 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
public componentDidUpdate(prevProps: IProps): void {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
if (prevProps.app.url !== this.props.app.url) {
|
||||||
public UNSAFE_componentWillReceiveProps(nextProps: IProps): void { // eslint-disable-line camelcase
|
this.getNewState(this.props);
|
||||||
if (nextProps.app.url !== this.props.app.url) {
|
|
||||||
this.getNewState(nextProps);
|
|
||||||
if (this.state.hasPermissionToLoad) {
|
if (this.state.hasPermissionToLoad) {
|
||||||
this.resetWidget(nextProps);
|
this.resetWidget(this.props);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextProps.widgetPageTitle !== this.props.widgetPageTitle) {
|
|
||||||
this.setState({
|
|
||||||
widgetPageTitle: nextProps.widgetPageTitle,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -474,8 +464,8 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||||
const name = this.formatAppTileName();
|
const name = this.formatAppTileName();
|
||||||
const titleSpacer = <span> - </span>;
|
const titleSpacer = <span> - </span>;
|
||||||
let title = '';
|
let title = '';
|
||||||
if (this.state.widgetPageTitle && this.state.widgetPageTitle !== this.formatAppTileName()) {
|
if (this.props.widgetPageTitle && this.props.widgetPageTitle !== this.formatAppTileName()) {
|
||||||
title = this.state.widgetPageTitle;
|
title = this.props.widgetPageTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -22,6 +22,7 @@ import AccessibleButton, { ButtonEvent } from './AccessibleButton';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||||
|
import { objectHasDiff } from "../../../utils/objects";
|
||||||
|
|
||||||
interface IMenuOptionProps {
|
interface IMenuOptionProps {
|
||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
|
@ -136,20 +137,18 @@ export default class Dropdown extends React.Component<DropdownProps, IState> {
|
||||||
document.addEventListener('click', this.onDocumentClick, false);
|
document.addEventListener('click', this.onDocumentClick, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentDidUpdate(prevProps: Readonly<DropdownProps>) {
|
||||||
document.removeEventListener('click', this.onDocumentClick, false);
|
if (objectHasDiff(this.props, prevProps) && this.props.children?.length) {
|
||||||
|
this.reindexChildren(this.props.children);
|
||||||
|
const firstChild = this.props.children[0];
|
||||||
|
this.setState({
|
||||||
|
highlightedOption: String(firstChild?.key) ?? null,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
componentWillUnmount() {
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line
|
document.removeEventListener('click', this.onDocumentClick, false);
|
||||||
if (!nextProps.children || nextProps.children.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.reindexChildren(nextProps.children);
|
|
||||||
const firstChild = nextProps.children[0];
|
|
||||||
this.setState({
|
|
||||||
highlightedOption: firstChild ? firstChild.key : null,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private reindexChildren(children: ReactElement[]): void {
|
private reindexChildren(children: ReactElement[]): void {
|
||||||
|
|
|
@ -70,11 +70,9 @@ export default class EditableText extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
public componentDidUpdate(prevProps: Readonly<IProps>): void {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention, camelcase
|
if (prevProps.initialValue !== this.props.initialValue) {
|
||||||
public UNSAFE_componentWillReceiveProps(nextProps: IProps): void {
|
this.value = this.props.initialValue;
|
||||||
if (nextProps.initialValue !== this.props.initialValue) {
|
|
||||||
this.value = nextProps.initialValue;
|
|
||||||
if (this.editableDiv.current) {
|
if (this.editableDiv.current) {
|
||||||
this.showPlaceholder(!this.value);
|
this.showPlaceholder(!this.value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { Action } from "../../../dispatcher/actions";
|
||||||
import Tooltip, { Alignment } from './Tooltip';
|
import Tooltip, { Alignment } from './Tooltip';
|
||||||
import RoomAvatar from '../avatars/RoomAvatar';
|
import RoomAvatar from '../avatars/RoomAvatar';
|
||||||
import MemberAvatar from '../avatars/MemberAvatar';
|
import MemberAvatar from '../avatars/MemberAvatar';
|
||||||
|
import { objectHasDiff } from "../../../utils/objects";
|
||||||
|
|
||||||
export enum PillType {
|
export enum PillType {
|
||||||
UserMention = 'TYPE_USER_MENTION',
|
UserMention = 'TYPE_USER_MENTION',
|
||||||
|
@ -86,19 +87,17 @@ export default class Pill extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
private load(): void {
|
||||||
// eslint-disable-next-line camelcase, @typescript-eslint/naming-convention
|
let resourceId: string;
|
||||||
public async UNSAFE_componentWillReceiveProps(nextProps: IProps): Promise<void> {
|
let prefix: string;
|
||||||
let resourceId;
|
|
||||||
let prefix;
|
|
||||||
|
|
||||||
if (nextProps.url) {
|
if (this.props.url) {
|
||||||
if (nextProps.inMessage) {
|
if (this.props.inMessage) {
|
||||||
const parts = parsePermalink(nextProps.url);
|
const parts = parsePermalink(this.props.url);
|
||||||
resourceId = parts.primaryEntityId; // The room/user ID
|
resourceId = parts.primaryEntityId; // The room/user ID
|
||||||
prefix = parts.sigil; // The first character of prefix
|
prefix = parts.sigil; // The first character of prefix
|
||||||
} else {
|
} else {
|
||||||
resourceId = getPrimaryPermalinkEntity(nextProps.url);
|
resourceId = getPrimaryPermalinkEntity(this.props.url);
|
||||||
prefix = resourceId ? resourceId[0] : undefined;
|
prefix = resourceId ? resourceId[0] : undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,15 +108,15 @@ export default class Pill extends React.Component<IProps, IState> {
|
||||||
'!': PillType.RoomMention,
|
'!': PillType.RoomMention,
|
||||||
}[prefix];
|
}[prefix];
|
||||||
|
|
||||||
let member;
|
let member: RoomMember;
|
||||||
let room;
|
let room: Room;
|
||||||
switch (pillType) {
|
switch (pillType) {
|
||||||
case PillType.AtRoomMention: {
|
case PillType.AtRoomMention: {
|
||||||
room = nextProps.room;
|
room = this.props.room;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PillType.UserMention: {
|
case PillType.UserMention: {
|
||||||
const localMember = nextProps.room ? nextProps.room.getMember(resourceId) : undefined;
|
const localMember = this.props.room?.getMember(resourceId);
|
||||||
member = localMember;
|
member = localMember;
|
||||||
if (!localMember) {
|
if (!localMember) {
|
||||||
member = new RoomMember(null, resourceId);
|
member = new RoomMember(null, resourceId);
|
||||||
|
@ -146,9 +145,13 @@ export default class Pill extends React.Component<IProps, IState> {
|
||||||
public componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
this.unmounted = false;
|
this.unmounted = false;
|
||||||
this.matrixClient = MatrixClientPeg.get();
|
this.matrixClient = MatrixClientPeg.get();
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line new-cap
|
public componentDidUpdate(prevProps: Readonly<IProps>) {
|
||||||
this.UNSAFE_componentWillReceiveProps(this.props); // HACK: We shouldn't be calling lifecycle functions ourselves.
|
if (objectHasDiff(this.props, prevProps)) {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount(): void {
|
public componentWillUnmount(): void {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { _t } from '../../../languageHandler';
|
||||||
import Field from "./Field";
|
import Field from "./Field";
|
||||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||||
|
import { objectHasDiff } from "../../../utils/objects";
|
||||||
|
|
||||||
const CUSTOM_VALUE = "SELECT_VALUE_CUSTOM";
|
const CUSTOM_VALUE = "SELECT_VALUE_CUSTOM";
|
||||||
|
|
||||||
|
@ -72,36 +73,35 @@ export default class PowerSelector extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
public componentDidMount() {
|
||||||
// eslint-disable-next-line camelcase, @typescript-eslint/naming-convention
|
this.initStateFromProps();
|
||||||
public UNSAFE_componentWillMount(): void {
|
|
||||||
this.initStateFromProps(this.props);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line camelcase, @typescript-eslint/naming-convention
|
public componentDidUpdate(prevProps: Readonly<IProps>) {
|
||||||
public UNSAFE_componentWillReceiveProps(newProps: IProps): void {
|
if (objectHasDiff(this.props, prevProps)) {
|
||||||
this.initStateFromProps(newProps);
|
this.initStateFromProps();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private initStateFromProps(newProps: IProps): void {
|
private initStateFromProps(): void {
|
||||||
// This needs to be done now because levelRoleMap has translated strings
|
// This needs to be done now because levelRoleMap has translated strings
|
||||||
const levelRoleMap = Roles.levelRoleMap(newProps.usersDefault);
|
const levelRoleMap = Roles.levelRoleMap(this.props.usersDefault);
|
||||||
const options = Object.keys(levelRoleMap).filter(level => {
|
const options = Object.keys(levelRoleMap).filter(level => {
|
||||||
return (
|
return (
|
||||||
level === undefined ||
|
level === undefined ||
|
||||||
parseInt(level) <= newProps.maxValue ||
|
parseInt(level) <= this.props.maxValue ||
|
||||||
parseInt(level) == newProps.value
|
parseInt(level) == this.props.value
|
||||||
);
|
);
|
||||||
}).map(level => parseInt(level));
|
}).map(level => parseInt(level));
|
||||||
|
|
||||||
const isCustom = levelRoleMap[newProps.value] === undefined;
|
const isCustom = levelRoleMap[this.props.value] === undefined;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
levelRoleMap,
|
levelRoleMap,
|
||||||
options,
|
options,
|
||||||
custom: isCustom,
|
custom: isCustom,
|
||||||
customValue: newProps.value,
|
customValue: this.props.value,
|
||||||
selectValue: isCustom ? CUSTOM_VALUE : newProps.value,
|
selectValue: isCustom ? CUSTOM_VALUE : this.props.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ export default class PowerSelector extends React.Component<IProps, IState> {
|
||||||
if (Number.isFinite(this.state.customValue)) {
|
if (Number.isFinite(this.state.customValue)) {
|
||||||
this.props.onChange(this.state.customValue, this.props.powerLevelKey);
|
this.props.onChange(this.state.customValue, this.props.powerLevelKey);
|
||||||
} else {
|
} else {
|
||||||
this.initStateFromProps(this.props); // reset, invalid input
|
this.initStateFromProps(); // reset, invalid input
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -369,12 +369,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Move into constructor
|
|
||||||
// eslint-disable-next-line
|
|
||||||
UNSAFE_componentWillMount() {
|
|
||||||
this.verifyEvent(this.props.mxEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.suppressReadReceiptAnimation = false;
|
this.suppressReadReceiptAnimation = false;
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
@ -405,6 +399,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||||
|
|
||||||
const room = client.getRoom(this.props.mxEvent.getRoomId());
|
const room = client.getRoom(this.props.mxEvent.getRoomId());
|
||||||
room?.on(ThreadEvent.New, this.onNewThread);
|
room?.on(ThreadEvent.New, this.onNewThread);
|
||||||
|
|
||||||
|
this.verifyEvent(this.props.mxEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get supportsThreadNotifications(): boolean {
|
private get supportsThreadNotifications(): boolean {
|
||||||
|
@ -451,16 +447,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||||
this.setState({ thread });
|
this.setState({ thread });
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
|
||||||
// eslint-disable-next-line
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps: EventTileProps) {
|
|
||||||
// re-check the sender verification as outgoing events progress through
|
|
||||||
// the send process.
|
|
||||||
if (nextProps.eventSendStatus !== this.props.eventSendStatus) {
|
|
||||||
this.verifyEvent(nextProps.mxEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: EventTileProps, nextState: IState): boolean {
|
shouldComponentUpdate(nextProps: EventTileProps, nextState: IState): boolean {
|
||||||
if (objectHasDiff(this.state, nextState)) {
|
if (objectHasDiff(this.state, nextState)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -490,12 +476,16 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate(prevProps: Readonly<EventTileProps>) {
|
||||||
// If we're not listening for receipts and expect to be, register a listener.
|
// If we're not listening for receipts and expect to be, register a listener.
|
||||||
if (!this.isListeningForReceipts && (this.shouldShowSentReceipt || this.shouldShowSendingReceipt)) {
|
if (!this.isListeningForReceipts && (this.shouldShowSentReceipt || this.shouldShowSendingReceipt)) {
|
||||||
MatrixClientPeg.get().on(RoomEvent.Receipt, this.onRoomReceipt);
|
MatrixClientPeg.get().on(RoomEvent.Receipt, this.onRoomReceipt);
|
||||||
this.isListeningForReceipts = true;
|
this.isListeningForReceipts = true;
|
||||||
}
|
}
|
||||||
|
// re-check the sender verification as outgoing events progress through the send process.
|
||||||
|
if (prevProps.eventSendStatus !== this.props.eventSendStatus) {
|
||||||
|
this.verifyEvent(this.props.mxEvent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onNewThread = (thread: Thread) => {
|
private onNewThread = (thread: Thread) => {
|
||||||
|
|
|
@ -96,8 +96,7 @@ export default class MemberList extends React.Component<IProps, IState> {
|
||||||
this.showPresence = enablePresenceByHsUrl?.[hsUrl] ?? true;
|
this.showPresence = enablePresenceByHsUrl?.[hsUrl] ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line
|
public componentDidMount() {
|
||||||
UNSAFE_componentWillMount() {
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
this.mounted = true;
|
this.mounted = true;
|
||||||
if (cli.hasLazyLoadMembersEnabled()) {
|
if (cli.hasLazyLoadMembersEnabled()) {
|
||||||
|
@ -121,7 +120,7 @@ export default class MemberList extends React.Component<IProps, IState> {
|
||||||
cli.on(UserEvent.CurrentlyActive, this.onUserPresenceChange);
|
cli.on(UserEvent.CurrentlyActive, this.onUserPresenceChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (cli) {
|
if (cli) {
|
||||||
|
|
|
@ -159,7 +159,9 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: ISendMessageComposerProps, context: React.ContextType<typeof RoomContext>) {
|
constructor(props: ISendMessageComposerProps, context: React.ContextType<typeof RoomContext>) {
|
||||||
super(props);
|
super(props, context);
|
||||||
|
this.context = context; // otherwise React will only set it prior to render due to type def above
|
||||||
|
|
||||||
if (this.props.mxClient.isCryptoEnabled() && this.props.mxClient.isRoomEncrypted(this.props.room.roomId)) {
|
if (this.props.mxClient.isCryptoEnabled() && this.props.mxClient.isRoomEncrypted(this.props.room.roomId)) {
|
||||||
this.prepareToEncrypt = throttle(() => {
|
this.prepareToEncrypt = throttle(() => {
|
||||||
this.props.mxClient.prepareToEncrypt(this.props.room);
|
this.props.mxClient.prepareToEncrypt(this.props.room);
|
||||||
|
@ -167,6 +169,12 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("beforeunload", this.saveStoredEditorState);
|
window.addEventListener("beforeunload", this.saveStoredEditorState);
|
||||||
|
|
||||||
|
const partCreator = new CommandPartCreator(this.props.room, this.props.mxClient);
|
||||||
|
const parts = this.restoreStoredEditorState(partCreator) || [];
|
||||||
|
this.model = new EditorModel(parts, partCreator);
|
||||||
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
|
this.sendHistoryManager = new SendHistoryManager(this.props.room.roomId, 'mx_cider_history_');
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: ISendMessageComposerProps): void {
|
public componentDidUpdate(prevProps: ISendMessageComposerProps): void {
|
||||||
|
@ -456,15 +464,6 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||||
this.saveStoredEditorState();
|
this.saveStoredEditorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Move this to constructor
|
|
||||||
UNSAFE_componentWillMount() { // eslint-disable-line
|
|
||||||
const partCreator = new CommandPartCreator(this.props.room, this.props.mxClient);
|
|
||||||
const parts = this.restoreStoredEditorState(partCreator) || [];
|
|
||||||
this.model = new EditorModel(parts, partCreator);
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
|
||||||
this.sendHistoryManager = new SendHistoryManager(this.props.room.roomId, 'mx_cider_history_');
|
|
||||||
}
|
|
||||||
|
|
||||||
private get editorStateKey() {
|
private get editorStateKey() {
|
||||||
let key = `mx_cider_state_${this.props.room.roomId}`;
|
let key = `mx_cider_state_${this.props.room.roomId}`;
|
||||||
if (this.props.relation?.rel_type === THREAD_RELATION_TYPE.name) {
|
if (this.props.relation?.rel_type === THREAD_RELATION_TYPE.name) {
|
||||||
|
|
|
@ -67,12 +67,12 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
public componentDidUpdate(prevProps: Readonly<IEmailAddressProps>) {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention, camelcase
|
if (this.props.email !== prevProps.email) {
|
||||||
public UNSAFE_componentWillReceiveProps(nextProps: IEmailAddressProps): void {
|
const { bound } = this.props.email;
|
||||||
const { bound } = nextProps.email;
|
|
||||||
this.setState({ bound });
|
this.setState({ bound });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async changeBinding({ bind, label, errorTitle }): Promise<void> {
|
private async changeBinding({ bind, label, errorTitle }): Promise<void> {
|
||||||
if (!(await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind())) {
|
if (!(await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind())) {
|
||||||
|
|
|
@ -63,12 +63,12 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
public componentDidUpdate(prevProps: Readonly<IPhoneNumberProps>) {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention, camelcase
|
if (this.props.msisdn !== prevProps.msisdn) {
|
||||||
public UNSAFE_componentWillReceiveProps(nextProps: IPhoneNumberProps): void {
|
const { bound } = this.props.msisdn;
|
||||||
const { bound } = nextProps.msisdn;
|
|
||||||
this.setState({ bound });
|
this.setState({ bound });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async changeBinding({ bind, label, errorTitle }): Promise<void> {
|
private async changeBinding({ bind, label, errorTitle }): Promise<void> {
|
||||||
if (!(await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind())) {
|
if (!(await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind())) {
|
||||||
|
|
|
@ -55,20 +55,16 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||||
|
|
||||||
this.roomProps = EchoChamber.forRoom(context.getRoom(this.props.roomId));
|
this.roomProps = EchoChamber.forRoom(context.getRoom(this.props.roomId));
|
||||||
|
|
||||||
this.state = {
|
let currentSound = "default";
|
||||||
currentSound: "default",
|
const soundData = Notifier.getSoundForRoom(this.props.roomId);
|
||||||
uploadedFile: null,
|
if (soundData) {
|
||||||
};
|
currentSound = soundData.name || soundData.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
this.state = {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention, camelcase
|
currentSound,
|
||||||
public UNSAFE_componentWillMount(): void {
|
uploadedFile: null,
|
||||||
const soundData = Notifier.getSoundForRoom(this.props.roomId);
|
};
|
||||||
if (!soundData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setState({ currentSound: soundData.name || soundData.url });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private triggerUploader = async (e: React.MouseEvent): Promise<void> => {
|
private triggerUploader = async (e: React.MouseEvent): Promise<void> => {
|
||||||
|
|
|
@ -107,25 +107,8 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
};
|
};
|
||||||
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Move this to constructor
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention, camelcase
|
|
||||||
public async UNSAFE_componentWillMount(): Promise<void> {
|
|
||||||
const cli = MatrixClientPeg.get();
|
|
||||||
|
|
||||||
const serverSupportsSeparateAddAndBind = await cli.doesServerSupportSeparateAddAndBind();
|
|
||||||
|
|
||||||
const capabilities = await cli.getCapabilities(); // this is cached
|
|
||||||
const changePasswordCap = capabilities['m.change_password'];
|
|
||||||
|
|
||||||
// You can change your password so long as the capability isn't explicitly disabled. The implicit
|
|
||||||
// behaviour is you can change your password when the capability is missing or has not-false as
|
|
||||||
// the enabled flag value.
|
|
||||||
const canChangePassword = !changePasswordCap || changePasswordCap['enabled'] !== false;
|
|
||||||
|
|
||||||
this.setState({ serverSupportsSeparateAddAndBind, canChangePassword });
|
|
||||||
|
|
||||||
|
this.getCapabilities();
|
||||||
this.getThreepidState();
|
this.getThreepidState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +146,22 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
this.setState({ msisdns });
|
this.setState({ msisdns });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private async getCapabilities(): Promise<void> {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
const serverSupportsSeparateAddAndBind = await cli.doesServerSupportSeparateAddAndBind();
|
||||||
|
|
||||||
|
const capabilities = await cli.getCapabilities(); // this is cached
|
||||||
|
const changePasswordCap = capabilities['m.change_password'];
|
||||||
|
|
||||||
|
// You can change your password so long as the capability isn't explicitly disabled. The implicit
|
||||||
|
// behaviour is you can change your password when the capability is missing or has not-false as
|
||||||
|
// the enabled flag value.
|
||||||
|
const canChangePassword = !changePasswordCap || changePasswordCap['enabled'] !== false;
|
||||||
|
|
||||||
|
this.setState({ serverSupportsSeparateAddAndBind, canChangePassword });
|
||||||
|
}
|
||||||
|
|
||||||
private async getThreepidState(): Promise<void> {
|
private async getThreepidState(): Promise<void> {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
@ -171,7 +170,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
|
|
||||||
// Need to get 3PIDs generally for Account section and possibly also for
|
// Need to get 3PIDs generally for Account section and possibly also for
|
||||||
// Discovery (assuming we have an IS and terms are agreed).
|
// Discovery (assuming we have an IS and terms are agreed).
|
||||||
let threepids = [];
|
let threepids: IThreepid[] = [];
|
||||||
try {
|
try {
|
||||||
threepids = await getThreepidsWithBindStatus(cli);
|
threepids = await getThreepidsWithBindStatus(cli);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
102
test/components/structures/auth/ForgotPassword-test.tsx
Normal file
102
test/components/structures/auth/ForgotPassword-test.tsx
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { fireEvent, render, screen, waitFor, waitForElementToBeRemoved } from "@testing-library/react";
|
||||||
|
import { createClient, MatrixClient } from 'matrix-js-sdk/src/matrix';
|
||||||
|
import { mocked } from 'jest-mock';
|
||||||
|
import fetchMock from "fetch-mock-jest";
|
||||||
|
|
||||||
|
import SdkConfig, { DEFAULTS } from '../../../../src/SdkConfig';
|
||||||
|
import { mkServerConfig, mockPlatformPeg, unmockPlatformPeg } from "../../../test-utils";
|
||||||
|
import ForgotPassword from "../../../../src/components/structures/auth/ForgotPassword";
|
||||||
|
import PasswordReset from "../../../../src/PasswordReset";
|
||||||
|
|
||||||
|
jest.mock('matrix-js-sdk/src/matrix');
|
||||||
|
jest.mock("../../../../src/PasswordReset", () => (jest.fn().mockReturnValue({
|
||||||
|
resetPassword: jest.fn().mockReturnValue(new Promise(() => {})),
|
||||||
|
})));
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
describe('<ForgotPassword/>', () => {
|
||||||
|
const mockClient = mocked({
|
||||||
|
doesServerSupportLogoutDevices: jest.fn().mockResolvedValue(true),
|
||||||
|
} as unknown as MatrixClient);
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
SdkConfig.put({
|
||||||
|
...DEFAULTS,
|
||||||
|
disable_custom_urls: true,
|
||||||
|
});
|
||||||
|
mocked(createClient).mockImplementation(opts => {
|
||||||
|
mockClient.idBaseUrl = opts.idBaseUrl;
|
||||||
|
mockClient.baseUrl = opts.baseUrl;
|
||||||
|
return mockClient;
|
||||||
|
});
|
||||||
|
fetchMock.get("https://matrix.org/_matrix/client/versions", {
|
||||||
|
unstable_features: {},
|
||||||
|
versions: [],
|
||||||
|
});
|
||||||
|
mockPlatformPeg({
|
||||||
|
startSingleSignOn: jest.fn(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
fetchMock.restore();
|
||||||
|
SdkConfig.unset(); // we touch the config, so clean up
|
||||||
|
unmockPlatformPeg();
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
defaultDeviceDisplayName: 'test-device-display-name',
|
||||||
|
onServerConfigChange: jest.fn(),
|
||||||
|
onLoginClick: jest.fn(),
|
||||||
|
onComplete: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
function getRawComponent(hsUrl = "https://matrix.org", isUrl = "https://vector.im") {
|
||||||
|
return <ForgotPassword
|
||||||
|
{...defaultProps}
|
||||||
|
serverConfig={mkServerConfig(hsUrl, isUrl)}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should handle serverConfig updates correctly", async () => {
|
||||||
|
const { container, rerender } = render(getRawComponent());
|
||||||
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||||
|
|
||||||
|
fetchMock.get("https://server2/_matrix/client/versions", {
|
||||||
|
unstable_features: {},
|
||||||
|
versions: [],
|
||||||
|
});
|
||||||
|
fetchMock.get("https://vector.im/_matrix/identity/api/v1", {});
|
||||||
|
rerender(getRawComponent("https://server2"));
|
||||||
|
|
||||||
|
const email = "email@addy.com";
|
||||||
|
const pass = "thisIsAT0tallySecurePassword";
|
||||||
|
|
||||||
|
fireEvent.change(container.querySelector('[label=Email]'), { target: { value: email } });
|
||||||
|
fireEvent.change(container.querySelector('[label="New Password"]'), { target: { value: pass } });
|
||||||
|
fireEvent.change(container.querySelector('[label=Confirm]'), { target: { value: pass } });
|
||||||
|
fireEvent.change(container.querySelector('[type=checkbox]')); // this allows us to bypass the modal
|
||||||
|
fireEvent.submit(container.querySelector("form"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
return expect(PasswordReset).toHaveBeenCalledWith("https://server2", expect.anything());
|
||||||
|
}, { timeout: 5000 });
|
||||||
|
});
|
||||||
|
});
|
|
@ -14,25 +14,24 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import { fireEvent, render, screen, waitForElementToBeRemoved } from "@testing-library/react";
|
||||||
import ReactTestUtils from 'react-dom/test-utils';
|
import { mocked, MockedObject } from 'jest-mock';
|
||||||
import { mocked } from 'jest-mock';
|
|
||||||
import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
|
import fetchMock from "fetch-mock-jest";
|
||||||
|
|
||||||
import SdkConfig from '../../../../src/SdkConfig';
|
import SdkConfig from '../../../../src/SdkConfig';
|
||||||
import { mkServerConfig } from "../../../test-utils";
|
import { mkServerConfig, mockPlatformPeg, unmockPlatformPeg } from "../../../test-utils";
|
||||||
import Login from "../../../../src/components/structures/auth/Login";
|
import Login from "../../../../src/components/structures/auth/Login";
|
||||||
import PasswordLogin from "../../../../src/components/views/auth/PasswordLogin";
|
import BasePlatform from "../../../../src/BasePlatform";
|
||||||
|
|
||||||
jest.mock("matrix-js-sdk/src/matrix");
|
jest.mock("matrix-js-sdk/src/matrix");
|
||||||
|
|
||||||
const flushPromises = async () => await new Promise(process.nextTick);
|
|
||||||
|
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
|
|
||||||
describe('Login', function() {
|
describe('Login', function() {
|
||||||
let parentDiv;
|
let platform: MockedObject<BasePlatform>;
|
||||||
|
|
||||||
const mockClient = mocked({
|
const mockClient = mocked({
|
||||||
login: jest.fn().mockResolvedValue({}),
|
login: jest.fn().mockResolvedValue({}),
|
||||||
loginFlows: jest.fn(),
|
loginFlows: jest.fn(),
|
||||||
|
@ -45,25 +44,37 @@ describe('Login', function() {
|
||||||
});
|
});
|
||||||
mockClient.login.mockClear().mockResolvedValue({});
|
mockClient.login.mockClear().mockResolvedValue({});
|
||||||
mockClient.loginFlows.mockClear().mockResolvedValue({ flows: [{ type: "m.login.password" }] });
|
mockClient.loginFlows.mockClear().mockResolvedValue({ flows: [{ type: "m.login.password" }] });
|
||||||
mocked(createClient).mockReturnValue(mockClient);
|
mocked(createClient).mockImplementation(opts => {
|
||||||
|
mockClient.idBaseUrl = opts.idBaseUrl;
|
||||||
parentDiv = document.createElement('div');
|
mockClient.baseUrl = opts.baseUrl;
|
||||||
document.body.appendChild(parentDiv);
|
return mockClient;
|
||||||
|
});
|
||||||
|
fetchMock.get("https://matrix.org/_matrix/client/versions", {
|
||||||
|
unstable_features: {},
|
||||||
|
versions: [],
|
||||||
|
});
|
||||||
|
platform = mockPlatformPeg({
|
||||||
|
startSingleSignOn: jest.fn(),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
ReactDOM.unmountComponentAtNode(parentDiv);
|
fetchMock.restore();
|
||||||
parentDiv.remove();
|
|
||||||
SdkConfig.unset(); // we touch the config, so clean up
|
SdkConfig.unset(); // we touch the config, so clean up
|
||||||
|
unmockPlatformPeg();
|
||||||
});
|
});
|
||||||
|
|
||||||
function render() {
|
function getRawComponent(hsUrl = "https://matrix.org", isUrl = "https://vector.im") {
|
||||||
return ReactDOM.render(<Login
|
return <Login
|
||||||
serverConfig={mkServerConfig("https://matrix.org", "https://vector.im")}
|
serverConfig={mkServerConfig(hsUrl, isUrl)}
|
||||||
onLoggedIn={() => { }}
|
onLoggedIn={() => { }}
|
||||||
onRegisterClick={() => { }}
|
onRegisterClick={() => { }}
|
||||||
onServerConfigChange={() => { }}
|
onServerConfigChange={() => { }}
|
||||||
/>, parentDiv) as unknown as Component<any, any, any>;
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getComponent(hsUrl?: string, isUrl?: string) {
|
||||||
|
return render(getRawComponent(hsUrl, isUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should show form with change server link', async () => {
|
it('should show form with change server link', async () => {
|
||||||
|
@ -71,54 +82,41 @@ describe('Login', function() {
|
||||||
brand: "test-brand",
|
brand: "test-brand",
|
||||||
disable_custom_urls: false,
|
disable_custom_urls: false,
|
||||||
});
|
});
|
||||||
const root = render();
|
const { container } = getComponent();
|
||||||
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||||
|
|
||||||
await flushPromises();
|
expect(container.querySelector("form")).toBeTruthy();
|
||||||
|
|
||||||
const form = ReactTestUtils.findRenderedComponentWithType(
|
expect(container.querySelector(".mx_ServerPicker_change")).toBeTruthy();
|
||||||
root,
|
|
||||||
PasswordLogin,
|
|
||||||
);
|
|
||||||
expect(form).toBeTruthy();
|
|
||||||
|
|
||||||
const changeServerLink = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_ServerPicker_change');
|
|
||||||
expect(changeServerLink).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show form without change server link when custom URLs disabled', async () => {
|
it('should show form without change server link when custom URLs disabled', async () => {
|
||||||
const root = render();
|
const { container } = getComponent();
|
||||||
await flushPromises();
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||||
|
|
||||||
const form = ReactTestUtils.findRenderedComponentWithType(
|
expect(container.querySelector("form")).toBeTruthy();
|
||||||
root,
|
expect(container.querySelectorAll(".mx_ServerPicker_change")).toHaveLength(0);
|
||||||
PasswordLogin,
|
|
||||||
);
|
|
||||||
expect(form).toBeTruthy();
|
|
||||||
|
|
||||||
const changeServerLinks = ReactTestUtils.scryRenderedDOMComponentsWithClass(root, 'mx_ServerPicker_change');
|
|
||||||
expect(changeServerLinks).toHaveLength(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show SSO button if that flow is available", async () => {
|
it("should show SSO button if that flow is available", async () => {
|
||||||
mockClient.loginFlows.mockResolvedValue({ flows: [{ type: "m.login.sso" }] });
|
mockClient.loginFlows.mockResolvedValue({ flows: [{ type: "m.login.sso" }] });
|
||||||
|
|
||||||
const root = render();
|
const { container } = getComponent();
|
||||||
await flushPromises();
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||||
|
|
||||||
const ssoButton = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_SSOButton");
|
const ssoButton = container.querySelector(".mx_SSOButton");
|
||||||
expect(ssoButton).toBeTruthy();
|
expect(ssoButton).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show both SSO button and username+password if both are available", async () => {
|
it("should show both SSO button and username+password if both are available", async () => {
|
||||||
mockClient.loginFlows.mockResolvedValue({ flows: [{ type: "m.login.password" }, { type: "m.login.sso" }] });
|
mockClient.loginFlows.mockResolvedValue({ flows: [{ type: "m.login.password" }, { type: "m.login.sso" }] });
|
||||||
|
|
||||||
const root = render();
|
const { container } = getComponent();
|
||||||
await flushPromises();
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||||
|
|
||||||
const form = ReactTestUtils.findRenderedComponentWithType(root, PasswordLogin);
|
expect(container.querySelector("form")).toBeTruthy();
|
||||||
expect(form).toBeTruthy();
|
|
||||||
|
|
||||||
const ssoButton = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_SSOButton");
|
const ssoButton = container.querySelector(".mx_SSOButton");
|
||||||
expect(ssoButton).toBeTruthy();
|
expect(ssoButton).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -139,11 +137,10 @@ describe('Login', function() {
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
const root = render();
|
const { container } = getComponent();
|
||||||
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||||
|
|
||||||
await flushPromises();
|
const ssoButtons = container.querySelectorAll(".mx_SSOButton");
|
||||||
|
|
||||||
const ssoButtons = ReactTestUtils.scryRenderedDOMComponentsWithClass(root, "mx_SSOButton");
|
|
||||||
expect(ssoButtons.length).toBe(3);
|
expect(ssoButtons.length).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -154,11 +151,33 @@ describe('Login', function() {
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
const root = render();
|
const { container } = getComponent();
|
||||||
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||||
|
|
||||||
await flushPromises();
|
const ssoButtons = container.querySelectorAll(".mx_SSOButton");
|
||||||
|
|
||||||
const ssoButtons = ReactTestUtils.scryRenderedDOMComponentsWithClass(root, "mx_SSOButton");
|
|
||||||
expect(ssoButtons.length).toBe(1);
|
expect(ssoButtons.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should handle serverConfig updates correctly", async () => {
|
||||||
|
mockClient.loginFlows.mockResolvedValue({
|
||||||
|
flows: [{
|
||||||
|
"type": "m.login.sso",
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { container, rerender } = render(getRawComponent());
|
||||||
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector(".mx_SSOButton"));
|
||||||
|
expect(platform.startSingleSignOn.mock.calls[0][0].baseUrl).toBe("https://matrix.org");
|
||||||
|
|
||||||
|
fetchMock.get("https://server2/_matrix/client/versions", {
|
||||||
|
unstable_features: {},
|
||||||
|
versions: [],
|
||||||
|
});
|
||||||
|
rerender(getRawComponent("https://server2"));
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector(".mx_SSOButton"));
|
||||||
|
expect(platform.startSingleSignOn.mock.calls[1][0].baseUrl).toBe("https://server2");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,93 +16,115 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import { fireEvent, render, screen, waitForElementToBeRemoved } from "@testing-library/react";
|
||||||
import ReactTestUtils from 'react-dom/test-utils';
|
import { createClient, MatrixClient } from 'matrix-js-sdk/src/matrix';
|
||||||
import { createClient } from 'matrix-js-sdk/src/matrix';
|
import { MatrixError } from 'matrix-js-sdk/src/http-api/errors';
|
||||||
import { mocked } from 'jest-mock';
|
import { mocked } from 'jest-mock';
|
||||||
|
import fetchMock from "fetch-mock-jest";
|
||||||
|
|
||||||
import SdkConfig, { DEFAULTS } from '../../../../src/SdkConfig';
|
import SdkConfig, { DEFAULTS } from '../../../../src/SdkConfig';
|
||||||
import { createTestClient, mkServerConfig } from "../../../test-utils";
|
import { mkServerConfig, mockPlatformPeg, unmockPlatformPeg } from "../../../test-utils";
|
||||||
import Registration from "../../../../src/components/structures/auth/Registration";
|
import Registration from "../../../../src/components/structures/auth/Registration";
|
||||||
import RegistrationForm from "../../../../src/components/views/auth/RegistrationForm";
|
|
||||||
|
|
||||||
jest.mock('matrix-js-sdk/src/matrix');
|
jest.mock('matrix-js-sdk/src/matrix');
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
|
||||||
describe('Registration', function() {
|
describe('Registration', function() {
|
||||||
let parentDiv;
|
const registerRequest = jest.fn();
|
||||||
|
const mockClient = mocked({
|
||||||
|
registerRequest,
|
||||||
|
loginFlows: jest.fn(),
|
||||||
|
} as unknown as MatrixClient);
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
SdkConfig.put({
|
SdkConfig.put({
|
||||||
...DEFAULTS,
|
...DEFAULTS,
|
||||||
disable_custom_urls: true,
|
disable_custom_urls: true,
|
||||||
});
|
});
|
||||||
parentDiv = document.createElement('div');
|
mockClient.registerRequest.mockRejectedValueOnce(new MatrixError({
|
||||||
document.body.appendChild(parentDiv);
|
flows: [{ stages: [] }],
|
||||||
mocked(createClient).mockImplementation(() => createTestClient());
|
}, 401));
|
||||||
|
mockClient.loginFlows.mockClear().mockResolvedValue({ flows: [{ type: "m.login.password" }] });
|
||||||
|
mocked(createClient).mockImplementation(opts => {
|
||||||
|
mockClient.idBaseUrl = opts.idBaseUrl;
|
||||||
|
mockClient.baseUrl = opts.baseUrl;
|
||||||
|
return mockClient;
|
||||||
|
});
|
||||||
|
fetchMock.get("https://matrix.org/_matrix/client/versions", {
|
||||||
|
unstable_features: {},
|
||||||
|
versions: [],
|
||||||
|
});
|
||||||
|
mockPlatformPeg({
|
||||||
|
startSingleSignOn: jest.fn(),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
ReactDOM.unmountComponentAtNode(parentDiv);
|
fetchMock.restore();
|
||||||
parentDiv.remove();
|
|
||||||
SdkConfig.unset(); // we touch the config, so clean up
|
SdkConfig.unset(); // we touch the config, so clean up
|
||||||
|
unmockPlatformPeg();
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
defaultDeviceDisplayName: 'test-device-display-name',
|
defaultDeviceDisplayName: 'test-device-display-name',
|
||||||
serverConfig: mkServerConfig("https://matrix.org", "https://vector.im"),
|
|
||||||
makeRegistrationUrl: jest.fn(),
|
makeRegistrationUrl: jest.fn(),
|
||||||
onLoggedIn: jest.fn(),
|
onLoggedIn: jest.fn(),
|
||||||
onLoginClick: jest.fn(),
|
onLoginClick: jest.fn(),
|
||||||
onServerConfigChange: jest.fn(),
|
onServerConfigChange: jest.fn(),
|
||||||
};
|
};
|
||||||
function render() {
|
|
||||||
return ReactDOM.render<typeof Registration>(<Registration
|
function getRawComponent(hsUrl = "https://matrix.org", isUrl = "https://vector.im") {
|
||||||
|
return <Registration
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
/>, parentDiv) as React.Component<typeof Registration>;
|
serverConfig={mkServerConfig(hsUrl, isUrl)}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getComponent(hsUrl?: string, isUrl?: string) {
|
||||||
|
return render(getRawComponent(hsUrl, isUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should show server picker', async function() {
|
it('should show server picker', async function() {
|
||||||
const root = render();
|
const { container } = getComponent();
|
||||||
const selector = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_ServerPicker");
|
expect(container.querySelector(".mx_ServerPicker")).toBeTruthy();
|
||||||
expect(selector).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show form when custom URLs disabled', async function() {
|
it('should show form when custom URLs disabled', async function() {
|
||||||
const root = render();
|
const { container } = getComponent();
|
||||||
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||||
// Set non-empty flows & matrixClient to get past the loading spinner
|
expect(container.querySelector("form")).toBeTruthy();
|
||||||
root.setState({
|
|
||||||
flows: [{
|
|
||||||
stages: [],
|
|
||||||
}],
|
|
||||||
matrixClient: {},
|
|
||||||
busy: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = ReactTestUtils.findRenderedComponentWithType(
|
|
||||||
root,
|
|
||||||
RegistrationForm,
|
|
||||||
);
|
|
||||||
expect(form).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show SSO options if those are available", async () => {
|
it("should show SSO options if those are available", async () => {
|
||||||
const root = render();
|
mockClient.loginFlows.mockClear().mockResolvedValue({ flows: [{ type: "m.login.sso" }] });
|
||||||
|
const { container } = getComponent();
|
||||||
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||||
|
|
||||||
// Set non-empty flows & matrixClient to get past the loading spinner
|
const ssoButton = container.querySelector(".mx_SSOButton");
|
||||||
root.setState({
|
|
||||||
flows: [{
|
|
||||||
stages: [],
|
|
||||||
}],
|
|
||||||
ssoFlow: {
|
|
||||||
type: "m.login.sso",
|
|
||||||
},
|
|
||||||
matrixClient: {},
|
|
||||||
busy: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const ssoButton = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_SSOButton");
|
|
||||||
expect(ssoButton).toBeTruthy();
|
expect(ssoButton).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should handle serverConfig updates correctly", async () => {
|
||||||
|
mockClient.loginFlows.mockResolvedValue({
|
||||||
|
flows: [{
|
||||||
|
"type": "m.login.sso",
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { container, rerender } = render(getRawComponent());
|
||||||
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector(".mx_SSOButton"));
|
||||||
|
expect(registerRequest.mock.instances[0].baseUrl).toBe("https://matrix.org");
|
||||||
|
|
||||||
|
fetchMock.get("https://server2/_matrix/client/versions", {
|
||||||
|
unstable_features: {},
|
||||||
|
versions: [],
|
||||||
|
});
|
||||||
|
rerender(getRawComponent("https://server2"));
|
||||||
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector(".mx_SSOButton"));
|
||||||
|
expect(registerRequest.mock.instances[1].baseUrl).toBe("https://server2");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -189,30 +189,7 @@ exports[`<BeaconMarker /> renders marker when beacon has location 1`] = `
|
||||||
>
|
>
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
height={36}
|
height={36}
|
||||||
hideTitle={false}
|
|
||||||
idName="@alice:server"
|
idName="@alice:server"
|
||||||
member={
|
|
||||||
RoomMember {
|
|
||||||
"_events": {},
|
|
||||||
"_eventsCount": 0,
|
|
||||||
"_isOutOfBand": false,
|
|
||||||
"_maxListeners": undefined,
|
|
||||||
"disambiguate": false,
|
|
||||||
"events": {},
|
|
||||||
"membership": undefined,
|
|
||||||
"modified": 1647270879403,
|
|
||||||
"name": "@alice:server",
|
|
||||||
"powerLevel": 0,
|
|
||||||
"powerLevelNorm": 0,
|
|
||||||
"rawDisplayName": "@alice:server",
|
|
||||||
"requestedProfileInfo": false,
|
|
||||||
"roomId": "!room:server",
|
|
||||||
"typing": false,
|
|
||||||
"user": undefined,
|
|
||||||
"userId": "@alice:server",
|
|
||||||
Symbol(kCapture): false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name="@alice:server"
|
name="@alice:server"
|
||||||
resizeMethod="crop"
|
resizeMethod="crop"
|
||||||
title="@alice:server"
|
title="@alice:server"
|
||||||
|
@ -220,29 +197,6 @@ exports[`<BeaconMarker /> renders marker when beacon has location 1`] = `
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="mx_BaseAvatar"
|
className="mx_BaseAvatar"
|
||||||
hideTitle={false}
|
|
||||||
member={
|
|
||||||
RoomMember {
|
|
||||||
"_events": {},
|
|
||||||
"_eventsCount": 0,
|
|
||||||
"_isOutOfBand": false,
|
|
||||||
"_maxListeners": undefined,
|
|
||||||
"disambiguate": false,
|
|
||||||
"events": {},
|
|
||||||
"membership": undefined,
|
|
||||||
"modified": 1647270879403,
|
|
||||||
"name": "@alice:server",
|
|
||||||
"powerLevel": 0,
|
|
||||||
"powerLevelNorm": 0,
|
|
||||||
"rawDisplayName": "@alice:server",
|
|
||||||
"requestedProfileInfo": false,
|
|
||||||
"roomId": "!room:server",
|
|
||||||
"typing": false,
|
|
||||||
"user": undefined,
|
|
||||||
"userId": "@alice:server",
|
|
||||||
Symbol(kCapture): false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
role="presentation"
|
role="presentation"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|
|
@ -59,4 +59,20 @@ describe('<PowerSelector />', () => {
|
||||||
await screen.findByDisplayValue(40);
|
await screen.findByDisplayValue(40);
|
||||||
expect(fn).toHaveBeenCalledWith(40, "key");
|
expect(fn).toHaveBeenCalledWith(40, "key");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should reset when props get changed", async () => {
|
||||||
|
const fn = jest.fn();
|
||||||
|
const { rerender } = render(<PowerSelector value={50} maxValue={100} usersDefault={0} onChange={fn} />);
|
||||||
|
|
||||||
|
const select = screen.getByLabelText("Power level");
|
||||||
|
fireEvent.change(select, { target: { value: "SELECT_VALUE_CUSTOM" } });
|
||||||
|
|
||||||
|
rerender(<PowerSelector value={51} maxValue={100} usersDefault={0} onChange={fn} />);
|
||||||
|
await screen.findByDisplayValue(51);
|
||||||
|
|
||||||
|
rerender(<PowerSelector value={50} maxValue={100} usersDefault={0} onChange={fn} />);
|
||||||
|
const option = await screen.findByText<HTMLOptionElement>("Moderator");
|
||||||
|
expect(option.selected).toBeTruthy();
|
||||||
|
expect(fn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,4 +14,4 @@ exports[`<TextualBody /> renders formatted m.text correctly pills do not appear
|
||||||
</span>"
|
</span>"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<TextualBody /> renders formatted m.text correctly pills get injected correctly into the DOM 1`] = `"<span class="mx_EventTile_body markdown-body" dir="auto">Hey <span><bdi><a class="mx_Pill mx_UserPill"><img class="mx_BaseAvatar mx_BaseAvatar_image" src="mxc://avatar.url/image.png" style="width: 16px; height: 16px;" alt="" data-testid="avatar-img" member="[object Object]" aria-hidden="true"><span class="mx_Pill_linkText">Member</span></a></bdi></span></span>"`;
|
exports[`<TextualBody /> renders formatted m.text correctly pills get injected correctly into the DOM 1`] = `"<span class="mx_EventTile_body markdown-body" dir="auto">Hey <span><bdi><a class="mx_Pill mx_UserPill"><img class="mx_BaseAvatar mx_BaseAvatar_image" src="mxc://avatar.url/image.png" style="width: 16px; height: 16px;" alt="" data-testid="avatar-img" aria-hidden="true"><span class="mx_Pill_linkText">Member</span></a></bdi></span></span>"`;
|
||||||
|
|
|
@ -38,7 +38,7 @@ import dis from "../../../../src/dispatcher/dispatcher";
|
||||||
import { Action } from "../../../../src/dispatcher/actions";
|
import { Action } from "../../../../src/dispatcher/actions";
|
||||||
import { SendMessageComposer } from "../../../../src/components/views/rooms/SendMessageComposer";
|
import { SendMessageComposer } from "../../../../src/components/views/rooms/SendMessageComposer";
|
||||||
import { E2EStatus } from "../../../../src/utils/ShieldUtils";
|
import { E2EStatus } from "../../../../src/utils/ShieldUtils";
|
||||||
import { addTextToComposer } from "../../../test-utils/composer";
|
import { addTextToComposerEnzyme } from "../../../test-utils/composer";
|
||||||
import UIStore, { UI_EVENTS } from "../../../../src/stores/UIStore";
|
import UIStore, { UI_EVENTS } from "../../../../src/stores/UIStore";
|
||||||
import { SendWysiwygComposer } from "../../../../src/components/views/rooms/wysiwyg_composer";
|
import { SendWysiwygComposer } from "../../../../src/components/views/rooms/wysiwyg_composer";
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ describe("MessageComposer", () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = wrapAndRender({ room });
|
wrapper = wrapAndRender({ room });
|
||||||
addTextToComposer(wrapper, "Hello");
|
addTextToComposerEnzyme(wrapper, "Hello");
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -15,17 +15,13 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { act } from "react-dom/test-utils";
|
import { fireEvent, render, waitFor } from "@testing-library/react";
|
||||||
import { sleep } from "matrix-js-sdk/src/utils";
|
import { MatrixClient, MsgType } from "matrix-js-sdk/src/matrix";
|
||||||
import { MatrixClient, MsgType, RelationType } from "matrix-js-sdk/src/matrix";
|
|
||||||
// eslint-disable-next-line deprecate/import
|
|
||||||
import { mount } from 'enzyme';
|
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
|
|
||||||
import SendMessageComposer, {
|
import SendMessageComposer, {
|
||||||
createMessageContent,
|
createMessageContent,
|
||||||
isQuickReaction,
|
isQuickReaction,
|
||||||
SendMessageComposer as SendMessageComposerClass,
|
|
||||||
} from "../../../../src/components/views/rooms/SendMessageComposer";
|
} from "../../../../src/components/views/rooms/SendMessageComposer";
|
||||||
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||||
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
|
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
|
||||||
|
@ -46,15 +42,6 @@ jest.mock("../../../../src/utils/local-room", () => ({
|
||||||
doMaybeLocalRoomAction: jest.fn(),
|
doMaybeLocalRoomAction: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const WrapWithProviders: React.FC<{
|
|
||||||
roomContext: IRoomState;
|
|
||||||
client: MatrixClient;
|
|
||||||
}> = ({ children, roomContext, client }) => <MatrixClientContext.Provider value={client}>
|
|
||||||
<RoomContext.Provider value={roomContext}>
|
|
||||||
{ children }
|
|
||||||
</RoomContext.Provider>
|
|
||||||
</MatrixClientContext.Provider>;
|
|
||||||
|
|
||||||
describe('<SendMessageComposer/>', () => {
|
describe('<SendMessageComposer/>', () => {
|
||||||
const defaultRoomContext: IRoomState = {
|
const defaultRoomContext: IRoomState = {
|
||||||
roomLoading: true,
|
roomLoading: true,
|
||||||
|
@ -194,44 +181,48 @@ describe('<SendMessageComposer/>', () => {
|
||||||
toggleStickerPickerOpen: jest.fn(),
|
toggleStickerPickerOpen: jest.fn(),
|
||||||
permalinkCreator: new RoomPermalinkCreator(mockRoom),
|
permalinkCreator: new RoomPermalinkCreator(mockRoom),
|
||||||
};
|
};
|
||||||
|
const getRawComponent = (props = {}, roomContext = defaultRoomContext, client = mockClient) => (
|
||||||
|
<MatrixClientContext.Provider value={client}>
|
||||||
|
<RoomContext.Provider value={roomContext}>
|
||||||
|
<SendMessageComposer {...defaultProps} {...props} />
|
||||||
|
</RoomContext.Provider>
|
||||||
|
</MatrixClientContext.Provider>
|
||||||
|
);
|
||||||
const getComponent = (props = {}, roomContext = defaultRoomContext, client = mockClient) => {
|
const getComponent = (props = {}, roomContext = defaultRoomContext, client = mockClient) => {
|
||||||
return mount(<SendMessageComposer {...defaultProps} {...props} />, {
|
return render(getRawComponent(props, roomContext, client));
|
||||||
wrappingComponent: WrapWithProviders,
|
|
||||||
wrappingComponentProps: { roomContext, client },
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
it("renders text and placeholder correctly", () => {
|
it("renders text and placeholder correctly", () => {
|
||||||
const wrapper = getComponent({ placeholder: "placeholder string" });
|
const { container } = getComponent({ placeholder: "placeholder string" });
|
||||||
|
|
||||||
expect(wrapper.find('[aria-label="placeholder string"]')).toHaveLength(1);
|
expect(container.querySelectorAll('[aria-label="placeholder string"]')).toHaveLength(1);
|
||||||
|
|
||||||
addTextToComposer(wrapper, "Test Text");
|
addTextToComposer(container, "Test Text");
|
||||||
|
|
||||||
expect(wrapper.text()).toBe("Test Text");
|
expect(container.textContent).toBe("Test Text");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("correctly persists state to and from localStorage", () => {
|
it("correctly persists state to and from localStorage", () => {
|
||||||
const wrapper = getComponent({ replyToEvent: mockEvent });
|
const props = { replyToEvent: mockEvent };
|
||||||
|
const { container, unmount, rerender } = getComponent(props);
|
||||||
|
|
||||||
addTextToComposer(wrapper, "Test Text");
|
addTextToComposer(container, "Test Text");
|
||||||
|
|
||||||
// @ts-ignore
|
const key = "mx_cider_state_myfakeroom";
|
||||||
const key = wrapper.find(SendMessageComposerClass).instance().editorStateKey;
|
|
||||||
|
|
||||||
expect(wrapper.text()).toBe("Test Text");
|
expect(container.textContent).toBe("Test Text");
|
||||||
expect(localStorage.getItem(key)).toBeNull();
|
expect(localStorage.getItem(key)).toBeNull();
|
||||||
|
|
||||||
// ensure the right state was persisted to localStorage
|
// ensure the right state was persisted to localStorage
|
||||||
wrapper.unmount();
|
unmount();
|
||||||
expect(JSON.parse(localStorage.getItem(key))).toStrictEqual({
|
expect(JSON.parse(localStorage.getItem(key))).toStrictEqual({
|
||||||
parts: [{ "type": "plain", "text": "Test Text" }],
|
parts: [{ "type": "plain", "text": "Test Text" }],
|
||||||
replyEventId: mockEvent.getId(),
|
replyEventId: mockEvent.getId(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// ensure the correct model is re-loaded
|
// ensure the correct model is re-loaded
|
||||||
wrapper.mount();
|
rerender(getRawComponent(props));
|
||||||
expect(wrapper.text()).toBe("Test Text");
|
expect(container.textContent).toBe("Test Text");
|
||||||
expect(spyDispatcher).toHaveBeenCalledWith({
|
expect(spyDispatcher).toHaveBeenCalledWith({
|
||||||
action: "reply_to_event",
|
action: "reply_to_event",
|
||||||
event: mockEvent,
|
event: mockEvent,
|
||||||
|
@ -239,21 +230,20 @@ describe('<SendMessageComposer/>', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// now try with localStorage wiped out
|
// now try with localStorage wiped out
|
||||||
wrapper.unmount();
|
unmount();
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
wrapper.mount();
|
rerender(getRawComponent(props));
|
||||||
expect(wrapper.text()).toBe("");
|
expect(container.textContent).toBe("");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("persists state correctly without replyToEvent onbeforeunload", () => {
|
it("persists state correctly without replyToEvent onbeforeunload", () => {
|
||||||
const wrapper = getComponent();
|
const { container } = getComponent();
|
||||||
|
|
||||||
addTextToComposer(wrapper, "Hello World");
|
addTextToComposer(container, "Hello World");
|
||||||
|
|
||||||
// @ts-ignore
|
const key = "mx_cider_state_myfakeroom";
|
||||||
const key = wrapper.find(SendMessageComposerClass).instance().editorStateKey;
|
|
||||||
|
|
||||||
expect(wrapper.text()).toBe("Hello World");
|
expect(container.textContent).toBe("Hello World");
|
||||||
expect(localStorage.getItem(key)).toBeNull();
|
expect(localStorage.getItem(key)).toBeNull();
|
||||||
|
|
||||||
// ensure the right state was persisted to localStorage
|
// ensure the right state was persisted to localStorage
|
||||||
|
@ -266,22 +256,20 @@ describe('<SendMessageComposer/>', () => {
|
||||||
it("persists to session history upon sending", async () => {
|
it("persists to session history upon sending", async () => {
|
||||||
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
|
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
|
||||||
|
|
||||||
const wrapper = getComponent({ replyToEvent: mockEvent });
|
const { container } = getComponent({ replyToEvent: mockEvent });
|
||||||
|
|
||||||
addTextToComposer(wrapper, "This is a message");
|
addTextToComposer(container, "This is a message");
|
||||||
act(() => {
|
fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer"), { key: "Enter" });
|
||||||
wrapper.find(".mx_SendMessageComposer").simulate("keydown", { key: "Enter" });
|
|
||||||
wrapper.update();
|
await waitFor(() => {
|
||||||
});
|
|
||||||
await sleep(10); // await the async _sendMessage
|
|
||||||
wrapper.update();
|
|
||||||
expect(spyDispatcher).toHaveBeenCalledWith({
|
expect(spyDispatcher).toHaveBeenCalledWith({
|
||||||
action: "reply_to_event",
|
action: "reply_to_event",
|
||||||
event: null,
|
event: null,
|
||||||
context: TimelineRenderingType.Room,
|
context: TimelineRenderingType.Room,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
expect(wrapper.text()).toBe("");
|
expect(container.textContent).toBe("");
|
||||||
const str = sessionStorage.getItem(`mx_cider_history_${mockRoom.roomId}[0]`);
|
const str = sessionStorage.getItem(`mx_cider_history_${mockRoom.roomId}[0]`);
|
||||||
expect(JSON.parse(str)).toStrictEqual({
|
expect(JSON.parse(str)).toStrictEqual({
|
||||||
parts: [{ "type": "plain", "text": "This is a message" }],
|
parts: [{ "type": "plain", "text": "This is a message" }],
|
||||||
|
@ -289,19 +277,6 @@ describe('<SendMessageComposer/>', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('correctly sets the editorStateKey for threads', () => {
|
|
||||||
const relation = {
|
|
||||||
rel_type: RelationType.Thread,
|
|
||||||
event_id: "myFakeThreadId",
|
|
||||||
};
|
|
||||||
const includeReplyLegacyFallback = false;
|
|
||||||
const wrapper = getComponent({ relation, includeReplyLegacyFallback });
|
|
||||||
const instance = wrapper.find(SendMessageComposerClass).instance();
|
|
||||||
// @ts-ignore
|
|
||||||
const key = instance.editorStateKey;
|
|
||||||
expect(key).toEqual('mx_cider_state_myfakeroom_myFakeThreadId');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("correctly sends a message", () => {
|
it("correctly sends a message", () => {
|
||||||
mocked(doMaybeLocalRoomAction).mockImplementation(<T extends {}>(
|
mocked(doMaybeLocalRoomAction).mockImplementation(<T extends {}>(
|
||||||
roomId: string,
|
roomId: string,
|
||||||
|
@ -312,13 +287,10 @@ describe('<SendMessageComposer/>', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
|
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
|
||||||
const wrapper = getComponent();
|
const { container } = getComponent();
|
||||||
|
|
||||||
addTextToComposer(wrapper, "test message");
|
addTextToComposer(container, "test message");
|
||||||
act(() => {
|
fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer"), { key: "Enter" });
|
||||||
wrapper.find(".mx_SendMessageComposer").simulate("keydown", { key: "Enter" });
|
|
||||||
wrapper.update();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mockClient.sendMessage).toHaveBeenCalledWith(
|
expect(mockClient.sendMessage).toHaveBeenCalledWith(
|
||||||
"myfakeroom",
|
"myfakeroom",
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { IThreepid, ThreepidMedium } from 'matrix-js-sdk/src/@types/threepids';
|
||||||
|
|
||||||
|
import { EmailAddress } from '../../../../../src/components/views/settings/discovery/EmailAddresses';
|
||||||
|
|
||||||
|
describe("<EmailAddress/>", () => {
|
||||||
|
it("should track props.email.bound changes", async () => {
|
||||||
|
const email: IThreepid = {
|
||||||
|
medium: ThreepidMedium.Email,
|
||||||
|
address: "foo@bar.com",
|
||||||
|
validated_at: 12345,
|
||||||
|
added_at: 12342,
|
||||||
|
bound: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { rerender } = render(<EmailAddress email={email} />);
|
||||||
|
await screen.findByText("Share");
|
||||||
|
|
||||||
|
email.bound = true;
|
||||||
|
rerender(<EmailAddress email={{ ...email }} />);
|
||||||
|
await screen.findByText("Revoke");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { IThreepid, ThreepidMedium } from 'matrix-js-sdk/src/@types/threepids';
|
||||||
|
|
||||||
|
import { PhoneNumber } from "../../../../../src/components/views/settings/discovery/PhoneNumbers";
|
||||||
|
|
||||||
|
describe("<PhoneNumber/>", () => {
|
||||||
|
it("should track props.msisdn.bound changes", async () => {
|
||||||
|
const msisdn: IThreepid = {
|
||||||
|
medium: ThreepidMedium.Phone,
|
||||||
|
address: "+441111111111",
|
||||||
|
validated_at: 12345,
|
||||||
|
added_at: 12342,
|
||||||
|
bound: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { rerender } = render(<PhoneNumber msisdn={msisdn} />);
|
||||||
|
await screen.findByText("Share");
|
||||||
|
|
||||||
|
msisdn.bound = true;
|
||||||
|
rerender(<PhoneNumber msisdn={{ ...msisdn }} />);
|
||||||
|
await screen.findByText("Revoke");
|
||||||
|
});
|
||||||
|
});
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { render, RenderResult } from "@testing-library/react";
|
import { render, RenderResult, screen } from "@testing-library/react";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ import { mkStubRoom, stubClient } from "../../../../../test-utils";
|
||||||
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
||||||
import { EchoChamber } from "../../../../../../src/stores/local-echo/EchoChamber";
|
import { EchoChamber } from "../../../../../../src/stores/local-echo/EchoChamber";
|
||||||
import { RoomEchoChamber } from "../../../../../../src/stores/local-echo/RoomEchoChamber";
|
import { RoomEchoChamber } from "../../../../../../src/stores/local-echo/RoomEchoChamber";
|
||||||
|
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||||
|
import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
|
||||||
|
|
||||||
describe("NotificatinSettingsTab", () => {
|
describe("NotificatinSettingsTab", () => {
|
||||||
const roomId = "!room:example.com";
|
const roomId = "!room:example.com";
|
||||||
|
@ -55,4 +57,23 @@ describe("NotificatinSettingsTab", () => {
|
||||||
|
|
||||||
expect(roomProps.notificationVolume).not.toBe("mentions_only");
|
expect(roomProps.notificationVolume).not.toBe("mentions_only");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should show the currently chosen custom notification sound", async () => {
|
||||||
|
SettingsStore.setValue("notificationSound", roomId, SettingLevel.ACCOUNT, {
|
||||||
|
url: "mxc://server/custom-sound-123",
|
||||||
|
name: "custom-sound-123",
|
||||||
|
});
|
||||||
|
renderTab();
|
||||||
|
|
||||||
|
await screen.findByText("custom-sound-123");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show the currently chosen custom notification sound url if no name", async () => {
|
||||||
|
SettingsStore.setValue("notificationSound", roomId, SettingLevel.ACCOUNT, {
|
||||||
|
url: "mxc://server/custom-sound-123",
|
||||||
|
});
|
||||||
|
renderTab();
|
||||||
|
|
||||||
|
await screen.findByText("http://this.is.a.url/server/custom-sound-123");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,7 +52,7 @@ class DOMRect {
|
||||||
window.DOMRect = DOMRect;
|
window.DOMRect = DOMRect;
|
||||||
|
|
||||||
// Work around missing ClipboardEvent type
|
// Work around missing ClipboardEvent type
|
||||||
class MyClipboardEvent {}
|
class MyClipboardEvent extends Event {}
|
||||||
window.ClipboardEvent = MyClipboardEvent as any;
|
window.ClipboardEvent = MyClipboardEvent as any;
|
||||||
|
|
||||||
// matchMedia is not included in jsdom
|
// matchMedia is not included in jsdom
|
||||||
|
|
|
@ -17,8 +17,22 @@ limitations under the License.
|
||||||
// eslint-disable-next-line deprecate/import
|
// eslint-disable-next-line deprecate/import
|
||||||
import { ReactWrapper } from "enzyme";
|
import { ReactWrapper } from "enzyme";
|
||||||
import { act } from "react-dom/test-utils";
|
import { act } from "react-dom/test-utils";
|
||||||
|
import { fireEvent } from "@testing-library/react";
|
||||||
|
|
||||||
export const addTextToComposer = (wrapper: ReactWrapper, text: string) => act(() => {
|
export const addTextToComposer = (container: HTMLElement, text: string) => act(() => {
|
||||||
|
// couldn't get input event on contenteditable to work
|
||||||
|
// paste works without illegal private method access
|
||||||
|
const pasteEvent = {
|
||||||
|
clipboardData: {
|
||||||
|
types: [],
|
||||||
|
files: [],
|
||||||
|
getData: type => type === "text/plain" ? text : undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
fireEvent.paste(container.querySelector('[role="textbox"]'), pasteEvent);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const addTextToComposerEnzyme = (wrapper: ReactWrapper, text: string) => act(() => {
|
||||||
// couldn't get input event on contenteditable to work
|
// couldn't get input event on contenteditable to work
|
||||||
// paste works without illegal private method access
|
// paste works without illegal private method access
|
||||||
const pasteEvent = {
|
const pasteEvent = {
|
||||||
|
|
|
@ -30,7 +30,7 @@ class MockPlatform extends BasePlatform {
|
||||||
/**
|
/**
|
||||||
* Mock Platform Peg
|
* Mock Platform Peg
|
||||||
* Creates a mock BasePlatform class
|
* Creates a mock BasePlatform class
|
||||||
* spys on PlatformPeg.get and returns mock platform
|
* spies on PlatformPeg.get and returns mock platform
|
||||||
* @returns MockPlatform instance
|
* @returns MockPlatform instance
|
||||||
*/
|
*/
|
||||||
export const mockPlatformPeg = (
|
export const mockPlatformPeg = (
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue