diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 0d56e1b512a..533b7c0c21d 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -17,6 +17,7 @@ @import "./components/views/beacon/_ShareLatestLocation.pcss"; @import "./components/views/beacon/_StyledLiveBeaconIcon.pcss"; @import "./components/views/context_menus/_KebabContextMenu.pcss"; +@import "./components/views/dialogs/polls/_PollDetailHeader.pcss"; @import "./components/views/dialogs/polls/_PollListItem.pcss"; @import "./components/views/dialogs/polls/_PollListItemEnded.pcss"; @import "./components/views/elements/_FilterDropdown.pcss"; diff --git a/res/css/components/views/dialogs/polls/_PollDetailHeader.pcss b/res/css/components/views/dialogs/polls/_PollDetailHeader.pcss new file mode 100644 index 00000000000..6f29b6e08fd --- /dev/null +++ b/res/css/components/views/dialogs/polls/_PollDetailHeader.pcss @@ -0,0 +1,27 @@ +/* +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_PollDetailHeader { + // override accessiblebutton style + font-size: $font-15px !important; +} + +.mx_PollDetailHeader_icon { + height: 15px; + width: 15px; + margin-right: $spacing-8; + vertical-align: middle; +} diff --git a/res/css/components/views/dialogs/polls/_PollListItem.pcss b/res/css/components/views/dialogs/polls/_PollListItem.pcss index 7b19e675943..d6036fa3787 100644 --- a/res/css/components/views/dialogs/polls/_PollListItem.pcss +++ b/res/css/components/views/dialogs/polls/_PollListItem.pcss @@ -16,12 +16,17 @@ limitations under the License. .mx_PollListItem { width: 100%; +} + +.mx_PollListItem_content { + width: 100%; display: grid; justify-content: left; align-items: center; grid-gap: $spacing-8; grid-template-columns: auto auto auto; grid-template-rows: auto; + cursor: pointer; color: $primary-content; } diff --git a/res/css/components/views/dialogs/polls/_PollListItemEnded.pcss b/res/css/components/views/dialogs/polls/_PollListItemEnded.pcss index 6518052ab61..16ea5dcce07 100644 --- a/res/css/components/views/dialogs/polls/_PollListItemEnded.pcss +++ b/res/css/components/views/dialogs/polls/_PollListItemEnded.pcss @@ -16,9 +16,14 @@ limitations under the License. .mx_PollListItemEnded { width: 100%; +} + +.mx_PollListItemEnded_content { + width: 100%; display: flex; flex-direction: column; color: $primary-content; + cursor: pointer; } .mx_PollListItemEnded_title { diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 4f27392e254..97932894160 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -262,7 +262,14 @@ export default class RightPanel extends React.Component { break; case RightPanelPhases.RoomSummary: - card = ; + card = ( + + ); break; case RightPanelPhases.Widget: diff --git a/src/components/views/dialogs/polls/PollDetail.tsx b/src/components/views/dialogs/polls/PollDetail.tsx new file mode 100644 index 00000000000..068687a7fd1 --- /dev/null +++ b/src/components/views/dialogs/polls/PollDetail.tsx @@ -0,0 +1,89 @@ +/* +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 { Poll } from "matrix-js-sdk/src/matrix"; + +import { _t } from "../../../../languageHandler"; +import dispatcher from "../../../../dispatcher/dispatcher"; +import { Action } from "../../../../dispatcher/actions"; +import { ViewRoomPayload } from "../../../../dispatcher/payloads/ViewRoomPayload"; +import { RoomPermalinkCreator } from "../../../../utils/permalinks/Permalinks"; +import { MediaEventHelper } from "../../../../utils/MediaEventHelper"; +import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton"; +import MPollBody from "../../messages/MPollBody"; + +interface Props { + poll: Poll; + requestModalClose: () => void; + permalinkCreator: RoomPermalinkCreator; +} + +const NOOP = (): void => {}; + +/** + * Content of the PollHistoryDialog when a specific poll is selected + */ +export const PollDetail: React.FC = ({ poll, permalinkCreator, requestModalClose }) => { + // link to end event for ended polls + const eventIdToLinkTo = poll.isEnded ? poll.endEventId! : poll.pollId; + const linkToTimeline = permalinkCreator.forEvent(eventIdToLinkTo); + + const onLinkClick = (e: ButtonEvent): void => { + if ((e as React.MouseEvent).ctrlKey || (e as React.MouseEvent).metaKey) { + // native behavior for link on ctrl/cmd + click + return; + } + // otherwise handle navigation in the app + e.preventDefault(); + dispatcher.dispatch({ + action: Action.ViewRoom, + event_id: eventIdToLinkTo, + highlighted: true, + room_id: poll.roomId, + metricsTrigger: undefined, // room doesn't change + }); + + requestModalClose(); + }; + return ( + <> + +
+
+ + {_t("View poll in timeline")} + +
+ + ); +}; diff --git a/src/components/views/dialogs/polls/PollDetailHeader.tsx b/src/components/views/dialogs/polls/PollDetailHeader.tsx new file mode 100644 index 00000000000..edabec5b0b9 --- /dev/null +++ b/src/components/views/dialogs/polls/PollDetailHeader.tsx @@ -0,0 +1,36 @@ +/* +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 { Icon as LeftCaretIcon } from "../../../../../res/img/element-icons/caret-left.svg"; +import { _t } from "../../../../languageHandler"; +import AccessibleButton from "../../elements/AccessibleButton"; +import { PollHistoryFilter } from "./types"; + +interface Props { + filter: PollHistoryFilter; + onNavigateBack: () => void; +} + +export const PollDetailHeader: React.FC = ({ filter, onNavigateBack }) => { + return ( + + + {filter === "ACTIVE" ? _t("Active polls") : _t("Past polls")} + + ); +}; diff --git a/src/components/views/dialogs/polls/PollHistoryDialog.tsx b/src/components/views/dialogs/polls/PollHistoryDialog.tsx index bd6c9fae8a5..ba16ed2b0cd 100644 --- a/src/components/views/dialogs/polls/PollHistoryDialog.tsx +++ b/src/components/views/dialogs/polls/PollHistoryDialog.tsx @@ -16,19 +16,23 @@ limitations under the License. import React, { useState } from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; -import { MatrixEvent, Poll } from "matrix-js-sdk/src/matrix"; +import { MatrixEvent, Poll, Room } from "matrix-js-sdk/src/matrix"; import { _t } from "../../../../languageHandler"; import BaseDialog from "../BaseDialog"; import { IDialogProps } from "../IDialogProps"; import { PollHistoryList } from "./PollHistoryList"; import { PollHistoryFilter } from "./types"; +import { PollDetailHeader } from "./PollDetailHeader"; +import { PollDetail } from "./PollDetail"; +import { RoomPermalinkCreator } from "../../../../utils/permalinks/Permalinks"; import { usePollsWithRelations } from "./usePollHistory"; import { useFetchPastPolls } from "./fetchPastPolls"; type PollHistoryDialogProps = Pick & { - roomId: string; + room: Room; matrixClient: MatrixClient; + permalinkCreator: RoomPermalinkCreator; }; const sortEventsByLatest = (left: MatrixEvent, right: MatrixEvent): number => right.getTs() - left.getTs(); @@ -46,25 +50,42 @@ const filterAndSortPolls = (polls: Map, filter: PollHistoryFilter) .sort(sortEventsByLatest); }; -export const PollHistoryDialog: React.FC = ({ roomId, matrixClient, onFinished }) => { - const room = matrixClient.getRoom(roomId)!; - const { isLoading } = useFetchPastPolls(room, matrixClient); - const { polls } = usePollsWithRelations(roomId, matrixClient); +export const PollHistoryDialog: React.FC = ({ + room, + matrixClient, + permalinkCreator, + onFinished, +}) => { + const { polls } = usePollsWithRelations(room.roomId, matrixClient); const [filter, setFilter] = useState("ACTIVE"); + const [focusedPollId, setFocusedPollId] = useState(null); + const { isLoading } = useFetchPastPolls(room, matrixClient); const pollStartEvents = filterAndSortPolls(polls, filter); const isLoadingPollResponses = [...polls.values()].some((poll) => poll.isFetchingResponses); + const focusedPoll = focusedPollId ? polls.get(focusedPollId) : undefined; + const title = focusedPoll ? ( + setFocusedPollId(null)} /> + ) : ( + _t("Polls history") + ); + return ( - +
- + {focusedPoll ? ( + + ) : ( + + )}
); diff --git a/src/components/views/dialogs/polls/PollHistoryList.tsx b/src/components/views/dialogs/polls/PollHistoryList.tsx index c686b82ebf2..d3b0cae5048 100644 --- a/src/components/views/dialogs/polls/PollHistoryList.tsx +++ b/src/components/views/dialogs/polls/PollHistoryList.tsx @@ -40,8 +40,9 @@ type PollHistoryListProps = { pollStartEvents: MatrixEvent[]; polls: Map; filter: PollHistoryFilter; - onFilterChange: (filter: PollHistoryFilter) => void; isLoading?: boolean; + onFilterChange: (filter: PollHistoryFilter) => void; + onItemClick: (pollId: string) => void; }; export const PollHistoryList: React.FC = ({ pollStartEvents, @@ -49,6 +50,7 @@ export const PollHistoryList: React.FC = ({ filter, isLoading, onFilterChange, + onItemClick, }) => { return (
@@ -65,12 +67,17 @@ export const PollHistoryList: React.FC = ({
    {pollStartEvents.map((pollStartEvent) => filter === "ACTIVE" ? ( - + onItemClick(pollStartEvent.getId()!)} + /> ) : ( onItemClick(pollStartEvent.getId()!)} /> ), )} diff --git a/src/components/views/dialogs/polls/PollListItem.tsx b/src/components/views/dialogs/polls/PollListItem.tsx index 7a4ace08b05..f3c73998840 100644 --- a/src/components/views/dialogs/polls/PollListItem.tsx +++ b/src/components/views/dialogs/polls/PollListItem.tsx @@ -20,22 +20,30 @@ import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { Icon as PollIcon } from "../../../../../res/img/element-icons/room/composer/poll.svg"; import { formatLocalDateShort } from "../../../../DateUtils"; +import { _t } from "../../../../languageHandler"; +import TooltipTarget from "../../elements/TooltipTarget"; +import { Alignment } from "../../elements/Tooltip"; interface Props { event: MatrixEvent; + onClick: () => void; } -export const PollListItem: React.FC = ({ event }) => { +export const PollListItem: React.FC = ({ event, onClick }) => { const pollEvent = event.unstableExtensibleEvent as unknown as PollStartEvent; if (!pollEvent) { return null; } const formattedDate = formatLocalDateShort(event.getTs()); return ( -
  1. - {formattedDate} - - {pollEvent.question.text} +
  2. + +
    + {formattedDate} + + {pollEvent.question.text} +
    +
  3. ); }; diff --git a/src/components/views/dialogs/polls/PollListItemEnded.tsx b/src/components/views/dialogs/polls/PollListItemEnded.tsx index 8a6b2c9595f..c78dd19d52d 100644 --- a/src/components/views/dialogs/polls/PollListItemEnded.tsx +++ b/src/components/views/dialogs/polls/PollListItemEnded.tsx @@ -25,10 +25,13 @@ import { formatLocalDateShort } from "../../../../DateUtils"; import { allVotes, collectUserVotes, countVotes } from "../../messages/MPollBody"; import { PollOption } from "../../polls/PollOption"; import { Caption } from "../../typography/Caption"; +import TooltipTarget from "../../elements/TooltipTarget"; +import { Alignment } from "../../elements/Tooltip"; interface Props { event: MatrixEvent; poll: Poll; + onClick: () => void; } type EndedPollState = { @@ -88,7 +91,7 @@ const usePollVotes = (poll: Poll): Partial => { * @param event - the poll start MatrixEvent * @param poll - Poll instance */ -export const PollListItemEnded: React.FC = ({ event, poll }) => { +export const PollListItemEnded: React.FC = ({ event, poll, onClick }) => { const pollEvent = poll.pollEvent; const { winningAnswers, totalVoteCount } = usePollVotes(poll); if (!pollEvent) { @@ -97,31 +100,35 @@ export const PollListItemEnded: React.FC = ({ event, poll }) => { const formattedDate = formatLocalDateShort(event.getTs()); return ( -
  4. -
    - - {pollEvent.question.text} - {formattedDate} -
    - {!!winningAnswers?.length && ( -
    - {winningAnswers?.map(({ answer, voteCount }) => ( - - ))} +
  5. + +
    +
    + + {pollEvent.question.text} + {formattedDate} +
    + {!!winningAnswers?.length && ( +
    + {winningAnswers?.map(({ answer, voteCount }) => ( + + ))} +
    + )} +
    + {_t("Final result based on %(count)s votes", { count: totalVoteCount })} +
    - )} -
    - {_t("Final result based on %(count)s votes", { count: totalVoteCount })} -
    +
  6. ); }; diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index b184fd7ba52..476a0e8f8bd 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -37,6 +37,7 @@ import WidgetAvatar from "../avatars/WidgetAvatar"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import WidgetStore, { IApp } from "../../../stores/WidgetStore"; import { E2EStatus } from "../../../utils/ShieldUtils"; +import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import RoomContext from "../../../contexts/RoomContext"; import { UIComponent, UIFeature } from "../../../settings/UIFeature"; import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu"; @@ -55,6 +56,7 @@ import { PollHistoryDialog } from "../dialogs/polls/PollHistoryDialog"; interface IProps { room: Room; + permalinkCreator: RoomPermalinkCreator; onClose(): void; } @@ -268,7 +270,7 @@ const onRoomSettingsClick = (ev: ButtonEvent): void => { PosthogTrackers.trackInteraction("WebRightPanelRoomInfoSettingsButton", ev); }; -const RoomSummaryCard: React.FC = ({ room, onClose }) => { +const RoomSummaryCard: React.FC = ({ room, permalinkCreator, onClose }) => { const cli = useContext(MatrixClientContext); const onShareRoomClick = (): void => { @@ -285,8 +287,9 @@ const RoomSummaryCard: React.FC = ({ room, onClose }) => { const onRoomPollHistoryClick = (): void => { Modal.createDialog(PollHistoryDialog, { - roomId: room.roomId, + room, matrixClient: cli, + permalinkCreator, }); }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4a0aca7aedf..a2cacaf4c92 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3139,9 +3139,13 @@ "Not a valid Security Key": "Not a valid Security Key", "Access your secure message history and set up secure messaging by entering your Security Key.": "Access your secure message history and set up secure messaging by entering your Security Key.", "If you've forgotten your Security Key you can ": "If you've forgotten your Security Key you can ", + "View poll in timeline": "View poll in timeline", + "Active polls": "Active polls", + "Past polls": "Past polls", "Loading polls": "Loading polls", "There are no active polls in this room": "There are no active polls in this room", "There are no past polls in this room": "There are no past polls in this room", + "View poll": "View poll", "Send custom account data event": "Send custom account data event", "Send custom room account data event": "Send custom room account data event", "Event Type": "Event Type", diff --git a/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx b/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx index 062fe2d0d89..f1da27141c0 100644 --- a/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx +++ b/test/components/views/dialogs/polls/PollHistoryDialog-test.tsx @@ -31,6 +31,9 @@ import { setupRoomWithPollEvents, unmockIntlDateTimeFormat, } from "../../../../test-utils"; +import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks"; +import defaultDispatcher from "../../../../../src/dispatcher/dispatcher"; +import { Action } from "../../../../../src/dispatcher/actions"; describe("", () => { // 14.03.2022 16:15 @@ -57,8 +60,9 @@ describe("", () => { }); const defaultProps = { - roomId, + room, matrixClient: mockClient, + permalinkCreator: new RoomPermalinkCreator(room), onFinished: jest.fn(), }; const getComponent = () => render(); @@ -70,9 +74,12 @@ describe("", () => { beforeEach(() => { room = new Room(roomId, mockClient, userId); mockClient.getRoom.mockReturnValue(room); + defaultProps.room = room; mockClient.relations.mockResolvedValue({ events: [] }); const timeline = room.getLiveTimeline(); jest.spyOn(timeline, "getEvents").mockReturnValue([]); + jest.spyOn(defaultDispatcher, "dispatch").mockClear(); + defaultProps.onFinished.mockClear(); jest.spyOn(room, "getOrCreateFilteredTimelineSet"); mockClient.getOrCreateFilter.mockResolvedValue(expectedFilter.filterId!); mockClient.paginateEventTimeline.mockReset().mockResolvedValue(false); @@ -314,4 +321,165 @@ describe("", () => { // this poll is ended expect(getByText("What?")).toBeInTheDocument(); }); + + describe("Poll detail", () => { + const timestamp = 1675300825090; + const pollStart1 = makePollStartEvent("Question?", userId, undefined, { ts: timestamp, id: "$1" }); + const pollStart2 = makePollStartEvent("Where?", userId, undefined, { ts: timestamp + 10000, id: "$2" }); + const pollStart3 = makePollStartEvent("What?", userId, undefined, { ts: timestamp + 70000, id: "$3" }); + const pollEnd3 = makePollEndEvent(pollStart3.getId()!, roomId, userId, timestamp + 1, "$4"); + + it("displays poll detail on active poll list item click", async () => { + await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room); + + const { getByText, queryByText } = getComponent(); + await flushPromises(); + + fireEvent.click(getByText("Question?")); + + expect(queryByText("Polls history")).not.toBeInTheDocument(); + // elements from MPollBody + expect(getByText("Question?")).toMatchSnapshot(); + expect(getByText("Socks")).toBeInTheDocument(); + expect(getByText("Shoes")).toBeInTheDocument(); + }); + + it("links to the poll start event from an active poll detail", async () => { + await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room); + + const { getByText } = getComponent(); + await flushPromises(); + + fireEvent.click(getByText("Question?")); + + // links to poll start event + expect(getByText("View poll in timeline").getAttribute("href")).toBe( + `https://matrix.to/#/!room:domain.org/${pollStart1.getId()!}`, + ); + }); + + it("navigates in app when clicking view in timeline button", async () => { + await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room); + + const { getByText } = getComponent(); + await flushPromises(); + + fireEvent.click(getByText("Question?")); + + const event = new MouseEvent("click", { bubbles: true, cancelable: true }); + jest.spyOn(event, "preventDefault"); + fireEvent(getByText("View poll in timeline"), event); + + expect(event.preventDefault).toHaveBeenCalled(); + + expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ + action: Action.ViewRoom, + event_id: pollStart1.getId()!, + highlighted: true, + metricsTrigger: undefined, + room_id: pollStart1.getRoomId()!, + }); + + // dialog closed + expect(defaultProps.onFinished).toHaveBeenCalled(); + }); + + it("doesnt navigate in app when view in timeline link is ctrl + clicked", async () => { + await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room); + + const { getByText } = getComponent(); + await flushPromises(); + + fireEvent.click(getByText("Question?")); + + const event = new MouseEvent("click", { bubbles: true, cancelable: true, ctrlKey: true }); + jest.spyOn(event, "preventDefault"); + fireEvent(getByText("View poll in timeline"), event); + + expect(event.preventDefault).not.toHaveBeenCalled(); + expect(defaultDispatcher.dispatch).not.toHaveBeenCalled(); + expect(defaultProps.onFinished).not.toHaveBeenCalled(); + }); + + it("navigates back to poll list from detail view on header click", async () => { + await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room); + + const { getByText, queryByText, getByTestId, container } = getComponent(); + await flushPromises(); + + fireEvent.click(getByText("Question?")); + + // detail view + expect(getByText("Question?")).toBeInTheDocument(); + + // header not shown + expect(queryByText("Polls history")).not.toBeInTheDocument(); + + expect(getByText("Active polls")).toMatchSnapshot(); + fireEvent.click(getByText("Active polls")); + + // main list header displayed again + expect(getByText("Polls history")).toBeInTheDocument(); + // active filter still active + expect(getByTestId("filter-tab-PollHistoryDialog_filter-ACTIVE").firstElementChild).toBeChecked(); + // list displayed + expect(container.getElementsByClassName("mx_PollHistoryList_list").length).toBeTruthy(); + }); + + it("displays poll detail on past poll list item click", async () => { + await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room); + + const { getByText } = getComponent(); + await flushPromises(); + + fireEvent.click(getByText("Past polls")); + + // pollStart3 is ended + fireEvent.click(getByText("What?")); + + expect(getByText("What?")).toMatchSnapshot(); + }); + + it("links to the poll end events from a ended poll detail", async () => { + await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room); + + const { getByText } = getComponent(); + await flushPromises(); + + fireEvent.click(getByText("Past polls")); + + // pollStart3 is ended + fireEvent.click(getByText("What?")); + + // links to poll end event + expect(getByText("View poll in timeline").getAttribute("href")).toBe( + `https://matrix.to/#/!room:domain.org/${pollEnd3.getId()!}`, + ); + }); + + it("navigates back to poll list from detail view on header click", async () => { + await setupRoomWithPollEvents([pollStart1, pollStart2, pollStart3], [], [pollEnd3], mockClient, room); + + const { getByText, queryByText, getByTestId, container } = getComponent(); + await flushPromises(); + + fireEvent.click(getByText("Question?")); + + // detail view + expect(getByText("Question?")).toBeInTheDocument(); + + // header not shown + expect(queryByText("Polls history")).not.toBeInTheDocument(); + + expect(getByText("Active polls")).toMatchSnapshot(); + fireEvent.click(getByText("Active polls")); + + // main list header displayed again + expect(getByText("Polls history")).toBeInTheDocument(); + // active filter still active + expect(getByTestId("filter-tab-PollHistoryDialog_filter-ACTIVE").firstElementChild).toBeChecked(); + // list displayed + expect(container.getElementsByClassName("mx_PollHistoryList_list").length).toBeTruthy(); + }); + }); }); diff --git a/test/components/views/dialogs/polls/PollListItem-test.tsx b/test/components/views/dialogs/polls/PollListItem-test.tsx index 5744a31df5d..0abe6db6bd4 100644 --- a/test/components/views/dialogs/polls/PollListItem-test.tsx +++ b/test/components/views/dialogs/polls/PollListItem-test.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React from "react"; -import { render } from "@testing-library/react"; +import { fireEvent, render } from "@testing-library/react"; import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { PollListItem } from "../../../../../src/components/views/dialogs/polls/PollListItem"; @@ -24,7 +24,7 @@ import { makePollStartEvent, mockIntlDateTimeFormat, unmockIntlDateTimeFormat } describe("", () => { const event = makePollStartEvent("Question?", "@me:domain.org"); event.getContent().origin; - const defaultProps = { event }; + const defaultProps = { event, onClick: jest.fn() }; const getComponent = (props = {}) => render(); beforeAll(() => { @@ -50,4 +50,13 @@ describe("", () => { const { container } = getComponent({ event }); expect(container.firstElementChild).toBeFalsy(); }); + + it("calls onClick handler on click", () => { + const onClick = jest.fn(); + const { getByText } = getComponent({ onClick }); + + fireEvent.click(getByText("Question?")); + + expect(onClick).toHaveBeenCalled(); + }); }); diff --git a/test/components/views/dialogs/polls/PollListItemEnded-test.tsx b/test/components/views/dialogs/polls/PollListItemEnded-test.tsx index c58de7c5209..b14982d2cdd 100644 --- a/test/components/views/dialogs/polls/PollListItemEnded-test.tsx +++ b/test/components/views/dialogs/polls/PollListItemEnded-test.tsx @@ -60,7 +60,8 @@ describe("", () => { }); const pollEndEvent = makePollEndEvent(pollId, roomId, userId, timestamp + 60000); - const getComponent = (props: { event: MatrixEvent; poll: Poll }) => render(); + const getComponent = (props: { event: MatrixEvent; poll: Poll }) => + render(); beforeAll(() => { // mock default locale to en-GB and set timezone diff --git a/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap b/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap index aae4cbc1961..76c76484fb3 100644 --- a/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap +++ b/test/components/views/dialogs/polls/__snapshots__/PollHistoryDialog-test.tsx.snap @@ -1,5 +1,47 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[` Poll detail displays poll detail on active poll list item click 1`] = ` +

    + Question? +

    +`; + +exports[` Poll detail displays poll detail on past poll list item click 1`] = ` +

    + What? +

    +`; + +exports[` Poll detail navigates back to poll list from detail view on header click 1`] = ` +
    +
    + Active polls +
    +`; + +exports[` Poll detail navigates back to poll list from detail view on header click 2`] = ` +
    +
    + Active polls +
    +`; + exports[` renders a list of active polls when there are polls in the room 1`] = `
    renders a list of active polls when there are pol class="mx_PollListItem" data-testid="pollListItem-$2" > - - 02/02/23 -
    - - Where? - +
    + + 02/02/23 + +
    + + Where? + +
    +
  7. - - 02/02/23 -
    - - Question? - +
    + + 02/02/23 + +
    + + Question? + +
    +
diff --git a/test/components/views/dialogs/polls/__snapshots__/PollListItem-test.tsx.snap b/test/components/views/dialogs/polls/__snapshots__/PollListItem-test.tsx.snap index 00ae9fbd06b..c6ebc5afc98 100644 --- a/test/components/views/dialogs/polls/__snapshots__/PollListItem-test.tsx.snap +++ b/test/components/views/dialogs/polls/__snapshots__/PollListItem-test.tsx.snap @@ -6,17 +6,25 @@ exports[` renders a poll 1`] = ` class="mx_PollListItem" data-testid="pollListItem-$mypoll" > - - 01/01/70 -
- - Question? - +
+ + 01/01/70 + +
+ + Question? + +
+
`; diff --git a/test/components/views/dialogs/polls/__snapshots__/PollListItemEnded-test.tsx.snap b/test/components/views/dialogs/polls/__snapshots__/PollListItemEnded-test.tsx.snap index d40f067faa1..eecd3e115df 100644 --- a/test/components/views/dialogs/polls/__snapshots__/PollListItemEnded-test.tsx.snap +++ b/test/components/views/dialogs/polls/__snapshots__/PollListItemEnded-test.tsx.snap @@ -7,30 +7,38 @@ exports[` renders a poll with no responses 1`] = ` data-testid="pollListItem-1" >
- - Question? - - - 02/02/23 - -
-
- - Final result based on 0 votes - +
+
+ + Question? + + + 02/02/23 + +
+
+ + Final result based on 0 votes + +
+
diff --git a/test/components/views/right_panel/RoomSummaryCard-test.tsx b/test/components/views/right_panel/RoomSummaryCard-test.tsx index 7ec61433957..a52d47717b8 100644 --- a/test/components/views/right_panel/RoomSummaryCard-test.tsx +++ b/test/components/views/right_panel/RoomSummaryCard-test.tsx @@ -30,6 +30,7 @@ import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore" import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases"; import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils"; import { PollHistoryDialog } from "../../../../src/components/views/dialogs/polls/PollHistoryDialog"; +import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; describe("", () => { const userId = "@alice:domain.org"; @@ -54,6 +55,7 @@ describe("", () => { const defaultProps = { room, onClose: jest.fn(), + permalinkCreator: new RoomPermalinkCreator(room), }; const getComponent = (props = {}) => render(, { @@ -145,7 +147,11 @@ describe("", () => { fireEvent.click(getByText("Polls history")); - expect(modalSpy).toHaveBeenCalledWith(PollHistoryDialog, { roomId, matrixClient: mockClient }); + expect(modalSpy).toHaveBeenCalledWith(PollHistoryDialog, { + room, + matrixClient: mockClient, + permalinkCreator: defaultProps.permalinkCreator, + }); }); }); diff --git a/test/test-utils/poll.ts b/test/test-utils/poll.ts index 81d95d532ab..9e11f46e8d7 100644 --- a/test/test-utils/poll.ts +++ b/test/test-utils/poll.ts @@ -66,9 +66,15 @@ export const makePollStartEvent = ( }); }; -export const makePollEndEvent = (pollStartEventId: string, roomId: string, sender: string, ts = 0): MatrixEvent => { +export const makePollEndEvent = ( + pollStartEventId: string, + roomId: string, + sender: string, + ts = 0, + id?: string, +): MatrixEvent => { return new MatrixEvent({ - event_id: uuid4(), + event_id: id || uuid4(), room_id: roomId, origin_server_ts: ts, type: M_POLL_END.name,