Hide composer and call buttons when the room is tombstoned (#7975)
This commit is contained in:
parent
675b4271e9
commit
0e60ad98c1
11 changed files with 196 additions and 93 deletions
|
@ -187,7 +187,8 @@ export interface IRoomState {
|
||||||
|
|
||||||
upgradeRecommendation?: IRecommendedVersion;
|
upgradeRecommendation?: IRecommendedVersion;
|
||||||
canReact: boolean;
|
canReact: boolean;
|
||||||
canReply: boolean;
|
canSendMessages: boolean;
|
||||||
|
tombstone?: MatrixEvent;
|
||||||
layout: Layout;
|
layout: Layout;
|
||||||
lowBandwidth: boolean;
|
lowBandwidth: boolean;
|
||||||
alwaysShowTimestamps: boolean;
|
alwaysShowTimestamps: boolean;
|
||||||
|
@ -259,7 +260,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
showTopUnreadMessagesBar: false,
|
showTopUnreadMessagesBar: false,
|
||||||
statusBarVisible: false,
|
statusBarVisible: false,
|
||||||
canReact: false,
|
canReact: false,
|
||||||
canReply: false,
|
canSendMessages: false,
|
||||||
layout: SettingsStore.getValue("layout"),
|
layout: SettingsStore.getValue("layout"),
|
||||||
lowBandwidth: SettingsStore.getValue("lowBandwidth"),
|
lowBandwidth: SettingsStore.getValue("lowBandwidth"),
|
||||||
alwaysShowTimestamps: SettingsStore.getValue("alwaysShowTimestamps"),
|
alwaysShowTimestamps: SettingsStore.getValue("alwaysShowTimestamps"),
|
||||||
|
@ -371,12 +372,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
: MainSplitContentType.Timeline;
|
: MainSplitContentType.Timeline;
|
||||||
};
|
};
|
||||||
|
|
||||||
private onReadReceiptsChange = () => {
|
|
||||||
this.setState({
|
|
||||||
showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
private onRoomViewStoreUpdate = async (initial?: boolean): Promise<void> => {
|
private onRoomViewStoreUpdate = async (initial?: boolean): Promise<void> => {
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return;
|
||||||
|
@ -1033,10 +1028,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
this.checkWidgets(room);
|
this.checkWidgets(room);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
tombstone: this.getRoomTombstone(),
|
||||||
liveTimeline: room.getLiveTimeline(),
|
liveTimeline: room.getLiveTimeline(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getRoomTombstone() {
|
||||||
|
return this.state.room?.currentState.getStateEvents(EventType.RoomTombstone, "");
|
||||||
|
}
|
||||||
|
|
||||||
private async calculateRecommendedVersion(room: Room) {
|
private async calculateRecommendedVersion(room: Room) {
|
||||||
const upgradeRecommendation = await room.getRecommendedVersion();
|
const upgradeRecommendation = await room.getRecommendedVersion();
|
||||||
if (this.unmounted) return;
|
if (this.unmounted) return;
|
||||||
|
@ -1167,17 +1167,23 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
// ignore if we don't have a room yet
|
// ignore if we don't have a room yet
|
||||||
if (!this.state.room || this.state.room.roomId !== state.roomId) return;
|
if (!this.state.room || this.state.room.roomId !== state.roomId) return;
|
||||||
|
|
||||||
if (ev.getType() === EventType.RoomCanonicalAlias) {
|
switch (ev.getType()) {
|
||||||
// re-view the room so MatrixChat can manage the alias in the URL properly
|
case EventType.RoomCanonicalAlias:
|
||||||
dis.dispatch<ViewRoomPayload>({
|
// re-view the room so MatrixChat can manage the alias in the URL properly
|
||||||
action: Action.ViewRoom,
|
dis.dispatch<ViewRoomPayload>({
|
||||||
room_id: this.state.room.roomId,
|
action: Action.ViewRoom,
|
||||||
metricsTrigger: undefined, // room doesn't change
|
room_id: this.state.room.roomId,
|
||||||
});
|
metricsTrigger: undefined, // room doesn't change
|
||||||
return; // this event cannot affect permissions so bail
|
});
|
||||||
}
|
break;
|
||||||
|
|
||||||
this.updatePermissions(this.state.room);
|
case EventType.RoomTombstone:
|
||||||
|
this.setState({ tombstone: this.getRoomTombstone() });
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
this.updatePermissions(this.state.room);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRoomStateUpdate = (state: RoomState) => {
|
private onRoomStateUpdate = (state: RoomState) => {
|
||||||
|
@ -1201,9 +1207,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
if (room) {
|
if (room) {
|
||||||
const me = this.context.getUserId();
|
const me = this.context.getUserId();
|
||||||
const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me);
|
const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me);
|
||||||
const canReply = room.maySendMessage();
|
const canSendMessages = room.maySendMessage();
|
||||||
|
|
||||||
this.setState({ canReact, canReply });
|
this.setState({ canReact, canSendMessages });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -334,7 +334,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
||||||
// Like the resend button, the react and reply buttons need to appear before the edit.
|
// Like the resend button, the react and reply buttons need to appear before the edit.
|
||||||
// The only catch is we do the reply button first so that we can make sure the react
|
// The only catch is we do the reply button first so that we can make sure the react
|
||||||
// button is the very first button without having to do length checks for `splice()`.
|
// button is the very first button without having to do length checks for `splice()`.
|
||||||
if (this.context.canReply) {
|
if (this.context.canSendMessages) {
|
||||||
if (this.showReplyInThreadAction) {
|
if (this.showReplyInThreadAction) {
|
||||||
toolbarOpts.splice(0, 0, threadTooltipButton);
|
toolbarOpts.splice(0, 0, threadTooltipButton);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { IEventRelation, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { EventType, RelationType } from 'matrix-js-sdk/src/@types/event';
|
import { EventType, RelationType } from 'matrix-js-sdk/src/@types/event';
|
||||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
|
||||||
import { Optional } from "matrix-events-sdk";
|
import { Optional } from "matrix-events-sdk";
|
||||||
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
@ -81,8 +80,6 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
tombstone: MatrixEvent;
|
|
||||||
canSendMessages: boolean;
|
|
||||||
isComposerEmpty: boolean;
|
isComposerEmpty: boolean;
|
||||||
haveRecording: boolean;
|
haveRecording: boolean;
|
||||||
recordingTimeLeftSeconds?: number;
|
recordingTimeLeftSeconds?: number;
|
||||||
|
@ -114,8 +111,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
||||||
VoiceRecordingStore.instance.on(UPDATE_EVENT, this.onVoiceStoreUpdate);
|
VoiceRecordingStore.instance.on(UPDATE_EVENT, this.onVoiceStoreUpdate);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
tombstone: this.getRoomTombstone(),
|
|
||||||
canSendMessages: this.props.room.maySendMessage(),
|
|
||||||
isComposerEmpty: true,
|
isComposerEmpty: true,
|
||||||
haveRecording: false,
|
haveRecording: false,
|
||||||
recordingTimeLeftSeconds: null, // when set to a number, shows a toast
|
recordingTimeLeftSeconds: null, // when set to a number, shows a toast
|
||||||
|
@ -154,7 +149,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
MatrixClientPeg.get().on(RoomStateEvent.Events, this.onRoomStateEvents);
|
|
||||||
this.waitForOwnMember();
|
this.waitForOwnMember();
|
||||||
UIStore.instance.trackElementDimensions(`MessageComposer${this.instanceId}`, this.ref.current);
|
UIStore.instance.trackElementDimensions(`MessageComposer${this.instanceId}`, this.ref.current);
|
||||||
UIStore.instance.on(`MessageComposer${this.instanceId}`, this.onResize);
|
UIStore.instance.on(`MessageComposer${this.instanceId}`, this.onResize);
|
||||||
|
@ -217,9 +211,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
if (MatrixClientPeg.get()) {
|
|
||||||
MatrixClientPeg.get().removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
|
|
||||||
}
|
|
||||||
VoiceRecordingStore.instance.off(UPDATE_EVENT, this.onVoiceStoreUpdate);
|
VoiceRecordingStore.instance.off(UPDATE_EVENT, this.onVoiceStoreUpdate);
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
UIStore.instance.stopTrackingElementDimensions(`MessageComposer${this.instanceId}`);
|
UIStore.instance.stopTrackingElementDimensions(`MessageComposer${this.instanceId}`);
|
||||||
|
@ -229,25 +220,10 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
||||||
this.voiceRecording = null;
|
this.voiceRecording = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onRoomStateEvents = (ev: MatrixEvent) => {
|
|
||||||
if (ev.getRoomId() !== this.props.room.roomId) return;
|
|
||||||
|
|
||||||
if (ev.getType() === EventType.RoomTombstone) {
|
|
||||||
this.setState({ tombstone: this.getRoomTombstone() });
|
|
||||||
}
|
|
||||||
if (ev.getType() === EventType.RoomPowerLevels) {
|
|
||||||
this.setState({ canSendMessages: this.props.room.maySendMessage() });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private getRoomTombstone() {
|
|
||||||
return this.props.room.currentState.getStateEvents(EventType.RoomTombstone, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
private onTombstoneClick = (ev) => {
|
private onTombstoneClick = (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
|
const replacementRoomId = this.context.tombstone.getContent()['replacement_room'];
|
||||||
const replacementRoom = MatrixClientPeg.get().getRoom(replacementRoomId);
|
const replacementRoom = MatrixClientPeg.get().getRoom(replacementRoomId);
|
||||||
let createEventId = null;
|
let createEventId = null;
|
||||||
if (replacementRoom) {
|
if (replacementRoom) {
|
||||||
|
@ -255,7 +231,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
||||||
if (createEvent && createEvent.getId()) createEventId = createEvent.getId();
|
if (createEvent && createEvent.getId()) createEventId = createEvent.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
const viaServers = [this.state.tombstone.getSender().split(':').slice(1).join(':')];
|
const viaServers = [this.context.tombstone.getSender().split(':').slice(1).join(':')];
|
||||||
dis.dispatch<ViewRoomPayload>({
|
dis.dispatch<ViewRoomPayload>({
|
||||||
action: Action.ViewRoom,
|
action: Action.ViewRoom,
|
||||||
highlighted: true,
|
highlighted: true,
|
||||||
|
@ -374,7 +350,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
||||||
menuPosition = aboveLeftOf(contentRect);
|
menuPosition = aboveLeftOf(contentRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.state.tombstone && this.state.canSendMessages) {
|
const canSendMessages = this.context.canSendMessages && !this.context.tombstone;
|
||||||
|
if (canSendMessages) {
|
||||||
controls.push(
|
controls.push(
|
||||||
<SendMessageComposer
|
<SendMessageComposer
|
||||||
ref={this.messageComposerInput}
|
ref={this.messageComposerInput}
|
||||||
|
@ -393,8 +370,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
||||||
key="controls_voice_record"
|
key="controls_voice_record"
|
||||||
ref={this.voiceRecordingButton}
|
ref={this.voiceRecordingButton}
|
||||||
room={this.props.room} />);
|
room={this.props.room} />);
|
||||||
} else if (this.state.tombstone) {
|
} else if (this.context.tombstone) {
|
||||||
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
|
const replacementRoomId = this.context.tombstone.getContent()['replacement_room'];
|
||||||
|
|
||||||
const continuesLink = replacementRoomId ? (
|
const continuesLink = replacementRoomId ? (
|
||||||
<a href={makeRoomPermalink(replacementRoomId)}
|
<a href={makeRoomPermalink(replacementRoomId)}
|
||||||
|
@ -467,7 +444,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
||||||
permalinkCreator={this.props.permalinkCreator} />
|
permalinkCreator={this.props.permalinkCreator} />
|
||||||
<div className="mx_MessageComposer_row">
|
<div className="mx_MessageComposer_row">
|
||||||
{ controls }
|
{ controls }
|
||||||
{ this.state.canSendMessages && <MessageComposerButtons
|
{ canSendMessages && <MessageComposerButtons
|
||||||
addEmoji={this.addEmoji}
|
addEmoji={this.addEmoji}
|
||||||
haveRecording={this.state.haveRecording}
|
haveRecording={this.state.haveRecording}
|
||||||
isMenuOpen={this.state.isMenuOpen}
|
isMenuOpen={this.state.isMenuOpen}
|
||||||
|
|
|
@ -40,6 +40,7 @@ import { contextMenuBelow } from './RoomTile';
|
||||||
import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore';
|
import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore';
|
||||||
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
|
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
|
||||||
import { NotificationStateEvents } from '../../../stores/notifications/NotificationState';
|
import { NotificationStateEvents } from '../../../stores/notifications/NotificationState';
|
||||||
|
import RoomContext from "../../../contexts/RoomContext";
|
||||||
|
|
||||||
export interface ISearchInfo {
|
export interface ISearchInfo {
|
||||||
searchTerm: string;
|
searchTerm: string;
|
||||||
|
@ -73,6 +74,9 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
excludedRightPanelPhaseButtons: [],
|
excludedRightPanelPhaseButtons: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static contextType = RoomContext;
|
||||||
|
public context!: React.ContextType<typeof RoomContext>;
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
const notiStore = RoomNotificationStateStore.instance.getRoomState(props.room);
|
const notiStore = RoomNotificationStateStore.instance.getRoomState(props.room);
|
||||||
|
@ -200,7 +204,10 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const buttons: JSX.Element[] = [];
|
const buttons: JSX.Element[] = [];
|
||||||
|
|
||||||
if (this.props.inRoom && SettingsStore.getValue("showCallButtonsInComposer")) {
|
if (this.props.inRoom &&
|
||||||
|
!this.context.tombstone &&
|
||||||
|
SettingsStore.getValue("showCallButtonsInComposer")
|
||||||
|
) {
|
||||||
const voiceCallButton = <AccessibleTooltipButton
|
const voiceCallButton = <AccessibleTooltipButton
|
||||||
className="mx_RoomHeader_button mx_RoomHeader_voiceCallButton"
|
className="mx_RoomHeader_button mx_RoomHeader_voiceCallButton"
|
||||||
onClick={() => this.props.onCallPlaced(CallType.Voice)}
|
onClick={() => this.props.onCallPlaced(CallType.Voice)}
|
||||||
|
|
|
@ -46,7 +46,7 @@ const RoomContext = createContext<IRoomState>({
|
||||||
showTopUnreadMessagesBar: false,
|
showTopUnreadMessagesBar: false,
|
||||||
statusBarVisible: false,
|
statusBarVisible: false,
|
||||||
canReact: false,
|
canReact: false,
|
||||||
canReply: false,
|
canSendMessages: false,
|
||||||
layout: Layout.Group,
|
layout: Layout.Group,
|
||||||
lowBandwidth: false,
|
lowBandwidth: false,
|
||||||
alwaysShowTimestamps: false,
|
alwaysShowTimestamps: false,
|
||||||
|
|
|
@ -50,7 +50,7 @@ class WrappedMessagePanel extends React.Component {
|
||||||
room,
|
room,
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
canReact: true,
|
canReact: true,
|
||||||
canReply: true,
|
canSendMessages: true,
|
||||||
showReadReceipts: true,
|
showReadReceipts: true,
|
||||||
showRedactions: false,
|
showRedactions: false,
|
||||||
showJoinLeaves: false,
|
showJoinLeaves: false,
|
||||||
|
|
90
test/components/views/rooms/MessageComposer-test.tsx
Normal file
90
test/components/views/rooms/MessageComposer-test.tsx
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
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 * as React from "react";
|
||||||
|
import { mount, ReactWrapper } from "enzyme";
|
||||||
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
|
import '../../../skinned-sdk'; // Must be first for skinning to work
|
||||||
|
import { createTestClient, mkEvent, mkStubRoom, stubClient } from "../../../test-utils";
|
||||||
|
import MessageComposer from "../../../../src/components/views/rooms/MessageComposer";
|
||||||
|
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||||
|
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||||
|
import RoomContext from "../../../../src/contexts/RoomContext";
|
||||||
|
|
||||||
|
describe("MessageComposer", () => {
|
||||||
|
stubClient();
|
||||||
|
const cli = createTestClient();
|
||||||
|
const room = mkStubRoom("!roomId:server", "Room 1", cli);
|
||||||
|
|
||||||
|
it("Renders a SendMessageComposer and MessageComposerButtons by default", () => {
|
||||||
|
const wrapper = wrapAndRender((
|
||||||
|
<MessageComposer room={room} resizeNotifier={jest.fn()} permalinkCreator={jest.fn()} />
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(wrapper.find("SendMessageComposer")).toHaveLength(1);
|
||||||
|
expect(wrapper.find("MessageComposerButtons")).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Does not render a SendMessageComposer or MessageComposerButtons when user has no permission", () => {
|
||||||
|
const wrapper = wrapAndRender((
|
||||||
|
<MessageComposer room={room} resizeNotifier={jest.fn()} permalinkCreator={jest.fn()} />
|
||||||
|
), false);
|
||||||
|
|
||||||
|
expect(wrapper.find("SendMessageComposer")).toHaveLength(0);
|
||||||
|
expect(wrapper.find("MessageComposerButtons")).toHaveLength(0);
|
||||||
|
expect(wrapper.find(".mx_MessageComposer_noperm_error")).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Does not render a SendMessageComposer or MessageComposerButtons when room is tombstoned", () => {
|
||||||
|
const wrapper = wrapAndRender((
|
||||||
|
<MessageComposer room={room} resizeNotifier={jest.fn()} permalinkCreator={jest.fn()} />
|
||||||
|
), true, mkEvent({
|
||||||
|
event: true,
|
||||||
|
type: "m.room.tombstone",
|
||||||
|
room: room.roomId,
|
||||||
|
user: "@user1:server",
|
||||||
|
skey: "",
|
||||||
|
content: {},
|
||||||
|
ts: Date.now(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(wrapper.find("SendMessageComposer")).toHaveLength(0);
|
||||||
|
expect(wrapper.find("MessageComposerButtons")).toHaveLength(0);
|
||||||
|
expect(wrapper.find(".mx_MessageComposer_roomReplaced_header")).toHaveLength(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function wrapAndRender(component: React.ReactElement, canSendMessages = true, tombstone?: MatrixEvent): ReactWrapper {
|
||||||
|
const mockClient = MatrixClientPeg.get();
|
||||||
|
const roomId = "myroomid";
|
||||||
|
const room: any = {
|
||||||
|
currentState: undefined,
|
||||||
|
roomId,
|
||||||
|
client: mockClient,
|
||||||
|
getMember: function(userId: string): RoomMember {
|
||||||
|
return new RoomMember(roomId, userId);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return mount(
|
||||||
|
<MatrixClientContext.Provider value={mockClient}>
|
||||||
|
<RoomContext.Provider value={{ room, canSendMessages, tombstone }}>
|
||||||
|
{ component }
|
||||||
|
</RoomContext.Provider>
|
||||||
|
</MatrixClientContext.Provider>,
|
||||||
|
);
|
||||||
|
}
|
|
@ -160,7 +160,7 @@ function createRoomState(room: Room, narrow: boolean): IRoomState {
|
||||||
showTopUnreadMessagesBar: false,
|
showTopUnreadMessagesBar: false,
|
||||||
statusBarVisible: false,
|
statusBarVisible: false,
|
||||||
canReact: false,
|
canReact: false,
|
||||||
canReply: false,
|
canSendMessages: false,
|
||||||
layout: Layout.Group,
|
layout: Layout.Group,
|
||||||
lowBandwidth: false,
|
lowBandwidth: false,
|
||||||
alwaysShowTimestamps: false,
|
alwaysShowTimestamps: false,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import { mount, ReactWrapper } from 'enzyme';
|
||||||
import { Room, PendingEventOrdering, MatrixEvent, MatrixClient } from 'matrix-js-sdk/src/matrix';
|
import { Room, PendingEventOrdering, MatrixEvent, MatrixClient } from 'matrix-js-sdk/src/matrix';
|
||||||
|
|
||||||
import "../../../skinned-sdk";
|
import "../../../skinned-sdk";
|
||||||
|
@ -11,6 +11,8 @@ import { SearchScope } from '../../../../src/components/views/rooms/SearchBar';
|
||||||
import { E2EStatus } from '../../../../src/utils/ShieldUtils';
|
import { E2EStatus } from '../../../../src/utils/ShieldUtils';
|
||||||
import { PlaceCallType } from '../../../../src/CallHandler';
|
import { PlaceCallType } from '../../../../src/CallHandler';
|
||||||
import { mkEvent } from '../../../test-utils';
|
import { mkEvent } from '../../../test-utils';
|
||||||
|
import { IRoomState } from "../../../../src/components/structures/RoomView";
|
||||||
|
import RoomContext from '../../../../src/contexts/RoomContext';
|
||||||
|
|
||||||
describe('RoomHeader', () => {
|
describe('RoomHeader', () => {
|
||||||
it('shows the room avatar in a room with only ourselves', () => {
|
it('shows the room avatar in a room with only ourselves', () => {
|
||||||
|
@ -20,11 +22,11 @@ describe('RoomHeader', () => {
|
||||||
|
|
||||||
// Then the room's avatar is the initial of its name
|
// Then the room's avatar is the initial of its name
|
||||||
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
|
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
|
||||||
expect(initial.innerHTML).toEqual("X");
|
expect(initial.text()).toEqual("X");
|
||||||
|
|
||||||
// And there is no image avatar (because it's not set on this room)
|
// And there is no image avatar (because it's not set on this room)
|
||||||
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
||||||
expect(image.src).toEqual("data:image/png;base64,00");
|
expect(image.prop("src")).toEqual("data:image/png;base64,00");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the room avatar in a room with 2 people', () => {
|
it('shows the room avatar in a room with 2 people', () => {
|
||||||
|
@ -35,26 +37,25 @@ describe('RoomHeader', () => {
|
||||||
|
|
||||||
// Then the room's avatar is the initial of its name
|
// Then the room's avatar is the initial of its name
|
||||||
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
|
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
|
||||||
expect(initial.innerHTML).toEqual("Y");
|
expect(initial.text()).toEqual("Y");
|
||||||
|
|
||||||
// And there is no image avatar (because it's not set on this room)
|
// And there is no image avatar (because it's not set on this room)
|
||||||
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
||||||
expect(image.src).toEqual("data:image/png;base64,00");
|
expect(image.prop("src")).toEqual("data:image/png;base64,00");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the room avatar in a room with >2 people', () => {
|
it('shows the room avatar in a room with >2 people', () => {
|
||||||
// When we render a non-DM room with 3 people in it
|
// When we render a non-DM room with 3 people in it
|
||||||
const room = createRoom(
|
const room = createRoom({ name: "Z Room", isDm: false, userIds: ["other1", "other2"] });
|
||||||
{ name: "Z Room", isDm: false, userIds: ["other1", "other2"] });
|
|
||||||
const rendered = render(room);
|
const rendered = render(room);
|
||||||
|
|
||||||
// Then the room's avatar is the initial of its name
|
// Then the room's avatar is the initial of its name
|
||||||
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
|
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
|
||||||
expect(initial.innerHTML).toEqual("Z");
|
expect(initial.text()).toEqual("Z");
|
||||||
|
|
||||||
// And there is no image avatar (because it's not set on this room)
|
// And there is no image avatar (because it's not set on this room)
|
||||||
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
||||||
expect(image.src).toEqual("data:image/png;base64,00");
|
expect(image.prop("src")).toEqual("data:image/png;base64,00");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the room avatar in a DM with only ourselves', () => {
|
it('shows the room avatar in a DM with only ourselves', () => {
|
||||||
|
@ -64,11 +65,11 @@ describe('RoomHeader', () => {
|
||||||
|
|
||||||
// Then the room's avatar is the initial of its name
|
// Then the room's avatar is the initial of its name
|
||||||
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
|
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
|
||||||
expect(initial.innerHTML).toEqual("Z");
|
expect(initial.text()).toEqual("Z");
|
||||||
|
|
||||||
// And there is no image avatar (because it's not set on this room)
|
// And there is no image avatar (because it's not set on this room)
|
||||||
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
||||||
expect(image.src).toEqual("data:image/png;base64,00");
|
expect(image.prop("src")).toEqual("data:image/png;base64,00");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the user avatar in a DM with 2 people', () => {
|
it('shows the user avatar in a DM with 2 people', () => {
|
||||||
|
@ -81,13 +82,10 @@ describe('RoomHeader', () => {
|
||||||
|
|
||||||
// Then we use the other user's avatar as our room's image avatar
|
// Then we use the other user's avatar as our room's image avatar
|
||||||
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
||||||
expect(image.src).toEqual(
|
expect(image.prop("src")).toEqual("http://this.is.a.url/example.org/other");
|
||||||
"http://this.is.a.url/example.org/other");
|
|
||||||
|
|
||||||
// And there is no initial avatar
|
// And there is no initial avatar
|
||||||
expect(
|
expect(rendered.find(".mx_BaseAvatar_initial")).toHaveLength(0);
|
||||||
rendered.querySelectorAll(".mx_BaseAvatar_initial"),
|
|
||||||
).toHaveLength(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the room avatar in a DM with >2 people', () => {
|
it('shows the room avatar in a DM with >2 people', () => {
|
||||||
|
@ -98,11 +96,37 @@ describe('RoomHeader', () => {
|
||||||
|
|
||||||
// Then the room's avatar is the initial of its name
|
// Then the room's avatar is the initial of its name
|
||||||
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
|
const initial = findSpan(rendered, ".mx_BaseAvatar_initial");
|
||||||
expect(initial.innerHTML).toEqual("Z");
|
expect(initial.text()).toEqual("Z");
|
||||||
|
|
||||||
// And there is no image avatar (because it's not set on this room)
|
// And there is no image avatar (because it's not set on this room)
|
||||||
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
const image = findImg(rendered, ".mx_BaseAvatar_image");
|
||||||
expect(image.src).toEqual("data:image/png;base64,00");
|
expect(image.prop("src")).toEqual("data:image/png;base64,00");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders call buttons normally", () => {
|
||||||
|
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
|
||||||
|
const wrapper = render(room);
|
||||||
|
|
||||||
|
expect(wrapper.find('[aria-label="Voice call"]').hostNodes()).toHaveLength(1);
|
||||||
|
expect(wrapper.find('[aria-label="Video call"]').hostNodes()).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hides call buttons when the room is tombstoned", () => {
|
||||||
|
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
|
||||||
|
const wrapper = render(room, {
|
||||||
|
tombstone: mkEvent({
|
||||||
|
event: true,
|
||||||
|
type: "m.room.tombstone",
|
||||||
|
room: room.roomId,
|
||||||
|
user: "@user1:server",
|
||||||
|
skey: "",
|
||||||
|
content: {},
|
||||||
|
ts: Date.now(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find('[aria-label="Voice call"]').hostNodes()).toHaveLength(0);
|
||||||
|
expect(wrapper.find('[aria-label="Video call"]').hostNodes()).toHaveLength(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -147,11 +171,9 @@ function createRoom(info: IRoomCreationInfo) {
|
||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
|
|
||||||
function render(room: Room): HTMLDivElement {
|
function render(room: Room, roomContext?: Partial<IRoomState>): ReactWrapper {
|
||||||
const parentDiv = document.createElement('div');
|
return mount((
|
||||||
document.body.appendChild(parentDiv);
|
<RoomContext.Provider value={{ ...roomContext, room }}>
|
||||||
ReactDOM.render(
|
|
||||||
(
|
|
||||||
<RoomHeader
|
<RoomHeader
|
||||||
room={room}
|
room={room}
|
||||||
inRoom={true}
|
inRoom={true}
|
||||||
|
@ -167,10 +189,8 @@ function render(room: Room): HTMLDivElement {
|
||||||
searchCount: 0,
|
searchCount: 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
</RoomContext.Provider>
|
||||||
parentDiv,
|
));
|
||||||
);
|
|
||||||
return parentDiv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mkCreationEvent(roomId: string, userId: string): MatrixEvent {
|
function mkCreationEvent(roomId: string, userId: string): MatrixEvent {
|
||||||
|
@ -233,14 +253,14 @@ function mkDirectEvent(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function findSpan(parent: HTMLElement, selector: string): HTMLSpanElement {
|
function findSpan(wrapper: ReactWrapper, selector: string): ReactWrapper {
|
||||||
const els = parent.querySelectorAll(selector);
|
const els = wrapper.find(selector).hostNodes();
|
||||||
expect(els.length).toEqual(1);
|
expect(els).toHaveLength(1);
|
||||||
return els[0] as HTMLSpanElement;
|
return els.at(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findImg(parent: HTMLElement, selector: string): HTMLImageElement {
|
function findImg(wrapper: ReactWrapper, selector: string): ReactWrapper {
|
||||||
const els = parent.querySelectorAll(selector);
|
const els = wrapper.find(selector).hostNodes();
|
||||||
expect(els.length).toEqual(1);
|
expect(els).toHaveLength(1);
|
||||||
return els[0] as HTMLImageElement;
|
return els.at(0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ describe('<SendMessageComposer/>', () => {
|
||||||
showTopUnreadMessagesBar: false,
|
showTopUnreadMessagesBar: false,
|
||||||
statusBarVisible: false,
|
statusBarVisible: false,
|
||||||
canReact: false,
|
canReact: false,
|
||||||
canReply: false,
|
canSendMessages: false,
|
||||||
layout: Layout.Group,
|
layout: Layout.Group,
|
||||||
lowBandwidth: false,
|
lowBandwidth: false,
|
||||||
alwaysShowTimestamps: false,
|
alwaysShowTimestamps: false,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
RoomState,
|
RoomState,
|
||||||
EventType,
|
EventType,
|
||||||
IEventRelation,
|
IEventRelation,
|
||||||
|
IUnsigned,
|
||||||
} from 'matrix-js-sdk/src/matrix';
|
} from 'matrix-js-sdk/src/matrix';
|
||||||
|
|
||||||
import { MatrixClientPeg as peg } from '../../src/MatrixClientPeg';
|
import { MatrixClientPeg as peg } from '../../src/MatrixClientPeg';
|
||||||
|
@ -132,6 +133,7 @@ export function createTestClient() {
|
||||||
setPusher: jest.fn().mockResolvedValue(),
|
setPusher: jest.fn().mockResolvedValue(),
|
||||||
setPushRuleEnabled: jest.fn().mockResolvedValue(),
|
setPushRuleEnabled: jest.fn().mockResolvedValue(),
|
||||||
setPushRuleActions: jest.fn().mockResolvedValue(),
|
setPushRuleActions: jest.fn().mockResolvedValue(),
|
||||||
|
isCryptoEnabled: jest.fn().mockReturnValue(false),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,6 +149,7 @@ type MakeEventProps = MakeEventPassThruProps & {
|
||||||
room: Room["roomId"];
|
room: Room["roomId"];
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
prev_content?: IContent;
|
prev_content?: IContent;
|
||||||
|
unsigned?: IUnsigned;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -176,13 +179,13 @@ export function mkEvent(opts: MakeEventProps): MatrixEvent {
|
||||||
origin_server_ts: opts.ts ?? 0,
|
origin_server_ts: opts.ts ?? 0,
|
||||||
unsigned: opts.unsigned,
|
unsigned: opts.unsigned,
|
||||||
};
|
};
|
||||||
if (opts.skey) {
|
if (opts.skey !== undefined) {
|
||||||
event.state_key = opts.skey;
|
event.state_key = opts.skey;
|
||||||
} else if ([
|
} else if ([
|
||||||
"m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules",
|
"m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules",
|
||||||
"m.room.power_levels", "m.room.topic", "m.room.history_visibility",
|
"m.room.power_levels", "m.room.topic", "m.room.history_visibility",
|
||||||
"m.room.encryption", "m.room.member", "com.example.state",
|
"m.room.encryption", "m.room.member", "com.example.state",
|
||||||
"m.room.guest_access",
|
"m.room.guest_access", "m.room.tombstone",
|
||||||
].indexOf(opts.type) !== -1) {
|
].indexOf(opts.type) !== -1) {
|
||||||
event.state_key = "";
|
event.state_key = "";
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue