From e700d56a14b80bd5f5b8ae65a95af4534dc87060 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Tue, 21 Feb 2023 08:37:17 +1300 Subject: [PATCH] revert MPollEndBody --- res/css/_components.pcss | 1 - res/css/views/messages/_MPollEndBody.pcss | 22 -- .../views/messages/MPollEndBody.tsx | 109 ---------- .../views/messages/MessageEvent.tsx | 5 +- src/events/EventTileFactory.tsx | 10 +- src/events/forward/getForwardableEvent.ts | 4 +- src/utils/EventRenderingUtils.ts | 3 +- src/utils/EventUtils.ts | 3 +- .../views/messages/MPollEndBody-test.tsx | 203 ------------------ .../__snapshots__/MPollEndBody-test.tsx.snap | 108 ---------- 10 files changed, 7 insertions(+), 461 deletions(-) delete mode 100644 res/css/views/messages/_MPollEndBody.pcss delete mode 100644 src/components/views/messages/MPollEndBody.tsx delete mode 100644 test/components/views/messages/MPollEndBody-test.tsx delete mode 100644 test/components/views/messages/__snapshots__/MPollEndBody-test.tsx.snap diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 0d56e1b512a..655d057f731 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -240,7 +240,6 @@ @import "./views/messages/_MLocationBody.pcss"; @import "./views/messages/_MNoticeBody.pcss"; @import "./views/messages/_MPollBody.pcss"; -@import "./views/messages/_MPollEndBody.pcss"; @import "./views/messages/_MStickerBody.pcss"; @import "./views/messages/_MTextBody.pcss"; @import "./views/messages/_MVideoBody.pcss"; diff --git a/res/css/views/messages/_MPollEndBody.pcss b/res/css/views/messages/_MPollEndBody.pcss deleted file mode 100644 index db302655043..00000000000 --- a/res/css/views/messages/_MPollEndBody.pcss +++ /dev/null @@ -1,22 +0,0 @@ -/* -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. -*/ - -.mx_MPollEndBody_icon { - height: 14px; - margin-right: $spacing-8; - vertical-align: middle; - color: $secondary-content; -} diff --git a/src/components/views/messages/MPollEndBody.tsx b/src/components/views/messages/MPollEndBody.tsx deleted file mode 100644 index 2ae6a73e86f..00000000000 --- a/src/components/views/messages/MPollEndBody.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* -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, { useEffect, useState, useContext } from "react"; -import { MatrixEvent } from "matrix-js-sdk/src/matrix"; -import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events"; -import { logger } from "matrix-js-sdk/src/logger"; - -import { Icon as PollIcon } from "../../../../res/img/element-icons/room/composer/poll.svg"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import { textForEvent } from "../../../TextForEvent"; -import { IBodyProps } from "./IBodyProps"; -import MPollBody from "./MPollBody"; - -const getRelatedPollStartEventId = (event: MatrixEvent): string | undefined => { - const relation = event.getRelation(); - return relation?.event_id; -}; - -/** - * Attempt to retrieve the related poll start event for this end event - * If the event already exists in the rooms timeline, return it - * Otherwise try to fetch the event from the server - * @param event - * @returns - */ -const usePollStartEvent = (event: MatrixEvent): { pollStartEvent?: MatrixEvent; isLoadingPollStartEvent: boolean } => { - const matrixClient = useContext(MatrixClientContext); - const [pollStartEvent, setPollStartEvent] = useState(); - const [isLoadingPollStartEvent, setIsLoadingPollStartEvent] = useState(false); - - const pollStartEventId = getRelatedPollStartEventId(event); - - useEffect(() => { - const room = matrixClient.getRoom(event.getRoomId()); - const fetchPollStartEvent = async (roomId: string, pollStartEventId: string): Promise => { - setIsLoadingPollStartEvent(true); - try { - const startEventJson = await matrixClient.fetchRoomEvent(roomId, pollStartEventId); - const startEvent = new MatrixEvent(startEventJson); - // add the poll to the room polls state - room?.processPollEvents([startEvent, event]); - - // end event is not a valid end to the related start event - // if not sent by the same user - if (startEvent.getSender() === event.getSender()) { - setPollStartEvent(startEvent); - } - } catch (error) { - logger.error("Failed to fetch related poll start event", error); - } finally { - setIsLoadingPollStartEvent(false); - } - }; - - if (pollStartEvent || !room || !pollStartEventId) { - return; - } - - const timelineSet = room.getUnfilteredTimelineSet(); - const localEvent = timelineSet - ?.getTimelineForEvent(pollStartEventId) - ?.getEvents() - .find((e) => e.getId() === pollStartEventId); - - if (localEvent) { - // end event is not a valid end to the related start event - // if not sent by the same user - if (localEvent.getSender() === event.getSender()) { - setPollStartEvent(localEvent); - } - } else { - // pollStartEvent is not in the current timeline, - // fetch it - fetchPollStartEvent(room.roomId, pollStartEventId); - } - }, [event, pollStartEventId, pollStartEvent, matrixClient]); - - return { pollStartEvent, isLoadingPollStartEvent }; -}; - -export const MPollEndBody = React.forwardRef(({ mxEvent, ...props }, ref) => { - const { pollStartEvent, isLoadingPollStartEvent } = usePollStartEvent(mxEvent); - - if (!pollStartEvent) { - const pollEndFallbackMessage = M_TEXT.findIn(mxEvent.getContent()) || textForEvent(mxEvent); - return ( - <> - - {!isLoadingPollStartEvent && pollEndFallbackMessage} - - ); - } - - return ; -}); diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx index b7a8f608312..d2272d12d6e 100644 --- a/src/components/views/messages/MessageEvent.tsx +++ b/src/components/views/messages/MessageEvent.tsx @@ -18,7 +18,7 @@ import React, { createRef } from "react"; import { EventType, MsgType } from "matrix-js-sdk/src/@types/event"; import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon"; import { M_LOCATION } from "matrix-js-sdk/src/@types/location"; -import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls"; +import { M_POLL_START } from "matrix-js-sdk/src/@types/polls"; import { MatrixEventEvent } from "matrix-js-sdk/src/models/event"; import SettingsStore from "../../../settings/SettingsStore"; @@ -37,7 +37,6 @@ import MVoiceOrAudioBody from "./MVoiceOrAudioBody"; import MVideoBody from "./MVideoBody"; import MStickerBody from "./MStickerBody"; import MPollBody from "./MPollBody"; -import { MPollEndBody } from "./MPollEndBody"; import MLocationBody from "./MLocationBody"; import MjolnirBody from "./MjolnirBody"; import MBeaconBody from "./MBeaconBody"; @@ -74,8 +73,6 @@ const baseEvTypes = new Map>>([ [EventType.Sticker, MStickerBody], [M_POLL_START.name, MPollBody], [M_POLL_START.altName, MPollBody], - [M_POLL_END.name, MPollEndBody], - [M_POLL_END.altName, MPollEndBody], [M_BEACON_INFO.name, MBeaconBody], [M_BEACON_INFO.altName, MBeaconBody], ]); diff --git a/src/events/EventTileFactory.tsx b/src/events/EventTileFactory.tsx index 8bb9c28c444..7f1a31518ad 100644 --- a/src/events/EventTileFactory.tsx +++ b/src/events/EventTileFactory.tsx @@ -18,7 +18,7 @@ import React from "react"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event"; import { Optional } from "matrix-events-sdk"; -import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls"; +import { M_POLL_START } from "matrix-js-sdk/src/@types/polls"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { GroupCallIntent } from "matrix-js-sdk/src/webrtc/groupCall"; @@ -99,8 +99,6 @@ const EVENT_TILE_TYPES = new Map([ [EventType.Sticker, MessageEventFactory], [M_POLL_START.name, MessageEventFactory], [M_POLL_START.altName, MessageEventFactory], - [M_POLL_END.name, MessageEventFactory], - [M_POLL_END.altName, MessageEventFactory], [EventType.KeyVerificationCancel, KeyVerificationConclFactory], [EventType.KeyVerificationDone, KeyVerificationConclFactory], [EventType.CallInvite, LegacyCallEventFactory], // note that this requires a special factory type @@ -414,11 +412,7 @@ export function renderReplyTile( // XXX: this'll eventually be dynamic based on the fields once we have extensible event types const messageTypes = [EventType.RoomMessage, EventType.Sticker]; export function isMessageEvent(ev: MatrixEvent): boolean { - return ( - messageTypes.includes(ev.getType() as EventType) || - M_POLL_START.matches(ev.getType()) || - M_POLL_END.matches(ev.getType()) - ); + return messageTypes.includes(ev.getType() as EventType) || M_POLL_START.matches(ev.getType()); } export function haveRendererForEvent(mxEvent: MatrixEvent, showHiddenEvents: boolean): boolean { diff --git a/src/events/forward/getForwardableEvent.ts b/src/events/forward/getForwardableEvent.ts index 380c0a5f9ab..2ceaedc1089 100644 --- a/src/events/forward/getForwardableEvent.ts +++ b/src/events/forward/getForwardableEvent.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls"; +import { M_POLL_START } from "matrix-js-sdk/src/@types/polls"; import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon"; import { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix"; @@ -26,7 +26,7 @@ import { VoiceBroadcastInfoEventType } from "../../voice-broadcast/types"; * If an event is not forwardable return null */ export const getForwardableEvent = (event: MatrixEvent, cli: MatrixClient): MatrixEvent | null => { - if (M_POLL_START.matches(event.getType()) || M_POLL_END.matches(event.getType())) { + if (M_POLL_START.matches(event.getType())) { return null; } diff --git a/src/utils/EventRenderingUtils.ts b/src/utils/EventRenderingUtils.ts index ccb0c25c90d..3acd95e48af 100644 --- a/src/utils/EventRenderingUtils.ts +++ b/src/utils/EventRenderingUtils.ts @@ -16,7 +16,7 @@ limitations under the License. import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { EventType, MsgType } from "matrix-js-sdk/src/@types/event"; -import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls"; +import { M_POLL_START } from "matrix-js-sdk/src/@types/polls"; import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon"; import { IContent } from "matrix-js-sdk/src/matrix"; @@ -41,7 +41,6 @@ const calcIsInfoMessage = ( eventType !== EventType.Sticker && eventType !== EventType.RoomCreate && !M_POLL_START.matches(eventType) && - !M_POLL_END.matches(eventType) && !M_BEACON_INFO.matches(eventType) && !(eventType === VoiceBroadcastInfoEventType && content?.state === VoiceBroadcastInfoState.Started) ); diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index c33a90cc865..b949ef9c790 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -18,7 +18,7 @@ import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { EventType, EVENT_VISIBILITY_CHANGE_TYPE, MsgType, RelationType } from "matrix-js-sdk/src/@types/event"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { logger } from "matrix-js-sdk/src/logger"; -import { M_POLL_END, M_POLL_START } from "matrix-js-sdk/src/@types/polls"; +import { M_POLL_START } from "matrix-js-sdk/src/@types/polls"; import { M_LOCATION } from "matrix-js-sdk/src/@types/location"; import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon"; import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread"; @@ -57,7 +57,6 @@ export function isContentActionable(mxEvent: MatrixEvent): boolean { } else if ( mxEvent.getType() === "m.sticker" || M_POLL_START.matches(mxEvent.getType()) || - M_POLL_END.matches(mxEvent.getType()) || M_BEACON_INFO.matches(mxEvent.getType()) || (mxEvent.getType() === VoiceBroadcastInfoEventType && mxEvent.getContent()?.state === VoiceBroadcastInfoState.Started) diff --git a/test/components/views/messages/MPollEndBody-test.tsx b/test/components/views/messages/MPollEndBody-test.tsx deleted file mode 100644 index 2e78646ceb6..00000000000 --- a/test/components/views/messages/MPollEndBody-test.tsx +++ /dev/null @@ -1,203 +0,0 @@ -/* -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 { render } from "@testing-library/react"; -import { EventTimeline, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; -import { logger } from "matrix-js-sdk/src/logger"; -import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events"; - -import { IBodyProps } from "../../../../src/components/views/messages/IBodyProps"; -import { MPollEndBody } from "../../../../src/components/views/messages/MPollEndBody"; -import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; -import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; -import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper"; -import { - flushPromises, - getMockClientWithEventEmitter, - makePollEndEvent, - makePollStartEvent, - mockClientMethodsEvents, - mockClientMethodsUser, - setupRoomWithPollEvents, -} from "../../../test-utils"; - -describe("", () => { - const userId = "@alice:domain.org"; - const roomId = "!room:domain.org"; - const mockClient = getMockClientWithEventEmitter({ - ...mockClientMethodsUser(userId), - ...mockClientMethodsEvents(), - getRoom: jest.fn(), - relations: jest.fn(), - fetchRoomEvent: jest.fn(), - }); - const pollStartEvent = makePollStartEvent("Question?", userId, undefined, { roomId }); - const pollEndEvent = makePollEndEvent(pollStartEvent.getId()!, roomId, userId, 123); - - const setupRoomWithEventsTimeline = async (pollEnd: MatrixEvent, pollStart?: MatrixEvent): Promise => { - if (pollStart) { - await setupRoomWithPollEvents([pollStart], [], [pollEnd], mockClient); - } - const room = mockClient.getRoom(roomId) || new Room(roomId, mockClient, userId); - - // end events validate against this - jest.spyOn(room.currentState, "maySendRedactionForEvent").mockImplementation( - (_evt: MatrixEvent, id: string) => { - return id === mockClient.getSafeUserId(); - }, - ); - - const timelineSet = room.getUnfilteredTimelineSet(); - const getTimelineForEventSpy = jest.spyOn(timelineSet, "getTimelineForEvent"); - // if we have a pollStart, mock the room timeline to include it - if (pollStart) { - const eventTimeline = { - getEvents: jest.fn().mockReturnValue([pollEnd, pollStart]), - } as unknown as EventTimeline; - getTimelineForEventSpy.mockReturnValue(eventTimeline); - } - mockClient.getRoom.mockReturnValue(room); - - return room; - }; - - const defaultProps = { - mxEvent: pollEndEvent, - highlightLink: "unused", - mediaEventHelper: {} as unknown as MediaEventHelper, - onHeightChanged: () => {}, - onMessageAllowed: () => {}, - permalinkCreator: {} as unknown as RoomPermalinkCreator, - ref: undefined as any, - }; - - const getComponent = (props: Partial = {}) => - render(, { - wrapper: ({ children }) => ( - {children} - ), - }); - - beforeEach(() => { - mockClient.getRoom.mockReset(); - mockClient.relations.mockResolvedValue({ - events: [], - }); - mockClient.fetchRoomEvent.mockResolvedValue(pollStartEvent.toJSON()); - }); - - afterEach(() => { - jest.spyOn(logger, "error").mockRestore(); - }); - - describe("when poll start event exists in current timeline", () => { - it("renders an ended poll", async () => { - await setupRoomWithEventsTimeline(pollEndEvent, pollStartEvent); - const { container } = getComponent(); - - // ended poll rendered - expect(container).toMatchSnapshot(); - - // didnt try to fetch start event while it was already in timeline - expect(mockClient.fetchRoomEvent).not.toHaveBeenCalled(); - }); - - it("does not render a poll tile when end event is invalid", async () => { - // sender of end event does not match start event - const invalidEndEvent = makePollEndEvent(pollStartEvent.getId()!, roomId, "@mallory:domain.org", 123); - await setupRoomWithEventsTimeline(invalidEndEvent, pollStartEvent); - const { getByText } = getComponent({ mxEvent: invalidEndEvent }); - - // no poll tile rendered - expect(getByText("The poll has ended. Something.")).toBeTruthy(); - }); - }); - - describe("when poll start event does not exist in current timeline", () => { - it("fetches the related poll start event and displays a poll tile", async () => { - await setupRoomWithEventsTimeline(pollEndEvent); - const { container, getByTestId } = getComponent(); - - // while fetching event, only icon is shown - expect(container).toMatchSnapshot(); - - // flush the fetch event promise - await flushPromises(); - - expect(mockClient.fetchRoomEvent).toHaveBeenCalledWith(roomId, pollStartEvent.getId()); - - // quick check for poll tile - expect(getByTestId("pollQuestion").innerHTML).toEqual("Question?"); - expect(getByTestId("totalVotes").innerHTML).toEqual("Final result based on 0 votes"); - }); - - it("does not render a poll tile when end event is invalid", async () => { - // sender of end event does not match start event - const invalidEndEvent = makePollEndEvent(pollStartEvent.getId()!, roomId, "@mallory:domain.org", 123); - await setupRoomWithEventsTimeline(invalidEndEvent); - const { getByText } = getComponent({ mxEvent: invalidEndEvent }); - - // flush the fetch event promise - await flushPromises(); - - // no poll tile rendered - expect(getByText("The poll has ended. Something.")).toBeTruthy(); - }); - - it("logs an error and displays the text fallback when fetching the start event fails", async () => { - await setupRoomWithEventsTimeline(pollEndEvent); - mockClient.fetchRoomEvent.mockRejectedValue({ code: 404 }); - const logSpy = jest.spyOn(logger, "error").mockImplementation(() => {}); - const { getByText } = getComponent(); - - // flush the fetch event promise - await flushPromises(); - - // poll end event fallback text used - expect(getByText("The poll has ended. Something.")).toBeTruthy(); - expect(logSpy).toHaveBeenCalledWith("Failed to fetch related poll start event", { code: 404 }); - }); - - it("logs an error and displays the extensible event text when fetching the start event fails", async () => { - await setupRoomWithEventsTimeline(pollEndEvent); - mockClient.fetchRoomEvent.mockRejectedValue({ code: 404 }); - const logSpy = jest.spyOn(logger, "error").mockImplementation(() => {}); - const { getByText } = getComponent(); - - // flush the fetch event promise - await flushPromises(); - - // poll end event fallback text used - expect(getByText("The poll has ended. Something.")).toBeTruthy(); - expect(logSpy).toHaveBeenCalledWith("Failed to fetch related poll start event", { code: 404 }); - }); - - it("displays fallback text when the poll end event does not have text", async () => { - const endWithoutText = makePollEndEvent(pollStartEvent.getId()!, roomId, userId, 123); - delete endWithoutText.getContent()[M_TEXT.name]; - await setupRoomWithEventsTimeline(endWithoutText); - mockClient.fetchRoomEvent.mockRejectedValue({ code: 404 }); - const { getByText } = getComponent({ mxEvent: endWithoutText }); - - // flush the fetch event promise - await flushPromises(); - - // default fallback text used - expect(getByText("@alice:domain.org has ended a poll")).toBeTruthy(); - }); - }); -}); diff --git a/test/components/views/messages/__snapshots__/MPollEndBody-test.tsx.snap b/test/components/views/messages/__snapshots__/MPollEndBody-test.tsx.snap deleted file mode 100644 index e98ec1f4fcc..00000000000 --- a/test/components/views/messages/__snapshots__/MPollEndBody-test.tsx.snap +++ /dev/null @@ -1,108 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` when poll start event does not exist in current timeline fetches the related poll start event and displays a poll tile 1`] = ` -
-
-
-`; - -exports[` when poll start event exists in current timeline renders an ended poll 1`] = ` -
-
-

- Question? -

-
-
-
-
-
- Socks -
-
- 0 votes -
-
-
-
-
-
-
-
-
-
-
- Shoes -
-
- 0 votes -
-
-
-
-
-
-
-
-
- Final result based on 0 votes -
-
-
-
-
-
-`;