mirror of
https://github.com/element-hq/element-web
synced 2024-11-25 10:45:51 +03:00
Poll history: detail screen (#10172)
* basic navigation to focused poll * add tooltip * drill permalinkCreator down to poll history * render poll tile and link to timeline * tidy and lint * unit test poll detail * add view poll link to ended pollliste item * strict fix * pr improvements * pass room as prop * permalinkcreator ts assertion
This commit is contained in:
parent
9b2b3ca42e
commit
f57495d3cd
21 changed files with 588 additions and 104 deletions
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -262,7 +262,14 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
|||
break;
|
||||
|
||||
case RightPanelPhases.RoomSummary:
|
||||
card = <RoomSummaryCard room={this.props.room} onClose={this.onClose} />;
|
||||
card = (
|
||||
<RoomSummaryCard
|
||||
room={this.props.room}
|
||||
onClose={this.onClose}
|
||||
// whenever RightPanel is passed a room it is passed a permalinkcreator
|
||||
permalinkCreator={this.props.permalinkCreator!}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
||||
case RightPanelPhases.Widget:
|
||||
|
|
89
src/components/views/dialogs/polls/PollDetail.tsx
Normal file
89
src/components/views/dialogs/polls/PollDetail.tsx
Normal file
|
@ -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<Props> = ({ 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<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
event_id: eventIdToLinkTo,
|
||||
highlighted: true,
|
||||
room_id: poll.roomId,
|
||||
metricsTrigger: undefined, // room doesn't change
|
||||
});
|
||||
|
||||
requestModalClose();
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<MPollBody
|
||||
mxEvent={poll.rootEvent}
|
||||
permalinkCreator={permalinkCreator}
|
||||
onHeightChanged={NOOP}
|
||||
onMessageAllowed={NOOP}
|
||||
// MPollBody doesn't use this
|
||||
// and MessageEvent only defines it for eligible events
|
||||
// should be fixed on IBodyProps types
|
||||
// cheat to fulfil the type here
|
||||
mediaEventHelper={{} as unknown as MediaEventHelper}
|
||||
/>
|
||||
<br />
|
||||
<div>
|
||||
<AccessibleButton
|
||||
kind="link_inline"
|
||||
element="a"
|
||||
href={linkToTimeline}
|
||||
onClick={onLinkClick}
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{_t("View poll in timeline")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
36
src/components/views/dialogs/polls/PollDetailHeader.tsx
Normal file
36
src/components/views/dialogs/polls/PollDetailHeader.tsx
Normal file
|
@ -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<Props> = ({ filter, onNavigateBack }) => {
|
||||
return (
|
||||
<AccessibleButton kind="content_inline" onClick={onNavigateBack} className="mx_PollDetailHeader">
|
||||
<LeftCaretIcon className="mx_PollDetailHeader_icon" />
|
||||
{filter === "ACTIVE" ? _t("Active polls") : _t("Past polls")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
};
|
|
@ -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<IDialogProps, "onFinished"> & {
|
||||
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<string, Poll>, filter: PollHistoryFilter)
|
|||
.sort(sortEventsByLatest);
|
||||
};
|
||||
|
||||
export const PollHistoryDialog: React.FC<PollHistoryDialogProps> = ({ roomId, matrixClient, onFinished }) => {
|
||||
const room = matrixClient.getRoom(roomId)!;
|
||||
const { isLoading } = useFetchPastPolls(room, matrixClient);
|
||||
const { polls } = usePollsWithRelations(roomId, matrixClient);
|
||||
export const PollHistoryDialog: React.FC<PollHistoryDialogProps> = ({
|
||||
room,
|
||||
matrixClient,
|
||||
permalinkCreator,
|
||||
onFinished,
|
||||
}) => {
|
||||
const { polls } = usePollsWithRelations(room.roomId, matrixClient);
|
||||
const [filter, setFilter] = useState<PollHistoryFilter>("ACTIVE");
|
||||
const [focusedPollId, setFocusedPollId] = useState<string | null>(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 ? (
|
||||
<PollDetailHeader filter={filter} onNavigateBack={() => setFocusedPollId(null)} />
|
||||
) : (
|
||||
_t("Polls history")
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseDialog title={_t("Polls history")} onFinished={onFinished}>
|
||||
<BaseDialog title={title} onFinished={onFinished}>
|
||||
<div className="mx_PollHistoryDialog_content">
|
||||
<PollHistoryList
|
||||
pollStartEvents={pollStartEvents}
|
||||
isLoading={isLoading || isLoadingPollResponses}
|
||||
polls={polls}
|
||||
filter={filter}
|
||||
onFilterChange={setFilter}
|
||||
/>
|
||||
{focusedPoll ? (
|
||||
<PollDetail poll={focusedPoll} permalinkCreator={permalinkCreator} requestModalClose={onFinished} />
|
||||
) : (
|
||||
<PollHistoryList
|
||||
pollStartEvents={pollStartEvents}
|
||||
isLoading={isLoading || isLoadingPollResponses}
|
||||
polls={polls}
|
||||
filter={filter}
|
||||
onFilterChange={setFilter}
|
||||
onItemClick={setFocusedPollId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</BaseDialog>
|
||||
);
|
||||
|
|
|
@ -40,8 +40,9 @@ type PollHistoryListProps = {
|
|||
pollStartEvents: MatrixEvent[];
|
||||
polls: Map<string, Poll>;
|
||||
filter: PollHistoryFilter;
|
||||
onFilterChange: (filter: PollHistoryFilter) => void;
|
||||
isLoading?: boolean;
|
||||
onFilterChange: (filter: PollHistoryFilter) => void;
|
||||
onItemClick: (pollId: string) => void;
|
||||
};
|
||||
export const PollHistoryList: React.FC<PollHistoryListProps> = ({
|
||||
pollStartEvents,
|
||||
|
@ -49,6 +50,7 @@ export const PollHistoryList: React.FC<PollHistoryListProps> = ({
|
|||
filter,
|
||||
isLoading,
|
||||
onFilterChange,
|
||||
onItemClick,
|
||||
}) => {
|
||||
return (
|
||||
<div className="mx_PollHistoryList">
|
||||
|
@ -65,12 +67,17 @@ export const PollHistoryList: React.FC<PollHistoryListProps> = ({
|
|||
<ol className={classNames("mx_PollHistoryList_list", `mx_PollHistoryList_list_${filter}`)}>
|
||||
{pollStartEvents.map((pollStartEvent) =>
|
||||
filter === "ACTIVE" ? (
|
||||
<PollListItem key={pollStartEvent.getId()!} event={pollStartEvent} />
|
||||
<PollListItem
|
||||
key={pollStartEvent.getId()!}
|
||||
event={pollStartEvent}
|
||||
onClick={() => onItemClick(pollStartEvent.getId()!)}
|
||||
/>
|
||||
) : (
|
||||
<PollListItemEnded
|
||||
key={pollStartEvent.getId()!}
|
||||
event={pollStartEvent}
|
||||
poll={polls.get(pollStartEvent.getId()!)!}
|
||||
onClick={() => onItemClick(pollStartEvent.getId()!)}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
|
|
|
@ -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<Props> = ({ event }) => {
|
||||
export const PollListItem: React.FC<Props> = ({ event, onClick }) => {
|
||||
const pollEvent = event.unstableExtensibleEvent as unknown as PollStartEvent;
|
||||
if (!pollEvent) {
|
||||
return null;
|
||||
}
|
||||
const formattedDate = formatLocalDateShort(event.getTs());
|
||||
return (
|
||||
<li data-testid={`pollListItem-${event.getId()!}`} className="mx_PollListItem">
|
||||
<span>{formattedDate}</span>
|
||||
<PollIcon className="mx_PollListItem_icon" />
|
||||
<span className="mx_PollListItem_question">{pollEvent.question.text}</span>
|
||||
<li data-testid={`pollListItem-${event.getId()!}`} className="mx_PollListItem" onClick={onClick}>
|
||||
<TooltipTarget label={_t("View poll")} alignment={Alignment.Top}>
|
||||
<div className="mx_PollListItem_content">
|
||||
<span>{formattedDate}</span>
|
||||
<PollIcon className="mx_PollListItem_icon" />
|
||||
<span className="mx_PollListItem_question">{pollEvent.question.text}</span>
|
||||
</div>
|
||||
</TooltipTarget>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<EndedPollState> => {
|
|||
* @param event - the poll start MatrixEvent
|
||||
* @param poll - Poll instance
|
||||
*/
|
||||
export const PollListItemEnded: React.FC<Props> = ({ event, poll }) => {
|
||||
export const PollListItemEnded: React.FC<Props> = ({ event, poll, onClick }) => {
|
||||
const pollEvent = poll.pollEvent;
|
||||
const { winningAnswers, totalVoteCount } = usePollVotes(poll);
|
||||
if (!pollEvent) {
|
||||
|
@ -97,31 +100,35 @@ export const PollListItemEnded: React.FC<Props> = ({ event, poll }) => {
|
|||
const formattedDate = formatLocalDateShort(event.getTs());
|
||||
|
||||
return (
|
||||
<li data-testid={`pollListItem-${event.getId()!}`} className="mx_PollListItemEnded">
|
||||
<div className="mx_PollListItemEnded_title">
|
||||
<PollIcon className="mx_PollListItemEnded_icon" />
|
||||
<span className="mx_PollListItemEnded_question">{pollEvent.question.text}</span>
|
||||
<Caption>{formattedDate}</Caption>
|
||||
</div>
|
||||
{!!winningAnswers?.length && (
|
||||
<div className="mx_PollListItemEnded_answers">
|
||||
{winningAnswers?.map(({ answer, voteCount }) => (
|
||||
<PollOption
|
||||
key={answer.id}
|
||||
answer={answer}
|
||||
voteCount={voteCount}
|
||||
totalVoteCount={totalVoteCount!}
|
||||
pollId={poll.pollId}
|
||||
displayVoteCount
|
||||
isChecked
|
||||
isEnded
|
||||
/>
|
||||
))}
|
||||
<li data-testid={`pollListItem-${event.getId()!}`} className="mx_PollListItemEnded" onClick={onClick}>
|
||||
<TooltipTarget label={_t("View poll")} alignment={Alignment.Top}>
|
||||
<div className="mx_PollListItemEnded_content">
|
||||
<div className="mx_PollListItemEnded_title">
|
||||
<PollIcon className="mx_PollListItemEnded_icon" />
|
||||
<span className="mx_PollListItemEnded_question">{pollEvent.question.text}</span>
|
||||
<Caption>{formattedDate}</Caption>
|
||||
</div>
|
||||
{!!winningAnswers?.length && (
|
||||
<div className="mx_PollListItemEnded_answers">
|
||||
{winningAnswers?.map(({ answer, voteCount }) => (
|
||||
<PollOption
|
||||
key={answer.id}
|
||||
answer={answer}
|
||||
voteCount={voteCount}
|
||||
totalVoteCount={totalVoteCount!}
|
||||
pollId={poll.pollId}
|
||||
displayVoteCount
|
||||
isChecked
|
||||
isEnded
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="mx_PollListItemEnded_voteCount">
|
||||
<Caption>{_t("Final result based on %(count)s votes", { count: totalVoteCount })}</Caption>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="mx_PollListItemEnded_voteCount">
|
||||
<Caption>{_t("Final result based on %(count)s votes", { count: totalVoteCount })}</Caption>
|
||||
</div>
|
||||
</TooltipTarget>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<IProps> = ({ room, onClose }) => {
|
||||
const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
const onShareRoomClick = (): void => {
|
||||
|
@ -285,8 +287,9 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, onClose }) => {
|
|||
|
||||
const onRoomPollHistoryClick = (): void => {
|
||||
Modal.createDialog(PollHistoryDialog, {
|
||||
roomId: room.roomId,
|
||||
room,
|
||||
matrixClient: cli,
|
||||
permalinkCreator,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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 <button>set up new recovery options</button>": "If you've forgotten your Security Key you can <button>set up new recovery options</button>",
|
||||
"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",
|
||||
|
|
|
@ -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("<PollHistoryDialog />", () => {
|
||||
// 14.03.2022 16:15
|
||||
|
@ -57,8 +60,9 @@ describe("<PollHistoryDialog />", () => {
|
|||
});
|
||||
|
||||
const defaultProps = {
|
||||
roomId,
|
||||
room,
|
||||
matrixClient: mockClient,
|
||||
permalinkCreator: new RoomPermalinkCreator(room),
|
||||
onFinished: jest.fn(),
|
||||
};
|
||||
const getComponent = () => render(<PollHistoryDialog {...defaultProps} />);
|
||||
|
@ -70,9 +74,12 @@ describe("<PollHistoryDialog />", () => {
|
|||
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("<PollHistoryDialog />", () => {
|
|||
// 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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("<PollListItem />", () => {
|
||||
const event = makePollStartEvent("Question?", "@me:domain.org");
|
||||
event.getContent().origin;
|
||||
const defaultProps = { event };
|
||||
const defaultProps = { event, onClick: jest.fn() };
|
||||
const getComponent = (props = {}) => render(<PollListItem {...defaultProps} {...props} />);
|
||||
|
||||
beforeAll(() => {
|
||||
|
@ -50,4 +50,13 @@ describe("<PollListItem />", () => {
|
|||
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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -60,7 +60,8 @@ describe("<PollListItemEnded />", () => {
|
|||
});
|
||||
const pollEndEvent = makePollEndEvent(pollId, roomId, userId, timestamp + 60000);
|
||||
|
||||
const getComponent = (props: { event: MatrixEvent; poll: Poll }) => render(<PollListItemEnded {...props} />);
|
||||
const getComponent = (props: { event: MatrixEvent; poll: Poll }) =>
|
||||
render(<PollListItemEnded {...props} onClick={jest.fn()} />);
|
||||
|
||||
beforeAll(() => {
|
||||
// mock default locale to en-GB and set timezone
|
||||
|
|
|
@ -1,5 +1,47 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<PollHistoryDialog /> Poll detail displays poll detail on active poll list item click 1`] = `
|
||||
<h2
|
||||
data-testid="pollQuestion"
|
||||
>
|
||||
Question?
|
||||
</h2>
|
||||
`;
|
||||
|
||||
exports[`<PollHistoryDialog /> Poll detail displays poll detail on past poll list item click 1`] = `
|
||||
<h2
|
||||
data-testid="pollQuestion"
|
||||
>
|
||||
What?
|
||||
</h2>
|
||||
`;
|
||||
|
||||
exports[`<PollHistoryDialog /> Poll detail navigates back to poll list from detail view on header click 1`] = `
|
||||
<div
|
||||
class="mx_AccessibleButton mx_PollDetailHeader mx_AccessibleButton_hasKind mx_AccessibleButton_kind_content_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_PollDetailHeader_icon"
|
||||
/>
|
||||
Active polls
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<PollHistoryDialog /> Poll detail navigates back to poll list from detail view on header click 2`] = `
|
||||
<div
|
||||
class="mx_AccessibleButton mx_PollDetailHeader mx_AccessibleButton_hasKind mx_AccessibleButton_kind_content_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_PollDetailHeader_icon"
|
||||
/>
|
||||
Active polls
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<PollHistoryDialog /> renders a list of active polls when there are polls in the room 1`] = `
|
||||
<div>
|
||||
<div
|
||||
|
@ -71,33 +113,49 @@ exports[`<PollHistoryDialog /> renders a list of active polls when there are pol
|
|||
class="mx_PollListItem"
|
||||
data-testid="pollListItem-$2"
|
||||
>
|
||||
<span>
|
||||
02/02/23
|
||||
</span>
|
||||
<div
|
||||
class="mx_PollListItem_icon"
|
||||
/>
|
||||
<span
|
||||
class="mx_PollListItem_question"
|
||||
tabindex="0"
|
||||
>
|
||||
Where?
|
||||
</span>
|
||||
<div
|
||||
class="mx_PollListItem_content"
|
||||
>
|
||||
<span>
|
||||
02/02/23
|
||||
</span>
|
||||
<div
|
||||
class="mx_PollListItem_icon"
|
||||
/>
|
||||
<span
|
||||
class="mx_PollListItem_question"
|
||||
>
|
||||
Where?
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="mx_PollListItem"
|
||||
data-testid="pollListItem-$1"
|
||||
>
|
||||
<span>
|
||||
02/02/23
|
||||
</span>
|
||||
<div
|
||||
class="mx_PollListItem_icon"
|
||||
/>
|
||||
<span
|
||||
class="mx_PollListItem_question"
|
||||
tabindex="0"
|
||||
>
|
||||
Question?
|
||||
</span>
|
||||
<div
|
||||
class="mx_PollListItem_content"
|
||||
>
|
||||
<span>
|
||||
02/02/23
|
||||
</span>
|
||||
<div
|
||||
class="mx_PollListItem_icon"
|
||||
/>
|
||||
<span
|
||||
class="mx_PollListItem_question"
|
||||
>
|
||||
Question?
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
|
|
@ -6,17 +6,25 @@ exports[`<PollListItem /> renders a poll 1`] = `
|
|||
class="mx_PollListItem"
|
||||
data-testid="pollListItem-$mypoll"
|
||||
>
|
||||
<span>
|
||||
01/01/70
|
||||
</span>
|
||||
<div
|
||||
class="mx_PollListItem_icon"
|
||||
/>
|
||||
<span
|
||||
class="mx_PollListItem_question"
|
||||
tabindex="0"
|
||||
>
|
||||
Question?
|
||||
</span>
|
||||
<div
|
||||
class="mx_PollListItem_content"
|
||||
>
|
||||
<span>
|
||||
01/01/70
|
||||
</span>
|
||||
<div
|
||||
class="mx_PollListItem_icon"
|
||||
/>
|
||||
<span
|
||||
class="mx_PollListItem_question"
|
||||
>
|
||||
Question?
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -7,30 +7,38 @@ exports[`<PollListItemEnded /> renders a poll with no responses 1`] = `
|
|||
data-testid="pollListItem-1"
|
||||
>
|
||||
<div
|
||||
class="mx_PollListItemEnded_title"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_PollListItemEnded_icon"
|
||||
/>
|
||||
<span
|
||||
class="mx_PollListItemEnded_question"
|
||||
class="mx_PollListItemEnded_content"
|
||||
>
|
||||
Question?
|
||||
</span>
|
||||
<span
|
||||
class="mx_Caption"
|
||||
>
|
||||
02/02/23
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PollListItemEnded_voteCount"
|
||||
>
|
||||
<span
|
||||
class="mx_Caption"
|
||||
>
|
||||
Final result based on 0 votes
|
||||
</span>
|
||||
<div
|
||||
class="mx_PollListItemEnded_title"
|
||||
>
|
||||
<div
|
||||
class="mx_PollListItemEnded_icon"
|
||||
/>
|
||||
<span
|
||||
class="mx_PollListItemEnded_question"
|
||||
>
|
||||
Question?
|
||||
</span>
|
||||
<span
|
||||
class="mx_Caption"
|
||||
>
|
||||
02/02/23
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_PollListItemEnded_voteCount"
|
||||
>
|
||||
<span
|
||||
class="mx_Caption"
|
||||
>
|
||||
Final result based on 0 votes
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
|
|
|
@ -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("<RoomSummaryCard />", () => {
|
||||
const userId = "@alice:domain.org";
|
||||
|
@ -54,6 +55,7 @@ describe("<RoomSummaryCard />", () => {
|
|||
const defaultProps = {
|
||||
room,
|
||||
onClose: jest.fn(),
|
||||
permalinkCreator: new RoomPermalinkCreator(room),
|
||||
};
|
||||
const getComponent = (props = {}) =>
|
||||
render(<RoomSummaryCard {...defaultProps} {...props} />, {
|
||||
|
@ -145,7 +147,11 @@ describe("<RoomSummaryCard />", () => {
|
|||
|
||||
fireEvent.click(getByText("Polls history"));
|
||||
|
||||
expect(modalSpy).toHaveBeenCalledWith(PollHistoryDialog, { roomId, matrixClient: mockClient });
|
||||
expect(modalSpy).toHaveBeenCalledWith(PollHistoryDialog, {
|
||||
room,
|
||||
matrixClient: mockClient,
|
||||
permalinkCreator: defaultProps.permalinkCreator,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue