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

View file

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

View file

@ -263,9 +263,9 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
params: { params: {
email: this.props.invitedEmail, email: this.props.invitedEmail,
signurl: this.props.signUrl, signurl: this.props.signUrl,
room_name: this.props.oobData ? this.props.oobData.room_name : null, room_name: this.props.oobData?.name ?? null,
room_avatar_url: this.props.oobData ? this.props.oobData.avatarUrl : null, room_avatar_url: this.props.oobData?.avatarUrl ?? null,
inviter_name: this.props.oobData ? this.props.oobData.inviterName : 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 inviterName?: string; // The display name of the person who invited us to the room
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
room_name?: string; // The name of the room, to be used until we are told better by the server 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_"; const STORAGE_PREFIX = "mx_threepid_invite_";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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