mirror of https://github.com/vector-im/riot-web
				
				
				
			Fix: Video Room Chat Header Button Removed (#11911)
* "@vector-im/compound-design-tokens": "^0.1.0" * add chat button to room header for video rooms * cleanup useEffect, comments * commentpull/28788/head^2
							parent
							
								
									4a0ffefae7
								
							
						
					
					
						commit
						668e3a3bd9
					
				| 
						 | 
				
			
			@ -70,7 +70,7 @@
 | 
			
		|||
        "@matrix-org/spec": "^1.7.0",
 | 
			
		||||
        "@sentry/browser": "^7.0.0",
 | 
			
		||||
        "@testing-library/react-hooks": "^8.0.1",
 | 
			
		||||
        "@vector-im/compound-design-tokens": "^0.0.7",
 | 
			
		||||
        "@vector-im/compound-design-tokens": "^0.1.0",
 | 
			
		||||
        "@vector-im/compound-web": "0.8.1",
 | 
			
		||||
        "@zxcvbn-ts/core": "^3.0.4",
 | 
			
		||||
        "@zxcvbn-ts/language-common": "^3.0.4",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,6 +50,7 @@ import { formatCount } from "../../../utils/FormattingUtils";
 | 
			
		|||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
 | 
			
		||||
import { Linkify, topicToHtml } from "../../../HtmlUtils";
 | 
			
		||||
import PosthogTrackers from "../../../PosthogTrackers";
 | 
			
		||||
import { VideoRoomChatButton } from "./RoomHeader/VideoRoomChatButton";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A helper to transform a notification color to the what the Compound Icon Button
 | 
			
		||||
| 
						 | 
				
			
			@ -215,6 +216,9 @@ export default function RoomHeader({
 | 
			
		|||
                    </Tooltip>
 | 
			
		||||
                )}
 | 
			
		||||
 | 
			
		||||
                {/* Renders nothing when room is not a video room */}
 | 
			
		||||
                <VideoRoomChatButton room={room} />
 | 
			
		||||
 | 
			
		||||
                <Tooltip label={_t("common|threads")}>
 | 
			
		||||
                    <IconButton
 | 
			
		||||
                        indicator={notificationColorToIndicator(threadNotifications)}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,75 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2023 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, { useContext } from "react";
 | 
			
		||||
import { Icon as ChatIcon } from "@vector-im/compound-design-tokens/icons/chat-solid.svg";
 | 
			
		||||
import { Room } from "matrix-js-sdk/src/matrix";
 | 
			
		||||
import { IconButton, Tooltip } from "@vector-im/compound-web";
 | 
			
		||||
 | 
			
		||||
import { isVideoRoom as calcIsVideoRoom } from "../../../../utils/video-rooms";
 | 
			
		||||
import { _t } from "../../../../languageHandler";
 | 
			
		||||
import { useEventEmitterState } from "../../../../hooks/useEventEmitter";
 | 
			
		||||
import { NotificationStateEvents } from "../../../../stores/notifications/NotificationState";
 | 
			
		||||
import { NotificationColor } from "../../../../stores/notifications/NotificationColor";
 | 
			
		||||
import { RightPanelPhases } from "../../../../stores/right-panel/RightPanelStorePhases";
 | 
			
		||||
import { SDKContext } from "../../../../contexts/SDKContext";
 | 
			
		||||
import { ButtonEvent } from "../../elements/AccessibleButton";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Display a button to toggle timeline for video rooms
 | 
			
		||||
 * @param room
 | 
			
		||||
 * @returns for a video room: a button to toggle timeline in the right panel
 | 
			
		||||
 *          otherwise null
 | 
			
		||||
 */
 | 
			
		||||
export const VideoRoomChatButton: React.FC<{ room: Room }> = ({ room }) => {
 | 
			
		||||
    const sdkContext = useContext(SDKContext);
 | 
			
		||||
 | 
			
		||||
    const isVideoRoom = calcIsVideoRoom(room);
 | 
			
		||||
 | 
			
		||||
    const notificationState = isVideoRoom ? sdkContext.roomNotificationStateStore.getRoomState(room) : undefined;
 | 
			
		||||
    const notificationColor = useEventEmitterState(
 | 
			
		||||
        notificationState,
 | 
			
		||||
        NotificationStateEvents.Update,
 | 
			
		||||
        () => notificationState?.color,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (!isVideoRoom) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const displayUnreadIndicator =
 | 
			
		||||
        !!notificationColor &&
 | 
			
		||||
        [NotificationColor.Bold, NotificationColor.Grey, NotificationColor.Red].includes(notificationColor);
 | 
			
		||||
 | 
			
		||||
    const onClick = (event: ButtonEvent): void => {
 | 
			
		||||
        // stop event propagating up and triggering RoomHeader bar click
 | 
			
		||||
        // which will open RoomSummary
 | 
			
		||||
        event.stopPropagation();
 | 
			
		||||
        sdkContext.rightPanelStore.showOrHidePanel(RightPanelPhases.Timeline);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Tooltip label={_t("right_panel|video_room_chat|title")}>
 | 
			
		||||
            <IconButton
 | 
			
		||||
                aria-label={_t("right_panel|video_room_chat|title")}
 | 
			
		||||
                onClick={onClick}
 | 
			
		||||
                indicator={displayUnreadIndicator ? "default" : undefined}
 | 
			
		||||
            >
 | 
			
		||||
                <ChatIcon />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
        </Tooltip>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,144 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2023 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 { MockedObject } from "jest-mock";
 | 
			
		||||
import { Room } from "matrix-js-sdk/src/matrix";
 | 
			
		||||
import { fireEvent, render, screen } from "@testing-library/react";
 | 
			
		||||
 | 
			
		||||
import { VideoRoomChatButton } from "../../../../../src/components/views/rooms/RoomHeader/VideoRoomChatButton";
 | 
			
		||||
import { SDKContext, SdkContextClass } from "../../../../../src/contexts/SDKContext";
 | 
			
		||||
import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore";
 | 
			
		||||
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../test-utils";
 | 
			
		||||
import { RoomNotificationState } from "../../../../../src/stores/notifications/RoomNotificationState";
 | 
			
		||||
import { NotificationColor } from "../../../../../src/stores/notifications/NotificationColor";
 | 
			
		||||
import { NotificationStateEvents } from "../../../../../src/stores/notifications/NotificationState";
 | 
			
		||||
import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases";
 | 
			
		||||
 | 
			
		||||
describe("<VideoRoomChatButton />", () => {
 | 
			
		||||
    const roomId = "!room:server.org";
 | 
			
		||||
    let sdkContext!: SdkContextClass;
 | 
			
		||||
    let rightPanelStore!: MockedObject<RightPanelStore>;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a room using mocked client
 | 
			
		||||
     * And mock isElementVideoRoom
 | 
			
		||||
     */
 | 
			
		||||
    const makeRoom = (isVideoRoom = true): Room => {
 | 
			
		||||
        const room = new Room(roomId, sdkContext.client!, sdkContext.client!.getSafeUserId());
 | 
			
		||||
        jest.spyOn(room, "isElementVideoRoom").mockReturnValue(isVideoRoom);
 | 
			
		||||
        // stub
 | 
			
		||||
        jest.spyOn(room, "getPendingEvents").mockReturnValue([]);
 | 
			
		||||
        return room;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const mockRoomNotificationState = (room: Room, color: NotificationColor): RoomNotificationState => {
 | 
			
		||||
        const roomNotificationState = new RoomNotificationState(room);
 | 
			
		||||
 | 
			
		||||
        // @ts-ignore ugly mocking
 | 
			
		||||
        roomNotificationState._color = color;
 | 
			
		||||
        jest.spyOn(sdkContext.roomNotificationStateStore, "getRoomState").mockReturnValue(roomNotificationState);
 | 
			
		||||
        return roomNotificationState;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const getComponent = (room: Room) =>
 | 
			
		||||
        render(<VideoRoomChatButton room={room} />, {
 | 
			
		||||
            wrapper: ({ children }) => <SDKContext.Provider value={sdkContext}>{children}</SDKContext.Provider>,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
        const client = getMockClientWithEventEmitter({
 | 
			
		||||
            ...mockClientMethodsUser(),
 | 
			
		||||
        });
 | 
			
		||||
        rightPanelStore = {
 | 
			
		||||
            showOrHidePanel: jest.fn(),
 | 
			
		||||
        } as unknown as MockedObject<RightPanelStore>;
 | 
			
		||||
        sdkContext = new SdkContextClass();
 | 
			
		||||
        sdkContext.client = client;
 | 
			
		||||
        jest.spyOn(sdkContext, "rightPanelStore", "get").mockReturnValue(rightPanelStore);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        jest.restoreAllMocks();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("does not render button when room is not a video room", () => {
 | 
			
		||||
        const room = makeRoom(false);
 | 
			
		||||
        getComponent(room);
 | 
			
		||||
 | 
			
		||||
        expect(screen.queryByLabelText("Chat")).not.toBeInTheDocument();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("renders button when room is a video room", () => {
 | 
			
		||||
        const room = makeRoom();
 | 
			
		||||
        getComponent(room);
 | 
			
		||||
 | 
			
		||||
        expect(screen.getByLabelText("Chat")).toMatchSnapshot();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("toggles timeline in right panel on click", () => {
 | 
			
		||||
        const room = makeRoom();
 | 
			
		||||
        getComponent(room);
 | 
			
		||||
 | 
			
		||||
        fireEvent.click(screen.getByLabelText("Chat"));
 | 
			
		||||
 | 
			
		||||
        expect(sdkContext.rightPanelStore.showOrHidePanel).toHaveBeenCalledWith(RightPanelPhases.Timeline);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("renders button with an unread marker when room is unread", () => {
 | 
			
		||||
        const room = makeRoom();
 | 
			
		||||
        mockRoomNotificationState(room, NotificationColor.Bold);
 | 
			
		||||
        getComponent(room);
 | 
			
		||||
 | 
			
		||||
        // snapshot includes `data-indicator` attribute
 | 
			
		||||
        expect(screen.getByLabelText("Chat")).toMatchSnapshot();
 | 
			
		||||
        expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeTruthy();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("adds unread marker when room notification state changes to unread", () => {
 | 
			
		||||
        const room = makeRoom();
 | 
			
		||||
        // start in read state
 | 
			
		||||
        const notificationState = mockRoomNotificationState(room, NotificationColor.None);
 | 
			
		||||
        getComponent(room);
 | 
			
		||||
 | 
			
		||||
        // no unread marker
 | 
			
		||||
        expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeFalsy();
 | 
			
		||||
 | 
			
		||||
        // @ts-ignore ugly mocking
 | 
			
		||||
        notificationState._color = NotificationColor.Red;
 | 
			
		||||
        notificationState.emit(NotificationStateEvents.Update);
 | 
			
		||||
 | 
			
		||||
        // unread marker
 | 
			
		||||
        expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeTruthy();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("clears unread marker when room notification state changes to read", () => {
 | 
			
		||||
        const room = makeRoom();
 | 
			
		||||
        // start in unread state
 | 
			
		||||
        const notificationState = mockRoomNotificationState(room, NotificationColor.Red);
 | 
			
		||||
        getComponent(room);
 | 
			
		||||
 | 
			
		||||
        // unread marker
 | 
			
		||||
        expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeTruthy();
 | 
			
		||||
 | 
			
		||||
        // @ts-ignore ugly mocking
 | 
			
		||||
        notificationState._color = NotificationColor.None;
 | 
			
		||||
        notificationState.emit(NotificationStateEvents.Update);
 | 
			
		||||
 | 
			
		||||
        // unread marker cleared
 | 
			
		||||
        expect(screen.getByLabelText("Chat").hasAttribute("data-indicator")).toBeFalsy();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
			
		||||
 | 
			
		||||
exports[`<VideoRoomChatButton /> renders button when room is a video room 1`] = `
 | 
			
		||||
<button
 | 
			
		||||
  aria-label="Chat"
 | 
			
		||||
  class="_icon-button_1qjaf_17"
 | 
			
		||||
  data-state="closed"
 | 
			
		||||
  style="--cpd-icon-button-size: 32px;"
 | 
			
		||||
>
 | 
			
		||||
  <div />
 | 
			
		||||
</button>
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`<VideoRoomChatButton /> renders button with an unread marker when room is unread 1`] = `
 | 
			
		||||
<button
 | 
			
		||||
  aria-label="Chat"
 | 
			
		||||
  class="_icon-button_1qjaf_17"
 | 
			
		||||
  data-indicator="default"
 | 
			
		||||
  data-state="closed"
 | 
			
		||||
  style="--cpd-icon-button-size: 32px;"
 | 
			
		||||
>
 | 
			
		||||
  <div />
 | 
			
		||||
</button>
 | 
			
		||||
`;
 | 
			
		||||
| 
						 | 
				
			
			@ -3037,10 +3037,10 @@
 | 
			
		|||
  resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
 | 
			
		||||
  integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
 | 
			
		||||
 | 
			
		||||
"@vector-im/compound-design-tokens@^0.0.7":
 | 
			
		||||
  version "0.0.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-0.0.7.tgz#b0716dd4782dd95900491e45b003b58f93748024"
 | 
			
		||||
  integrity sha512-RCQc6qr+s8cp4xKbNi/I3OL43uPCH+N4L9vYf0r+qwRy4WCKdI4QL0TBTV4bOo8hF49z8e+BgU5ZIu5TVQXNMQ==
 | 
			
		||||
"@vector-im/compound-design-tokens@^0.1.0":
 | 
			
		||||
  version "0.1.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-0.1.0.tgz#1a574fba872ff93b1de8490f475e30b922cd02a2"
 | 
			
		||||
  integrity sha512-vnDrd1CPPR7CwQLss/JnIE1ga6QwmCkhgBvXm1huMhCs7nIiqf90Sbgc0WugbHNaRXGEEhMVGrE69DaQIUcqOA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    svg2vectordrawable "^2.9.1"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue