Ensure spaces in the spotlight dialog have rounded square avatars (#9480)

This commit is contained in:
Michael Telatynski 2022-10-24 09:58:36 +01:00 committed by GitHub
parent 913af09e61
commit eafc2d23a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 73 additions and 50 deletions

View file

@ -16,11 +16,10 @@ limitations under the License.
import React, { ComponentProps } from 'react';
import { Room } from 'matrix-js-sdk/src/models/room';
import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import classNames from "classnames";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import BaseAvatar from './BaseAvatar';
import ImageView from '../elements/ImageView';
@ -39,11 +38,7 @@ interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idNam
oobData?: IOOBData & {
roomId?: string;
};
width?: number;
height?: number;
resizeMethod?: ResizeMethod;
viewAvatarOnClick?: boolean;
className?: string;
onClick?(): void;
}
@ -72,10 +67,7 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
}
public componentWillUnmount() {
const cli = MatrixClientPeg.get();
if (cli) {
cli.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
}
MatrixClientPeg.get()?.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
}
public static getDerivedStateFromProps(nextProps: IProps): IState {
@ -133,7 +125,7 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
public render() {
const { room, oobData, viewAvatarOnClick, onClick, className, ...otherProps } = this.props;
const roomName = room ? room.name : oobData.name;
const roomName = room?.name ?? oobData.name;
// If the room is a DM, we use the other user's ID for the color hash
// in order to match the room avatar with their avatar
const idName = room ? (DMRoomMap.shared().getUserIdForRoomId(room.roomId) ?? room.roomId) : oobData.roomId;
@ -142,7 +134,7 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
<BaseAvatar
{...otherProps}
className={classNames(className, {
mx_RoomAvatar_isSpaceRoom: room?.isSpaceRoom(),
mx_RoomAvatar_isSpaceRoom: (room?.getType() ?? this.props.oobData?.roomType) === RoomType.Space,
})}
name={roomName}
idName={idName}

View file

@ -93,6 +93,7 @@ import { TooltipOption } from "./TooltipOption";
import { isLocalRoom } from "../../../../utils/localRoom/isLocalRoom";
import { useSlidingSyncRoomSearch } from "../../../../hooks/useSlidingSyncRoomSearch";
import { shouldShowFeedback } from "../../../../utils/Feedback";
import RoomAvatar from "../../avatars/RoomAvatar";
const MAX_RECENT_SEARCHES = 10;
const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons
@ -656,6 +657,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
shouldPeek: result.publicRoom.world_readable || cli.isGuest(),
}, true, ev.type !== "click");
};
return (
<Option
id={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}`}
@ -674,13 +676,14 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
aria-describedby={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_alias`}
aria-details={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_details`}
>
<BaseAvatar
<RoomAvatar
className="mx_SearchResultAvatar"
url={result?.publicRoom?.avatar_url
? mediaFromMxc(result?.publicRoom?.avatar_url).getSquareThumbnailHttp(AVATAR_SIZE)
: null}
name={result.publicRoom.name}
idName={result.publicRoom.room_id}
oobData={{
roomId: result.publicRoom.room_id,
name: result.publicRoom.name,
avatarUrl: result.publicRoom.avatar_url,
roomType: result.publicRoom.room_type,
}}
width={AVATAR_SIZE}
height={AVATAR_SIZE}
/>

View file

@ -263,9 +263,9 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
params: {
email: this.props.invitedEmail,
signurl: this.props.signUrl,
room_name: this.props.oobData ? this.props.oobData.room_name : null,
room_avatar_url: this.props.oobData ? this.props.oobData.avatarUrl : null,
inviter_name: this.props.oobData ? this.props.oobData.inviterName : null,
room_name: this.props.oobData?.name ?? null,
room_avatar_url: this.props.oobData?.avatarUrl ?? null,
inviter_name: this.props.oobData?.inviterName ?? null,
},
};
}

View file

@ -56,7 +56,7 @@ export interface IOOBData {
inviterName?: string; // The display name of the person who invited us to the room
// eslint-disable-next-line camelcase
room_name?: string; // The name of the room, to be used until we are told better by the server
roomType?: RoomType; // The type of the room, to be used until we are told better by the server
roomType?: RoomType | string; // The type of the room, to be used until we are told better by the server
}
const STORAGE_PREFIX = "mx_threepid_invite_";

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import { mocked } from "jest-mock";
import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
import { Room, RoomMember, RoomType } from "matrix-js-sdk/src/matrix";
import { avatarUrlForRoom } from "../src/Avatar";
import { Media, mediaFromMxc } from "../src/customisations/Media";
@ -46,6 +46,7 @@ describe("avatarUrlForRoom", () => {
roomId,
getMxcAvatarUrl: jest.fn(),
isSpaceRoom: jest.fn(),
getType: jest.fn(),
getAvatarFallbackMember: jest.fn(),
} as unknown as Room;
dmRoomMap = {
@ -70,6 +71,7 @@ describe("avatarUrlForRoom", () => {
it("should return null for a space room", () => {
mocked(room.isSpaceRoom).mockReturnValue(true);
mocked(room.getType).mockReturnValue(RoomType.Space);
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
});

View file

@ -16,6 +16,7 @@ limitations under the License.
import React from "react";
import { render, screen } from "@testing-library/react";
import { RoomType } from "matrix-js-sdk/src/@types/event";
import InviteDialog from "../../../../src/components/views/dialogs/InviteDialog";
import { KIND_INVITE } from "../../../../src/components/views/dialogs/InviteDialogTypes";
@ -74,6 +75,7 @@ describe("InviteDialog", () => {
it("should label with space name", () => {
mockClient.getRoom(roomId).isSpaceRoom = jest.fn().mockReturnValue(true);
mockClient.getRoom(roomId).getType = jest.fn().mockReturnValue(RoomType.Space);
mockClient.getRoom(roomId).name = "Space";
render((
<InviteDialog

View file

@ -140,6 +140,7 @@ describe('<UserInfo />', () => {
describe('with a room', () => {
const room = {
roomId: '!fkfk',
getType: jest.fn().mockReturnValue(undefined),
isSpaceRoom: jest.fn().mockReturnValue(false),
getMember: jest.fn().mockReturnValue(undefined),
getMxcAvatarUrl: jest.fn().mockReturnValue('mock-avatar-url'),

View file

@ -15,18 +15,14 @@ limitations under the License.
*/
import React from 'react';
import {
renderIntoDocument,
Simulate,
findRenderedDOMComponentWithClass,
act,
} from 'react-dom/test-utils';
import { render, fireEvent, RenderResult, waitFor } from "@testing-library/react";
import { Room, RoomMember, MatrixError, IContent } from 'matrix-js-sdk/src/matrix';
import { stubClient } from '../../../test-utils';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import DMRoomMap from '../../../../src/utils/DMRoomMap';
import RoomPreviewBar from '../../../../src/components/views/rooms/RoomPreviewBar';
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
jest.mock('../../../../src/IdentityAuthClient', () => {
return jest.fn().mockImplementation(() => {
@ -79,19 +75,18 @@ describe('<RoomPreviewBar />', () => {
const defaultProps = {
room: createRoom(roomId, userId),
};
const wrapper = renderIntoDocument<React.Component>(
<RoomPreviewBar {...defaultProps} {...props} />,
) as React.Component;
return findRenderedDOMComponentWithClass(wrapper, 'mx_RoomPreviewBar') as HTMLDivElement;
return render(<RoomPreviewBar {...defaultProps} {...props} />);
};
const isSpinnerRendered = (element: Element) => !!element.querySelector('.mx_Spinner');
const getMessage = (element: Element) => element.querySelector<HTMLDivElement>('.mx_RoomPreviewBar_message');
const getActions = (element: Element) => element.querySelector<HTMLDivElement>('.mx_RoomPreviewBar_actions');
const getPrimaryActionButton = (element: Element) =>
getActions(element).querySelector('.mx_AccessibleButton_kind_primary');
const getSecondaryActionButton = (element: Element) =>
getActions(element).querySelector('.mx_AccessibleButton_kind_secondary');
const isSpinnerRendered = (wrapper: RenderResult) => !!wrapper.container.querySelector('.mx_Spinner');
const getMessage = (wrapper: RenderResult) =>
wrapper.container.querySelector<HTMLDivElement>('.mx_RoomPreviewBar_message');
const getActions = (wrapper: RenderResult) =>
wrapper.container.querySelector<HTMLDivElement>('.mx_RoomPreviewBar_actions');
const getPrimaryActionButton = (wrapper: RenderResult) =>
getActions(wrapper).querySelector('.mx_AccessibleButton_kind_primary');
const getSecondaryActionButton = (wrapper: RenderResult) =>
getActions(wrapper).querySelector('.mx_AccessibleButton_kind_secondary');
beforeEach(() => {
stubClient();
@ -128,6 +123,36 @@ describe('<RoomPreviewBar />', () => {
expect(getMessage(component).textContent).toEqual('Join the conversation with an account');
});
it("should send room oob data to start login", async () => {
MatrixClientPeg.get().isGuest = jest.fn().mockReturnValue(true);
const component = getComponent({
oobData: {
name: "Room Name",
avatarUrl: "mxc://foo/bar",
inviterName: "Charlie",
},
});
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
expect(getMessage(component).textContent).toEqual('Join the conversation with an account');
fireEvent.click(getPrimaryActionButton(component));
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith(expect.objectContaining({
screenAfterLogin: {
screen: 'room',
params: expect.objectContaining({
room_name: "Room Name",
room_avatar_url: "mxc://foo/bar",
inviter_name: "Charlie",
}),
},
})));
defaultDispatcher.unregister(dispatcherRef);
});
it('renders kicked message', () => {
const room = createRoom(roomId, otherUserId);
jest.spyOn(room, 'getMember').mockReturnValue(makeMockRoomMember({ isKicked: true }));
@ -233,18 +258,14 @@ describe('<RoomPreviewBar />', () => {
it('joins room on primary button click', () => {
const component = getComponent({ inviterName, room, onJoinClick, onRejectClick });
act(() => {
Simulate.click(getPrimaryActionButton(component));
});
fireEvent.click(getPrimaryActionButton(component));
expect(onJoinClick).toHaveBeenCalled();
});
it('rejects invite on secondary button click', () => {
const component = getComponent({ inviterName, room, onJoinClick, onRejectClick });
act(() => {
Simulate.click(getSecondaryActionButton(component));
});
fireEvent.click(getSecondaryActionButton(component));
expect(onRejectClick).toHaveBeenCalled();
});
@ -296,9 +317,7 @@ describe('<RoomPreviewBar />', () => {
await new Promise(setImmediate);
expect(getPrimaryActionButton(component)).toBeTruthy();
expect(getSecondaryActionButton(component)).toBeFalsy();
act(() => {
Simulate.click(getPrimaryActionButton(component));
});
fireEvent.click(getPrimaryActionButton(component));
expect(onJoinClick).toHaveBeenCalled();
};

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import { mocked } from "jest-mock";
import { Room } from "matrix-js-sdk/src/matrix";
import { Room, RoomType } from "matrix-js-sdk/src/matrix";
import { VisibilityProvider } from "../../../../src/stores/room-list/filters/VisibilityProvider";
import LegacyCallHandler from "../../../../src/LegacyCallHandler";
@ -43,6 +43,7 @@ jest.mock("../../../../src/customisations/RoomList", () => ({
const createRoom = (isSpaceRoom = false): Room => {
return {
isSpaceRoom: () => isSpaceRoom,
getType: () => isSpaceRoom ? RoomType.Space : undefined,
} as unknown as Room;
};

View file

@ -31,6 +31,7 @@ import {
IEventRelation,
IUnsigned,
IPusher,
RoomType,
} from 'matrix-js-sdk/src/matrix';
import { normalize } from "matrix-js-sdk/src/utils";
import { ReEmitter } from "matrix-js-sdk/src/ReEmitter";
@ -447,6 +448,7 @@ export function mkStubRoom(roomId: string = null, name: string, client: MatrixCl
getAvatarUrl: () => 'mxc://avatar.url/room.png',
getMxcAvatarUrl: () => 'mxc://avatar.url/room.png',
isSpaceRoom: jest.fn().mockReturnValue(false),
getType: jest.fn().mockReturnValue(undefined),
isElementVideoRoom: jest.fn().mockReturnValue(false),
getUnreadNotificationCount: jest.fn(() => 0),
getEventReadUpTo: jest.fn(() => null),
@ -544,6 +546,7 @@ export const mkSpace = (
): MockedObject<Room> => {
const space = mocked(mkRoom(client, spaceId, rooms));
space.isSpaceRoom.mockReturnValue(true);
space.getType.mockReturnValue(RoomType.Space);
mocked(space.currentState).getStateEvents.mockImplementation(mockStateEventImplementation(children.map(roomId =>
mkEvent({
event: true,