Conform more code to strictNullChecks (#10444

* Conform more code to `strictNullChecks`

* Fix tests

* Fix tests
This commit is contained in:
Michael Telatynski 2023-03-27 08:01:09 +01:00 committed by GitHub
parent ba2608ec74
commit c225b8ec29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 85 additions and 75 deletions

View file

@ -24,8 +24,6 @@ import { logger } from "matrix-js-sdk/src/logger";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
import { isSupportedReceiptType } from "matrix-js-sdk/src/utils";
import { ReadReceipt } from "matrix-js-sdk/src/models/read-receipt";
import { ListenerMap } from "matrix-js-sdk/src/models/typed-event-emitter";
import shouldHideEvent from "../../shouldHideEvent";
import { wantsDateSeparator } from "../../DateUtils";
@ -543,7 +541,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return null;
}
private collectGhostReadMarker = (node: HTMLElement): void => {
private collectGhostReadMarker = (node: HTMLElement | null): void => {
if (node) {
// now the element has appeared, change the style which will trigger the CSS transition
requestAnimationFrame(() => {
@ -788,13 +786,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
continuation={continuation}
isRedacted={mxEv.isRedacted()}
replacingEventId={mxEv.replacingEventId()}
editState={isEditing && this.props.editState}
editState={isEditing ? this.props.editState : undefined}
onHeightChanged={this.onHeightChanged}
readReceipts={readReceipts}
readReceiptMap={this.readReceiptMap}
showUrlPreview={this.props.showUrlPreview}
checkUnmounting={this.isUnmounting}
eventSendStatus={mxEv.getAssociatedStatus()}
eventSendStatus={mxEv.getAssociatedStatus() ?? undefined}
isTwelveHour={this.props.isTwelveHour}
permalinkCreator={this.props.permalinkCreator}
last={last}
@ -836,9 +834,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return null;
}
const receiptDestination: ReadReceipt<string, ListenerMap<string>> = this.context.threadId
? room.getThread(this.context.threadId)
: room;
const receiptDestination = this.context.threadId ? room.getThread(this.context.threadId) : room;
const receipts: IReadReceiptProps[] = [];
@ -1215,7 +1211,7 @@ class CreationGrouper extends BaseGrouper {
let summaryText: string;
const roomId = ev.getRoomId();
const creator = ev.sender ? ev.sender.name : ev.getSender();
const creator = ev.sender?.name ?? ev.getSender();
if (DMRoomMap.shared().getUserIdForRoomId(roomId)) {
summaryText = _t("%(creator)s created this DM.", { creator });
} else {

View file

@ -377,7 +377,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private roomView = createRef<HTMLElement>();
private searchResultsPanel = createRef<ScrollPanel>();
private messagePanel?: TimelinePanel;
private messagePanel: TimelinePanel | null = null;
private roomViewBody = createRef<HTMLDivElement>();
public static contextType = SDKContext;
@ -611,11 +611,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const newState: Partial<IRoomState> = {
roomId: roomId ?? undefined,
roomAlias: this.context.roomViewStore.getRoomAlias(),
roomAlias: this.context.roomViewStore.getRoomAlias() ?? undefined,
roomLoading: this.context.roomViewStore.isRoomLoading(),
roomLoadError: this.context.roomViewStore.getRoomLoadError(),
roomLoadError: this.context.roomViewStore.getRoomLoadError() ?? undefined,
joining: this.context.roomViewStore.isJoining(),
replyToEvent: this.context.roomViewStore.getQuotingEvent(),
replyToEvent: this.context.roomViewStore.getQuotingEvent() ?? undefined,
// we should only peek once we have a ready client
shouldPeek: this.state.matrixClientIsReady && this.context.roomViewStore.shouldPeek(),
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
@ -654,7 +654,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// and the root event.
// The rest will be lost for now, until the aggregation API on the server
// becomes available to fetch a whole thread
if (!initialEvent) {
if (!initialEvent && this.context.client) {
initialEvent = (await fetchInitialEvent(this.context.client, roomId, initialEventId)) ?? undefined;
}
@ -848,7 +848,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
isPeeking: true, // this will change to false if peeking fails
});
this.context.client
.peekInRoom(roomId)
?.peekInRoom(roomId)
.then((room) => {
if (this.unmounted) {
return;
@ -883,7 +883,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
});
} else if (room) {
// Stop peeking because we have joined this room previously
this.context.client.stopPeeking();
this.context.client?.stopPeeking();
this.setState({ isPeeking: false });
}
}
@ -909,9 +909,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.onRoomViewStoreUpdate(true);
const call = this.getCallForRoom();
const callState = call ? call.state : null;
const callState = call?.state;
this.setState({
callState: callState,
callState,
});
this.context.legacyCallHandler.on(LegacyCallHandlerEvent.CallState, this.onCallState);
@ -959,7 +959,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}
if (this.state.shouldPeek) {
this.context.client.stopPeeking();
this.context.client?.stopPeeking();
}
// stop tracking room changes to format permalinks
@ -1010,7 +1010,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (this.viewsLocalRoom) {
// clean up if this was a local room
this.context.client.store.removeRoom(this.state.room.roomId);
this.context.client?.store.removeRoom(this.state.room.roomId);
}
}
@ -1469,7 +1469,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private updatePermissions(room: Room): void {
if (room) {
const me = this.context.client.getUserId();
const me = this.context.client.getSafeUserId();
const canReact =
room.getMyMembership() === "join" && room.currentState.maySendEvent(EventType.Reaction, me);
const canSendMessages = room.maySendMessage();
@ -1866,7 +1866,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// this has to be a proper method rather than an unnamed function,
// otherwise react calls it with null on each update.
private gatherTimelinePanelRef = (r?: TimelinePanel): void => {
private gatherTimelinePanelRef = (r: TimelinePanel | null): void => {
this.messagePanel = r;
};

View file

@ -171,7 +171,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
if (payload.event && !payload.event.getThread()) return;
this.setState(
{
editState: payload.event ? new EditorStateTransfer(payload.event) : null,
editState: payload.event ? new EditorStateTransfer(payload.event) : undefined,
},
() => {
if (payload.event) {
@ -213,9 +213,11 @@ export default class ThreadView extends React.Component<IProps, IState> {
};
private get threadLastReply(): MatrixEvent | undefined {
return this.state.thread?.lastReply((ev: MatrixEvent) => {
return ev.isRelation(THREAD_RELATION_TYPE.name) && !ev.status;
});
return (
this.state.thread?.lastReply((ev: MatrixEvent) => {
return ev.isRelation(THREAD_RELATION_TYPE.name) && !ev.status;
}) ?? undefined
);
}
private updateThread = (thread?: Thread): void => {
@ -245,7 +247,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
private setupThreadListeners(thread?: Thread | undefined, oldThread?: Thread | undefined): void {
if (oldThread) {
this.state.thread.off(ThreadEvent.NewReply, this.updateThreadRelation);
this.state.thread?.off(ThreadEvent.NewReply, this.updateThreadRelation);
this.props.room.off(RoomEvent.LocalEchoUpdated, this.updateThreadRelation);
}
if (thread) {
@ -344,7 +346,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
};
public render(): React.ReactNode {
const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() : null;
const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() : undefined;
const threadRelation = this.threadRelation;

View file

@ -79,7 +79,7 @@ interface IProps {
// representing. This may or may not have a room, depending on what it's
// a timeline representing. If it has a room, we maintain RRs etc for
// that room.
timelineSet?: EventTimelineSet;
timelineSet: EventTimelineSet;
// overlay events from a second timelineset on the main timeline
// added to support virtual rooms
// events from the overlay timeline set will be added by localTimestamp

View file

@ -33,6 +33,7 @@ interface IProps {
const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
const info = SettingsStore.getBetaInfo(featureId);
if (!info) return null;
return (
<GenericFeatureFeedbackDialog

View file

@ -42,7 +42,7 @@ const BulkRedactDialog: React.FC<Props> = (props) => {
const { matrixClient: cli, room, member, onFinished } = props;
const [keepStateEvents, setKeepStateEvents] = useState(true);
let timeline = room.getLiveTimeline();
let timeline: EventTimeline | null = room.getLiveTimeline();
let eventsToRedact: MatrixEvent[] = [];
while (timeline) {
eventsToRedact = [
@ -93,7 +93,7 @@ const BulkRedactDialog: React.FC<Props> = (props) => {
await Promise.all(
eventsToRedact.reverse().map(async (event): Promise<void> => {
try {
await cli.redactEvent(room.roomId, event.getId());
await cli.redactEvent(room.roomId, event.getId()!);
} catch (err) {
// log and swallow errors
logger.error("Could not redact", event.getId());

View file

@ -71,7 +71,7 @@ interface IProps {
type ToolInfo = [label: string, tool: Tool];
const DevtoolsDialog: React.FC<IProps> = ({ roomId, onFinished }) => {
const [tool, setTool] = useState<ToolInfo>(null);
const [tool, setTool] = useState<ToolInfo | null>(null);
let body: JSX.Element;
let onBack: () => void;

View file

@ -51,6 +51,7 @@ import { ButtonEvent } from "../elements/AccessibleButton";
import { isLocationEvent } from "../../../utils/EventUtils";
import { isSelfLocation, locationEventGeoUri } from "../../../utils/location";
import { RoomContextDetails } from "../rooms/RoomContextDetails";
import { filterBoolean } from "../../../utils/arrays";
const AVATAR_SIZE = 30;
@ -194,7 +195,7 @@ const transformEvent = (event: MatrixEvent): { type: string; content: IContent }
};
const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCreator, onFinished }) => {
const userId = cli.getUserId();
const userId = cli.getSafeUserId();
const [profileInfo, setProfileInfo] = useState<any>({});
useEffect(() => {
cli.getProfileInfo(userId).then((info) => setProfileInfo(info));
@ -242,7 +243,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
if (lcQuery) {
rooms = new QueryMatcher<Room>(rooms, {
keys: ["name"],
funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
funcs: [(r) => filterBoolean([r.getCanonicalAlias(), ...r.getAltAliases()])],
shouldMatchWordsOnly: false,
}).match(lcQuery);
}

View file

@ -34,6 +34,7 @@ const RegistrationEmailPromptDialog: React.FC<IProps> = ({ onFinished }) => {
const onSubmit = async (e: SyntheticEvent): Promise<void> => {
e.preventDefault();
if (!fieldRef.current) return;
if (email) {
const valid = await fieldRef.current.validate({});

View file

@ -141,7 +141,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
return !error;
},
invalid: function ({ error }) {
return error;
return error ?? null;
},
},
],

View file

@ -49,7 +49,7 @@ interface ITermsDialogProps {
/**
* urls that the user has already agreed to
*/
agreedUrls?: string[];
agreedUrls: string[];
/**
* Called with:
@ -127,7 +127,7 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
};
public render(): React.ReactNode {
const rows = [];
const rows: JSX.Element[] = [];
for (const policiesAndService of this.props.policiesAndServicePairs) {
const parsedBaseUrl = url.parse(policiesAndService.service.baseUrl);
@ -135,8 +135,8 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
for (let i = 0; i < policyValues.length; ++i) {
const termDoc = policyValues[i];
const termsLang = pickBestLanguage(Object.keys(termDoc).filter((k) => k !== "version"));
let serviceName;
let summary;
let serviceName: JSX.Element | undefined;
let summary: JSX.Element | undefined;
if (i === 0) {
serviceName = this.nameForServiceType(policiesAndService.service.serviceType, parsedBaseUrl.host);
summary = this.summaryForServiceType(policiesAndService.service.serviceType);

View file

@ -100,7 +100,7 @@ export default class TextInputDialog extends React.Component<IProps, IState> {
};
private onValidate = async (fieldState: IFieldState): Promise<IValidationResult> => {
const result = await this.props.validator(fieldState);
const result = await this.props.validator!(fieldState);
this.setState({
valid: !!result.valid,
});

View file

@ -38,7 +38,7 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element {
const [generalMenuPosition, setGeneralMenuPosition] = useState<DOMRect | null>(null);
const [notificationMenuPosition, setNotificationMenuPosition] = useState<DOMRect | null>(null);
let generalMenu: JSX.Element;
let generalMenu: JSX.Element | undefined;
if (generalMenuPosition !== null) {
if (room.isSpaceRoom()) {
generalMenu = (
@ -59,7 +59,7 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element {
}
}
let notificationMenu: JSX.Element;
let notificationMenu: JSX.Element | undefined;
if (notificationMenuPosition !== null) {
notificationMenu = (
<RoomNotificationContextMenu

View file

@ -440,7 +440,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
// Sort results by most recent activity
const myUserId = cli.getUserId();
const myUserId = cli.getSafeUserId();
for (const resultArray of Object.values(results)) {
resultArray.sort((a: Result, b: Result) => {
if (isRoomResult(a) || isRoomResult(b)) {

View file

@ -27,17 +27,17 @@ enum Phases {
interface IProps {
onValueChanged?: (value: string, shouldSubmit: boolean) => void;
initialValue?: string;
label?: string;
placeholder?: string;
className?: string;
initialValue: string;
label: string;
placeholder: string;
className: string;
labelClassName?: string;
placeholderClassName?: string;
placeholderClassName: string;
// Overrides blurToSubmit if true
blurToCancel?: boolean;
// Will cause onValueChanged(value, true) to fire on blur
blurToSubmit?: boolean;
editable?: boolean;
blurToSubmit: boolean;
editable: boolean;
}
interface IState {
@ -109,11 +109,11 @@ export default class EditableText extends React.Component<IProps, IState> {
this.value = this.props.initialValue;
this.showPlaceholder(!this.value);
this.onValueChanged(false);
this.editableDiv.current.blur();
this.editableDiv.current?.blur();
};
private onValueChanged = (shouldSubmit: boolean): void => {
this.props.onValueChanged(this.value, shouldSubmit);
this.props.onValueChanged?.(this.value, shouldSubmit);
};
private onKeyDown = (ev: React.KeyboardEvent<HTMLDivElement>): void => {
@ -171,7 +171,7 @@ export default class EditableText extends React.Component<IProps, IState> {
private onFinish = (
ev: React.KeyboardEvent<HTMLDivElement> | React.FocusEvent<HTMLDivElement>,
shouldSubmit?: boolean,
shouldSubmit = false,
): void => {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;

View file

@ -187,8 +187,8 @@ export default class EventListSummary extends React.Component<IProps> {
let transition = t;
if (i < transitions.length - 1 && modMap[t] && modMap[t].after === t2) {
transition = modMap[t].newTransition;
if (i < transitions.length - 1 && modMap[t] && modMap[t]!.after === t2) {
transition = modMap[t]!.newTransition;
i++;
}
@ -380,7 +380,7 @@ export default class EventListSummary extends React.Component<IProps> {
return res ?? null;
}
private static getTransitionSequence(events: IUserEvents[]): TransitionType[] {
private static getTransitionSequence(events: IUserEvents[]): Array<TransitionType | null> {
return events.map(EventListSummary.getTransition);
}

View file

@ -148,6 +148,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
private async getNextEvent(ev: MatrixEvent): Promise<MatrixEvent | null> {
try {
const inReplyToEventId = getParentEventId(ev);
if (!inReplyToEventId) return null;
return await this.getEvent(inReplyToEventId);
} catch (e) {
return null;
@ -196,7 +197,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
};
private getReplyChainColorClass(ev: MatrixEvent): string {
return getUserNameColorClass(ev.getSender()).replace("Username", "ReplyChain");
return getUserNameColorClass(ev.getSender()!).replace("Username", "ReplyChain");
}
public render(): React.ReactNode {
@ -231,8 +232,8 @@ export default class ReplyChain extends React.Component<IProps, IState> {
pill: (
<Pill
type={PillType.UserMention}
room={room}
url={makeUserPermalink(ev.getSender())}
room={room ?? undefined}
url={makeUserPermalink(ev.getSender()!)}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/>
),

View file

@ -63,7 +63,7 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
const event = this.props.mxEvent;
const room = cli.getRoom(event.getRoomId());
event.localRedactionEvent()?.on(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
const canRedact = room.currentState.maySendRedactionForEvent(event, userId);
const canRedact = room?.currentState.maySendRedactionForEvent(event, userId) ?? false;
this.state = { canRedact, sendStatus: event.getAssociatedStatus() };
}

View file

@ -71,7 +71,7 @@ export const usePinnedEvents = (room?: Room): string[] => {
};
function getReadPinnedEventIds(room?: Room): Set<string> {
return new Set(room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []);
return new Set(room?.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []);
}
export const useReadPinnedEvents = (room?: Room): Set<string> => {

View file

@ -448,7 +448,7 @@ export const UserOptionsSection: React.FC<{
const inviter = new MultiInviter(roomId || "");
await inviter.invite([member.userId]).then(() => {
if (inviter.getCompletionState(member.userId) !== "invited") {
throw new Error(inviter.getErrorText(member.userId));
throw new Error(inviter.getErrorText(member.userId) ?? undefined);
}
});
} catch (err) {
@ -766,8 +766,8 @@ export const BanToggleButton = ({
const myMember = child.getMember(cli.credentials.userId || "");
const theirMember = child.getMember(member.userId);
return (
myMember &&
theirMember &&
!!myMember &&
!!theirMember &&
theirMember.membership !== "ban" &&
myMember.powerLevel > theirMember.powerLevel &&
child.currentState.hasSufficientPowerLevelFor("ban", myMember.powerLevel)

View file

@ -25,7 +25,7 @@ import { OverflowMenuContext } from "./MessageComposerButtons";
interface IEmojiButtonProps {
addEmoji: (unicode: string) => boolean;
menuPosition: MenuProps;
menuPosition?: MenuProps;
className?: string;
}

View file

@ -73,8 +73,8 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
return null;
}
let mainButtons: ReactElement[];
let moreButtons: ReactElement[];
let mainButtons: ReactNode[];
let moreButtons: ReactNode[];
if (narrow) {
mainButtons = [
isWysiwygLabEnabled ? (

View file

@ -75,8 +75,8 @@ export const usePermalinkEvent = (
const fetchRoomEvent = async (): Promise<void> => {
try {
const eventData = await MatrixClientPeg.get().fetchRoomEvent(
parseResult.roomIdOrAlias,
parseResult.eventId,
parseResult.roomIdOrAlias!,
parseResult.eventId!,
);
setEvent(new MatrixEvent(eventData));
} catch {}

View file

@ -83,7 +83,7 @@ const findRoom = (roomIdOrAlias: string): Room | null => {
export const usePermalinkTargetRoom = (
type: PillType | null,
parseResult: PermalinkParts | null,
permalinkRoom: Room | null,
permalinkRoom: Room | undefined,
): Room | null => {
// The listed permalink types require a room.
// If it cannot be initially determined, it will be looked up later by a memo hook.

View file

@ -15,7 +15,6 @@ limitations under the License.
*/
import React from "react";
import { Optional } from "matrix-events-sdk";
import { _t } from "../languageHandler";
import SdkConfig from "../SdkConfig";
@ -75,7 +74,7 @@ const onLearnMorePreviouslyOptedIn = (): void => {
const TOAST_KEY = "analytics";
export function getPolicyUrl(): Optional<string> {
export function getPolicyUrl(): string | undefined {
return SdkConfig.get("privacy_policy_url");
}

View file

@ -22,7 +22,10 @@ import { Member } from "./direct-messages";
import DMRoomMap from "./DMRoomMap";
export const compareMembers =
(activityScores: Record<string, IActivityScore>, memberScores: Record<string, IMemberScore>) =>
(
activityScores: Record<string, IActivityScore | undefined>,
memberScores: Record<string, IMemberScore | undefined>,
) =>
(a: Member | RoomMember, b: Member | RoomMember): number => {
const aActivityScore = activityScores[a.userId]?.score ?? 0;
const aMemberScore = memberScores[a.userId]?.score ?? 0;

View file

@ -716,7 +716,7 @@ describe("MessagePanel", function () {
// Increase the length of the loop here to test performance issues with
// rendering
const events = [];
const events: MatrixEvent[] = [];
for (let i = 0; i < 100; i++) {
events.push(
TestUtilsMatrix.mkMembership({

View file

@ -54,6 +54,7 @@ describe("ForwardDialog", () => {
});
const mockClient = getMockClientWithEventEmitter({
getUserId: jest.fn().mockReturnValue(aliceId),
getSafeUserId: jest.fn().mockReturnValue(aliceId),
isGuest: jest.fn().mockReturnValue(false),
getVisibleRooms: jest.fn().mockReturnValue([]),
getRoom: jest.fn(),
@ -92,6 +93,7 @@ describe("ForwardDialog", () => {
DMRoomMap.makeShared();
jest.clearAllMocks();
mockClient.getUserId.mockReturnValue("@bob:example.org");
mockClient.getSafeUserId.mockReturnValue("@bob:example.org");
mockClient.sendEvent.mockReset();
});

View file

@ -112,6 +112,7 @@ const mockClient = mocked({
setIgnoredUsers: jest.fn(),
isCryptoEnabled: jest.fn(),
getUserId: jest.fn(),
getSafeUserId: jest.fn(),
on: jest.fn(),
off: jest.fn(),
isSynapseAdministrator: jest.fn().mockResolvedValue(false),
@ -348,6 +349,7 @@ describe("<DeviceItem />", () => {
});
it("when userId is the same as userId from client, uses isCrossSigningVerified to determine if button is shown", () => {
mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
mockClient.getUserId.mockReturnValueOnce(defaultUserId);
renderComponent();
@ -431,6 +433,7 @@ describe("<UserOptionsSection />", () => {
});
it("does not show ignore or direct message buttons when member userId matches client userId", () => {
mockClient.getSafeUserId.mockReturnValueOnce(member.userId);
mockClient.getUserId.mockReturnValueOnce(member.userId);
renderComponent();
@ -676,6 +679,7 @@ describe("<PowerLevelEditor />", () => {
content: { users: { [defaultUserId]: startPowerLevel }, users_default: 1 },
});
mockRoom.currentState.getStateEvents.mockReturnValue(powerLevelEvent);
mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
mockClient.getUserId.mockReturnValueOnce(defaultUserId);
mockClient.setPowerLevel.mockResolvedValueOnce({ event_id: "123" });
renderComponent();
@ -879,7 +883,7 @@ describe("<BanToggleButton />", () => {
},
};
expect(callback(mockRoom)).toBe(null);
expect(callback(mockRoom)).toBe(false);
expect(callback(mockRoom)).toBe(true);
});