From dbcfbcd7e8a2ad80aab1c38152af993eaac2e342 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 24 Jul 2024 16:09:41 +0200 Subject: [PATCH 01/13] Fix pinning event loading after restart --- src/components/views/right_panel/PinnedMessagesCard.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index f813dd3427f..94f36456113 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -118,6 +118,11 @@ const PinnedMessagesCard: React.FC = ({ room, onClose, permalinkCreator ?.getTimelineForEvent(eventId) ?.getEvents() .find((e) => e.getId() === eventId); + + if (localEvent?.isEncrypted()) { + await cli.decryptEventIfNeeded(localEvent); + } + if (localEvent) return PinningUtils.isPinnable(localEvent) ? localEvent : null; try { From 58b21f159771babeccaba6b9fb88710daeb4f10c Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 6 Aug 2024 17:36:31 +0200 Subject: [PATCH 02/13] Update deps --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b5429f98ef2..0295ed3a73a 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "@matrix-org/spec": "^1.7.0", "@sentry/browser": "^8.0.0", "@testing-library/react-hooks": "^8.0.1", - "@vector-im/compound-design-tokens": "^1.6.1", + "@vector-im/compound-design-tokens": "^1.8.0", "@vector-im/compound-web": "^5.5.0", "@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4", diff --git a/yarn.lock b/yarn.lock index 83be6aa604d..8496377c4fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3012,10 +3012,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@^1.6.1": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.6.1.tgz#3f1bb5b2b9f8aff10144aab19dfa11165c3c927b" - integrity sha512-u5xG/8AN7QkPPWhugj0ZrQtWsAjuKHzuOoP0s3bbDg7ZkKTE9l5tM29bdOHnSv9mEYKO+KVMMfsl0W1rlaTmAw== +"@vector-im/compound-design-tokens@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.8.0.tgz#bc844cb6b9842c1eb8e5c42f5cedcaf51a49b86f" + integrity sha512-PtQMG7kDzwtjw/fLKD63uWP5rJ8cgWc/aXarfEzxYUf9ceWxBajnYOBI2jDqtE3WIUe9uTVBzNEvmOBG/VIgTA== "@vector-im/compound-web@^5.5.0": version "5.5.0" From d7df4d9828f724daebef3bd549cae44c0597c52d Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 25 Jul 2024 16:54:00 +0200 Subject: [PATCH 03/13] Replace pinned event list --- .../right_panel/_PinnedMessagesCard.pcss | 34 +++ res/css/views/rooms/_PinnedEventTile.pcss | 112 ++------ .../views/right_panel/PinnedMessagesCard.tsx | 111 ++++++-- .../views/rooms/PinnedEventTile.tsx | 266 ++++++++++++------ src/i18n/strings/en_EN.json | 10 +- 5 files changed, 328 insertions(+), 205 deletions(-) diff --git a/res/css/views/right_panel/_PinnedMessagesCard.pcss b/res/css/views/right_panel/_PinnedMessagesCard.pcss index 5cdafcf7c5d..169c5dc06da 100644 --- a/res/css/views/right_panel/_PinnedMessagesCard.pcss +++ b/res/css/views/right_panel/_PinnedMessagesCard.pcss @@ -59,6 +59,40 @@ limitations under the License. } } + --unpin-height: 76px; + + .mx_PinnedMessagesCard_wrapper { + display: flex; + flex-direction: column; + padding: var(--cpd-space-4x); + gap: var(--cpd-space-6x); + overflow-y: auto; + + .mx_PinnedMessagesCard_Separator { + min-height: 1px; + /* Override default compound value */ + margin-block: 0; + } + } + + .mx_PinnedMessagesCard_wrapper_unpin_all { + /* Remove the unpin all button height and the top and bottom padding */ + height: calc(100% - var(--unpin-height) - calc(var(--cpd-space-4x) * 2)); + } + + .mx_PinnedMessagesCard_unpin { + /* Make it float at the bottom of the unpin panel */ + position: absolute; + bottom: 0; + width: 100%; + height: var(--unpin-height); + display: flex; + justify-content: center; + align-items: center; + box-shadow: 0 4px 24px 0 rgba(27, 29, 34, 0.1); + background: var(--cpd-color-bg-canvas-default); + } + .mx_EventTile_body { word-break: break-word; } diff --git a/res/css/views/rooms/_PinnedEventTile.pcss b/res/css/views/rooms/_PinnedEventTile.pcss index e755c3a71d5..52bf7c2d52e 100644 --- a/res/css/views/rooms/_PinnedEventTile.pcss +++ b/res/css/views/rooms/_PinnedEventTile.pcss @@ -15,95 +15,29 @@ limitations under the License. */ .mx_PinnedEventTile { - min-height: 40px; - width: 100%; - padding: 0 4px 12px; - - display: grid; - grid-template-areas: - "avatar name remove" - "content content content" - "footer footer footer"; - grid-template-rows: max-content auto max-content; - grid-template-columns: 24px auto 24px; - grid-row-gap: 12px; - grid-column-gap: 8px; - - & + .mx_PinnedEventTile { - padding: 12px 4px; - border-top: 1px solid $menu-border-color; - } - - .mx_PinnedEventTile_senderAvatar, - .mx_PinnedEventTile_sender, - .mx_PinnedEventTile_unpinButton, - .mx_PinnedEventTile_message, - .mx_PinnedEventTile_footer { - min-width: 0; /* Prevent a grid blowout */ - } - - .mx_PinnedEventTile_senderAvatar { - grid-area: avatar; - } - - .mx_PinnedEventTile_sender { - grid-area: name; - font-weight: var(--cpd-font-weight-semibold); - font-size: $font-15px; - line-height: $font-24px; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - } - - .mx_PinnedEventTile_unpinButton { - visibility: hidden; - grid-area: remove; - position: relative; - width: 24px; - height: 24px; - border-radius: 8px; - - &:hover { - background-color: $roomheader-addroom-bg-color; - } - - &::before { - content: ""; - position: absolute; - height: inherit; - width: inherit; - background: $secondary-content; - mask-position: center; - mask-size: 8px; - mask-repeat: no-repeat; - mask-image: url("$(res)/img/image-view/close.svg"); - } - } - - .mx_PinnedEventTile_message { - grid-area: content; - } - - .mx_PinnedEventTile_footer { - grid-area: footer; - font-size: $font-10px; - line-height: 12px; - - .mx_PinnedEventTile_timestamp { - color: $secondary-content; - display: unset; - width: unset; /* Cancel the default width value */ - } - - .mx_AccessibleButton_kind_link { - margin-left: 12px; - } - } - - &:hover { - .mx_PinnedEventTile_unpinButton { - visibility: visible; + display: flex; + gap: var(--cpd-space-4x); + align-items: flex-start; + + .mx_PinnedEventTile_wrapper { + display: flex; + flex-direction: column; + /* No 3px token in compound */ + gap: 3px; + width: 100%; + + .mx_PinnedEventTile_top { + display: flex; + /* No 3px token in compound */ + gap: 3px; + justify-content: space-between; + align-items: center; + + .mx_PinnedEventTile_sender { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } } } } diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 94f36456113..4a0179230d2 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -14,9 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback, useContext, useEffect, useState } from "react"; +import React, { useCallback, useContext, useEffect, useState, JSX } from "react"; import { Room, RoomEvent, RoomStateEvent, MatrixEvent, EventType, RelationType } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; +import { Button, Separator } from "@vector-im/compound-web"; +import classNames from "classnames"; import { Icon as ContextMenuIcon } from "../../../../res/img/element-icons/context-menu.svg"; import { Icon as EmojiIcon } from "../../../../res/img/element-icons/room/message-bar/emoji.svg"; @@ -24,11 +26,11 @@ import { Icon as ReplyIcon } from "../../../../res/img/element-icons/room/messag import { _t } from "../../../languageHandler"; import BaseCard from "./BaseCard"; import Spinner from "../elements/Spinner"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import MatrixClientContext, { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; import { useTypedEventEmitter } from "../../../hooks/useEventEmitter"; import PinningUtils from "../../../utils/PinningUtils"; import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; -import PinnedEventTile from "../rooms/PinnedEventTile"; +import { PinnedEventTile } from "../rooms/PinnedEventTile"; import { useRoomState } from "../../../hooks/useRoomState"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import { ReadPinsEventId } from "./types"; @@ -95,7 +97,6 @@ export const useReadPinnedEvents = (room?: Room): Set => { const PinnedMessagesCard: React.FC = ({ room, onClose, permalinkCreator }) => { const cli = useContext(MatrixClientContext); const roomContext = useContext(RoomContext); - const canUnpin = useRoomState(room, (state) => state.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli)); const pinnedEventIds = usePinnedEvents(room); const readPinnedEvents = useReadPinnedEvents(room); @@ -138,7 +139,7 @@ const PinnedMessagesCard: React.FC = ({ room, onClose, permalinkCreator ]); const event = new MatrixEvent(evJson); if (event.isEncrypted()) { - await cli.decryptEventIfNeeded(event); // TODO await? + await cli.decryptEventIfNeeded(event); } await room.processPollEvents([event]); @@ -196,29 +197,9 @@ const PinnedMessagesCard: React.FC = ({ room, onClose, permalinkCreator ); } else if (pinnedEvents?.length) { - const onUnpinClicked = async (event: MatrixEvent): Promise => { - const pinnedEvents = room.currentState.getStateEvents(EventType.RoomPinnedEvents, ""); - if (pinnedEvents?.getContent()?.pinned) { - const pinned = pinnedEvents.getContent().pinned; - const index = pinned.indexOf(event.getId()); - if (index !== -1) { - pinned.splice(index, 1); - await cli.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned }, ""); - } - } - }; - - // show them in reverse, with latest pinned at the top - content = filterBoolean(pinnedEvents) - .reverse() - .map((ev) => ( - onUnpinClicked(ev) : undefined} - permalinkCreator={permalinkCreator} - /> - )); + content = ( + + ); } else { content = ; } @@ -228,7 +209,7 @@ const PinnedMessagesCard: React.FC = ({ room, onClose, permalinkCreator header={
- {_t("right_panel|pinned_messages|title")} + {_t("right_panel|pinned_messages|header", { count: pinnedEventIds.length })}
} @@ -248,3 +229,75 @@ const PinnedMessagesCard: React.FC = ({ room, onClose, permalinkCreator }; export default PinnedMessagesCard; + +/** + * The pinned messages in a room. + */ +interface PinnedMessagesProps { + /** + * The pinned events. + */ + events: MatrixEvent[]; + /** + * The room the events are in. + */ + room: Room; + /** + * The permalink creator to use. + */ + permalinkCreator: RoomPermalinkCreator; +} + +/** + * The pinned messages in a room. + */ +function PinnedMessages({ events, room, permalinkCreator }: PinnedMessagesProps): JSX.Element { + const matrixClient = useMatrixClientContext(); + + /** + * Whether the client can unpin events from the room. + */ + const canUnpin = useRoomState(room, (state) => + state.mayClientSendStateEvent(EventType.RoomPinnedEvents, matrixClient), + ); + + /** + * Unpin all events from the room. + */ + const onUnpinAll = useCallback(async (): Promise => { + await matrixClient.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned: [] }, ""); + }, [room, matrixClient]); + + return ( + <> +
+ {events.reverse().map((event, i) => ( + <> + + {/* Add a separator if this isn't the last pinned message */} + {events.length - 1 !== i && ( + + )} + + ))} +
+ {canUnpin && ( +
+ +
+ )} + + ); +} diff --git a/src/components/views/rooms/PinnedEventTile.tsx b/src/components/views/rooms/PinnedEventTile.tsx index 6c63efc3520..5252e5124d7 100644 --- a/src/components/views/rooms/PinnedEventTile.tsx +++ b/src/components/views/rooms/PinnedEventTile.tsx @@ -15,112 +15,206 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; -import { MatrixEvent, EventType, RelationType, Relations } from "matrix-js-sdk/src/matrix"; +import React, { JSX, useCallback, useState } from "react"; +import { EventTimeline, EventType, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; +import { IconButton, Menu, MenuItem, Separator, Text } from "@vector-im/compound-web"; +import { Icon as ViewIcon } from "@vector-im/compound-design-tokens/icons/visibility-on.svg"; +import { Icon as UnpinIcon } from "@vector-im/compound-design-tokens/icons/unpin.svg"; +import { Icon as ForwardIcon } from "@vector-im/compound-design-tokens/icons/forward.svg"; +import { Icon as TriggerIcon } from "@vector-im/compound-design-tokens/icons/overflow-horizontal.svg"; +import { Icon as DeleteIcon } from "@vector-im/compound-design-tokens/icons/delete.svg"; +import classNames from "classnames"; import dis from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; -import AccessibleButton from "../elements/AccessibleButton"; import MessageEvent from "../messages/MessageEvent"; import MemberAvatar from "../avatars/MemberAvatar"; import { _t } from "../../../languageHandler"; -import { formatDate } from "../../../DateUtils"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { getUserNameColorClass } from "../../../utils/FormattingUtils"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; - -interface IProps { +import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; +import { useRoomState } from "../../../hooks/useRoomState"; +import { isContentActionable } from "../../../utils/EventUtils"; +import { getForwardableEvent } from "../../../events"; +import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload"; +import { createRedactEventDialog } from "../dialogs/ConfirmRedactDialog"; + +const AVATAR_SIZE = "32px"; + +/** + * Properties for {@link PinnedEventTile}. + */ +interface PinnedEventTileProps { + /** + * The event to display. + */ event: MatrixEvent; + /** + * The permalink creator to use. + */ permalinkCreator: RoomPermalinkCreator; - onUnpinClicked?(): void; + /** + * The room the event is in. + */ + room: Room; } -const AVATAR_SIZE = "24px"; +/** + * A pinned event tile. + */ +export function PinnedEventTile({ event, room, permalinkCreator }: PinnedEventTileProps): JSX.Element { + const sender = event.getSender(); + if (!sender) { + throw new Error("Pinned event unexpectedly has no sender"); + } -export default class PinnedEventTile extends React.Component { - public static contextType = MatrixClientContext; - public declare context: React.ContextType; + return ( +
+
+ +
+
+
+ + {event.sender?.name || sender} + + +
+ {}} // we need to give this, apparently + permalinkCreator={permalinkCreator} + replacingEventId={event.replacingEventId()} + /> +
+
+ ); +} - private onTileClicked = (): void => { +/** + * Properties for {@link PinMenu}. + */ +interface PinMenuProps extends PinnedEventTileProps {} + +/** + * A popover menu with actions on the pinned event + */ +function PinMenu({ event, room, permalinkCreator }: PinMenuProps): JSX.Element { + const [open, setOpen] = useState(false); + const matrixClient = useMatrixClientContext(); + + /** + * View the event in the timeline. + */ + const onViewInTimeline = useCallback(() => { dis.dispatch({ action: Action.ViewRoom, - event_id: this.props.event.getId(), + event_id: event.getId(), highlighted: true, - room_id: this.props.event.getRoomId(), + room_id: event.getRoomId(), metricsTrigger: undefined, // room doesn't change }); - }; - - // For event types like polls that use relations, we fetch those manually on - // mount and store them here, exposing them through getRelationsForEvent - private relations = new Map>(); - private getRelationsForEvent = ( - eventId: string, - relationType: RelationType | string, - eventType: EventType | string, - ): Relations | undefined => { - if (eventId === this.props.event.getId()) { - return this.relations.get(relationType)?.get(eventType); - } - }; - - public render(): React.ReactNode { - const sender = this.props.event.getSender(); - - if (!sender) { - throw new Error("Pinned event unexpectedly has no sender"); + }, [event]); + + /** + * Whether the client can unpin the event. + * Pin and unpin are using the same permission. + */ + const canUnpin = useRoomState(room, (state) => + state.mayClientSendStateEvent(EventType.RoomPinnedEvents, matrixClient), + ); + + /** + * Unpin the event. + * @param event + */ + const onUnpin = useCallback(async (): Promise => { + const pinnedEvents = room + .getLiveTimeline() + .getState(EventTimeline.FORWARDS) + ?.getStateEvents(EventType.RoomPinnedEvents, ""); + if (pinnedEvents?.getContent()?.pinned) { + const pinned = pinnedEvents.getContent().pinned; + const index = pinned.indexOf(event.getId()); + if (index !== -1) { + pinned.splice(index, 1); + await matrixClient.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned }, ""); + } } - - let unpinButton: JSX.Element | undefined; - if (this.props.onUnpinClicked) { - unpinButton = ( - - ); + }, [event, room, matrixClient]); + + const contentActionable = isContentActionable(event); + // Get the forwardable event for the given event + const forwardableEvent = contentActionable && getForwardableEvent(event, matrixClient); + /** + * Open the forward dialog. + */ + const onForward = useCallback(() => { + if (forwardableEvent) { + dis.dispatch({ + action: Action.OpenForwardDialog, + event: forwardableEvent, + permalinkCreator: permalinkCreator, + }); } - - return ( -
- - - - {this.props.event.sender?.name || sender} - - - {unpinButton} - -
- {}} // we need to give this, apparently - permalinkCreator={this.props.permalinkCreator} - replacingEventId={this.props.event.replacingEventId()} - /> -
- -
- - {formatDate(new Date(this.props.event.getTs()))} - - - - {_t("common|view_message")} - -
-
- ); - } + }, [forwardableEvent, permalinkCreator]); + + /** + * Whether the client can redact the event. + */ + const canRedact = + room + .getLiveTimeline() + .getState(EventTimeline.FORWARDS) + ?.maySendRedactionForEvent(event, matrixClient.getSafeUserId()) && + event.getType() !== EventType.RoomServerAcl && + event.getType() !== EventType.RoomEncryption; + + /** + * Redact the event. + */ + const onRedact = useCallback( + (): void => + createRedactEventDialog({ + mxEvent: event, + }), + [event], + ); + + return ( + + + + } + > + + {canUnpin && } + {forwardableEvent && } + {canRedact && ( + <> + + + + )} + + ); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ff216691ec3..c4347731143 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1841,10 +1841,18 @@ "pinned_messages": { "empty": "Nothing pinned, yet", "explainer": "If you have permissions, open the menu on any message and select Pin to stick them here.", + "header": { + "one": "1 Pinned message", + "other": "%(count)s Pinned messages", + "zero": "Pinned message" + }, "limits": { "other": "You can only pin up to %(count)s widgets" }, - "title": "Pinned messages" + "menu": "Open menu", + "title": "Pinned messages", + "unpin_all": "Unpin all messages", + "view": "View in timeline" }, "pinned_messages_button": "Pinned messages", "poll": { From 30a0e8f514713c78d9d6721840b4e66a0c494213 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 7 Aug 2024 18:03:50 +0200 Subject: [PATCH 04/13] Add a dialog to confirm to unpin all messages --- res/css/_common.pcss | 12 +-- res/css/_components.pcss | 1 + res/css/views/dialogs/_UnpinAllDialog.pcss | 38 +++++++++ .../views/dialogs/UnpinAllDialog.tsx | 77 +++++++++++++++++++ .../views/right_panel/PinnedMessagesCard.tsx | 11 ++- src/i18n/strings/en_EN.json | 6 +- 6 files changed, 135 insertions(+), 10 deletions(-) create mode 100644 res/css/views/dialogs/_UnpinAllDialog.pcss create mode 100644 src/components/views/dialogs/UnpinAllDialog.tsx diff --git a/res/css/_common.pcss b/res/css/_common.pcss index de698e23248..605ad41b81d 100644 --- a/res/css/_common.pcss +++ b/res/css/_common.pcss @@ -604,7 +604,7 @@ legend { .mx_Dialog button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( .mx_UserProfileSettings button - ):not(.mx_ThemeChoicePanel_CustomTheme button), + ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button), .mx_Dialog input[type="submit"], .mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton), .mx_Dialog_buttons input[type="submit"] { @@ -624,14 +624,14 @@ legend { .mx_Dialog button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( .mx_UserProfileSettings button - ):not(.mx_ThemeChoicePanel_CustomTheme button):last-child { + ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):last-child { margin-right: 0px; } .mx_Dialog button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( .mx_UserProfileSettings button - ):not(.mx_ThemeChoicePanel_CustomTheme button):focus, + ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):focus, .mx_Dialog input[type="submit"]:focus, .mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):focus, .mx_Dialog_buttons input[type="submit"]:focus { @@ -643,7 +643,7 @@ legend { .mx_Dialog_buttons button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not( .mx_UserProfileSettings button - ):not(.mx_ThemeChoicePanel_CustomTheme button), + ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button), .mx_Dialog_buttons input[type="submit"].mx_Dialog_primary { color: var(--cpd-color-text-on-solid-primary); background-color: var(--cpd-color-bg-action-primary-rest); @@ -656,7 +656,7 @@ legend { .mx_Dialog_buttons button.danger:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(.mx_UserProfileSettings button):not( .mx_ThemeChoicePanel_CustomTheme button - ), + ):not(.mx_UnpinAllDialog button), .mx_Dialog_buttons input[type="submit"].danger { background-color: var(--cpd-color-bg-critical-primary); border: solid 1px var(--cpd-color-bg-critical-primary); @@ -672,7 +672,7 @@ legend { .mx_Dialog button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( .mx_UserProfileSettings button - ):not(.mx_ThemeChoicePanel_CustomTheme button):disabled, + ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):disabled, .mx_Dialog input[type="submit"]:disabled, .mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):disabled, .mx_Dialog_buttons input[type="submit"]:disabled { diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 85ac596d082..2698e773ec8 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -167,6 +167,7 @@ @import "./views/dialogs/_SpaceSettingsDialog.pcss"; @import "./views/dialogs/_SpotlightDialog.pcss"; @import "./views/dialogs/_TermsDialog.pcss"; +@import "./views/dialogs/_UnpinAllDialog.pcss"; @import "./views/dialogs/_UntrustedDeviceDialog.pcss"; @import "./views/dialogs/_UploadConfirmDialog.pcss"; @import "./views/dialogs/_UserSettingsDialog.pcss"; diff --git a/res/css/views/dialogs/_UnpinAllDialog.pcss b/res/css/views/dialogs/_UnpinAllDialog.pcss new file mode 100644 index 00000000000..fb05809523d --- /dev/null +++ b/res/css/views/dialogs/_UnpinAllDialog.pcss @@ -0,0 +1,38 @@ +/* + * Copyright 2024 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. + */ + +.mx_UnpinAllDialog { + /* 396 is coming from figma and we remove the left and right paddings of the dialog */ + width: calc(396px - (var(--cpd-space-10x) * 2)); + padding-bottom: var(--cpd-space-2x); + + .mx_UnpinAllDialog_title { + /* Override the default heading style */ + font: var(--cpd-font-heading-sm-semibold) !important; + margin-bottom: var(--cpd-space-3x); + } + + .mx_UnpinAllDialog_buttons { + display: flex; + flex-direction: column; + gap: var(--cpd-space-4x); + margin: var(--cpd-space-8x) var(--cpd-space-2x) 0 var(--cpd-space-2x); + + button { + width: 100%; + } + } +} diff --git a/src/components/views/dialogs/UnpinAllDialog.tsx b/src/components/views/dialogs/UnpinAllDialog.tsx new file mode 100644 index 00000000000..ef7439d858e --- /dev/null +++ b/src/components/views/dialogs/UnpinAllDialog.tsx @@ -0,0 +1,77 @@ +/* + * Copyright 2024 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, { JSX } from "react"; +import { Button, Text } from "@vector-im/compound-web"; +import { EventType, MatrixClient } from "matrix-js-sdk/src/matrix"; +import { logger } from "matrix-js-sdk/src/logger"; + +import BaseDialog from "../dialogs/BaseDialog"; +import { _t } from "../../../languageHandler"; + +/** + * Properties for {@link UnpinAllDialog}. + */ +interface UnpinAllDialogProps { + /* + * The matrix client to use. + */ + matrixClient: MatrixClient; + /* + * The room ID to unpin all events in. + */ + roomId: string; + /* + * Callback for when the dialog is closed. + */ + onFinished: () => void; +} + +/** + * A dialog that asks the user to confirm unpinning all events in a room. + */ +export function UnpinAllDialog({ matrixClient, roomId, onFinished }: UnpinAllDialogProps): JSX.Element { + return ( + + {_t("right_panel|pinned_messages|unpin_all|content")} +
+ + +
+
+ ); +} diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 4a0179230d2..366cd9bbe54 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -37,6 +37,8 @@ import { ReadPinsEventId } from "./types"; import Heading from "../typography/Heading"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { filterBoolean } from "../../../utils/arrays"; +import Modal from "../../../Modal"; +import { UnpinAllDialog } from "../dialogs/UnpinAllDialog"; interface IProps { room: Room; @@ -262,10 +264,13 @@ function PinnedMessages({ events, room, permalinkCreator }: PinnedMessagesProps) ); /** - * Unpin all events from the room. + * Opens the unpin all dialog. */ const onUnpinAll = useCallback(async (): Promise => { - await matrixClient.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned: [] }, ""); + Modal.createDialog(UnpinAllDialog, { + roomId: room.roomId, + matrixClient, + }); }, [room, matrixClient]); return ( @@ -294,7 +299,7 @@ function PinnedMessages({ events, room, permalinkCreator }: PinnedMessagesProps) {canUnpin && (
)} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c4347731143..fbbd44f3530 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1851,7 +1851,11 @@ }, "menu": "Open menu", "title": "Pinned messages", - "unpin_all": "Unpin all messages", + "unpin_all": { + "button": "Unpin all messages", + "content": "Make sure that you really want to remove all pinned messages. This action can’t be undone.", + "title": "Unpin all messages?" + }, "view": "View in timeline" }, "pinned_messages_button": "Pinned messages", From a863ff507b1e55d29459cf3ebdfa63284f01713b Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 7 Aug 2024 18:32:03 +0200 Subject: [PATCH 05/13] Use `EmptyState` when there is no pinned messages --- .../right_panel/_PinnedMessagesCard.pcss | 44 ------------------- .../views/right_panel/PinnedMessagesCard.tsx | 37 +++------------- src/i18n/strings/en_EN.json | 4 +- 3 files changed, 9 insertions(+), 76 deletions(-) diff --git a/res/css/views/right_panel/_PinnedMessagesCard.pcss b/res/css/views/right_panel/_PinnedMessagesCard.pcss index 169c5dc06da..23e23bae858 100644 --- a/res/css/views/right_panel/_PinnedMessagesCard.pcss +++ b/res/css/views/right_panel/_PinnedMessagesCard.pcss @@ -15,50 +15,6 @@ limitations under the License. */ .mx_PinnedMessagesCard { - .mx_PinnedMessagesCard_empty_wrapper { - display: flex; - height: 100%; - - .mx_PinnedMessagesCard_empty { - height: max-content; - text-align: center; - margin: auto 40px; - - .mx_PinnedMessagesCard_MessageActionBar { - pointer-events: none; - width: max-content; - margin: 0 auto; - - /* Cancel the default values for non-interactivity */ - position: unset; - visibility: visible; - cursor: unset; - - &::before { - content: unset; - } - - .mx_MessageActionBar_optionsButton { - background: var(--MessageActionBar-item-hover-background); - border-radius: var(--MessageActionBar-item-hover-borderRadius); - z-index: var(--MessageActionBar-item-hover-zIndex); - color: var(--cpd-color-icon-primary); - } - } - - .mx_PinnedMessagesCard_empty_header { - color: $primary-content; - margin-block: $spacing-24 $spacing-20; - } - - > span { - font-size: $font-12px; - line-height: $font-15px; - color: $secondary-content; - } - } - } - --unpin-height: 76px; .mx_PinnedMessagesCard_wrapper { diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 366cd9bbe54..efe69a9a357 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -19,10 +19,8 @@ import { Room, RoomEvent, RoomStateEvent, MatrixEvent, EventType, RelationType } import { logger } from "matrix-js-sdk/src/logger"; import { Button, Separator } from "@vector-im/compound-web"; import classNames from "classnames"; +import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin"; -import { Icon as ContextMenuIcon } from "../../../../res/img/element-icons/context-menu.svg"; -import { Icon as EmojiIcon } from "../../../../res/img/element-icons/room/message-bar/emoji.svg"; -import { Icon as ReplyIcon } from "../../../../res/img/element-icons/room/message-bar/reply.svg"; import { _t } from "../../../languageHandler"; import BaseCard from "./BaseCard"; import Spinner from "../elements/Spinner"; @@ -39,6 +37,7 @@ import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { filterBoolean } from "../../../utils/arrays"; import Modal from "../../../Modal"; import { UnpinAllDialog } from "../dialogs/UnpinAllDialog"; +import EmptyState from "./EmptyState"; interface IProps { room: Room; @@ -170,33 +169,11 @@ const PinnedMessagesCard: React.FC = ({ room, onClose, permalinkCreator let content: JSX.Element[] | JSX.Element | undefined; if (!pinnedEventIds.length) { content = ( -
-
- {/* XXX: We reuse the classes for simplicity, but deliberately not the components for non-interactivity. */} -
-
- -
-
- -
-
- -
-
- - - {_t("right_panel|pinned_messages|empty")} - - {_t( - "right_panel|pinned_messages|explainer", - {}, - { - b: (sub) => {sub}, - }, - )} -
-
+ ); } else if (pinnedEvents?.length) { content = ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fbbd44f3530..6a345ac5754 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1839,8 +1839,8 @@ "files_button": "Files", "info": "Info", "pinned_messages": { - "empty": "Nothing pinned, yet", - "explainer": "If you have permissions, open the menu on any message and select Pin to stick them here.", + "empty_description": "Select a message and choose “Pin message” to include here.", + "empty_title": "Pin important messages so that they can be easily discovered", "header": { "one": "1 Pinned message", "other": "%(count)s Pinned messages", From 29ee31e1af478781660aaf90bd57a21752eecfa0 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 12 Aug 2024 10:52:40 +0200 Subject: [PATCH 06/13] Rework `PinnedEventTile` tests --- .../views/rooms/PinnedEventTile-test.tsx | 193 +++++++++-- .../PinnedEventTile-test.tsx.snap | 317 ++++++++++++++++-- 2 files changed, 451 insertions(+), 59 deletions(-) diff --git a/test/components/views/rooms/PinnedEventTile-test.tsx b/test/components/views/rooms/PinnedEventTile-test.tsx index 7febe0b4bd3..ed27a4e495e 100644 --- a/test/components/views/rooms/PinnedEventTile-test.tsx +++ b/test/components/views/rooms/PinnedEventTile-test.tsx @@ -15,32 +15,44 @@ limitations under the License. */ import React from "react"; -import { render } from "@testing-library/react"; -import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; +import { render, screen, waitFor } from "@testing-library/react"; +import { EventTimeline, EventType, IEvent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; +import userEvent from "@testing-library/user-event"; import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; -import PinnedEventTile from "../../../../src/components/views/rooms/PinnedEventTile"; -import { getMockClientWithEventEmitter } from "../../../test-utils"; +import { PinnedEventTile } from "../../../../src/components/views/rooms/PinnedEventTile"; +import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; +import { stubClient } from "../../../test-utils"; +import dis from "../../../../src/dispatcher/dispatcher"; +import { Action } from "../../../../src/dispatcher/actions"; +import { getForwardableEvent } from "../../../../src/events"; +import { createRedactEventDialog } from "../../../../src/components/views/dialogs/ConfirmRedactDialog"; + +jest.mock("../../../../src/components/views/dialogs/ConfirmRedactDialog", () => ({ + createRedactEventDialog: jest.fn(), +})); describe("", () => { const userId = "@alice:server.org"; const roomId = "!room:server.org"; - const mockClient = getMockClientWithEventEmitter({ - getRoom: jest.fn(), - }); - const room = new Room(roomId, mockClient, userId); - const permalinkCreator = new RoomPermalinkCreator(room); - - const getComponent = (event: MatrixEvent) => - render(); + let mockClient: MatrixClient; + let room: Room; + let permalinkCreator: RoomPermalinkCreator; beforeEach(() => { - mockClient.getRoom.mockReturnValue(room); + mockClient = stubClient(); + room = new Room(roomId, mockClient, userId); + permalinkCreator = new RoomPermalinkCreator(room); + jest.spyOn(dis, "dispatch").mockReturnValue(undefined); }); - it("should render pinned event", () => { - const pin1 = new MatrixEvent({ - type: "m.room.message", + /** + * Create a pinned event with the given content. + * @param content + */ + function makePinEvent(content?: Partial) { + return new MatrixEvent({ + type: EventType.RoomMessage, sender: userId, content: { body: "First pinned message", @@ -48,25 +60,150 @@ describe("", () => { }, room_id: roomId, origin_server_ts: 0, + event_id: "$eventId", + ...content, }); + } + + /** + * Render the component with the given event. + * @param event - pinned event + */ + function renderComponent(event: MatrixEvent) { + return render( + + + , + ); + } - const { container } = getComponent(pin1); + /** + * Render the component and open the menu. + */ + async function renderAndOpenMenu() { + const pinEvent = makePinEvent(); + const renderResult = renderComponent(pinEvent); + await userEvent.click(screen.getByRole("button", { name: "Open menu" })); + return { pinEvent, renderResult }; + } + it("should throw when pinned event has no sender", () => { + const pinEventWithoutSender = makePinEvent({ sender: undefined }); + expect(() => renderComponent(pinEventWithoutSender)).toThrow("Pinned event unexpectedly has no sender"); + }); + + it("should render pinned event", () => { + const { container } = renderComponent(makePinEvent()); expect(container).toMatchSnapshot(); }); - it("should throw when pinned event has no sender", () => { - const pin1 = new MatrixEvent({ - type: "m.room.message", - sender: undefined, - content: { - body: "First pinned message", - msgtype: "m.text", - }, - room_id: roomId, - origin_server_ts: 0, + it("should render the menu without unpin and delete", async () => { + jest.spyOn(room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, "mayClientSendStateEvent").mockReturnValue( + false, + ); + jest.spyOn( + room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "maySendRedactionForEvent", + ).mockReturnValue(false); + + await renderAndOpenMenu(); + + // Unpin and delete should not be present + await waitFor(() => expect(screen.getByRole("menu")).toBeInTheDocument()); + expect(screen.getByRole("menuitem", { name: "View in timeline" })).toBeInTheDocument(); + expect(screen.getByRole("menuitem", { name: "Forward" })).toBeInTheDocument(); + expect(screen.queryByRole("menuitem", { name: "Unpin" })).toBeNull(); + expect(screen.queryByRole("menuitem", { name: "Delete" })).toBeNull(); + expect(screen.getByRole("menu")).toMatchSnapshot(); + }); + + it("should render the menu with all the options", async () => { + // Enable unpin + jest.spyOn(room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, "mayClientSendStateEvent").mockReturnValue( + true, + ); + // Enable redaction + jest.spyOn( + room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "maySendRedactionForEvent", + ).mockReturnValue(true); + + await renderAndOpenMenu(); + + await waitFor(() => expect(screen.getByRole("menu")).toBeInTheDocument()); + ["View in timeline", "Forward", "Unpin", "Delete"].forEach((name) => + expect(screen.getByRole("menuitem", { name })).toBeInTheDocument(), + ); + expect(screen.getByRole("menu")).toMatchSnapshot(); + }); + + it("should view in the timeline", async () => { + const { pinEvent } = await renderAndOpenMenu(); + + // Test view in timeline button + await userEvent.click(screen.getByRole("menuitem", { name: "View in timeline" })); + expect(dis.dispatch).toHaveBeenCalledWith({ + action: Action.ViewRoom, + event_id: pinEvent.getId(), + highlighted: true, + room_id: pinEvent.getRoomId(), + metricsTrigger: undefined, // room doesn't change }); + }); + + it("should open forward dialog", async () => { + const { pinEvent } = await renderAndOpenMenu(); + + // Test forward button + await userEvent.click(screen.getByRole("menuitem", { name: "Forward" })); + expect(dis.dispatch).toHaveBeenCalledWith({ + action: Action.OpenForwardDialog, + event: getForwardableEvent(pinEvent, mockClient), + permalinkCreator: permalinkCreator, + }); + }); - expect(() => getComponent(pin1)).toThrow("Pinned event unexpectedly has no sender"); + it("should unpin the event", async () => { + const { pinEvent } = await renderAndOpenMenu(); + const pinEvent2 = makePinEvent({ event_id: "$eventId2" }); + + const stateEvent = { + getContent: jest.fn().mockReturnValue({ pinned: [pinEvent.getId(), pinEvent2.getId()] }), + } as unknown as MatrixEvent; + + // Enable unpin + jest.spyOn(room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, "mayClientSendStateEvent").mockReturnValue( + true, + ); + // Mock the state event + jest.spyOn(room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, "getStateEvents").mockReturnValue( + stateEvent, + ); + + // Test unpin button + await userEvent.click(screen.getByRole("menuitem", { name: "Unpin" })); + expect(mockClient.sendStateEvent).toHaveBeenCalledWith( + room.roomId, + EventType.RoomPinnedEvents, + { + pinned: [pinEvent2.getId()], + }, + "", + ); + }); + + it("should delete the event", async () => { + // Enable redaction + jest.spyOn( + room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "maySendRedactionForEvent", + ).mockReturnValue(true); + + const { pinEvent } = await renderAndOpenMenu(); + + await userEvent.click(screen.getByRole("menuitem", { name: "Delete" })); + expect(createRedactEventDialog).toHaveBeenCalledWith({ + mxEvent: pinEvent, + }); }); }); diff --git a/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap b/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap index 960177a38e5..b44b6a41a0a 100644 --- a/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap +++ b/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap @@ -4,25 +4,52 @@ exports[` should render pinned event 1`] = `
- - a - - - @alice:server.org - +
+ + a + +
+
+ + @alice:server.org + + +
@@ -34,22 +61,250 @@ exports[` should render pinned event 1`] = `
+
+ +`; + +exports[` should render the menu with all the options 1`] = ` + + View in timeline + + + + + + +`; + +exports[` should render the menu without unpin and delete 1`] = ` + `; From 15657ddb2e0c5739d967510b38beccf99028d8ec Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 13 Aug 2024 17:09:26 +0200 Subject: [PATCH 07/13] Add comments and refactor `PinnedMessageCard` --- src/components/structures/RightPanel.tsx | 2 +- .../views/right_panel/PinnedMessagesCard.tsx | 132 +++++++++++++----- 2 files changed, 99 insertions(+), 35 deletions(-) diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 9e228de611b..8f1ba7aecfb 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -34,7 +34,7 @@ import ThreadView from "./ThreadView"; import ThreadPanel from "./ThreadPanel"; import NotificationPanel from "./NotificationPanel"; import ResizeNotifier from "../../utils/ResizeNotifier"; -import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard"; +import { PinnedMessagesCard } from "../views/right_panel/PinnedMessagesCard"; import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks"; import { E2EStatus } from "../../utils/ShieldUtils"; import TimelineCard from "../views/right_panel/TimelineCard"; diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index efe69a9a357..7ac8996c45b 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -14,8 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback, useContext, useEffect, useState, JSX } from "react"; -import { Room, RoomEvent, RoomStateEvent, MatrixEvent, EventType, RelationType } from "matrix-js-sdk/src/matrix"; +import React, { useCallback, useEffect, useState, JSX } from "react"; +import { + Room, + RoomEvent, + RoomStateEvent, + MatrixEvent, + EventType, + RelationType, + EventTimeline, +} from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { Button, Separator } from "@vector-im/compound-web"; import classNames from "classnames"; @@ -24,13 +32,13 @@ import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin"; import { _t } from "../../../languageHandler"; import BaseCard from "./BaseCard"; import Spinner from "../elements/Spinner"; -import MatrixClientContext, { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; +import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; import { useTypedEventEmitter } from "../../../hooks/useEventEmitter"; import PinningUtils from "../../../utils/PinningUtils"; import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; import { PinnedEventTile } from "../rooms/PinnedEventTile"; import { useRoomState } from "../../../hooks/useRoomState"; -import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; +import RoomContext, { TimelineRenderingType, useRoomContext } from "../../../contexts/RoomContext"; import { ReadPinsEventId } from "./types"; import Heading from "../typography/Heading"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; @@ -39,19 +47,29 @@ import Modal from "../../../Modal"; import { UnpinAllDialog } from "../dialogs/UnpinAllDialog"; import EmptyState from "./EmptyState"; -interface IProps { - room: Room; - permalinkCreator: RoomPermalinkCreator; - onClose(): void; -} - +/** + * Get the pinned event IDs from a room. + * @param room + */ function getPinnedEventIds(room?: Room): string[] { - return room?.currentState.getStateEvents(EventType.RoomPinnedEvents, "")?.getContent()?.pinned ?? []; + return ( + room + ?.getLiveTimeline() + .getState(EventTimeline.FORWARDS) + ?.getStateEvents(EventType.RoomPinnedEvents, "") + ?.getContent()?.pinned ?? [] + ); } +/** + * Get the pinned event IDs from a room. + * @param room + */ export const usePinnedEvents = (room?: Room): string[] => { const [pinnedEvents, setPinnedEvents] = useState(getPinnedEventIds(room)); + // Update the pinned events when the room state changes + // Filter out events that are not pinned events const update = useCallback( (ev?: MatrixEvent) => { if (ev && ev.getType() !== EventType.RoomPinnedEvents) return; @@ -60,7 +78,7 @@ export const usePinnedEvents = (room?: Room): string[] => { [room], ); - useTypedEventEmitter(room?.currentState, RoomStateEvent.Events, update); + useTypedEventEmitter(room?.getLiveTimeline().getState(EventTimeline.FORWARDS), RoomStateEvent.Events, update); useEffect(() => { setPinnedEvents(getPinnedEventIds(room)); return () => { @@ -70,13 +88,23 @@ export const usePinnedEvents = (room?: Room): string[] => { return pinnedEvents; }; +/** + * Get the read pinned event IDs from a room. + * @param room + */ function getReadPinnedEventIds(room?: Room): Set { return new Set(room?.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []); } +/** + * Get the read pinned event IDs from a room. + * @param room + */ export const useReadPinnedEvents = (room?: Room): Set => { const [readPinnedEvents, setReadPinnedEvents] = useState>(new Set()); + // Update the read pinned events when the room state changes + // Filter out events that are not read pinned events const update = useCallback( (ev?: MatrixEvent) => { if (ev && ev.getType() !== ReadPinsEventId) return; @@ -95,40 +123,36 @@ export const useReadPinnedEvents = (room?: Room): Set => { return readPinnedEvents; }; -const PinnedMessagesCard: React.FC = ({ room, onClose, permalinkCreator }) => { - const cli = useContext(MatrixClientContext); - const roomContext = useContext(RoomContext); - const pinnedEventIds = usePinnedEvents(room); - const readPinnedEvents = useReadPinnedEvents(room); - - useEffect(() => { - if (!cli || cli.isGuest()) return; // nothing to do - const newlyRead = pinnedEventIds.filter((id) => !readPinnedEvents.has(id)); - if (newlyRead.length > 0) { - // clear out any read pinned events which no longer are pinned - cli.setRoomAccountData(room.roomId, ReadPinsEventId, { - event_ids: pinnedEventIds, - }); - } - }, [cli, room.roomId, pinnedEventIds, readPinnedEvents]); +/** + * Fetch the pinned events + * @param room + * @param pinnedEventIds + */ +function useFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array | null { + const cli = useMatrixClientContext(); - const pinnedEvents = useAsyncMemo( + return useAsyncMemo( () => { const promises = pinnedEventIds.map(async (eventId): Promise => { const timelineSet = room.getUnfilteredTimelineSet(); + // Get the event from the local timeline const localEvent = timelineSet ?.getTimelineForEvent(eventId) ?.getEvents() .find((e) => e.getId() === eventId); + // Decrypt the event if it's encrypted + // Can happen when the tab is refreshed and the pinned events card is opened directly if (localEvent?.isEncrypted()) { await cli.decryptEventIfNeeded(localEvent); } + // If the event is available locally, return it if it's pinnable + // Otherwise, return null if (localEvent) return PinningUtils.isPinnable(localEvent) ? localEvent : null; try { - // Fetch the event and latest edit in parallel + // The event is not available locally, so we fetch the event and latest edit in parallel const [ evJson, { @@ -138,10 +162,15 @@ const PinnedMessagesCard: React.FC = ({ room, onClose, permalinkCreator cli.fetchRoomEvent(room.roomId, eventId), cli.relations(room.roomId, eventId, RelationType.Replace, null, { limit: 1 }), ]); + const event = new MatrixEvent(evJson); + + // Decrypt the event if it's encrypted if (event.isEncrypted()) { await cli.decryptEventIfNeeded(event); } + + // Handle poll events await room.processPollEvents([event]); const senderUserId = event.getSender(); @@ -165,8 +194,45 @@ const PinnedMessagesCard: React.FC = ({ room, onClose, permalinkCreator [cli, room, pinnedEventIds], null, ); +} + +/** + * List the pinned messages in a room inside a Card. + */ +interface PinnedMessagesCardProps { + /** + * The room to list the pinned messages for. + */ + room: Room; + /** + * Permalink of the room. + */ + permalinkCreator: RoomPermalinkCreator; + /** + * Callback for when the card is closed. + */ + onClose(): void; +} + +export function PinnedMessagesCard({ room, onClose, permalinkCreator }: PinnedMessagesCardProps): JSX.Element { + const cli = useMatrixClientContext(); + const roomContext = useRoomContext(); + const pinnedEventIds = usePinnedEvents(room); + const readPinnedEvents = useReadPinnedEvents(room); + const pinnedEvents = useFetchedPinnedEvents(room, pinnedEventIds); + + useEffect(() => { + if (!cli || cli.isGuest()) return; // nothing to do + const newlyRead = pinnedEventIds.filter((id) => !readPinnedEvents.has(id)); + if (newlyRead.length > 0) { + // clear out any read pinned events which no longer are pinned + cli.setRoomAccountData(room.roomId, ReadPinsEventId, { + event_ids: pinnedEventIds, + }); + } + }, [cli, room.roomId, pinnedEventIds, readPinnedEvents]); - let content: JSX.Element[] | JSX.Element | undefined; + let content: JSX.Element; if (!pinnedEventIds.length) { content = ( = ({ room, onClose, permalinkCreator ); -}; - -export default PinnedMessagesCard; +} /** * The pinned messages in a room. From 64da605241c1eced3cb72a716e03be23d38f854b Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 13 Aug 2024 16:42:39 +0200 Subject: [PATCH 08/13] Rework `PinnedMessageCard` tests --- .../right_panel/PinnedMessagesCard-test.tsx | 344 ++++++++----- .../PinnedMessagesCard-test.tsx.snap | 457 ++++++++++++++++++ 2 files changed, 667 insertions(+), 134 deletions(-) create mode 100644 test/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap diff --git a/test/components/views/right_panel/PinnedMessagesCard-test.tsx b/test/components/views/right_panel/PinnedMessagesCard-test.tsx index bff9b695798..64961ca1447 100644 --- a/test/components/views/right_panel/PinnedMessagesCard-test.tsx +++ b/test/components/views/right_panel/PinnedMessagesCard-test.tsx @@ -15,37 +15,44 @@ limitations under the License. */ import React from "react"; -import { render, act, RenderResult, fireEvent, waitForElementToBeRemoved, screen } from "@testing-library/react"; -import { mocked } from "jest-mock"; +import { render, act, RenderResult, waitForElementToBeRemoved, screen } from "@testing-library/react"; +import { mocked, MockedObject } from "jest-mock"; import { MatrixEvent, RoomStateEvent, IEvent, Room, - EventTimelineSet, IMinimalEvent, EventType, RelationType, MsgType, M_POLL_KIND_DISCLOSED, + EventTimeline, + MatrixClient, } from "matrix-js-sdk/src/matrix"; import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent"; import { PollResponseEvent } from "matrix-js-sdk/src/extensible_events_v1/PollResponseEvent"; import { PollEndEvent } from "matrix-js-sdk/src/extensible_events_v1/PollEndEvent"; import { sleep } from "matrix-js-sdk/src/utils"; +import userEvent from "@testing-library/user-event"; import { stubClient, mkEvent, mkMessage, flushPromises } from "../../../test-utils"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; -import PinnedMessagesCard from "../../../../src/components/views/right_panel/PinnedMessagesCard"; +import { PinnedMessagesCard } from "../../../../src/components/views/right_panel/PinnedMessagesCard"; import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; +import Modal from "../../../../src/Modal"; +import { UnpinAllDialog } from "../../../../src/components/views/dialogs/UnpinAllDialog"; describe("", () => { - stubClient(); - const cli = mocked(MatrixClientPeg.safeGet()); - cli.getUserId.mockReturnValue("@alice:example.org"); - cli.setRoomAccountData.mockResolvedValue({}); - cli.relations.mockResolvedValue({ originalEvent: {} as unknown as MatrixEvent, events: [] }); + let cli: MockedObject; + beforeEach(() => { + stubClient(); + cli = mocked(MatrixClientPeg.safeGet()); + cli.getUserId.mockReturnValue("@alice:example.org"); + cli.setRoomAccountData.mockResolvedValue({}); + cli.relations.mockResolvedValue({ originalEvent: {} as unknown as MatrixEvent, events: [] }); + }); const mkRoom = (localPins: MatrixEvent[], nonLocalPins: MatrixEvent[]): Room => { const room = new Room("!room:example.org", cli, "@me:example.org"); @@ -53,27 +60,27 @@ describe("", () => { const pins = () => [...localPins, ...nonLocalPins]; // Insert pin IDs into room state - jest.spyOn(room.currentState, "getStateEvents").mockImplementation((): any => - mkEvent({ - event: true, - type: EventType.RoomPinnedEvents, - content: { - pinned: pins().map((e) => e.getId()), - }, - user: "@user:example.org", - room: "!room:example.org", - }), + jest.spyOn(room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, "getStateEvents").mockImplementation( + (): any => + mkEvent({ + event: true, + type: EventType.RoomPinnedEvents, + content: { + pinned: pins().map((e) => e.getId()), + }, + user: "@user:example.org", + room: "!room:example.org", + }), ); - jest.spyOn(room.currentState, "on"); - - // Insert local pins into local timeline set - room.getUnfilteredTimelineSet = () => - ({ - getTimelineForEvent: () => ({ - getEvents: () => localPins, - }), - }) as unknown as EventTimelineSet; + jest.spyOn(room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, "mayClientSendStateEvent").mockReturnValue( + true, + ); + // poll end event validates against this + jest.spyOn( + room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "maySendRedactionForEvent", + ).mockReturnValue(true); // Return all pins over fetchRoomEvent cli.fetchRoomEvent.mockImplementation((roomId, eventId) => { @@ -86,8 +93,8 @@ describe("", () => { return room; }; - const mountPins = async (room: Room): Promise => { - const pins = render( + async function renderMessagePinList(room: Room): Promise { + const renderResult = render( ", () => { // Wait a tick for state updates await act(() => sleep(0)); - return pins; - }; - - const emitPinUpdates = async (room: Room) => { - const pinListener = mocked(room.currentState).on.mock.calls.find( - ([eventName, listener]) => eventName === RoomStateEvent.Events, - )![1]; + return renderResult; + } + /** + * + * @param room + */ + async function emitPinUpdate(room: Room) { await act(async () => { - // Emit the update - // @ts-ignore what is going on here? - pinListener(room.currentState.getStateEvents()); - // Wait a tick for state updates - await sleep(0); + const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS)!; + roomState.emit( + RoomStateEvent.Events, + new MatrixEvent({ type: EventType.RoomPinnedEvents }), + roomState, + null, + ); }); - }; + } + + /** + * Initialize the pinned messages card with the given pinned messages. + * Return the room, testing library helpers and functions to add and remove pinned messages. + * @param localPins + * @param nonLocalPins + */ + async function initPinnedMessagesCard(localPins: MatrixEvent[], nonLocalPins: MatrixEvent[]) { + const room = mkRoom(localPins, nonLocalPins); + const addLocalPinEvent = async (event: MatrixEvent) => { + localPins.push(event); + await emitPinUpdate(room); + }; + const removeLastLocalPinEvent = async () => { + localPins.pop(); + await emitPinUpdate(room); + }; + const addNonLocalPinEvent = async (event: MatrixEvent) => { + nonLocalPins.push(event); + await emitPinUpdate(room); + }; + const removeLastNonLocalPinEvent = async () => { + nonLocalPins.pop(); + await emitPinUpdate(room); + }; + const renderResult = await renderMessagePinList(room); + + return { + ...renderResult, + addLocalPinEvent, + removeLastLocalPinEvent, + addNonLocalPinEvent, + removeLastNonLocalPinEvent, + room, + }; + } const pin1 = mkMessage({ event: true, @@ -129,75 +174,66 @@ describe("", () => { msg: "The second one", }); - it("updates when messages are pinned", async () => { + it("should show spinner whilst loading", async () => { + const room = mkRoom([], [pin1]); + render( + + + , + ); + + await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar")); + }); + + it("should show the empty state when there are no pins", async () => { + const { asFragment } = await initPinnedMessagesCard([], []); + + expect(screen.getByText("Pin important messages so that they can be easily discovered")).toBeInTheDocument(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should show two pinned messages", async () => { + //const room = mkRoom([pin1], [pin2]); + const { asFragment } = await initPinnedMessagesCard([pin1], [pin2]); + + expect(screen.queryAllByRole("listitem")).toHaveLength(2); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should updates when messages are pinned", async () => { // Start with nothing pinned - const localPins: MatrixEvent[] = []; - const nonLocalPins: MatrixEvent[] = []; - const room = mkRoom(localPins, nonLocalPins); - const pins = await mountPins(room); - expect(pins.container.querySelectorAll(".mx_PinnedEventTile")).toHaveLength(0); + const { addLocalPinEvent, addNonLocalPinEvent } = await initPinnedMessagesCard([], []); + + expect(screen.queryAllByRole("listitem")).toHaveLength(0); // Pin the first message - localPins.push(pin1); - await emitPinUpdates(room); - expect(pins.container.querySelectorAll(".mx_PinnedEventTile")).toHaveLength(1); + await addLocalPinEvent(pin1); + expect(screen.getAllByRole("listitem")).toHaveLength(1); // Pin the second message - nonLocalPins.push(pin2); - await emitPinUpdates(room); - expect(pins.container.querySelectorAll(".mx_PinnedEventTile")).toHaveLength(2); + await addNonLocalPinEvent(pin2); + expect(screen.getAllByRole("listitem")).toHaveLength(2); }); - it("updates when messages are unpinned", async () => { + it("should updates when messages are unpinned", async () => { // Start with two pins - const localPins = [pin1]; - const nonLocalPins = [pin2]; - const room = mkRoom(localPins, nonLocalPins); - const pins = await mountPins(room); - expect(pins.container.querySelectorAll(".mx_PinnedEventTile")).toHaveLength(2); + const { removeLastLocalPinEvent, removeLastNonLocalPinEvent } = await initPinnedMessagesCard([pin1], [pin2]); + expect(screen.getAllByRole("listitem")).toHaveLength(2); // Unpin the first message - localPins.pop(); - await emitPinUpdates(room); - expect(pins.container.querySelectorAll(".mx_PinnedEventTile")).toHaveLength(1); + await removeLastLocalPinEvent(); + expect(screen.getAllByRole("listitem")).toHaveLength(1); // Unpin the second message - nonLocalPins.pop(); - await emitPinUpdates(room); - expect(pins.container.querySelectorAll(".mx_PinnedEventTile")).toHaveLength(0); - }); - - it("hides unpinnable events found in local timeline", async () => { - // Redacted messages are unpinnable - const pin = mkEvent({ - event: true, - type: EventType.RoomMessage, - content: {}, - unsigned: { redacted_because: {} as unknown as IEvent }, - room: "!room:example.org", - user: "@alice:example.org", - }); - - const pins = await mountPins(mkRoom([pin], [])); - expect(pins.container.querySelectorAll(".mx_PinnedEventTile")).toHaveLength(0); + await removeLastNonLocalPinEvent(); + expect(screen.queryAllByRole("listitem")).toHaveLength(0); }); - it("hides unpinnable events not found in local timeline", async () => { - // Redacted messages are unpinnable - const pin = mkEvent({ - event: true, - type: EventType.RoomMessage, - content: {}, - unsigned: { redacted_because: {} as unknown as IEvent }, - room: "!room:example.org", - user: "@alice:example.org", - }); - - const pins = await mountPins(mkRoom([], [pin])); - expect(pins.container.querySelectorAll(".mx_PinnedEventTile")).toHaveLength(0); - }); - - it("accounts for edits", async () => { + it("should display an edited pinned event", async () => { const messageEvent = mkEvent({ event: true, type: EventType.RoomMessage, @@ -221,13 +257,78 @@ describe("", () => { events: [messageEvent], }); - const pins = await mountPins(mkRoom([], [pin1])); - const pinTile = pins.container.querySelectorAll(".mx_PinnedEventTile"); - expect(pinTile.length).toBe(1); - expect(pinTile[0].querySelector(".mx_EventTile_body")!).toHaveTextContent("First pinned message, edited"); + await initPinnedMessagesCard([], [pin1]); + expect(screen.getByText("First pinned message, edited")).toBeInTheDocument(); + }); + + describe("unpinnable event", () => { + it("should hide unpinnable events found in local timeline", async () => { + // Redacted messages are unpinnable + const pin = mkEvent({ + event: true, + type: EventType.RoomMessage, + content: {}, + unsigned: { redacted_because: {} as unknown as IEvent }, + room: "!room:example.org", + user: "@alice:example.org", + }); + await initPinnedMessagesCard([pin], []); + expect(screen.queryAllByRole("listitem")).toHaveLength(0); + }); + + it("hides unpinnable events not found in local timeline", async () => { + // Redacted messages are unpinnable + const pin = mkEvent({ + event: true, + type: EventType.RoomMessage, + content: {}, + unsigned: { redacted_because: {} as unknown as IEvent }, + room: "!room:example.org", + user: "@alice:example.org", + }); + await initPinnedMessagesCard([], [pin]); + expect(screen.queryAllByRole("listitem")).toHaveLength(0); + }); + }); + + describe("unpin all", () => { + it("should not allow to unpinall", async () => { + const room = mkRoom([pin1], [pin2]); + jest.spyOn( + room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "mayClientSendStateEvent", + ).mockReturnValue(false); + + const { asFragment } = render( + + + , + ); + + // Wait a tick for state updates + await act(() => sleep(0)); + + expect(screen.queryByText("Unpin all messages")).toBeNull(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should allow unpinning all messages", async () => { + jest.spyOn(Modal, "createDialog"); + + const { room } = await initPinnedMessagesCard([pin1], [pin2]); + expect(screen.getByText("Unpin all messages")).toBeInTheDocument(); + + await userEvent.click(screen.getByText("Unpin all messages")); + // Should open the UnpinAllDialog dialog + expect(Modal.createDialog).toHaveBeenCalledWith(UnpinAllDialog, { roomId: room.roomId, matrixClient: cli }); + }); }); - it("displays votes on polls not found in local timeline", async () => { + it("should displays votes on polls not found in local timeline", async () => { const poll = mkEvent({ ...PollStartEvent.from("A poll", ["Option 1", "Option 2"], M_POLL_KIND_DISCLOSED).serialize(), event: true, @@ -270,11 +371,8 @@ describe("", () => { return { originalEvent: undefined as unknown as MatrixEvent, events: [] }; }); - const room = mkRoom([], [poll]); - // poll end event validates against this - jest.spyOn(room.currentState, "maySendRedactionForEvent").mockReturnValue(true); + const { room } = await initPinnedMessagesCard([], [poll]); - const pins = await mountPins(room); // two pages of results await flushPromises(); await flushPromises(); @@ -282,34 +380,12 @@ describe("", () => { const pollInstance = room.polls.get(poll.getId()!); expect(pollInstance).toBeTruthy(); - const pinTile = pins.container.querySelectorAll(".mx_MPollBody"); - - expect(pinTile).toHaveLength(1); - expect(pinTile[0].querySelectorAll(".mx_PollOption_ended")).toHaveLength(2); - expect(pinTile[0].querySelectorAll(".mx_PollOption_optionVoteCount")[0]).toHaveTextContent("2 votes"); - expect([...pinTile[0].querySelectorAll(".mx_PollOption_optionVoteCount")].at(-1)).toHaveTextContent("1 vote"); - }); - - it("should allow admins to unpin messages", async () => { - const nonLocalPins = [pin1]; - const room = mkRoom([], nonLocalPins); - jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true); - const sendStateEvent = jest.spyOn(cli, "sendStateEvent"); - - const pins = await mountPins(room); - const pinTile = pins.container.querySelectorAll(".mx_PinnedEventTile"); - expect(pinTile).toHaveLength(1); + expect(screen.getByText("A poll")).toBeInTheDocument(); - fireEvent.click(pinTile[0].querySelector(".mx_PinnedEventTile_unpinButton")!); - expect(sendStateEvent).toHaveBeenCalledWith(room.roomId, "m.room.pinned_events", { pinned: [] }, ""); + expect(screen.getByText("Option 1")).toBeInTheDocument(); + expect(screen.getByText("2 votes")).toBeInTheDocument(); - nonLocalPins.pop(); - await Promise.all([waitForElementToBeRemoved(pinTile[0]), emitPinUpdates(room)]); - }); - - it("should show spinner whilst loading", async () => { - const room = mkRoom([], [pin1]); - mountPins(room); - await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar")); + expect(screen.getByText("Option 2")).toBeInTheDocument(); + expect(screen.getByText("1 vote")).toBeInTheDocument(); }); }); diff --git a/test/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap b/test/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap new file mode 100644 index 00000000000..1a3052f2b09 --- /dev/null +++ b/test/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap @@ -0,0 +1,457 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should show the empty state when there are no pins 1`] = ` + +
+
+
+

+ Pinned message +

+
+ +
+
+
+ + + +

+ Pin important messages so that they can be easily discovered +

+

+ Select a message and choose “Pin message” to include here. +

+
+
+
+
+`; + +exports[` should show two pinned messages 1`] = ` + +
+
+
+

+ 2 Pinned messages +

+
+ +
+
+
+
+
+ + a + +
+
+
+ + @alice:example.org + + +
+
+
+ The second one +
+
+
+
+ +
+ +
+
+
+ +`; + +exports[` unpin all should not allow to unpinall 1`] = ` + +
+
+
+

+ 2 Pinned messages +

+
+ +
+
+
+
+
+ + a + +
+
+
+ + @alice:example.org + + +
+
+
+ The second one +
+
+
+
+ +
+
+ +`; From 0251e40f1c15ef4bc19b62d3b9d6b5e265582b73 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 13 Aug 2024 17:22:46 +0200 Subject: [PATCH 09/13] Add tests for `UnpinAllDialog` --- .../views/dialogs/UnpinAllDialog-test.tsx | 46 +++++++++++++ .../UnpinAllDialog-test.tsx.snap | 66 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 test/components/views/dialogs/UnpinAllDialog-test.tsx create mode 100644 test/components/views/dialogs/__snapshots__/UnpinAllDialog-test.tsx.snap diff --git a/test/components/views/dialogs/UnpinAllDialog-test.tsx b/test/components/views/dialogs/UnpinAllDialog-test.tsx new file mode 100644 index 00000000000..95018cc72dd --- /dev/null +++ b/test/components/views/dialogs/UnpinAllDialog-test.tsx @@ -0,0 +1,46 @@ +/* + * Copyright 2024 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 { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { EventType } from "matrix-js-sdk/src/matrix"; + +import { UnpinAllDialog } from "../../../../src/components/views/dialogs/UnpinAllDialog"; +import { createTestClient } from "../../../test-utils"; + +describe("", () => { + const client = createTestClient(); + const roomId = "!room:example.org"; + + function renderDialog(onFinished = jest.fn()) { + return render(); + } + + it("should render", () => { + const { asFragment } = renderDialog(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should remove all pinned events when clicked on Continue", async () => { + const onFinished = jest.fn(); + renderDialog(onFinished); + + await userEvent.click(screen.getByText("Continue")); + expect(client.sendStateEvent).toHaveBeenCalledWith(roomId, EventType.RoomPinnedEvents, { pinned: [] }, ""); + expect(onFinished).toHaveBeenCalled(); + }); +}); diff --git a/test/components/views/dialogs/__snapshots__/UnpinAllDialog-test.tsx.snap b/test/components/views/dialogs/__snapshots__/UnpinAllDialog-test.tsx.snap new file mode 100644 index 00000000000..8213a9b9171 --- /dev/null +++ b/test/components/views/dialogs/__snapshots__/UnpinAllDialog-test.tsx.snap @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render 1`] = ` + +
+ From 48dbe2dd0e6e614489b3960ab98bd8067a0d83ce Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 16 Aug 2024 12:21:21 +0200 Subject: [PATCH 13/13] Update playright sceenshot for empty state --- .../pinned-messages-list-empty-linux.png | Bin 82075 -> 80948 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-empty-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-empty-linux.png index df826eae07fdf1066382c3c2bbd04002afdc5f3e..28099d338c179df8b861471496409acdbe548799 100644 GIT binary patch delta 48045 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 delta 49244 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 B%^v^&